Update documentation rules for various contexts and functionalities (#235)

Update cusor rules for various contexts and functionalities
This commit is contained in:
Giancarlo Buomprisco
2025-04-16 09:11:20 +07:00
committed by GitHub
parent 53b09fcb8e
commit 1030c84eee
17 changed files with 854 additions and 276 deletions

View File

@@ -1,9 +1,8 @@
---
description: Personal Accounts context and functionality
globs: apps/*/app/home/(user),packages/features/accounts/**
globs:
alwaysApply: false
---
# Personal Account Context
This rule provides guidance for working with personal account related components in the application.

View File

@@ -3,7 +3,6 @@ description: Fetch data from the Database using the Supabase Clients
globs: apps/**,packages/**
alwaysApply: false
---
# Data Fetching
## General Data Flow

View File

@@ -3,7 +3,6 @@ description: Detailed Database Schema and Architecture
globs:
alwaysApply: true
---
# Database Rules
## Database Architecture
@@ -12,10 +11,15 @@ alwaysApply: true
- Accounts are the general concept of a user account, defined by the having the same ID as Supabase Auth's users (personal). They can be a team account or a personal account.
- Generally speaking, other tables will be used to store data related to the account. For example, a table `notes` would have a foreign key `account_id` to link it to an account.
## Schemas
- The DB schemas are available at `apps/web/supabase/schemas`
- To edit the DB schema, we can either change the schema files, or created new ones
- To create a new schema, create a file at `apps/web/supabase/schemas/<number>-<name>.sql`
## Migrations
- Migration files are placed at `apps/<app>/supabase/migrations`
- The main migration schema can be found at [20221215192558_schema.sql](mdc:apps/web/supabase/migrations/20221215192558_schema.sql)
- Use the command `pnpm --filter web supabase migrations new <name>` for creating well timestamped migrations
- After creating a schema, we can create a migration
- Use the command `pnpm --filter web supabase:db:diff` for creating migrations from schemas
- After generating a migration, reset the database for applying the changes using the command `pnpm --filter web supabase:db:reset`
## Security & RLS
- Using RLS, we must ensure that only the account owner can access the data. Always write safe RLS policies and ensure that the policies are enforced.
@@ -25,94 +29,67 @@ alwaysApply: true
- Always consider the security of the data and explain the security implications of the data.
- Always use Postgres schemas explicitly (e.g., `public.accounts`)
- Consider the required compromises between simplicity, functionality and developer experience. However, never compromise on security, which is paramount and fundamental.
- Use existing helper functions for access control instead of making your own queries, unless unavailable
## Schema Overview
Makerkit uses a Supabase Postgres database with a well-defined schema focused on multi-tenancy through the concepts of accounts (both personal and team) and robust permission systems.
### Core Entity Relationships
### Database Schema
1. **User ↔ Account**:
- Each user has a personal account (1:1)
- Users can belong to multiple team accounts (M:N through `accounts_memberships`)
- Accounts can have multiple users (M:N through `accounts_memberships`)
2. **Account ↔ Role**:
- Each user has a role in each account they belong to
- Roles define permissions through `role_permissions`
3. **Subscription System**:
- Accounts can have subscriptions
- Subscriptions have multiple subscription items
- Billing providers include Stripe, Lemon Squeezy, and Paddle
4. **Invitation System**:
- Team accounts can invite users via email
- Invitations specify roles for the invited user
5. **One-Time Tokens**:
- Used for secure verification processes
- Generic system that can be used for various purposes
## Table Relationships
```
auth.users
├── public.accounts (personal_account=true, id=user_id)
└── public.accounts_memberships
└── public.accounts (personal_account=false)
└── public.roles (hierarchy_level)
└── public.role_permissions
└── app_permissions (enum)
```
```
public.accounts
├── public.billing_customers
│ └── public.subscriptions
│ └── public.subscription_items
└── public.invitations
```
```
public.nonces
└── auth.users (optional relationship)
```
## Schema Overview
Makerkit implements a multi-tenant SaaS architecture through a robust account and permission system:
1. **Core Entities**:
- `auth.users`: Supabase Auth users
- `public.accounts`: Both personal and team accounts
- `public.accounts_memberships`: Links users to accounts with roles
- `public.roles` and `public.role_permissions`: Define permission hierarchy
- `public.invitations`: For inviting users to team accounts
2. **Billing System**:
- `public.billing_customers`: Account's connection to billing providers
- `public.subscriptions` and `public.subscription_items`: For subscription tracking
- `public.orders` and `public.order_items`: For one-time purchases
3. **Security**:
- `public.nonces`: One-time tokens for secure operations
1. Enums [01-enums.sql](mdc:apps/web/supabase/schemas/01-enums.sql)
2. Config [02-config.sql](mdc:apps/web/supabase/schemas/02-config.sql)
3. Accounts [03-accounts.sql](mdc:apps/web/supabase/schemas/03-accounts.sql)
4. Roles [04-roles.sql](mdc:apps/web/supabase/schemas/04-roles.sql)
5. Memberships [05-memberships.sql](mdc:apps/web/supabase/schemas/05-memberships.sql)
6. Roles Permissions [06-roles-permissions.sql](mdc:apps/web/supabase/schemas/06-roles-permissions.sql)
7. Invitations [07-invitations.sql](mdc:apps/web/supabase/schemas/07-invitations.sql)
8. Billing Customers [08-billing-customers.sql](mdc:apps/web/supabase/schemas/08-billing-customers.sql)
9. Subscriptions [09-subscriptions.sql](mdc:apps/web/supabase/schemas/09-subscriptions.sql)
10. Orders [10-orders.sql](mdc:apps/web/supabase/schemas/10-orders.sql)
11. Notifications [11-notifications.sql](mdc:apps/web/supabase/schemas/11-notifications.sql)
12. One Time Tokens [12-one-time-tokens.sql](mdc:apps/web/supabase/schemas/12-one-time-tokens.sql)
13. Multi Factor Auth [13-mfa.sql](mdc:apps/web/supabase/schemas/13-mfa.sql)
14. Super Admin [14-super-admin.sql](mdc:apps/web/supabase/schemas/14-super-admin.sql)
15. Account Views [15-account-views.sql](mdc:apps/web/supabase/schemas/15-account-views.sql)
16. Storage [16-storage.sql](mdc:apps/web/supabase/schemas/16-storage.sql)
## Database Best Practices
### Inferring Database types
Fetch auto-generated data types using the `@kit/supabase/database` import. Do not write types manually if the shape is the same as the one from the database row.
```tsx
import { Tables } from '@kit/supabase/database';
// public.accounts
type Account = Tables<'accounts'>;
// public.subscriptions
type Subscription = Tables<'subscriptions'>;
// public.notifications
type Notification = Tables<'notifications'>;
// ...
```
### Security
- **Always enable RLS** on new tables unless explicitly instructed otherwise
- **Create proper RLS policies** for all CRUD operations following existing patterns
- **Always associate data with accounts** using a foreign key to ensure proper access control
- **Use explicit schema references** (`public.table_name` not just `table_name`)
- **Place internal functions in the `kit` schema**
- **Private schema**: Place internal functions in the `kit` schema
- **Search Path**: Always set search path to '' when defining functions
- **Security Definer**: Do not use `security definer` functions unless stricly required
### Data Access Patterns
- Use `has_role_on_account(account_id, role?)` to check membership
- Use `has_permission(user_id, account_id, permission)` for permission checks
- Use `is_account_owner(account_id)` to identify account ownership
- Use `public.has_role_on_account(account_id, role?)` to check membership
- Use `public.has_permission(user_id, account_id, permission)` for permission checks
- Use `public.is_account_owner(account_id)` to identify account ownership
### SQL Coding Style
@@ -136,12 +113,15 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
### 1. RLS Policy Management
- **Always Enable RLS**: Always enable RLS for your tables unless you have a specific reason not to.
#### Always Enable RLS
Always enable RLS for your tables unless you have a specific reason not to.
```sql
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY;
```
- **Follow the Standard Policies Pattern**: Use the existing structure for policies:
#### Use Helper Functions to validate permissions and access control
Use the existing structure for policies:
```sql
-- SELECT policy
CREATE POLICY "my_table_read" ON public.my_table FOR SELECT
@@ -153,11 +133,19 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
-- INSERT/UPDATE/DELETE policies follow similar patterns
```
When using RLS at team-account level, use `public.has_role_on_account(account_id)` for a generic check to understand if a user is part of a team.
When using RLS at user-account level, use `account_id = (select auth.uid())`.
When an entity can belong to both, use both.
When requiring a specific role, use the role parameter `public.has_role_on_account(account_id, 'owner')`
### 2. Account Association
- **Associate Data with Accounts**: Always link data to accounts using a foreign key:
```sql
CREATE TABLE public.my_data (
CREATE TABLE if not exists public.my_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_id UUID REFERENCES public.accounts(id) ON DELETE CASCADE NOT NULL,
/* other fields */
@@ -201,14 +189,14 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
```sql
CREATE TYPE public.my_status AS ENUM('active', 'inactive', 'pending');
CREATE TABLE public.my_table (
CREATE TABLE if not exists public.my_table (
status public.my_status NOT NULL DEFAULT 'pending'
);
```
- **Apply Appropriate Constraints**: Use constraints to ensure data integrity:
```sql
CREATE TABLE public.my_table (
CREATE TABLE if not exists public.my_table (
email VARCHAR(255) NOT NULL CHECK (email ~* '^.+@.+\..+$'),
count INTEGER NOT NULL CHECK (count >= 0),
/* other fields */
@@ -265,6 +253,8 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
SELECT ...
```
You always must use `(security_invoker = true)` for views.
## Key Functions to Know
1. **Account Access**

View File

@@ -3,16 +3,18 @@ description: Writing Forms with Shadcn UI, Server Actions, Zod
globs: apps/**/*.tsx,packages/**/*.tsx
alwaysApply: false
---
# Forms
- Use React Hook Form for form validation and submission.
- Use Zod for form validation.
- Use the `zodResolver` function to resolve the Zod schema to the form.
- Use Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) for server-side code handling
- Use Sonner for writing toasters for UI feedback
Follow the example below to create all forms:
## Define the schema
Zod schemas should be defined in the `schema` folder and exported, so we can reuse them across a Server Action and the client-side form:
```tsx
@@ -27,19 +29,15 @@ export const CreateNoteSchema = z.object({
## Create the Server Action
Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) can help us create endpoints for our forms.
```tsx
// _lib/server/server-actions.ts
'use server';
import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions';
import { CreateNoteSchema } from '../schema/create-note.schema';
const CreateNoteSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});
export const createNoteAction = enhanceAction(
async function (data, user) {
// 1. "data" has been validated against the Zod schema, and it's safe to use
@@ -62,18 +60,22 @@ export const createNoteAction = enhanceAction(
Then create a client component to handle the form submission:
```tsx
// _components/create-note-form.tsx
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Textarea } from '@kit/ui/textarea';
import { Input } from '@kit/ui/input';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
import { toast } from '@kit/ui/sonner';
import { useTranslation } from 'react-i18next';
import { CreateNoteSchema } from '../_lib/schema/create-note.schema';
export function CreateNoteForm() {
const [pending, startTransition] = useTransition();
const { t } = useTranslation();
const form = useForm({
resolver: zodResolver(CreateNoteSchema),
@@ -85,58 +87,58 @@ export function CreateNoteForm() {
const onSubmit = (data) => {
startTransition(async () => {
try {
await createNoteAction(data);
} catch {
// handle error
}
await toast.promise(createNoteAction(data), {
loading: t('notes:creatingNote`),
success: t('notes:createNoteSuccess`),
error: t('notes:createNoteError`)
})
});
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<Form {...form}>
<FormField name={'title'} render={({ field }) => (
<FormItem>
<FormLabel>
<span className={'text-sm font-medium'}>Title</span>
</FormLabel>
<Form {...form}>
<FormField name={'title'} render={({ field }) => (
<FormItem>
<FormLabel>
<span className={'text-sm font-medium'}>Title</span>
</FormLabel>
<FormControl>
<input
type={'text'}
className={'w-full'}
placeholder={'Title'}
{...field}
/>
</FormControl>
<FormControl>
<Input
type={'text'}
className={'w-full'}
placeholder={'Title'}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)} />
<FormMessage />
</FormItem>
)} />
<FormField name={'content'} render={({ field }) => (
<FormItem>
<FormLabel>
<span className={'text-sm font-medium'}>Content</span>
</FormLabel>
<FormField name={'content'} render={({ field }) => (
<FormItem>
<FormLabel>
<span className={'text-sm font-medium'}>Content</span>
</FormLabel>
<FormControl>
<textarea
className={'w-full'}
placeholder={'Content'}
{...field}
/>
</FormControl>
<FormControl>
<Textarea
className={'w-full'}
placeholder={'Content'}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)} />
<FormMessage />
</FormItem>
)} />
<button disabled={pending} type={'submit'} className={'w-full'}>
Submit
</button>
</Form>
<button disabled={pending} type={'submit'} className={'w-full'}>
Submit
</button>
</Form>
</form>
);
}

