Files
medreport_mrb2b/.windsurfrules

225 lines
5.7 KiB
Plaintext

# Makerkit Guidelines
## Project Stack
- Framework: Next.js 15 App Router, TypeScript, React, Node.js
- Backend: Supabase with Postgres
- UI: Shadcn UI, Tailwind CSS
- Key libraries: React Hook Form, React Query, Zod, Lucide React
- Focus: Code clarity, Readability, Best practices, Maintainability
## Project Structure
```
/app
/home # protected routes
/(user) # user workspace
/[account] # team workspace
/(marketing) # marketing pages
/auth # auth pages
/components # global components
/config # global config
/lib # global utils
/content # markdoc content
/supabase # supabase root
```
## Core Principles
### Data Flow
1. Server Components
- Use Supabase Client directly via `getSupabaseServerClient`
- Handle errors with proper boundaries
- Example:
```tsx
async function ServerComponent() {
const client = getSupabaseServerClient();
const { data, error } = await client.from('notes').select('*');
if (error) return <ErrorComponent error={error} />;
return <ClientComponent data={data} />;
}
```
2. Client Components
- Use React Query for data fetching
- Implement proper loading states
- Example:
```tsx
function useNotes() {
const { data, isLoading } = useQuery({
queryKey: ['notes'],
queryFn: async () => {
const { data } = await fetch('/api/notes');
return data;
}
});
return { data, isLoading };
}
```
### Server Actions
- Name files as "server-actions.ts" in `_lib/server` folder
- Export with "Action" suffix
- Use `enhanceAction` with proper typing
- Example:
```tsx
export const createNoteAction = enhanceAction(
async function (data, user) {
const client = getSupabaseServerClient();
const { error } = await client
.from('notes')
.insert({ ...data, user_id: user.id });
if (error) throw error;
return { success: true };
},
{
auth: true,
schema: NoteSchema,
}
);
```
### Route Handlers
- Use `enhanceRouteHandler` to wrap route handlers
- Use Route Handlers when data fetching from Client Components
## Database & Security
### RLS Policies
- Strive to create a safe, robust, secure and consistent database schema
- Always consider the compromises you need to make and explain them so I can make an educated decision. Follow up with the considerations make and explain them.
- Enable RLS by default and propose the required RLS policies
- `public.accounts` are the root tables for the application
- Implement cascading deletes when appropriate
- Ensure strong consistency considering triggers and constraints
- Always use Postgres schemas explicitly (e.g., `public.accounts`)
## Forms Pattern
### 1. Schema Definition
```tsx
// schema/note.schema.ts
import { z } from 'zod';
export const NoteSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
category: z.enum(['work', 'personal']),
});
```
### 2. Form Component
```tsx
'use client';
export function NoteForm() {
const [pending, startTransition] = useTransition();
const form = useForm({
resolver: zodResolver(NoteSchema),
defaultValues: { title: '', content: '', category: 'personal' }
});
const onSubmit = (data: z.infer<typeof NoteSchema>) => {
startTransition(async () => {
try {
await createNoteAction(data);
form.reset();
} catch (error) {
// Handle error
}
});
};
return (
<Form {...form}>
<FormField name="title" render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)} />
{/* Other fields */}
</Form>
);
}
```
## Error Handling
- Consider logging asynchronous requests in server code using the `@kit/shared/logger`
- Handle promises and async/await gracefully
- Consider the unhappy path and handle errors appropriately
### Structured Logging
```tsx
const ctx = {
name: 'create-note',
userId: user.id,
noteId: note.id
};
logger.info(ctx, 'Creating new note...');
try {
await createNote();
logger.info(ctx, 'Note created successfully');
} catch (error) {
logger.error(ctx, 'Failed to create note', { error });
throw error;
}
```
## Context Management
In client components, we can use the `useUserWorkspace` hook to access the user's workspace data.
### Personal Account
```tsx
'use client';
function PersonalDashboard() {
const { workspace, user } = useUserWorkspace();
if (!workspace) return null;
return (
<div>
<h1>Welcome, {user.email}</h1>
<SubscriptionStatus status={workspace.subscription_status} />
</div>
);
}
```
### Team Account
In client components, we can use the `useTeamAccountWorkspace` hook to access the team account's workspace data. It only works under the `/home/[account]` route.
```tsx
'use client';
function TeamDashboard() {
const { account, user } = useTeamAccountWorkspace();
return (
<div>
<h1>{account.name}</h1>
<RoleDisplay role={account.role} />
<PermissionsList permissions={account.permissions} />
</div>
);
}
```
## UI Components
- Reusable UI components are defined in the "packages/ui" package named "@kit/ui".
- By exporting the component from the "exports" field, we can import it using the "@kit/ui/{component-name}" format.
## Creating Pages
When creating new pages ensure:
- The page is exported using `withI18n(Page)` to enable i18n.
- The page has the required and correct metadata using the `metadata` or `generateMetadata` function.
- Don't worry about authentication, it's handled in the middleware.