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 description: Personal Accounts context and functionality
globs: apps/*/app/home/(user),packages/features/accounts/** globs:
alwaysApply: false alwaysApply: false
--- ---
# Personal Account Context # Personal Account Context
This rule provides guidance for working with personal account related components in the application. 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/** globs: apps/**,packages/**
alwaysApply: false alwaysApply: false
--- ---
# Data Fetching # Data Fetching
## General Data Flow ## General Data Flow

View File

@@ -3,7 +3,6 @@ description: Detailed Database Schema and Architecture
globs: globs:
alwaysApply: true alwaysApply: true
--- ---
# Database Rules # Database Rules
## Database Architecture ## 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. - 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. - 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 ## Migrations
- Migration files are placed at `apps/<app>/supabase/migrations` - After creating a schema, we can create a migration
- 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:db:diff` for creating migrations from schemas
- Use the command `pnpm --filter web supabase migrations new <name>` for creating well timestamped migrations - After generating a migration, reset the database for applying the changes using the command `pnpm --filter web supabase:db:reset`
## Security & RLS ## 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. - 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 consider the security of the data and explain the security implications of the data.
- Always use Postgres schemas explicitly (e.g., `public.accounts`) - 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. - 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 ## 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. 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**: 1. Enums [01-enums.sql](mdc:apps/web/supabase/schemas/01-enums.sql)
- Each user has a personal account (1:1) 2. Config [02-config.sql](mdc:apps/web/supabase/schemas/02-config.sql)
- Users can belong to multiple team accounts (M:N through `accounts_memberships`) 3. Accounts [03-accounts.sql](mdc:apps/web/supabase/schemas/03-accounts.sql)
- Accounts can have multiple users (M:N through `accounts_memberships`) 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)
2. **Account ↔ Role**: 6. Roles Permissions [06-roles-permissions.sql](mdc:apps/web/supabase/schemas/06-roles-permissions.sql)
- Each user has a role in each account they belong to 7. Invitations [07-invitations.sql](mdc:apps/web/supabase/schemas/07-invitations.sql)
- Roles define permissions through `role_permissions` 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)
3. **Subscription System**: 10. Orders [10-orders.sql](mdc:apps/web/supabase/schemas/10-orders.sql)
- Accounts can have subscriptions 11. Notifications [11-notifications.sql](mdc:apps/web/supabase/schemas/11-notifications.sql)
- Subscriptions have multiple subscription items 12. One Time Tokens [12-one-time-tokens.sql](mdc:apps/web/supabase/schemas/12-one-time-tokens.sql)
- Billing providers include Stripe, Lemon Squeezy, and Paddle 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)
4. **Invitation System**: 15. Account Views [15-account-views.sql](mdc:apps/web/supabase/schemas/15-account-views.sql)
- Team accounts can invite users via email 16. Storage [16-storage.sql](mdc:apps/web/supabase/schemas/16-storage.sql)
- 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
## Database Best Practices ## 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 ### Security
- **Always enable RLS** on new tables unless explicitly instructed otherwise - **Always enable RLS** on new tables unless explicitly instructed otherwise
- **Create proper RLS policies** for all CRUD operations following existing patterns - **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 - **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`) - **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 ### Data Access Patterns
- Use `has_role_on_account(account_id, role?)` to check membership - Use `public.has_role_on_account(account_id, role?)` to check membership
- Use `has_permission(user_id, account_id, permission)` for permission checks - Use `public.has_permission(user_id, account_id, permission)` for permission checks
- Use `is_account_owner(account_id)` to identify account ownership - Use `public.is_account_owner(account_id)` to identify account ownership
### SQL Coding Style ### SQL Coding Style
@@ -136,12 +113,15 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
### 1. RLS Policy Management ### 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 ```sql
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY; 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 ```sql
-- SELECT policy -- SELECT policy
CREATE POLICY "my_table_read" ON public.my_table FOR SELECT 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 -- 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 ### 2. Account Association
- **Associate Data with Accounts**: Always link data to accounts using a foreign key: - **Associate Data with Accounts**: Always link data to accounts using a foreign key:
```sql ```sql
CREATE TABLE public.my_data ( CREATE TABLE if not exists public.my_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_id UUID REFERENCES public.accounts(id) ON DELETE CASCADE NOT NULL, account_id UUID REFERENCES public.accounts(id) ON DELETE CASCADE NOT NULL,
/* other fields */ /* other fields */
@@ -201,14 +189,14 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
```sql ```sql
CREATE TYPE public.my_status AS ENUM('active', 'inactive', 'pending'); 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' status public.my_status NOT NULL DEFAULT 'pending'
); );
``` ```
- **Apply Appropriate Constraints**: Use constraints to ensure data integrity: - **Apply Appropriate Constraints**: Use constraints to ensure data integrity:
```sql ```sql
CREATE TABLE public.my_table ( CREATE TABLE if not exists public.my_table (
email VARCHAR(255) NOT NULL CHECK (email ~* '^.+@.+\..+$'), email VARCHAR(255) NOT NULL CHECK (email ~* '^.+@.+\..+$'),
count INTEGER NOT NULL CHECK (count >= 0), count INTEGER NOT NULL CHECK (count >= 0),
/* other fields */ /* other fields */
@@ -265,6 +253,8 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
SELECT ... SELECT ...
``` ```
You always must use `(security_invoker = true)` for views.
## Key Functions to Know ## Key Functions to Know
1. **Account Access** 1. **Account Access**

View File

@@ -3,16 +3,18 @@ description: Writing Forms with Shadcn UI, Server Actions, Zod
globs: apps/**/*.tsx,packages/**/*.tsx globs: apps/**/*.tsx,packages/**/*.tsx
alwaysApply: false alwaysApply: false
--- ---
# Forms # Forms
- Use React Hook Form for form validation and submission. - Use React Hook Form for form validation and submission.
- Use Zod for form validation. - Use Zod for form validation.
- Use the `zodResolver` function to resolve the Zod schema to the form. - 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: Follow the example below to create all forms:
## Define the schema ## 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: 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 ```tsx
@@ -27,19 +29,15 @@ export const CreateNoteSchema = z.object({
## Create the Server Action ## Create the Server Action
Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) can help us create endpoints for our forms.
```tsx ```tsx
// _lib/server/server-actions.ts
'use server'; 'use server';
import { z } from 'zod'; import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions'; import { enhanceAction } from '@kit/next/actions';
import { CreateNoteSchema } from '../schema/create-note.schema'; 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( export const createNoteAction = enhanceAction(
async function (data, user) { async function (data, user) {
// 1. "data" has been validated against the Zod schema, and it's safe to use // 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: Then create a client component to handle the form submission:
```tsx ```tsx
// _components/create-note-form.tsx
'use client'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; 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 { 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'; import { CreateNoteSchema } from '../_lib/schema/create-note.schema';
export function CreateNoteForm() { export function CreateNoteForm() {
const [pending, startTransition] = useTransition(); const [pending, startTransition] = useTransition();
const { t } = useTranslation();
const form = useForm({ const form = useForm({
resolver: zodResolver(CreateNoteSchema), resolver: zodResolver(CreateNoteSchema),
@@ -85,58 +87,58 @@ export function CreateNoteForm() {
const onSubmit = (data) => { const onSubmit = (data) => {
startTransition(async () => { startTransition(async () => {
try { await toast.promise(createNoteAction(data), {
await createNoteAction(data); loading: t('notes:creatingNote`),
} catch { success: t('notes:createNoteSuccess`),
// handle error error: t('notes:createNoteError`)
} })
}); });
}; };
return ( return (
<form onSubmit={form.handleSubmit(onSubmit)}> <form onSubmit={form.handleSubmit(onSubmit)}>
<Form {...form}> <Form {...form}>
<FormField name={'title'} render={({ field }) => ( <FormField name={'title'} render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
<span className={'text-sm font-medium'}>Title</span> <span className={'text-sm font-medium'}>Title</span>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<input <Input
type={'text'} type={'text'}
className={'w-full'} className={'w-full'}
placeholder={'Title'} placeholder={'Title'}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} /> )} />
<FormField name={'content'} render={({ field }) => ( <FormField name={'content'} render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
<span className={'text-sm font-medium'}>Content</span> <span className={'text-sm font-medium'}>Content</span>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<textarea <Textarea
className={'w-full'} className={'w-full'}
placeholder={'Content'} placeholder={'Content'}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} /> )} />
<button disabled={pending} type={'submit'} className={'w-full'}> <button disabled={pending} type={'submit'} className={'w-full'}>
Submit Submit
</button> </button>
</Form> </Form>
</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/** globs: apps/**
alwaysApply: false alwaysApply: false
--- ---
# Creating Pages # Creating Pages
# Makerkit Page & Layout Guidelines # 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` - Define translation keys in the appropriate namespace in `apps/web/public/locales/<locale>/<namespace>.json`
5. **Metadata**: 5. **Metadata**:
- Always include `generateMetadata` for SEO - Always include `generateMetadata` for SEO and UX
- Use translations for page titles and descriptions - Use translations for page titles and descriptions
6. **Loading States**: 6. **Loading States**:

View File

@@ -3,7 +3,6 @@ description: Permissions
globs: apps/**,packages/** globs: apps/**,packages/**
alwaysApply: false alwaysApply: false
--- ---
# Access Control & Permissions Guidelines # Access Control & Permissions Guidelines
This rule provides guidance for implementing access control, permissions, and subscription-related functionality in the application. This rule provides guidance for implementing access control, permissions, and subscription-related functionality in the application.

View File

@@ -3,7 +3,6 @@ description: Detailed Project Structure of the app
globs: apps/** globs: apps/**
alwaysApply: false alwaysApply: false
--- ---
# Project Structure # Project Structure
``` ```
@@ -219,7 +218,6 @@ apps/web/app/ # Root directory (apps/web/app)
- `/lib/` - Global utilities (not shown) - `/lib/` - Global utilities (not shown)
4. **Data Fetching** 4. **Data Fetching**
- `*.loader.ts` files - Server-side data fetching functions
- Use of React's `cache()` function for request deduplication - Use of React's `cache()` function for request deduplication
5. **Server Actions** 5. **Server Actions**
@@ -234,5 +232,5 @@ apps/web/app/ # Root directory (apps/web/app)
- `route.ts` - API route handlers - `route.ts` - API route handlers
7. **Dynamic Routes** 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 - `[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} globs: apps/**/route.{ts,tsx}
alwaysApply: false alwaysApply: false
--- ---
# Route Handler / API Routes # Route Handler / API Routes
- Use Route Handlers when data fetching from Client Components - 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 the server actions file as "server-actions.ts"
- Always name exported Server Actions suffixed as "Action", ex. "createPostAction" - 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 `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 ```tsx
'use server'; 'use server';
import { z } from 'zod'; import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions'; import { enhanceAction } from '@kit/next/actions';
import { EntitySchema } from '../entity.schema.ts`;
const ZodSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
export const myServerAction = enhanceAction( export const myServerAction = enhanceAction(
async function (data, user) { 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 // 2. "user" is the authenticated user
// ... your code here // ... your code here
@@ -32,30 +31,7 @@ export const myServerAction = enhanceAction(
}, },
{ {
auth: true, 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/** globs: apps/*/app/admin/**,packages/features/admin/**
alwaysApply: false alwaysApply: false
--- ---
## Super Admin
1. Page Authentication: 1. Page Authentication:
- All pages in the admin section must be wrapped with the `AdminGuard` HOC - 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 () => { export const loadYourAdminData = cache(async () => {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const { data, error } = await client.from('your_table').select('*'); const { data, error } = await client.from('your_table').select('*');
if (error) throw error; 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/** globs: apps/*/app/home/[account],packages/features/team-accounts/**
alwaysApply: false alwaysApply: false
--- ---
## Team Accounts ## 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. 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 globs: **/*.tsx
alwaysApply: false alwaysApply: false
--- ---
# UI Components # UI Components
- Reusable UI components are defined in the "packages/ui" package named "@kit/ui". - 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 Shadcn UI components
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { Card } from '@kit/ui/card'; import { Card } from '@kit/ui/card';
import { toast } from '@kit/ui/sonner';
// Import Makerkit-specific components // Import Makerkit-specific components
import { If } from '@kit/ui/if'; import { If } from '@kit/ui/if';
@@ -34,57 +34,56 @@ import { ProfileAvatar } from '@kit/ui/profile-avatar';
| Component | Description | Import Path | | Component | Description | Import Path |
|-----------|-------------|-------------| |-----------|-------------|-------------|
| `Accordion` | Expandable/collapsible content sections | `@kit/ui/accordion` | | `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` | | `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` | 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` | 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` | Small status indicators | `@kit/ui/badge` [badge.tsx](mdc:packages/ui/src/shadcn/badge.tsx) |
| `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` | | `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` [breadcrumb.tsx](mdc:packages/ui/src/shadcn/breadcrumb.tsx) |
| `Button` | Clickable action elements | `@kit/ui/button` | | `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` | 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` | Container for grouped content | `@kit/ui/card` [card.tsx](mdc:packages/ui/src/shadcn/card.tsx) |
| `Checkbox` | Selection input | `@kit/ui/checkbox` | | `Checkbox` | Selection input | `@kit/ui/checkbox` [checkbox.tsx](mdc:packages/ui/src/shadcn/checkbox.tsx) |
| `Command` | Command palette interface | `@kit/ui/command` | | `Command` | Command palette interface | `@kit/ui/command` [command.tsx](mdc:packages/ui/src/shadcn/command.tsx) |
| `DataTable` | Table with enhanced functionality | `@kit/ui/data-table` | | `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` | 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` | | `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` | Form components with validation | `@kit/ui/form` [form.tsx](mdc:packages/ui/src/shadcn/form.tsx) |
| `Input` | Text input field | `@kit/ui/input` | | `Input` | Text input field | `@kit/ui/input` [input.tsx](mdc:packages/ui/src/shadcn/input.tsx) |
| `Label` | Text label for form elements | `@kit/ui/label` | | `Input OTP` | OTP Text input field | `@kit/ui/input-otp` [input-otp.tsx](mdc:packages/ui/src/shadcn/input-otp.tsx) |
| `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` | | `Label` | Text label for form elements | `@kit/ui/label` [label.tsx](mdc:packages/ui/src/shadcn/label.tsx) |
| `Popover` | Floating content triggered by interaction | `@kit/ui/popover` | | `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` [navigation-menu.tsx](mdc:packages/ui/src/shadcn/navigation-menu.tsx) |
| `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` | | `Popover` | Floating content triggered by interaction | `@kit/ui/popover` [popover.tsx](mdc:packages/ui/src/shadcn/popover.tsx) |
| `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` | | `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` [radio-group.tsx](mdc:packages/ui/src/shadcn/radio-group.tsx) |
| `Select` | Dropdown selection menu | `@kit/ui/select` | | `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` [scroll-area.tsx](mdc:packages/ui/src/shadcn/scroll-area.tsx) |
| `Separator` | Visual divider between content | `@kit/ui/separator` | | `Select` | Dropdown selection menu | `@kit/ui/select` [select.tsx](mdc:packages/ui/src/shadcn/select.tsx) |
| `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` | | `Separator` | Visual divider between content | `@kit/ui/separator` [separator.tsx](mdc:packages/ui/src/shadcn/separator.tsx) |
| `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` | | `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` [sheet.tsx](mdc:packages/ui/src/shadcn/sheet.tsx) |
| `Skeleton` | Loading placeholder | `@kit/ui/skeleton` | | `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` [sidebar.tsx](mdc:packages/ui/src/shadcn/sidebar.tsx) |
| `Switch` | Toggle control | `@kit/ui/switch` | | `Skeleton` | Loading placeholder | `@kit/ui/skeleton` [skeleton.tsx](mdc:packages/ui/src/shadcn/skeleton.tsx) |
| `Table` | Data display in rows and columns | `@kit/ui/table` | | `Switch` | Toggle control | `@kit/ui/switch` [switch.tsx](mdc:packages/ui/src/shadcn/switch.tsx) |
| `Tabs` | Tab-based navigation | `@kit/ui/tabs` | | `Toast` | Toaster | `@kit/ui/sonner` [sonner.tsx](mdc:packages/ui/src/shadcn/sonner.tsx) |
| `Textarea` | Multi-line text input | `@kit/ui/textarea` | | `Tabs` | Tab-based navigation | `@kit/ui/tabs` [tabs.tsx](mdc:packages/ui/src/shadcn/tabs.tsx) |
| `Tooltip` | Contextual information on hover | `@kit/ui/tooltip` | | `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 ## Makerkit-specific Components
| Component | Description | Import Path | | Component | Description | Import Path |
|-----------|-------------|-------------| |-----------|-------------|-------------|
| `If` | Conditional rendering component | `@kit/ui/if` | | `If` | Conditional rendering component | `@kit/ui/if` [if.tsx](mdc:packages/ui/src/makerkit/if.tsx) |
| `Trans` | Internationalization text component | `@kit/ui/trans` | | `Trans` | Internationalization text component | `@kit/ui/trans` [trans.tsx](mdc:packages/ui/src/makerkit/trans.tsx) |
| `Sidebar` (Makerkit) | Navigation sidebar with context | `@kit/ui/sidebar` | | `Page` | Page layout with navigation | `@kit/ui/page` [page.tsx](mdc:packages/ui/src/makerkit/page.tsx) |
| `Page` | Page layout with navigation | `@kit/ui/page` | | `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` [global-loader.tsx](mdc:packages/ui/src/makerkit/global-loader.tsx) |
| `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` | | `ImageUploader` | Image upload component | `@kit/ui/image-uploader` [image-uploader.tsx](mdc:packages/ui/src/makerkit/image-uploader.tsx) |
| `ImageUploader` | Image upload component | `@kit/ui/image-uploader` | | `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` [profile-avatar.tsx](mdc:packages/ui/src/makerkit/profile-avatar.tsx) |
| `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` | | `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` [data-table.tsx](mdc:packages/ui/src/makerkit/data-table.tsx) |
| `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` | | `Stepper` | Multi-step process indicator | `@kit/ui/stepper` [stepper.tsx](mdc:packages/ui/src/makerkit/stepper.tsx) |
| `Stepper` | Multi-step process indicator | `@kit/ui/stepper` | | `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` [cookie-banner.tsx](mdc:packages/ui/src/makerkit/cookie-banner.tsx) |
| `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` | | `CardButton` | Card-styled button | `@kit/ui/card-button` [card-button.tsx](mdc:packages/ui/src/makerkit/card-button.tsx) |
| `CardButton` | Card-styled button | `@kit/ui/card-button` | | `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` [multi-step-form.tsx](mdc:packages/ui/src/makerkit/multi-step-form.tsx) |
| `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` | | `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` [empty-state.tsx](mdc:packages/ui/src/makerkit/empty-state.tsx) |
| `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` | | `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` [app-breadcrumbs.tsx](mdc:packages/ui/src/makerkit/app-breadcrumbs.tsx) |
| `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` |
| `VersionUpdater` | App version update notifier | `@kit/ui/version-updater` |
## Marketing Components ## Marketing Components
@@ -99,62 +98,10 @@ import {
``` ```
Key marketing components: Key marketing components:
- `Hero`, `SecondaryHero` - Hero sections - `Hero` - Hero sections [hero.tsx](mdc:packages/ui/src/makerkit/marketing/hero.tsx)
- `FeatureCard`, `FeatureGrid` - Feature showcases - `SecondaryHero` [secondary-hero.tsx](mdc:packages/ui/src/makerkit/marketing/secondary-hero.tsx)
- `Footer`, `Header` - Page structure - `FeatureCard`, `FeatureGrid` - Feature showcases [feature-card.tsx](mdc:packages/ui/src/makerkit/marketing/feature-card.tsx)
- `NewsletterSignup` - Email collection - `Footer` - Page Footer [footer.tsx](mdc:packages/ui/src/makerkit/marketing/footer.tsx)
- `ComingSoon` - Coming soon page template - `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)
## Utility Functions - `ComingSoon` - Coming soon page template [coming-soon.tsx](mdc:packages/ui/src/makerkit/marketing/coming-soon.tsx)
```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>
```