206
.cursor/rules/jsx.mdc Normal file
View File

@@ -0,0 +1,206 @@
---
description:
globs: *.tsx
alwaysApply: false
---
# JSX Best Practices
This guide outlines our conventions for writing clean, maintainable JSX in React applications.
## Utility Functions
### Class Name Management
When merging complex classes, always use the `cn` utility from `clsx`/`tailwind-merge`:
```tsx
import { cn } from '@kit/ui/utils';
// Simple usage
<button className={cn('btn', className)}>Submit</button>
// Conditional classes
<div className={cn('base-class', {
'text-lg': isLarge,
'bg-primary': isPrimary,
'opacity-50': isDisabled
})}>
Content
</div>
// Array syntax for dynamic classes
<span className={cn([
'badge',
variant === 'success' && 'badge-success',
variant === 'error' && 'badge-error'
])}>
{label}
</span>
```
Why use `cn`:
- Handles merging tailwind classes correctly
- Automatically removes duplicate classes
- Resolves conflicting classes by keeping the last one
- Provides type-safety with TypeScript
## Common Patterns
### Conditional Rendering with `If`
Prefer the `If` component to complex ternary operators in JSX:
```tsx
import { If } from '@kit/ui/if';
// Basic usage
<If condition={isLoading}>
<Spinner />
</If>
// With fallback
<If condition={isLoading} fallback={<Content />}>
<Spinner />
</If>
// With callback function for condition match
<If condition={user}>
{(userData) => <UserProfile data={userData} />}
</If>
```
Benefits:
- Improves readability compared to ternary operators
- Type-safe with TypeScript
- Reduces nesting and complexity in JSX
### List Rendering
Consistently use these patterns for list rendering:
```tsx
// Empty state handling, avoid ternaries
{items.length > 0 ? (
<ul className="list">
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
) : (
<EmptyState message="No items found" />
)}
// Even better with If component
<If condition={items.length > 0} fallback={
<EmptyState message="No items found" />
}>
<ul className="list">
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</If>
```
### Using Translations
All user-facing text should use the `Trans` component unless specified otherwise:
```tsx
import { Trans } from '@kit/ui/trans';
// Basic usage
<Trans i18nKey="common:welcomeMessage" defaults="Welcome!" />
// With variables
<Trans
i18nKey="user:lastLogin"
values={{ date: formatDate(lastLogin) }}
defaults="Last login: {date}"
/>
// With HTML elements
<Trans
i18nKey="terms:agreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
PrivacyLink: <a href="/privacy" className="underline" />
}}
defaults="I agree to the <TermsLink>Terms</TermsLink> and <PrivacyLink>Privacy Policy</PrivacyLink>."
/>
// Pluralization
<Trans
i18nKey="notifications:count"
count={notifications.length}
defaults="{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
/>
```
Important rules:
- Always provide a `defaults` prop with the English text as fallback
- Ensure the key exists in the appropriate translation file
- Keep HTML elements minimal in translations
## Error and Loading States
Use consistent patterns for handling loading and error states:
```tsx
// Loading state
<If condition={isLoading}>
<div className="flex justify-center p-8">
<Spinner />
</div>
</If>
// Error state that infer the type of the condition. The type of the variable "err" is now inferred
// Always use this pattern when the value of the condition is used within the body
<If condition={error}>
{(err) => (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>
<Trans i18nKey="common:errorTitle" />
</AlertTitle>
<AlertDescription>
{err.message}
</AlertDescription>
</Alert>
)}
</If>
// Empty state
<If condition={items.length === 0}>
<div className="flex flex-col items-center justify-center p-8 text-center">
<EmptyIcon className="h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 text-lg font-medium">
<Trans i18nKey="common:noData" />
</h3>
<p className="text-sm text-muted-foreground">
<Trans i18nKey="common:noDataDescription" />
</p>
</div>
</If>
```
## Testing Attributes
Add consistent data attributes for testing:
```tsx
<button data-test="submit-button">
Submit
</button>
<div data-test="user-profile" data-user-id={user.id}>
{/* User profile content */}
</div>
<form data-test="signup-form">
{/* Form fields */}
</form>
```

