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/2] 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 (
+
+ );
+}
diff --git a/webapp/src/app/(root)/components/TodoItem.tsx b/webapp/src/app/(root)/components/TodoItem.tsx
new file mode 100644
index 0000000..d14d4c0
--- /dev/null
+++ b/webapp/src/app/(root)/components/TodoItem.tsx
@@ -0,0 +1,192 @@
+'use client';
+
+import { TodoItem, TodoItemStatus } from '@prisma/client';
+import { useState } from 'react';
+import { useHookFormAction } from '@next-safe-action/adapter-react-hook-form/hooks';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { updateTodo, deleteTodo, updateTodoStatus, runTranslateJob } from '../actions';
+import { updateTodoSchema } from '../schemas';
+import { useAction } from 'next-safe-action/hooks';
+import { toast } from 'sonner';
+
+interface TodoItemProps {
+ todo: TodoItem;
+}
+
+export default function TodoItemComponent({ todo }: TodoItemProps) {
+ const [isEditing, setIsEditing] = useState(false);
+ const { execute, status: translateStatus } = useAction(runTranslateJob, {
+ onSuccess: () => {
+ toast.success('Translation job started successfully');
+ },
+ onError: (error) => {
+ toast.error(typeof error === 'string' ? error : 'Failed to start translation job');
+ },
+ });
+
+ // Update Todo Form Action with React Hook Form
+ const {
+ form: { register: registerUpdate, formState: updateFormState },
+ action: updateAction,
+ handleSubmitWithAction: handleUpdate,
+ } = useHookFormAction(updateTodo, zodResolver(updateTodoSchema), {
+ actionProps: {
+ onSuccess: () => {
+ toast.success('Todo updated successfully');
+ setIsEditing(false);
+ },
+ onError: ({ error }) => {
+ toast.error(typeof error === 'string' ? error : 'Failed to update todo');
+ },
+ },
+ formProps: {
+ defaultValues: {
+ id: todo.id,
+ title: todo.title,
+ description: todo.description,
+ status: todo.status,
+ },
+ },
+ });
+
+ // Delete Todo Action - Simple action without form
+ const { execute: executeDelete, status: deleteStatus } = useAction(deleteTodo, {
+ onSuccess: () => {
+ toast.success('Todo deleted successfully');
+ },
+ onError: (error) => {
+ toast.error(typeof error === 'string' ? error : 'Failed to delete todo');
+ },
+ });
+
+ // Update Status Action - Simple action without form
+ const { execute: executeStatusUpdate, status: statusStatus } = useAction(updateTodoStatus, {
+ onSuccess: () => {
+ toast.success('Status updated successfully');
+ },
+ onError: (error) => {
+ toast.error(typeof error === 'string' ? error : 'Failed to update status');
+ },
+ });
+
+ const handleDelete = () => {
+ if (confirm('Are you sure you want to delete this todo?')) {
+ executeDelete({ id: todo.id });
+ }
+ };
+
+ const toggleStatus = () => {
+ const newStatus = todo.status === TodoItemStatus.PENDING ? TodoItemStatus.COMPLETED : TodoItemStatus.PENDING;
+
+ executeStatusUpdate({
+ id: todo.id,
+ status: newStatus,
+ });
+ };
+
+ if (isEditing) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {todo.title}
+
+
+ {todo.description}
+
+
Created: {new Date(todo.createdAt).toLocaleDateString()}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/webapp/src/app/page.tsx b/webapp/src/app/(root)/page.tsx
similarity index 94%
rename from webapp/src/app/page.tsx
rename to webapp/src/app/(root)/page.tsx
index 125ec02..2bd439b 100644
--- a/webapp/src/app/page.tsx
+++ b/webapp/src/app/(root)/page.tsx
@@ -1,7 +1,7 @@
import { prisma } from '@/lib/prisma';
import { getSession } from '@/lib/auth';
-import TodoItemComponent from '@/components/TodoItem';
-import CreateTodoForm from '@/components/CreateTodoForm';
+import TodoItemComponent from './components/TodoItem';
+import CreateTodoForm from './components/CreateTodoForm';
import { TodoItemStatus } from '@prisma/client';
import Header from '@/components/Header';
diff --git a/webapp/src/app/(root)/schemas.ts b/webapp/src/app/(root)/schemas.ts
new file mode 100644
index 0000000..b2cc019
--- /dev/null
+++ b/webapp/src/app/(root)/schemas.ts
@@ -0,0 +1,27 @@
+import { z } from 'zod';
+import TodoItemStatusSchema from '@/lib/generated/prisma/zod/inputTypeSchemas/TodoItemStatusSchema';
+
+export const createTodoSchema = z.object({
+ title: z.string().min(1, 'Title is required'),
+ description: z.string().min(1, 'Description is required'),
+});
+
+export const updateTodoSchema = z.object({
+ id: z.string().uuid(),
+ title: z.string().min(1, 'Title is required'),
+ description: z.string().min(1, 'Description is required'),
+ status: TodoItemStatusSchema,
+});
+
+export const deleteTodoSchema = z.object({
+ id: z.string().uuid(),
+});
+
+export const updateTodoStatusSchema = z.object({
+ id: z.string().uuid(),
+ status: TodoItemStatusSchema,
+});
+
+export const runTranslateJobSchema = z.object({
+ id: z.string().uuid(),
+});
From 7b24d290409e124f4e04d6ce0194cfdd8c8e6f09 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 06:07:43 +0000
Subject: [PATCH 2/2] chore: remove unnecessary files after directory
restructuring
---
webapp/src/actions/schemas/todo.ts | 27 ----
webapp/src/actions/todo.ts | 94 -----------
webapp/src/components/CreateTodoForm.tsx | 112 -------------
webapp/src/components/TodoItem.tsx | 192 -----------------------
4 files changed, 425 deletions(-)
delete mode 100644 webapp/src/actions/schemas/todo.ts
delete mode 100644 webapp/src/actions/todo.ts
delete mode 100644 webapp/src/components/CreateTodoForm.tsx
delete mode 100644 webapp/src/components/TodoItem.tsx
diff --git a/webapp/src/actions/schemas/todo.ts b/webapp/src/actions/schemas/todo.ts
deleted file mode 100644
index b2cc019..0000000
--- a/webapp/src/actions/schemas/todo.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { z } from 'zod';
-import TodoItemStatusSchema from '@/lib/generated/prisma/zod/inputTypeSchemas/TodoItemStatusSchema';
-
-export const createTodoSchema = z.object({
- title: z.string().min(1, 'Title is required'),
- description: z.string().min(1, 'Description is required'),
-});
-
-export const updateTodoSchema = z.object({
- id: z.string().uuid(),
- title: z.string().min(1, 'Title is required'),
- description: z.string().min(1, 'Description is required'),
- status: TodoItemStatusSchema,
-});
-
-export const deleteTodoSchema = z.object({
- id: z.string().uuid(),
-});
-
-export const updateTodoStatusSchema = z.object({
- id: z.string().uuid(),
- status: TodoItemStatusSchema,
-});
-
-export const runTranslateJobSchema = z.object({
- id: z.string().uuid(),
-});
diff --git a/webapp/src/actions/todo.ts b/webapp/src/actions/todo.ts
deleted file mode 100644
index 0d0e0c2..0000000
--- a/webapp/src/actions/todo.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-'use server';
-
-import { authActionClient } from '@/lib/safe-action';
-import {
- createTodoSchema,
- deleteTodoSchema,
- runTranslateJobSchema,
- updateTodoSchema,
- updateTodoStatusSchema,
-} from './schemas/todo';
-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/components/CreateTodoForm.tsx b/webapp/src/components/CreateTodoForm.tsx
deleted file mode 100644
index 023562a..0000000
--- a/webapp/src/components/CreateTodoForm.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-'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/todo';
-import { createTodoSchema } from '@/actions/schemas/todo';
-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
-
-
- );
-}
diff --git a/webapp/src/components/TodoItem.tsx b/webapp/src/components/TodoItem.tsx
deleted file mode 100644
index aebb498..0000000
--- a/webapp/src/components/TodoItem.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-'use client';
-
-import { TodoItem, TodoItemStatus } from '@prisma/client';
-import { useState } from 'react';
-import { useHookFormAction } from '@next-safe-action/adapter-react-hook-form/hooks';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { updateTodo, deleteTodo, updateTodoStatus, runTranslateJob } from '@/actions/todo';
-import { updateTodoSchema } from '@/actions/schemas/todo';
-import { useAction } from 'next-safe-action/hooks';
-import { toast } from 'sonner';
-
-interface TodoItemProps {
- todo: TodoItem;
-}
-
-export default function TodoItemComponent({ todo }: TodoItemProps) {
- const [isEditing, setIsEditing] = useState(false);
- const { execute, status: translateStatus } = useAction(runTranslateJob, {
- onSuccess: () => {
- toast.success('Translation job started successfully');
- },
- onError: (error) => {
- toast.error(typeof error === 'string' ? error : 'Failed to start translation job');
- },
- });
-
- // Update Todo Form Action with React Hook Form
- const {
- form: { register: registerUpdate, formState: updateFormState },
- action: updateAction,
- handleSubmitWithAction: handleUpdate,
- } = useHookFormAction(updateTodo, zodResolver(updateTodoSchema), {
- actionProps: {
- onSuccess: () => {
- toast.success('Todo updated successfully');
- setIsEditing(false);
- },
- onError: ({ error }) => {
- toast.error(typeof error === 'string' ? error : 'Failed to update todo');
- },
- },
- formProps: {
- defaultValues: {
- id: todo.id,
- title: todo.title,
- description: todo.description,
- status: todo.status,
- },
- },
- });
-
- // Delete Todo Action - Simple action without form
- const { execute: executeDelete, status: deleteStatus } = useAction(deleteTodo, {
- onSuccess: () => {
- toast.success('Todo deleted successfully');
- },
- onError: (error) => {
- toast.error(typeof error === 'string' ? error : 'Failed to delete todo');
- },
- });
-
- // Update Status Action - Simple action without form
- const { execute: executeStatusUpdate, status: statusStatus } = useAction(updateTodoStatus, {
- onSuccess: () => {
- toast.success('Status updated successfully');
- },
- onError: (error) => {
- toast.error(typeof error === 'string' ? error : 'Failed to update status');
- },
- });
-
- const handleDelete = () => {
- if (confirm('Are you sure you want to delete this todo?')) {
- executeDelete({ id: todo.id });
- }
- };
-
- const toggleStatus = () => {
- const newStatus = todo.status === TodoItemStatus.PENDING ? TodoItemStatus.COMPLETED : TodoItemStatus.PENDING;
-
- executeStatusUpdate({
- id: todo.id,
- status: newStatus,
- });
- };
-
- if (isEditing) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
- {todo.title}
-
-
- {todo.description}
-
-
Created: {new Date(todo.createdAt).toLocaleDateString()}
-
-
-
-
-
-
-
-
-
- );
-}