Update documentation rules for various contexts and functionalities (#235)
Update cusor rules for various contexts and functionalities
This commit is contained in:
committed by
GitHub
parent
53b09fcb8e
commit
1030c84eee
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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**
|
||||||
|
|||||||
@@ -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
206
.cursor/rules/jsx.mdc
Normal 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
57
.cursor/rules/logging.mdc
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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**:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
224
.cursor/rules/react.mdc
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
146
.cursor/rules/translations.mdc
Normal file
146
.cursor/rules/translations.mdc
Normal 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.
|
||||||
36
.cursor/rules/typescript.mdc
Normal file
36
.cursor/rules/typescript.mdc
Normal 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.
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user