57
.cursor/rules/logging.mdc Normal file
View File

@@ -0,0 +1,57 @@
---
description: Server side functions logging
globs:
alwaysApply: false
---
## Logging
Consider logging asynchronous requests using the `@kit/shared/logger` [logger.ts](mdc:packages/shared/src/logger/logger.ts) package in a structured way to provide context to the logs in both server actions and route handlers.
The logger uses the following interface:
```tsx
type LogFn = {
<T extends object>(obj: T, msg?: string, ...args: unknown[]): void;
(obj: unknown, msg?: string, ...args: unknown[]): void;
(msg: string, ...args: unknown[]): void;
};
/**
* @name Logger
* @description Logger interface for logging messages
*/
export interface Logger {
info: LogFn;
error: LogFn;
warn: LogFn;
debug: LogFn;
fatal: LogFn;
}
```
Using the logger:
```tsx
import { getLogger } from '@kit/shared/logger';
async function fetchNotes() {
const logger = await getLogger();
const ctx = {
name: 'notes', // use a meaningful name
userId: user.id, // use the authenticated user's ID
};
logger.info(ctx, 'Request started...');
const { data, error } = await supabase.from('notes').select('*');
if (error) {
logger.error({ ...ctx, error }, 'Request failed...');
// handle error
} else {
logger.info(ctx, 'Request succeeded...');
// use data
}
}
```

