From ed750a998932d187eb4b2ef62161025a9b1c67a3 Mon Sep 17 00:00:00 2001 From: "remote-swe-app[bot]" <123456+remote-swe-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 05:59:25 +0000 Subject: [PATCH 1/4] refactor: directory structure to feature-based --- webapp/src/app/(root)/actions.ts | 94 +++++++++ .../app/(root)/components/CreateTodoForm.tsx | 112 ++++++++++ webapp/src/app/(root)/components/TodoItem.tsx | 192 ++++++++++++++++++ webapp/src/app/{ => (root)}/page.tsx | 4 +- webapp/src/app/(root)/schemas.ts | 27 +++ 5 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 webapp/src/app/(root)/actions.ts create mode 100644 webapp/src/app/(root)/components/CreateTodoForm.tsx create mode 100644 webapp/src/app/(root)/components/TodoItem.tsx rename webapp/src/app/{ => (root)}/page.tsx (94%) create mode 100644 webapp/src/app/(root)/schemas.ts diff --git a/webapp/src/app/(root)/actions.ts b/webapp/src/app/(root)/actions.ts new file mode 100644 index 0000000..22d7b85 --- /dev/null +++ b/webapp/src/app/(root)/actions.ts @@ -0,0 +1,94 @@ +'use server'; + +import { authActionClient } from '@/lib/safe-action'; +import { + createTodoSchema, + deleteTodoSchema, + runTranslateJobSchema, + updateTodoSchema, + updateTodoStatusSchema, +} from './schemas'; +import { prisma } from '@/lib/prisma'; +import { revalidatePath } from 'next/cache'; +import { TodoItemStatus } from '@prisma/client'; +import { runJob } from '@/lib/jobs'; + +export const createTodo = authActionClient.schema(createTodoSchema).action(async ({ parsedInput, ctx }) => { + const { title, description } = parsedInput; + const { userId } = ctx; + + const todo = await prisma.todoItem.create({ + data: { + title, + description, + userId, + status: TodoItemStatus.PENDING, + }, + }); + + revalidatePath('/'); + return { todo }; +}); + +export const updateTodo = authActionClient.schema(updateTodoSchema).action(async ({ parsedInput, ctx }) => { + const { id, title, description, status } = parsedInput; + const { userId } = ctx; + + const todo = await prisma.todoItem.update({ + where: { + id, + userId, + }, + data: { + title, + description, + status, + }, + }); + + revalidatePath('/'); + return { todo }; +}); + +export const deleteTodo = authActionClient.schema(deleteTodoSchema).action(async ({ parsedInput, ctx }) => { + const { id } = parsedInput; + const { userId } = ctx; + + await prisma.todoItem.delete({ + where: { + id, + userId, + }, + }); + + revalidatePath('/'); + return { success: true }; +}); + +export const updateTodoStatus = authActionClient.schema(updateTodoStatusSchema).action(async ({ parsedInput, ctx }) => { + const { id, status } = parsedInput; + const { userId } = ctx; + + const todo = await prisma.todoItem.update({ + where: { + id, + userId, + }, + data: { + status, + }, + }); + + revalidatePath('/'); + return { todo }; +}); + +export const runTranslateJob = authActionClient.schema(runTranslateJobSchema).action(async ({ parsedInput, ctx }) => { + const { id } = parsedInput; + const { userId } = ctx; + await runJob({ + type: 'translate', + todoItemId: id, + userId: userId, + }); +}); diff --git a/webapp/src/app/(root)/components/CreateTodoForm.tsx b/webapp/src/app/(root)/components/CreateTodoForm.tsx new file mode 100644 index 0000000..ed6ea6c --- /dev/null +++ b/webapp/src/app/(root)/components/CreateTodoForm.tsx @@ -0,0 +1,112 @@ +'use client'; + +import { useState } from 'react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useHookFormAction } from '@next-safe-action/adapter-react-hook-form/hooks'; +import { createTodo } from '../actions'; +import { createTodoSchema } from '../schemas'; +import { toast } from 'sonner'; +import { useEventBus } from '@/hooks/use-event-bus'; +import { useRouter } from 'next/navigation'; + +export default function CreateTodoForm(props: { userId: string }) { + const [isFormOpen, setIsFormOpen] = useState(false); + const router = useRouter(); + + useEventBus({ + channelName: `user/${props.userId}/jobs`, + onReceived: (data) => { + console.log('received', data); + router.refresh(); + }, + }); + + const { + form: { register, formState, reset }, + action, + handleSubmitWithAction, + } = useHookFormAction(createTodo, zodResolver(createTodoSchema), { + actionProps: { + onSuccess: () => { + toast.success('Todo created successfully'); + reset(); + setIsFormOpen(false); + }, + onError: ({ error }) => { + toast.error(typeof error === 'string' ? error : 'Failed to create todo'); + }, + }, + formProps: { + defaultValues: { + title: '', + description: '', + }, + }, + }); + + if (!isFormOpen) { + return ( +
+ +
+ ); + } + + return ( +
+

Create New Todo

+
+
+ + + {formState.errors.title &&

{formState.errors.title.message}

} +
+ +
+ +