From 2cfec3e902773df43e8cf7c052ab6d7e906c1a78 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sat, 23 Sep 2023 22:13:14 +0800 Subject: [PATCH] Implement form actions with resource locations Process redirects and parse resource locations for both server-side and client-side. --- README.md | 2 +- packages/iceform-next-sandbox/.gitignore | 1 + .../iceform-next-sandbox/src/handlers/note.ts | 3 + .../src/pages/a/notes/[noteId].ts | 4 +- .../src/pages/a/notes/index.ts | 16 ++- .../src/pages/api/notes/index.ts | 5 +- .../iceform-next-sandbox/src/pages/greet.tsx | 4 +- .../src/pages/notes/[noteId].tsx | 58 +++++++++- .../src/pages/notes/index.tsx | 37 ++++++- .../src/client/hooks/useResponse.ts | 5 + packages/iceform-next/src/server/constants.ts | 8 ++ .../src/{server.ts => server/index.ts} | 101 +++++++++--------- packages/iceform-next/src/server/response.ts | 93 ++++++++++++++++ packages/iceform-next/src/utils/request.ts | 7 +- 14 files changed, 281 insertions(+), 63 deletions(-) create mode 100644 packages/iceform-next/src/server/constants.ts rename packages/iceform-next/src/{server.ts => server/index.ts} (64%) create mode 100644 packages/iceform-next/src/server/response.ts diff --git a/README.md b/README.md index d980029..ba135b6 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ In theory, any API route may have a corresponding action route. - [X] Content negotiation (custom request data) - [ ] `
` (on hold, see https://github.com/whatwg/html/issues/9625) - [ ] Tests - - [ ] Form with redirects + - [X] Form with redirects - [ ] Form with files - [ ] Documentation - [ ] Remix support diff --git a/packages/iceform-next-sandbox/.gitignore b/packages/iceform-next-sandbox/.gitignore index 8f322f0..bc7c3e7 100644 --- a/packages/iceform-next-sandbox/.gitignore +++ b/packages/iceform-next-sandbox/.gitignore @@ -33,3 +33,4 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +.db/ diff --git a/packages/iceform-next-sandbox/src/handlers/note.ts b/packages/iceform-next-sandbox/src/handlers/note.ts index 93d9d66..55ed12f 100644 --- a/packages/iceform-next-sandbox/src/handlers/note.ts +++ b/packages/iceform-next-sandbox/src/handlers/note.ts @@ -214,6 +214,9 @@ const createNote = (params: NoteCollectionParams): NextApiHandler => async (req, } // how to genericize the location URL? + // TODO check if req.url only returns the path of this API + // req.url returns '/a/notes' when accessed from /a/notes, so we need to associate each action + // route to each API route res.setHeader('Location', `${params.basePath}/${newId}`); res.status(201).json({ diff --git a/packages/iceform-next-sandbox/src/pages/a/notes/[noteId].ts b/packages/iceform-next-sandbox/src/pages/a/notes/[noteId].ts index 0d28175..9f7d994 100644 --- a/packages/iceform-next-sandbox/src/pages/a/notes/[noteId].ts +++ b/packages/iceform-next-sandbox/src/pages/a/notes/[noteId].ts @@ -2,7 +2,7 @@ import {NextPage} from 'next'; import * as Iceform from '@modal-sh/iceform-next'; import {noteResource} from '@/handlers/note'; -const ActionNotesIndexPage: NextPage = () => null; +const ActionNotesResourcePage: NextPage = () => null; const getServerSideProps = Iceform.action.getServerSideProps({ fn: noteResource, @@ -10,5 +10,5 @@ const getServerSideProps = Iceform.action.getServerSideProps({ export { getServerSideProps, - ActionNotesIndexPage as default, + ActionNotesResourcePage as default, }; diff --git a/packages/iceform-next-sandbox/src/pages/a/notes/index.ts b/packages/iceform-next-sandbox/src/pages/a/notes/index.ts index cfd6e2a..a20a35d 100644 --- a/packages/iceform-next-sandbox/src/pages/a/notes/index.ts +++ b/packages/iceform-next-sandbox/src/pages/a/notes/index.ts @@ -2,15 +2,25 @@ import {NextPage} from 'next'; import * as Iceform from '@modal-sh/iceform-next'; import {noteCollection} from '@/handlers/note'; -const ActionNotesIndexPage: NextPage = () => null; +const ActionNotesCollectionPage: NextPage = () => null; + +// this serves as an ID to associate the action URL to the API URL +const RESOURCE_BASE_PATH = '/api/notes'; const getServerSideProps = Iceform.action.getServerSideProps({ fn: noteCollection({ - basePath: '/api/notes' + basePath: RESOURCE_BASE_PATH }), + mapLocationToRedirectDestination: (referer, url) => { + const resourceBaseUrl = `${RESOURCE_BASE_PATH}/`; + if (url.startsWith(resourceBaseUrl)) { + return `/notes/${url.slice(resourceBaseUrl.length)}`; + } + return referer; + }, }); export { getServerSideProps, - ActionNotesIndexPage as default, + ActionNotesCollectionPage as default, }; diff --git a/packages/iceform-next-sandbox/src/pages/api/notes/index.ts b/packages/iceform-next-sandbox/src/pages/api/notes/index.ts index 249cbac..45b7cc1 100644 --- a/packages/iceform-next-sandbox/src/pages/api/notes/index.ts +++ b/packages/iceform-next-sandbox/src/pages/api/notes/index.ts @@ -1,9 +1,12 @@ import * as Iceform from '@modal-sh/iceform-next'; import { noteCollection } from '@/handlers/note'; +// this serves as an ID to associate the action URL to the API URL +const RESOURCE_BASE_PATH = '/api/notes'; + const handler = Iceform.action.wrapApiHandler({ fn: noteCollection({ - basePath: '/api/notes' + basePath: RESOURCE_BASE_PATH, }), }); diff --git a/packages/iceform-next-sandbox/src/pages/greet.tsx b/packages/iceform-next-sandbox/src/pages/greet.tsx index 6e272af..00ee4f0 100644 --- a/packages/iceform-next-sandbox/src/pages/greet.tsx +++ b/packages/iceform-next-sandbox/src/pages/greet.tsx @@ -5,7 +5,9 @@ const GreetPage: Iceform.NextPage = ({ req, res, }) => { - const {response, ...isoformProps} = Iceform.useResponse(res); + const {response, loading, ...isoformProps} = Iceform.useResponse({ + res + }); const [responseData, setResponseData] = React.useState(); React.useEffect(() => { diff --git a/packages/iceform-next-sandbox/src/pages/notes/[noteId].tsx b/packages/iceform-next-sandbox/src/pages/notes/[noteId].tsx index b6511b8..18efec1 100644 --- a/packages/iceform-next-sandbox/src/pages/notes/[noteId].tsx +++ b/packages/iceform-next-sandbox/src/pages/notes/[noteId].tsx @@ -1 +1,57 @@ -// TODO view note page +import * as Iceform from '@modal-sh/iceform-next'; +import * as React from 'react'; + +const NotesItemPage: Iceform.NextPage = ({ + req, + res, +}) => { + const body = (res.body ?? {}) as Record; + const {response, loading, ...isoformProps} = Iceform.useResponse({ + res + }); + + const [responseData, setResponseData] = React.useState(); + React.useEffect(() => { + // response.bodyUsed might be undefined, so we use a strict comparison + if (response?.bodyUsed === false) { + response?.json().then((responseData) => { + setResponseData(responseData); + }); + } + }, [response]); + + return ( + +
+ +
+
+ +
+
+