diff --git a/README.md b/README.md index 899ad35..acfa40b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Full documentation can be found in the [documentation](documentation) folder. - [React](https://react.dev/) `18.*` - [react-hexgrid](https://github.com/Hellenic/react-hexgrid) `2.0.0@beta` - [emotion](https://emotion.sh/docs/introduction) `11.11.3` (dependency not imported by react-hexgrid) +- [Zod](https://zod.dev/) `3.22.4` ### Dev Dependencies diff --git a/documentation/functional_requirements_specification.md b/documentation/functional_requirements_specification.md index 7493726..91e8a4e 100644 --- a/documentation/functional_requirements_specification.md +++ b/documentation/functional_requirements_specification.md @@ -115,9 +115,33 @@ tiles, see Appendix C. ### 4 API +The API for this project consists of a single endpoint to handle the generate form submission. + ### 4.1 API Implementation -### 4.2 Board Unique Identifier +Next.js suggests using [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations), +instead of the older Node.js `/api/` endpoints. + +The endpoint should: validate incoming form data using [Zod](https://zod.dev/); pass the validated data to the Board +Generation Algorithm (described in 3); and then redirect back to a URL with a Unique Board Identifier (described in 4.2). + +### 4.2 Unique Board Identifier + +To be able to save and share boards, each board needs a unique identifier encoded in the URL. Because the Board +Generation Algorithm (described in 3) uses unique characters for each tile type, an encoded board can look as follows: + +``` +RST-WBSB-WTDTR-TRWS-BWS +``` + +Each block of letters describes a row of the board, so the middle block is the largest, and decreases by 1 for each row +further out. + +This identifier could easily be extended to include additional metadata, and even allows a user to create or modify a board +solely from the identifier. + +To render the identifier, it can be pulled from the URL using Next.js [useParams](https://nextjs.org/docs/app/api-reference/functions/use-params) +functionality. ### 5 Testing diff --git a/package-lock.json b/package-lock.json index 0a0be18..c3d5ed7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "react": "^18", "react-dom": "^18", "react-hexgrid": "^2.0.0-beta.4", - "react-hook-form": "^7.50.1" + "react-hook-form": "^7.50.1", + "zod": "^3.22.4" }, "devDependencies": { "@testing-library/react": "^14.2.1", @@ -9380,6 +9381,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 0979cf0..0b7058b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "react": "^18", "react-dom": "^18", "react-hexgrid": "^2.0.0-beta.4", - "react-hook-form": "^7.50.1" + "react-hook-form": "^7.50.1", + "zod": "^3.22.4" }, "devDependencies": { "@testing-library/react": "^14.2.1", diff --git a/src/actions/actions.ts b/src/actions/actions.ts index b8e2fa3..1da2401 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -1,9 +1,34 @@ "use server"; import { redirect } from "next/navigation"; +import { z } from "zod"; +import { ALGORITHM_ARRAY, PLAYERS_4, PLAYERS_6 } from "@/lib/constants"; +import CatanBoardGenerator from "@/lib/CatanBoardGenerator"; + +const schema = z.object({ + useSeafarers: z.boolean(), + randAlgorithm: z.enum(ALGORITHM_ARRAY), + numOfPlayers: z.enum([PLAYERS_4, PLAYERS_6]), +}); export async function generateBoard(formData: FormData) { - const serializedBoard = "RST-WBSB-WTDTR-TRWS-BWS"; - console.log(formData); + const validatedFields = schema.safeParse({ + useSeafarers: formData.has("useSeafarers"), + randAlgorithm: formData.get("randAlgorithm"), + numOfPlayers: formData.get("numOfPlayers"), + }); + + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + }; + } + // const serializedBoard = "RST-WBSB-WTDTR-TRWS-BWS"; + + const serializedBoard = new CatanBoardGenerator( + validatedFields.data.useSeafarers, + validatedFields.data.numOfPlayers, + validatedFields.data.randAlgorithm, + ).toString(); redirect(`/${serializedBoard}`); } diff --git a/src/app/[board]/page.tsx b/src/app/[board]/page.tsx new file mode 100644 index 0000000..45d5b8b --- /dev/null +++ b/src/app/[board]/page.tsx @@ -0,0 +1,12 @@ +import BoardGrid from "@/app/components/ui/BoardGrid"; +import BoardForm from "@/app/components/ui/BoardForm"; +import * as React from "react"; + +export default function Page() { + return ( + <> + + + + ); +} diff --git a/src/app/[catan-board]/page.tsx b/src/app/[catan-board]/page.tsx deleted file mode 100644 index 5609f83..0000000 --- a/src/app/[catan-board]/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Catan Board Page

; -} diff --git a/src/app/components/ui/BoardForm.tsx b/src/app/components/ui/BoardForm.tsx index e85342a..a98abe0 100644 --- a/src/app/components/ui/BoardForm.tsx +++ b/src/app/components/ui/BoardForm.tsx @@ -9,11 +9,13 @@ import { PLAYERS_4, } from "@/lib/constants"; import { generateBoard } from "@/actions/actions"; +import { useFormStatus } from "react-dom"; const BoardForm: React.FC = () => { const { register, watch } = useForm(); const watchUseSeafarers = watch("useSeafarers"); const [getAlgorithms, setAlgorithms] = useState(ALGORITHMS_BASE); + const { pending } = useFormStatus(); useEffect(() => { if (watchUseSeafarers === true) { @@ -85,6 +87,7 @@ const BoardForm: React.FC = () => {