Skip to content

Commit ed750a9

Browse files
author
remote-swe-app[bot]
committed
refactor: directory structure to feature-based
1 parent 3ad51f3 commit ed750a9

5 files changed

Lines changed: 427 additions & 2 deletions

File tree

webapp/src/app/(root)/actions.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use server';
2+
3+
import { authActionClient } from '@/lib/safe-action';
4+
import {
5+
createTodoSchema,
6+
deleteTodoSchema,
7+
runTranslateJobSchema,
8+
updateTodoSchema,
9+
updateTodoStatusSchema,
10+
} from './schemas';
11+
import { prisma } from '@/lib/prisma';
12+
import { revalidatePath } from 'next/cache';
13+
import { TodoItemStatus } from '@prisma/client';
14+
import { runJob } from '@/lib/jobs';
15+
16+
export const createTodo = authActionClient.schema(createTodoSchema).action(async ({ parsedInput, ctx }) => {
17+
const { title, description } = parsedInput;
18+
const { userId } = ctx;
19+
20+
const todo = await prisma.todoItem.create({
21+
data: {
22+
title,
23+
description,
24+
userId,
25+
status: TodoItemStatus.PENDING,
26+
},
27+
});
28+
29+
revalidatePath('/');
30+
return { todo };
31+
});
32+
33+
export const updateTodo = authActionClient.schema(updateTodoSchema).action(async ({ parsedInput, ctx }) => {
34+
const { id, title, description, status } = parsedInput;
35+
const { userId } = ctx;
36+
37+
const todo = await prisma.todoItem.update({
38+
where: {
39+
id,
40+
userId,
41+
},
42+
data: {
43+
title,
44+
description,
45+
status,
46+
},
47+
});
48+
49+
revalidatePath('/');
50+
return { todo };
51+
});
52+
53+
export const deleteTodo = authActionClient.schema(deleteTodoSchema).action(async ({ parsedInput, ctx }) => {
54+
const { id } = parsedInput;
55+
const { userId } = ctx;
56+
57+
await prisma.todoItem.delete({
58+
where: {
59+
id,
60+
userId,
61+
},
62+
});
63+
64+
revalidatePath('/');
65+
return { success: true };
66+
});
67+
68+
export const updateTodoStatus = authActionClient.schema(updateTodoStatusSchema).action(async ({ parsedInput, ctx }) => {
69+
const { id, status } = parsedInput;
70+
const { userId } = ctx;
71+
72+
const todo = await prisma.todoItem.update({
73+
where: {
74+
id,
75+
userId,
76+
},
77+
data: {
78+
status,
79+
},
80+
});
81+
82+
revalidatePath('/');
83+
return { todo };
84+
});
85+
86+
export const runTranslateJob = authActionClient.schema(runTranslateJobSchema).action(async ({ parsedInput, ctx }) => {
87+
const { id } = parsedInput;
88+
const { userId } = ctx;
89+
await runJob({
90+
type: 'translate',
91+
todoItemId: id,
92+
userId: userId,
93+
});
94+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { zodResolver } from '@hookform/resolvers/zod';
5+
import { useHookFormAction } from '@next-safe-action/adapter-react-hook-form/hooks';
6+
import { createTodo } from '../actions';
7+
import { createTodoSchema } from '../schemas';
8+
import { toast } from 'sonner';
9+
import { useEventBus } from '@/hooks/use-event-bus';
10+
import { useRouter } from 'next/navigation';
11+
12+
export default function CreateTodoForm(props: { userId: string }) {
13+
const [isFormOpen, setIsFormOpen] = useState(false);
14+
const router = useRouter();
15+
16+
useEventBus({
17+
channelName: `user/${props.userId}/jobs`,
18+
onReceived: (data) => {
19+
console.log('received', data);
20+
router.refresh();
21+
},
22+
});
23+
24+
const {
25+
form: { register, formState, reset },
26+
action,
27+
handleSubmitWithAction,
28+
} = useHookFormAction(createTodo, zodResolver(createTodoSchema), {
29+
actionProps: {
30+
onSuccess: () => {
31+
toast.success('Todo created successfully');
32+
reset();
33+
setIsFormOpen(false);
34+
},
35+
onError: ({ error }) => {
36+
toast.error(typeof error === 'string' ? error : 'Failed to create todo');
37+
},
38+
},
39+
formProps: {
40+
defaultValues: {
41+
title: '',
42+
description: '',
43+
},
44+
},
45+
});
46+
47+
if (!isFormOpen) {
48+
return (
49+
<div className="mb-6">
50+
<button
51+
onClick={() => setIsFormOpen(true)}
52+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
53+
>
54+
+ Add New Todo
55+
</button>
56+
</div>
57+
);
58+
}
59+
60+
return (
61+
<div className="bg-white shadow-md rounded-lg p-6 mb-6">
62+
<h2 className="text-lg font-medium text-gray-900 mb-4">Create New Todo</h2>
63+
<form onSubmit={handleSubmitWithAction} className="space-y-4">
64+
<div>
65+
<label htmlFor="title" className="block text-sm font-medium text-gray-700">
66+
Title
67+
</label>
68+
<input
69+
id="title"
70+
{...register('title')}
71+
placeholder="Your TODO item title."
72+
className="mt-1 p-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
73+
/>
74+
{formState.errors.title && <p className="mt-1 text-sm text-red-600">{formState.errors.title.message}</p>}
75+
</div>
76+
77+
<div>
78+
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
79+
Description
80+
</label>
81+
<textarea
82+
id="description"
83+
{...register('description')}
84+
rows={3}
85+
placeholder="Describe your TODO item."
86+
className="mt-1 p-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
87+
/>
88+
{formState.errors.description && (
89+
<p className="mt-1 text-sm text-red-600">{formState.errors.description.message}</p>
90+
)}
91+
</div>
92+
93+
<div className="flex justify-end space-x-2">
94+
<button
95+
type="button"
96+
onClick={() => setIsFormOpen(false)}
97+
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-gray-700 bg-gray-200 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
98+
>
99+
Cancel
100+
</button>
101+
<button
102+
type="submit"
103+
disabled={action.isExecuting}
104+
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
105+
>
106+
{action.isExecuting ? 'Creating...' : 'Create Todo'}
107+
</button>
108+
</div>
109+
</form>
110+
</div>
111+
);
112+
}

0 commit comments

Comments
 (0)