-
Notifications
You must be signed in to change notification settings - Fork 317
/
.cursorrules
137 lines (122 loc) · 4.96 KB
/
.cursorrules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
You are an expert in TypeScript, Node.js, Next.js App Router, React, Prisma, PostgreSQL, Turborepo, Shadcn UI, Radix UI and Tailwind.
- Write concise, technical TypeScript code with accurate examples.
- Use functional and declarative programming patterns; avoid classes.
- Prefer iteration and modularization over code duplication. But if you don't have a proper abstraction then writing similar code twice is fine.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
- Structure files: exported component, subcomponents, helpers, static content, types.
- Use kebab case for route directories (e.g., `api/hello-world/route`). Use PascalCase for components (e.g. `components/Button.tsx`)
- Shadcn components are in `components/ui`. All other components are in `components/`.
- Colocate files in the folder they're used. Unless the component can be used in many places across the app. Then it can be placed in the `components` folder.
- We use Turborepo with pnpm workspaces.
- We have one app called `web` in `apps/web`.
- We have packages in the `packages` folder.
- We have Next.js server actions in the `apps/web/utils/actions` folder. Eg. `apps/web/utils/actions/cold-email` has actions related to cold email management.
- Favor named exports for components.
- Use TypeScript for all code.
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
- Use Shadcn UI and Tailwind for components and styling.
- Implement responsive design with Tailwind CSS; use a mobile-first approach.
- Use `next/image` package for images.
- For API get requests to server use the `swr` package. Example:
```typescript
const searchParams = useSearchParams();
const page = searchParams.get("page") || "1";
const { data, isLoading, error } = useSWR<PlanHistoryResponse>(
`/api/user/planned/history?page=${page}`
);
```
- Use the `Loading` component. Example:
```tsx
<Card>
<LoadingContent loading={isLoading} error={error}>
{data && <MyComponent data={data} />}
</LoadingContent>
</Card>
```
- If we're in a server file, you can fetch the data inline.
- For mutating data, use Next.js server actions.
- Use `isActionError` with `toastError` and `toastSuccess`. Success toast is optional. Example:
```typescript
import { toastError, toastSuccess } from "@/components/Toast";
const onSubmit: SubmitHandler<TestRulesInputs> = useCallback(async (data) => {
const result = await testAiCustomContentAction({ content: data.message });
if (isActionError(result)) {
toastError({
title: "Error testing email",
description: result.error,
});
} else {
toastSuccess({ description: "Saved!" });
}
}, []);
```
- Implement type-safe server actions with proper validation.
- Define input schemas using Zod for robust type checking and validation.
- Handle errors gracefully and return appropriate responses.
- Ensure all server actions return the `Promise<ServerActionResponse>` type
- Use React Hook Form with Zod for validation. The same validation should be done in the server action too. Example:
```tsx
import { Input } from "@/components/Input";
import { Button } from "@/components/ui/button";
export const ProcessHistory = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<ProcessHistoryOptions>({
resolver: zodResolver(processHistorySchema),
});
const onSubmit: SubmitHandler<ProcessHistoryOptions> = useCallback(
async (data) => {
const result = await processHistoryAction(data.email);
handleActionResult(result, `Processed history for ${data.email}`);
},
[]
);
return (
<form className="max-w-sm space-y-4" onSubmit={handleSubmit(onSubmit)}>
<Input
type="email"
name="email"
label="Email"
registerProps={register("email", { required: true })}
error={errors.email}
/>
<Button type="submit" loading={isSubmitting}>
Process History
</Button>
</form>
);
};
```
- This is how you do a text area. Example:
```tsx
<Input
type="text"
autosizeTextarea
rows={3}
name="message"
placeholder="Paste in email content"
registerProps={register("message", { required: true })}
error={errors.message}
/>
```
- This is the format of a server action. Example:
```typescript
export const deactivateApiKeyAction = withActionInstrumentation(
"deactivateApiKey",
async (unsafeData: DeactivateApiKeyBody) => {
const session = await auth();
const userId = session?.user.id;
if (!userId) return { error: "Not logged in" };
const { data, success, error } =
deactivateApiKeyBody.safeParse(unsafeData);
if (!success) return { error: error.message };
await prisma.apiKey.update({
where: { id: data.id, userId },
data: { isActive: false },
});
revalidatePath("/settings");
}
);
```