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 = () => {