From 7029b1476420e54635b362c1eaf98a2af914c9b3 Mon Sep 17 00:00:00 2001 From: Johannes Brandt Nielsen Date: Thu, 22 Jun 2023 06:13:37 +0200 Subject: [PATCH 1/2] install packages --- package-lock.json | 141 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- 2 files changed, 144 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d381b2..147001d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,15 @@ "@remix-run/node": "^1.17.0", "@remix-run/react": "^1.17.0", "@remix-run/serve": "^1.17.0", + "@remix-validated-form/with-zod": "^2.0.6", "bcryptjs": "^2.4.3", "isbot": "^3.6.8", "marked": "^5.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "tiny-invariant": "^1.3.1" + "remix-validated-form": "^5.0.2", + "tiny-invariant": "^1.3.1", + "zod": "^3.21.4" }, "devDependencies": { "@faker-js/faker": "^7.6.0", @@ -3176,6 +3179,15 @@ "web-streams-polyfill": "^3.1.1" } }, + "node_modules/@remix-validated-form/with-zod": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@remix-validated-form/with-zod/-/with-zod-2.0.6.tgz", + "integrity": "sha512-i8H0PPFSSKIMGPVO/8cUMO1QoGa2bBQZb6RH3DoXGVE1heu52d1vwrFVsYYQB8Vc8lp5BGQk1kbxZuN9RzH1OA==", + "peerDependencies": { + "remix-validated-form": "^4.x || ^5.x", + "zod": "^3.11.x" + } + }, "node_modules/@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -8922,6 +8934,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -10088,6 +10109,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -13579,6 +13605,29 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remeda": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.19.0.tgz", + "integrity": "sha512-iwZohiXDhC1K+adRI6OB+tYxOfXyX7DaPXQDZrR5s1k7umrkG3Yd2+QDfSrYFlC7oc0IqeUns6RqSjNkERXeLw==" + }, + "node_modules/remix-validated-form": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/remix-validated-form/-/remix-validated-form-5.0.2.tgz", + "integrity": "sha512-jM3uuvCP6AO9G117MAEGgTb3x/aOy5qVrOM85XdDhWnpJFt7WC6u1gBQ/tSd1UBCTxDSFwU6TZPCJex73D9rjQ==", + "dependencies": { + "immer": "^9.0.12", + "lodash.get": "^4.4.2", + "nanoid": "3.3.6", + "remeda": "^1.2.0", + "tiny-invariant": "^1.2.0", + "zustand": "^4.3.0" + }, + "peerDependencies": { + "@remix-run/react": ">= 1.15.0", + "@remix-run/server-runtime": "1.x", + "react": "^17.0.2 || ^18.0.0" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -15435,6 +15484,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -16219,6 +16276,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.8.tgz", + "integrity": "sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -18352,6 +18440,11 @@ "web-streams-polyfill": "^3.1.1" } }, + "@remix-validated-form/with-zod": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@remix-validated-form/with-zod/-/with-zod-2.0.6.tgz", + "integrity": "sha512-i8H0PPFSSKIMGPVO/8cUMO1QoGa2bBQZb6RH3DoXGVE1heu52d1vwrFVsYYQB8Vc8lp5BGQk1kbxZuN9RzH1OA==" + }, "@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -22658,6 +22751,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" }, + "immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -23497,6 +23595,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -25876,6 +25979,24 @@ "unified": "^10.0.0" } }, + "remeda": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.19.0.tgz", + "integrity": "sha512-iwZohiXDhC1K+adRI6OB+tYxOfXyX7DaPXQDZrR5s1k7umrkG3Yd2+QDfSrYFlC7oc0IqeUns6RqSjNkERXeLw==" + }, + "remix-validated-form": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/remix-validated-form/-/remix-validated-form-5.0.2.tgz", + "integrity": "sha512-jM3uuvCP6AO9G117MAEGgTb3x/aOy5qVrOM85XdDhWnpJFt7WC6u1gBQ/tSd1UBCTxDSFwU6TZPCJex73D9rjQ==", + "requires": { + "immer": "^9.0.12", + "lodash.get": "^4.4.2", + "nanoid": "3.3.6", + "remeda": "^1.2.0", + "tiny-invariant": "^1.2.0", + "zustand": "^4.3.0" + } + }, "request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -27285,6 +27406,11 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + }, "util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -27813,6 +27939,19 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" + }, + "zustand": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.8.tgz", + "integrity": "sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==", + "requires": { + "use-sync-external-store": "1.2.0" + } + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 8a9a566..f8a6335 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,15 @@ "@remix-run/node": "^1.17.0", "@remix-run/react": "^1.17.0", "@remix-run/serve": "^1.17.0", + "@remix-validated-form/with-zod": "^2.0.6", "bcryptjs": "^2.4.3", "isbot": "^3.6.8", "marked": "^5.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "tiny-invariant": "^1.3.1" + "remix-validated-form": "^5.0.2", + "tiny-invariant": "^1.3.1", + "zod": "^3.21.4" }, "devDependencies": { "@faker-js/faker": "^7.6.0", From 7778bab7a0e300d50408fe66de3a972b9b675b9d Mon Sep 17 00:00:00 2001 From: Johannes Brandt Nielsen Date: Thu, 22 Jun 2023 06:15:34 +0200 Subject: [PATCH 2/2] use ValidatedForm in posts new --- app/routes/posts.admin.new.tsx | 153 ++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 68 deletions(-) diff --git a/app/routes/posts.admin.new.tsx b/app/routes/posts.admin.new.tsx index 7d923e1..934fcc6 100644 --- a/app/routes/posts.admin.new.tsx +++ b/app/routes/posts.admin.new.tsx @@ -1,90 +1,107 @@ import type { ActionArgs } from "@remix-run/node"; -import { json, redirect } from "@remix-run/node"; -import { Form, useActionData, useNavigation } from "@remix-run/react"; -import invariant from "tiny-invariant"; +import { redirect } from "@remix-run/node"; import { createPost } from "~/models/post.server"; +import { + ValidatedForm, + useField, + useIsSubmitting, + validationError, +} from "remix-validated-form"; +import { withZod } from "@remix-validated-form/with-zod"; +import { z } from "zod"; + const inputClassName = `w-full rounded border border-gray-500 px-2 py-1 text-lg`; +export const validator = withZod( + z.object({ + title: z.string().min(1, { message: "Title is required" }), + slug: z.string().min(1, { message: "Slug name is required" }), + markdown: z.string().min(1, { message: "Markdown content is required" }), + }) +); + export const action = async ({ request }: ActionArgs) => { - const formData = await request.formData(); + const result = await validator.validate(await request.formData()); - const title = formData.get("title"); - const slug = formData.get("slug"); - const markdown = formData.get("markdown"); + if (result.error) { + return validationError(result.error); + } await new Promise((res) => setTimeout(res, 1000)); - const errors = { - title: title ? null : "Title is required", - slug: slug ? null : "Slug is required", - markdown: markdown ? null : "Markdown is required", - }; + await createPost(result.data); - const hasErrors = Object.values(errors).some((errorMessage) => errorMessage); - if (hasErrors) { - return json(errors); - } + return redirect("/posts/admin"); +}; + +export default function NewPost() { + return ( + + + - invariant(typeof title === "string", "title must be a string"); - invariant(typeof slug === "string", "slug must be a string"); - invariant(typeof markdown === "string", "markdown must be a string"); +