From 049394778658ef4692b4a00d638a39e4314a7ac2 Mon Sep 17 00:00:00 2001 From: Web Dev Cody Date: Sat, 18 Nov 2023 01:32:52 -0500 Subject: [PATCH] validation helper function; split actions into files --- .../create-item-action.ts} | 76 ++++++++++--------- src/app/_actions/delete-item-action.ts | 9 +++ src/app/create-item-form.tsx | 15 ++-- src/app/items-list.tsx | 3 +- src/entites/item.ts | 2 - src/use-cases/items.tsx | 16 ++-- 6 files changed, 69 insertions(+), 52 deletions(-) rename src/app/{actions.ts => _actions/create-item-action.ts} (62%) create mode 100644 src/app/_actions/delete-item-action.ts diff --git a/src/app/actions.ts b/src/app/_actions/create-item-action.ts similarity index 62% rename from src/app/actions.ts rename to src/app/_actions/create-item-action.ts index e1c8a6a..3166b03 100644 --- a/src/app/actions.ts +++ b/src/app/_actions/create-item-action.ts @@ -1,27 +1,58 @@ "use server"; import { createItem, deleteItem } from "@/data-access/items"; -import { ItemEntityValidationError } from "@/entites/item"; import { auth } from "@/lib/auth"; import { ValidationError, createItemUseCase } from "@/use-cases/items"; import { revalidatePath } from "next/cache"; -type CreateItemState = { - errors: Partial<{ name: string }>; - form: { name: string }; - error?: string; - status: "pending" | "success" | "errors" | "field-errors"; -}; +type CreateItemState = { form: { name: string } } & ( + | { + status: "success"; + } + | { + status: "error"; + errors: string; + } + | { + status: "field-errors"; + errors: Partial<{ name: string }>; + } + | { + status: "default"; + } +); + +async function handleFieldValidation( + submittedForm: any, + callback: () => Promise +) { + try { + return await callback(); + } catch (err) { + const error = err as Error; + if (error instanceof ValidationError) { + return { + form: submittedForm, + status: "field-errors", + errors: error.getErrors(), + }; + } else { + return { + form: submittedForm, + status: "error", + errors: error.message, + }; + } + } +} export async function createItemAction( state: CreateItemState, formData: FormData ): Promise { - "use server"; - const session = await auth(); - try { + return handleFieldValidation(formData.entries(), async () => { await createItemUseCase( { getUser: () => session?.user && { userId: session.user.id }, @@ -31,35 +62,12 @@ export async function createItemAction( ); revalidatePath("/"); return { - errors: {}, form: { name: "", }, status: "success", }; - } catch (err) { - const error = err as Error; - if (error instanceof ValidationError) { - return { - form: { - name: formData.get("name") as string, - }, - status: "field-errors", - errors: { - name: error.getErrors().name, - }, - }; - } else { - return { - form: { - name: formData.get("name") as string, - }, - status: "errors", - error: error.message, - errors: {}, - }; - } - } + }); } export async function deleteItemAction(itemId: number) { diff --git a/src/app/_actions/delete-item-action.ts b/src/app/_actions/delete-item-action.ts new file mode 100644 index 0000000..1a6dfcd --- /dev/null +++ b/src/app/_actions/delete-item-action.ts @@ -0,0 +1,9 @@ +"use server"; + +import { deleteItem } from "@/data-access/items"; +import { revalidatePath } from "next/cache"; + +export async function deleteItemAction(itemId: number) { + await deleteItem(itemId); + revalidatePath("/"); +} diff --git a/src/app/create-item-form.tsx b/src/app/create-item-form.tsx index 85ea8ee..582646e 100644 --- a/src/app/create-item-form.tsx +++ b/src/app/create-item-form.tsx @@ -3,12 +3,12 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { createItemAction } from "./actions"; import { useFormState, useFormStatus } from "react-dom"; import { useEffect, useRef } from "react"; import { useToast } from "@/components/ui/use-toast"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Terminal } from "lucide-react"; +import { createItemAction } from "./_actions/create-item-action"; export function CreateItemForm() { const { toast } = useToast(); @@ -16,10 +16,7 @@ export function CreateItemForm() { form: { name: "", }, - status: "pending", - errors: { - name: "", - }, + status: "default", }); const formRef = useRef(null); @@ -35,11 +32,11 @@ export function CreateItemForm() { return ( <> - {formState.status === "errors" && ( + {formState.status === "error" && ( Uh oh! - {formState.error} + {formState.errors} )}
- + {formState.status === "field-errors" && ( + + )} diff --git a/src/app/items-list.tsx b/src/app/items-list.tsx index d378fbe..6856e96 100644 --- a/src/app/items-list.tsx +++ b/src/app/items-list.tsx @@ -1,8 +1,7 @@ import { Button } from "@/components/ui/button"; -import { deleteItemAction } from "./actions"; -import { Item } from "@/db/schema"; import { sortBy } from "lodash"; import { ItemDto } from "@/data-access/items"; +import { deleteItemAction } from "./_actions/create-item-action"; export function ItemsList({ items }: { items: ItemDto[] }) { const sortedItems = sortBy(items, ["name"]); diff --git a/src/entites/item.ts b/src/entites/item.ts index f84c8b1..510dce9 100644 --- a/src/entites/item.ts +++ b/src/entites/item.ts @@ -45,7 +45,6 @@ export class ItemEntity { } validate() { - console.log("name", this.name); const itemSchema = z.object({ name: z.string().min(1), userId: z.string().min(1), @@ -56,7 +55,6 @@ export class ItemEntity { } catch (err) { const error = err as ZodError; const errors = error.flatten().fieldErrors; - console.log(errors); throw new ItemEntityValidationError({ name: errors.name?.[0], userId: errors.userId?.[0], diff --git a/src/use-cases/items.tsx b/src/use-cases/items.tsx index 25c3812..27b78d3 100644 --- a/src/use-cases/items.tsx +++ b/src/use-cases/items.tsx @@ -18,7 +18,7 @@ export class ValidationError extends Error { } } -export class AuthenticationdError extends Error { +export class AuthenticationError extends Error { constructor() { super("You must be authenticated to do this action"); } @@ -27,6 +27,13 @@ export class AuthenticationdError extends Error { export type CreateItem = (item: CreateItemDto) => void; export type GetUser = () => User | undefined; +function itemToCreateItemDtoMapper(item: ItemEntity): CreateItemDto { + return { + name: item.getName(), + userId: item.getUserId(), + }; +} + export async function createItemUseCase( context: { getUser: GetUser; createItem: CreateItem }, data: { name: string } @@ -34,7 +41,7 @@ export async function createItemUseCase( const user = context.getUser(); if (!user) { - throw new AuthenticationdError(); + throw new AuthenticationError(); } const item = new ItemEntity({ @@ -49,8 +56,5 @@ export async function createItemUseCase( throw new ValidationError(error.getErrors()); } - await context.createItem({ - name: item.getName(), - userId: item.getUserId(), - }); + await context.createItem(itemToCreateItemDtoMapper(item)); }