View File

@@ -3,7 +3,6 @@ description: Creating new Pages in the app
globs: apps/**
alwaysApply: false
---
# Creating Pages
# Makerkit Page & Layout Guidelines
@@ -310,7 +309,7 @@ async function getLayoutState() {
- Define translation keys in the appropriate namespace in `apps/web/public/locales/<locale>/<namespace>.json`
5. **Metadata**:
- Always include `generateMetadata` for SEO
- Always include `generateMetadata` for SEO and UX
- Use translations for page titles and descriptions
6. **Loading States**:

View File

@@ -3,7 +3,6 @@ description: Permissions
globs: apps/**,packages/**
alwaysApply: false
---
# Access Control & Permissions Guidelines
This rule provides guidance for implementing access control, permissions, and subscription-related functionality in the application.
@@ -66,4 +65,4 @@ This rule provides guidance for implementing access control, permissions, and su
### Actions on Members
- Higher roles can update/remove lower roles but not equal or higher roles
- Primary owner cannot be removed from their account
- Ownership transfer requires OTP verification and is limited to primary owners
- Ownership transfer requires OTP verification and is limited to primary owners

View File

@@ -3,7 +3,6 @@ description: Detailed Project Structure of the app
globs: apps/**
alwaysApply: false
---
# Project Structure
```
@@ -219,7 +218,6 @@ apps/web/app/ # Root directory (apps/web/app)
- `/lib/` - Global utilities (not shown)
4. **Data Fetching**
- `*.loader.ts` files - Server-side data fetching functions
- Use of React's `cache()` function for request deduplication
5. **Server Actions**
@@ -234,5 +232,5 @@ apps/web/app/ # Root directory (apps/web/app)
- `route.ts` - API route handlers
7. **Dynamic Routes**
- `[account]` - Dynamic route for team accounts
- `[account]` - Dynamic route for team accounts. The [account] property is the account slug in the table `public.accounts`.
- `[slug]` - Dynamic route for blog posts and documentation

224
.cursor/rules/react.mdc Normal file
View File

@@ -0,0 +1,224 @@
---
description:
globs: *.tsx
alwaysApply: false
---
# React
## Core Principles
- **Component-Driven Development**: Build applications as a composition of isolated, reusable components
- **One-Way Data Flow**: Follow React's unidirectional data flow pattern
- **Single Responsibility**: Each component should have a clear, singular purpose
- **TypeScript First**: Use TypeScript for type safety and better developer experience
- **Internationalization (i18n) By Default**: All user-facing text should be translatable
## React Components
### Component Structure
- Always use functional components with TypeScript
- Name components using PascalCase (e.g., `UserProfile`)
- Use named exports for components, not default exports
- Split components by responsibility and avoid "god components"
- Name files to match their component name (e.g., `user-profile.tsx`)
### Props
- Always type props using TypeScript interfaces or type aliases
- Use discriminated unions for complex prop types with conditional rendering
- Destructure props at the start of component functions
- Use prop spreading cautiously and only when appropriate
- Provide default props for optional parameters when it makes sense
```typescript
type ButtonProps = {
variant: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
disabled?: boolean;
onClick?: () => void;
};
function Button({
variant,
size = 'md',
children,
disabled = false,
onClick
}: ButtonProps) {
// Component implementation
}
```
### State Management
- Keep state as local as possible
- Lift state up when multiple components need access
- Use Context sparingly and only for truly global state
- Prefer the "Container/Presenter" pattern when separating data and UI
```typescript
// Container component (manages data)
function UserProfileContainer() {
const userData = useUserData();
if (userData.isLoading) {
return <LoadingSpinner />;
}
if (userData.error) {
return <ErrorMessage error={userData.error} />;
}
return <UserProfilePresenter data={userData.data} />;
}
// Presenter component (renders UI)
function UserProfilePresenter({ data }: { data: UserData }) {
return (
<div>
<h1>{data.name}</h1>
{/* Rest of the UI */}
</div>
);
}
```
### Hooks
- Follow the Rules of Hooks (only call hooks at the top level, only call them from React functions)
- Create custom hooks for reusable logic
- Keep custom hooks focused on a single concern
- Name custom hooks with a 'use' prefix (e.g., `useUserProfile`)
- Extract complex effect logic into separate functions
- Always provide a complete dependencies array to `useEffect`
### Performance Optimization
- Apply `useMemo` for expensive calculations
- Use `useCallback` for functions passed as props to child components
- Split code using dynamic imports and `React.lazy()`
```typescript
const MemoizedComponent = React.memo(function Component(props: Props) {
// Component implementation
});
// For expensive calculations
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// For callback functions passed as props
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
```
### Internationalization (i18n)
- Always use the `Trans` component for text rendering (no hardcoded strings)
- Ensure all i18n keys are available in locale files
- Use namespaces to organize translations logically
- Include interpolation variables in translation keys
- Test UI with different languages, especially those with longer text
```typescript
// Correct
<Trans i18nKey="user:profile.welcomeMessage" values={{ name: user.name }} />
// Incorrect
<p>Welcome, {user.name}!</p>
```
## Server Components
### Fundamentals
- Server Components render React server-side and never run on the client
- Use Server Components as the default choice, especially for data fetching
- No use of hooks, browser APIs, or event handlers in Server Components
- No use of `useState`, `useEffect`, or any other React hooks
- Server Components can render Client Components but not vice versa
### Data Fetching
- Fetch data directly using async/await in Server Components
- Use Suspense boundaries around data-fetching components
- Apply security checks before fetching sensitive data
- Never pass sensitive data (API keys, tokens) to Client Components
- Use React's `cache()` function for caching data requests
### Error Handling
- Implement error boundaries at appropriate levels
- Use the Next.js `error.tsx` file for route-level error handling
- Create fallback UI for when data fetching fails
- Log server errors appropriately without exposing details to clients
### Streaming and Suspense
- Use React Suspense for progressive loading experiences if specified
- Implement streaming rendering for large or complex pages
- Structure components to enable meaningful loading states
- Prioritize above-the-fold content when using streaming
## Client Components
### Fundamentals
- Add the `'use client'` directive at the top of files for Client Components
- Keep Client Components focused on interactivity and browser APIs
- Use hooks appropriately following the Rules of Hooks
- Implement controlled components for form elements
- Handle all browser events in Client Components
### Data Fetching
- Use React Query (TanStack Query) for data fetching in Client Components
- Create custom hooks for data fetching logic (e.g., `useUserData`)
- Always handle loading, success, and error states
### Form Handling
- Use libraries like React Hook Form for complex forms
- Implement proper validation with libraries like Zod
- Create reusable form components
- Handle form submissions with loading and error states
- Use controlled components for form inputs
### Error Handling
- Implement error boundaries to catch and handle component errors if using client components
- Always handle network request errors
- Provide user-friendly error messages
- Log errors appropriately
- Implement retry mechanisms where applicable
```typescript
'use client';
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
export function UserProfileWithErrorHandling() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// Reset application state here if needed
}}
>
<UserProfile userId="123" />
</ErrorBoundary>
);
}
```

View File

@@ -3,7 +3,6 @@ description: Next.js API Endpoints/Route Handlers
globs: apps/**/route.{ts,tsx}
alwaysApply: false
---
# Route Handler / API Routes
- Use Route Handlers when data fetching from Client Components

View File

@@ -9,20 +9,19 @@ alwaysApply: false
- Always name the server actions file as "server-actions.ts"
- Always name exported Server Actions suffixed as "Action", ex. "createPostAction"
- Always use the `enhanceAction` function from the "@kit/supabase/actions" package [index.ts](mdc:packages/next/src/actions/index.ts)
- Always use the 'use server' directive at the top of the file
- Place the Zod schema in a separate file so it can be reused with `react-hook-form`
```tsx
'use server';
import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions';
const ZodSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
import { EntitySchema } from '../entity.schema.ts`;
export const myServerAction = enhanceAction(
async function (data, user) {
// 1. "data" is already a valid ZodSchema and it's safe to use
// 1. "data" is already a valid EntitySchema and it's safe to use
// 2. "user" is the authenticated user
// ... your code here
@@ -32,30 +31,7 @@ export const myServerAction = enhanceAction(
},
{
auth: true,
schema: ZodSchema,
schema: EntitySchema,
},
);
```
## Logging
Consider logging asynchronous requests using the `@kit/shared/logger` package in a structured way to provide context to the logs in both server actions and route handlers.
```tsx
const ctx = {
name: 'my-server-action', // use a meaningful name
userId: user.id, // use the authenticated user's ID
};
logger.info(ctx, 'Request started...');
const { data, error } = await supabase.from('notes').select('*');
if (error) {
logger.error(ctx, 'Request failed...');
// handle error
} else {
logger.info(ctx, 'Request succeeded...');
// use data
}
```

View File

@@ -3,6 +3,7 @@ description: Super Admin functionalities
globs: apps/*/app/admin/**,packages/features/admin/**
alwaysApply: false
---
## Super Admin
1. Page Authentication:
- All pages in the admin section must be wrapped with the `AdminGuard` HOC
@@ -90,6 +91,7 @@ The Super Admin section requires strict access control as it provides elevated p
export const loadYourAdminData = cache(async () => {
const client = getSupabaseServerClient();
const { data, error } = await client.from('your_table').select('*');
if (error) throw error;

View File

@@ -3,7 +3,6 @@ description: Team Accounts context and functionality
globs: apps/*/app/home/[account],packages/features/team-accounts/**
alwaysApply: false
---
## Team Accounts
The team account context in the application lives under the path `app/home/[account]`. The `[account]` segment is the slug of the team account, from which we can identify the team.

View File

@@ -0,0 +1,146 @@
---
description: I18n and Translations
globs:
alwaysApply: false
---
# i18n System Guide
This document provides a comprehensive overview of the internationalization (i18n) system in our Next.js application.
## Architecture Overview
The i18n system consists of:
1. **Core i18n Package**: Located in `packages/i18n`, providing the foundation for i18n functionality
2. **Application-specific Implementation**: Located in `apps/web/lib/i18n`, customizing the core functionality
3. **Translation Files**: Located in `apps/web/public/locales/[language]/[namespace].json`
## Usage Guide
### 1. Setting Up a Page or Layout with i18n
Wrap your page or layout component with the `withI18n` HOC:
```typescript
import { withI18n } from '~/lib/i18n/with-i18n';
function HomePage() {
// Your component code
}
export default withI18n(HomePage);
```
### 2. Using Translations in Client Components
Use the `useTranslation` hook from react-i18next:
```tsx
'use client';
import { useTranslation } from 'react-i18next';
export function MyComponent() {
const { t } = useTranslation('common');
return <h1>{t('homeTabLabel')}</h1>;
}
```
### 3. Using Translations with the Trans Component
For complex translations that include HTML or variables:
```tsx
import { Trans } from '@kit/ui/trans';
export function MyComponent() {
return (
<div>
<Trans
i18nKey="teams:inviteAlertBody"
values={{ accountName: 'My Team' }}
/>
</div>
);
}
```
### 4. Adding Language Selection to Your UI
Use the `LanguageSelector` component:
```tsx
import { LanguageSelector } from '@kit/ui/language-selector';
export function SettingsPage() {
return (
<div>
<h2>Language Settings</h2>
<LanguageSelector />
</div>
);
}
```
### 5. Adding New Translations
1. Create or update JSON files in `apps/web/public/locales/[language]/[namespace].json`
2. Follow the existing structure, adding your new keys
For example, in `apps/web/public/locales/en/common.json`:
```json
{
"existingKey": "Existing translation",
"newKey": "New translation text"
}
```
### 6. Adding a New Language
1. Add the language code to the `languages` array in `i18n.settings.ts`
2. Create corresponding translation files in `apps/web/public/locales/[new-language]/`
3. Copy the structure from the English files as a template
### 7. Adding a New Namespace
1. Add the namespace to `defaultI18nNamespaces` in `i18n.settings.ts`
2. Create corresponding translation files for all supported languages
## Advanced Usage
### Dynamic Namespace Loading
When you need translations from namespaces not included in the default set:
```typescript
import { getI18nSettings } from '~/lib/i18n/i18n.settings';
// Load specific namespaces
const settings = getI18nSettings(language, ['specific-namespace']);
```
### Language Priority
The system uses the following priority to determine the language:
1. User-selected language (from cookie)
2. Browser language (if priority is set to 'user')
3. Default language from environment variable
### Common Issues
- **Translation not showing**: Check that you're using the correct namespace
- **Dynamic content not interpolated**: Make sure to use the `values` prop with `Trans` component
## Available Namespaces and Keys
Here's a brief overview of the available namespaces:
- **common**: General UI elements, navigation, errors [common.json](mdc:apps/web/public/locales/en/common.json)
- **auth**: Authentication-related text [auth.json](mdc:apps/web/public/locales/en/auth.json)
- **account**: Account settings and profile [account.json](mdc:apps/web/public/locales/en/account.json)
- **teams**: Team management [teams.json](mdc:apps/web/public/locales/en/teams.json)
- **billing**: Subscription and payment [billing.json](mdc:apps/web/public/locales/en/billing.json)
- **marketing**: Landing pages, blog, etc. [marketing.json](mdc:apps/web/public/locales/en/marketing.json)
When creating a new functionality, it can be useful to add a new namespace.

View File

@@ -0,0 +1,36 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
# Typescript
- Write clean, clear, well-designed, explicit Typescript
- Make sure types are validated strictly
- Use implicit type inference, unless impossible
- Consider using classes for server-side services, but export a function instead of the class
```tsx
// service.ts
class UserService {
getUser(id: number) {
// ... implementation ...
return { id, name: 'Example User' };
}
}
export function createUserService() {
return new UserService();
}
```
- Follow the Single Responsibility Principle (SRP). Each module/function/class should have one reason to change.
- Favor composition over inheritance.
- Handle errors gracefully using try/catch and appropriate error types.
- Keep functions short and focused.
- Use descriptive names for variables, functions, and classes.
- Avoid unnecessary complexity.
- Avoid using `any` type as much as possible. If necessary, use `unknown`
- Use enums only when appropriate. Consider union types of string literals as an alternative.
- Be aware of performance implications of your code.

View File

@@ -3,7 +3,6 @@ description: UI Components API reference and guidelines
globs: **/*.tsx
alwaysApply: false
---
# UI Components
- Reusable UI components are defined in the "packages/ui" package named "@kit/ui".
@@ -23,6 +22,7 @@ Makerkit leverages two sets of UI components:
// Import Shadcn UI components
import { Button } from '@kit/ui/button';
import { Card } from '@kit/ui/card';
import { toast } from '@kit/ui/sonner';
// Import Makerkit-specific components
import { If } from '@kit/ui/if';
@@ -34,57 +34,56 @@ import { ProfileAvatar } from '@kit/ui/profile-avatar';
| Component | Description | Import Path |
|-----------|-------------|-------------|
| `Accordion` | Expandable/collapsible content sections | `@kit/ui/accordion` |
| `AlertDialog` | Modal dialog for important actions | `@kit/ui/alert-dialog` |
| `Alert` | Status/notification messages | `@kit/ui/alert` |
| `Avatar` | User profile images with fallback | `@kit/ui/avatar` |
| `Badge` | Small status indicators | `@kit/ui/badge` |
| `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` |
| `Button` | Clickable action elements | `@kit/ui/button` |
| `Calendar` | Date picker and date display | `@kit/ui/calendar` |
| `Card` | Container for grouped content | `@kit/ui/card` |
| `Checkbox` | Selection input | `@kit/ui/checkbox` |
| `Command` | Command palette interface | `@kit/ui/command` |
| `DataTable` | Table with enhanced functionality | `@kit/ui/data-table` |
| `Dialog` | Modal window for focused interactions | `@kit/ui/dialog` |
| `DropdownMenu` | Menu triggered by a button | `@kit/ui/dropdown-menu` |
| `Form` | Form components with validation | `@kit/ui/form` |
| `Input` | Text input field | `@kit/ui/input` |
| `Label` | Text label for form elements | `@kit/ui/label` |
| `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` |
| `Popover` | Floating content triggered by interaction | `@kit/ui/popover` |
| `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` |
| `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` |
| `Select` | Dropdown selection menu | `@kit/ui/select` |
| `Separator` | Visual divider between content | `@kit/ui/separator` |
| `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` |
| `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` |
| `Skeleton` | Loading placeholder | `@kit/ui/skeleton` |
| `Switch` | Toggle control | `@kit/ui/switch` |
| `Table` | Data display in rows and columns | `@kit/ui/table` |
| `Tabs` | Tab-based navigation | `@kit/ui/tabs` |
| `Textarea` | Multi-line text input | `@kit/ui/textarea` |
| `Tooltip` | Contextual information on hover | `@kit/ui/tooltip` |
| `Accordion` | Expandable/collapsible content sections | `@kit/ui/accordion` [accordion.tsx](mdc:packages/ui/src/shadcn/accordion.tsx) |
| `AlertDialog` | Modal dialog for important actions | `@kit/ui/alert-dialog` [alert-dialog.tsx](mdc:packages/ui/src/shadcn/alert-dialog.tsx) |
| `Alert` | Status/notification messages | `@kit/ui/alert` [alert.tsx](mdc:packages/ui/src/shadcn/alert.tsx) |
| `Avatar` | User profile images with fallback | `@kit/ui/avatar` [avatar.tsx](mdc:packages/ui/src/shadcn/avatar.tsx) |
| `Badge` | Small status indicators | `@kit/ui/badge` [badge.tsx](mdc:packages/ui/src/shadcn/badge.tsx) |
| `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` [breadcrumb.tsx](mdc:packages/ui/src/shadcn/breadcrumb.tsx) |
| `Button` | Clickable action elements | `@kit/ui/button` [button.tsx](mdc:packages/ui/src/shadcn/button.tsx) |
| `Calendar` | Date picker and date display | `@kit/ui/calendar` [calendar.tsx](mdc:packages/ui/src/shadcn/calendar.tsx) |
| `Card` | Container for grouped content | `@kit/ui/card` [card.tsx](mdc:packages/ui/src/shadcn/card.tsx) |
| `Checkbox` | Selection input | `@kit/ui/checkbox` [checkbox.tsx](mdc:packages/ui/src/shadcn/checkbox.tsx) |
| `Command` | Command palette interface | `@kit/ui/command` [command.tsx](mdc:packages/ui/src/shadcn/command.tsx) |
| `DataTable` | Table | `@kit/ui/data-table` [data-table.tsx](mdc:packages/ui/src/shadcn/data-table.tsx) |
| `Dialog` | Modal window for focused interactions | `@kit/ui/dialog` [dialog.tsx](mdc:packages/ui/src/shadcn/dialog.tsx) |
| `DropdownMenu` | Menu triggered by a button | `@kit/ui/dropdown-menu` [dropdown-menu.tsx](mdc:packages/ui/src/shadcn/dropdown-menu.tsx) |
| `Form` | Form components with validation | `@kit/ui/form` [form.tsx](mdc:packages/ui/src/shadcn/form.tsx) |
| `Input` | Text input field | `@kit/ui/input` [input.tsx](mdc:packages/ui/src/shadcn/input.tsx) |
| `Input OTP` | OTP Text input field | `@kit/ui/input-otp` [input-otp.tsx](mdc:packages/ui/src/shadcn/input-otp.tsx) |
| `Label` | Text label for form elements | `@kit/ui/label` [label.tsx](mdc:packages/ui/src/shadcn/label.tsx) |
| `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` [navigation-menu.tsx](mdc:packages/ui/src/shadcn/navigation-menu.tsx) |
| `Popover` | Floating content triggered by interaction | `@kit/ui/popover` [popover.tsx](mdc:packages/ui/src/shadcn/popover.tsx) |
| `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` [radio-group.tsx](mdc:packages/ui/src/shadcn/radio-group.tsx) |
| `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` [scroll-area.tsx](mdc:packages/ui/src/shadcn/scroll-area.tsx) |
| `Select` | Dropdown selection menu | `@kit/ui/select` [select.tsx](mdc:packages/ui/src/shadcn/select.tsx) |
| `Separator` | Visual divider between content | `@kit/ui/separator` [separator.tsx](mdc:packages/ui/src/shadcn/separator.tsx) |
| `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` [sheet.tsx](mdc:packages/ui/src/shadcn/sheet.tsx) |
| `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` [sidebar.tsx](mdc:packages/ui/src/shadcn/sidebar.tsx) |
| `Skeleton` | Loading placeholder | `@kit/ui/skeleton` [skeleton.tsx](mdc:packages/ui/src/shadcn/skeleton.tsx) |
| `Switch` | Toggle control | `@kit/ui/switch` [switch.tsx](mdc:packages/ui/src/shadcn/switch.tsx) |
| `Toast` | Toaster | `@kit/ui/sonner` [sonner.tsx](mdc:packages/ui/src/shadcn/sonner.tsx) |
| `Tabs` | Tab-based navigation | `@kit/ui/tabs` [tabs.tsx](mdc:packages/ui/src/shadcn/tabs.tsx) |
| `Textarea` | Multi-line text input | `@kit/ui/textarea` [textarea.tsx](mdc:packages/ui/src/shadcn/textarea.tsx) |
| `Tooltip` | Contextual information on hover | `@kit/ui/tooltip` [tooltip.tsx](mdc:packages/ui/src/shadcn/tooltip.tsx) |
## Makerkit-specific Components
| Component | Description | Import Path |
|-----------|-------------|-------------|
| `If` | Conditional rendering component | `@kit/ui/if` |
| `Trans` | Internationalization text component | `@kit/ui/trans` |
| `Sidebar` (Makerkit) | Navigation sidebar with context | `@kit/ui/sidebar` |
| `Page` | Page layout with navigation | `@kit/ui/page` |
| `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` |
| `ImageUploader` | Image upload component | `@kit/ui/image-uploader` |
| `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` |
| `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` |
| `Stepper` | Multi-step process indicator | `@kit/ui/stepper` |
| `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` |
| `CardButton` | Card-styled button | `@kit/ui/card-button` |
| `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` |
| `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` |
| `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` |
| `VersionUpdater` | App version update notifier | `@kit/ui/version-updater` |
| `If` | Conditional rendering component | `@kit/ui/if` [if.tsx](mdc:packages/ui/src/makerkit/if.tsx) |
| `Trans` | Internationalization text component | `@kit/ui/trans` [trans.tsx](mdc:packages/ui/src/makerkit/trans.tsx) |
| `Page` | Page layout with navigation | `@kit/ui/page` [page.tsx](mdc:packages/ui/src/makerkit/page.tsx) |
| `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` [global-loader.tsx](mdc:packages/ui/src/makerkit/global-loader.tsx) |
| `ImageUploader` | Image upload component | `@kit/ui/image-uploader` [image-uploader.tsx](mdc:packages/ui/src/makerkit/image-uploader.tsx) |
| `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` [profile-avatar.tsx](mdc:packages/ui/src/makerkit/profile-avatar.tsx) |
| `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` [data-table.tsx](mdc:packages/ui/src/makerkit/data-table.tsx) |
| `Stepper` | Multi-step process indicator | `@kit/ui/stepper` [stepper.tsx](mdc:packages/ui/src/makerkit/stepper.tsx) |
| `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` [cookie-banner.tsx](mdc:packages/ui/src/makerkit/cookie-banner.tsx) |
| `CardButton` | Card-styled button | `@kit/ui/card-button` [card-button.tsx](mdc:packages/ui/src/makerkit/card-button.tsx) |
| `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` [multi-step-form.tsx](mdc:packages/ui/src/makerkit/multi-step-form.tsx) |
| `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` [empty-state.tsx](mdc:packages/ui/src/makerkit/empty-state.tsx) |
| `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` [app-breadcrumbs.tsx](mdc:packages/ui/src/makerkit/app-breadcrumbs.tsx) |
## Marketing Components
@@ -99,62 +98,10 @@ import {
```
Key marketing components:
- `Hero`, `SecondaryHero` - Hero sections
- `FeatureCard`, `FeatureGrid` - Feature showcases
- `Footer`, `Header` - Page structure
- `NewsletterSignup` - Email collection
- `ComingSoon` - Coming soon page template
## Utility Functions
```tsx
import { cn } from '@kit/ui/utils';
```
- `cn`: Utility for conditional class name joining using Tailwind
## Common Patterns
### Conditional Rendering with If
```tsx
<If condition={isLoading} fallback={<Content />}>
<Spinner />
</If>
```
### Using Translations
```tsx
<Trans i18nKey="common:welcomeMessage" />
```
### Page Layouts
```tsx
<Page style="sidebar">
<PageNavigation>
<SidebarNavigation config={navigationConfig} />
</PageNavigation>
<PageBody>
{/* Page content */}
</PageBody>
</Page>
```
### Form with Validation
```tsx
<Form {...form}>
<FormField
name="email"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</Form>
```
- `Hero` - Hero sections [hero.tsx](mdc:packages/ui/src/makerkit/marketing/hero.tsx)
- `SecondaryHero` [secondary-hero.tsx](mdc:packages/ui/src/makerkit/marketing/secondary-hero.tsx)
- `FeatureCard`, `FeatureGrid` - Feature showcases [feature-card.tsx](mdc:packages/ui/src/makerkit/marketing/feature-card.tsx)
- `Footer` - Page Footer [footer.tsx](mdc:packages/ui/src/makerkit/marketing/footer.tsx)
- `Header` - Page Header [header.tsx](mdc:packages/ui/src/makerkit/marketing/header.tsx)
- `NewsletterSignup` - Email collection [newsletter-signup-container.tsx](mdc:packages/ui/src/makerkit/marketing/newsletter-signup-container.tsx)
- `ComingSoon` - Coming soon page template [coming-soon.tsx](mdc:packages/ui/src/makerkit/marketing/coming-soon.tsx)