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
|
||||
globs: apps/*/app/home/(user),packages/features/accounts/**
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Personal Account Context
|
||||
|
||||
This rule provides guidance for working with personal account related components in the application.
|
||||
|
||||
@@ -3,7 +3,6 @@ description: Fetch data from the Database using the Supabase Clients
|
||||
globs: apps/**,packages/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Data Fetching
|
||||
|
||||
## General Data Flow
|
||||
|
||||
@@ -3,7 +3,6 @@ description: Detailed Database Schema and Architecture
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Database Rules
|
||||
|
||||
## Database Architecture
|
||||
@@ -12,10 +11,15 @@ alwaysApply: true
|
||||
- Accounts are the general concept of a user account, defined by the having the same ID as Supabase Auth's users (personal). They can be a team account or a personal account.
|
||||
- Generally speaking, other tables will be used to store data related to the account. For example, a table `notes` would have a foreign key `account_id` to link it to an account.
|
||||
|
||||
## Schemas
|
||||
- The DB schemas are available at `apps/web/supabase/schemas`
|
||||
- To edit the DB schema, we can either change the schema files, or created new ones
|
||||
- To create a new schema, create a file at `apps/web/supabase/schemas/<number>-<name>.sql`
|
||||
|
||||
## Migrations
|
||||
- Migration files are placed at `apps/<app>/supabase/migrations`
|
||||
- The main migration schema can be found at [20221215192558_schema.sql](mdc:apps/web/supabase/migrations/20221215192558_schema.sql)
|
||||
- Use the command `pnpm --filter web supabase migrations new <name>` for creating well timestamped migrations
|
||||
- After creating a schema, we can create a migration
|
||||
- Use the command `pnpm --filter web supabase:db:diff` for creating migrations from schemas
|
||||
- After generating a migration, reset the database for applying the changes using the command `pnpm --filter web supabase:db:reset`
|
||||
|
||||
## Security & RLS
|
||||
- Using RLS, we must ensure that only the account owner can access the data. Always write safe RLS policies and ensure that the policies are enforced.
|
||||
@@ -25,94 +29,67 @@ alwaysApply: true
|
||||
- Always consider the security of the data and explain the security implications of the data.
|
||||
- Always use Postgres schemas explicitly (e.g., `public.accounts`)
|
||||
- Consider the required compromises between simplicity, functionality and developer experience. However, never compromise on security, which is paramount and fundamental.
|
||||
- Use existing helper functions for access control instead of making your own queries, unless unavailable
|
||||
|
||||
## Schema Overview
|
||||
|
||||
Makerkit uses a Supabase Postgres database with a well-defined schema focused on multi-tenancy through the concepts of accounts (both personal and team) and robust permission systems.
|
||||
|
||||
### Core Entity Relationships
|
||||
### Database Schema
|
||||
|
||||
1. **User ↔ Account**:
|
||||
- Each user has a personal account (1:1)
|
||||
- Users can belong to multiple team accounts (M:N through `accounts_memberships`)
|
||||
- Accounts can have multiple users (M:N through `accounts_memberships`)
|
||||
|
||||
2. **Account ↔ Role**:
|
||||
- Each user has a role in each account they belong to
|
||||
- Roles define permissions through `role_permissions`
|
||||
|
||||
3. **Subscription System**:
|
||||
- Accounts can have subscriptions
|
||||
- Subscriptions have multiple subscription items
|
||||
- Billing providers include Stripe, Lemon Squeezy, and Paddle
|
||||
|
||||
4. **Invitation System**:
|
||||
- Team accounts can invite users via email
|
||||
- Invitations specify roles for the invited user
|
||||
|
||||
5. **One-Time Tokens**:
|
||||
- Used for secure verification processes
|
||||
- Generic system that can be used for various purposes
|
||||
|
||||
## Table Relationships
|
||||
|
||||
```
|
||||
auth.users
|
||||
├── public.accounts (personal_account=true, id=user_id)
|
||||
└── public.accounts_memberships
|
||||
└── public.accounts (personal_account=false)
|
||||
└── public.roles (hierarchy_level)
|
||||
└── public.role_permissions
|
||||
└── app_permissions (enum)
|
||||
```
|
||||
|
||||
```
|
||||
public.accounts
|
||||
├── public.billing_customers
|
||||
│ └── public.subscriptions
|
||||
│ └── public.subscription_items
|
||||
└── public.invitations
|
||||
```
|
||||
|
||||
```
|
||||
public.nonces
|
||||
└── auth.users (optional relationship)
|
||||
```
|
||||
|
||||
## Schema Overview
|
||||
|
||||
Makerkit implements a multi-tenant SaaS architecture through a robust account and permission system:
|
||||
|
||||
1. **Core Entities**:
|
||||
- `auth.users`: Supabase Auth users
|
||||
- `public.accounts`: Both personal and team accounts
|
||||
- `public.accounts_memberships`: Links users to accounts with roles
|
||||
- `public.roles` and `public.role_permissions`: Define permission hierarchy
|
||||
- `public.invitations`: For inviting users to team accounts
|
||||
|
||||
2. **Billing System**:
|
||||
- `public.billing_customers`: Account's connection to billing providers
|
||||
- `public.subscriptions` and `public.subscription_items`: For subscription tracking
|
||||
- `public.orders` and `public.order_items`: For one-time purchases
|
||||
|
||||
3. **Security**:
|
||||
- `public.nonces`: One-time tokens for secure operations
|
||||
1. Enums [01-enums.sql](mdc:apps/web/supabase/schemas/01-enums.sql)
|
||||
2. Config [02-config.sql](mdc:apps/web/supabase/schemas/02-config.sql)
|
||||
3. Accounts [03-accounts.sql](mdc:apps/web/supabase/schemas/03-accounts.sql)
|
||||
4. Roles [04-roles.sql](mdc:apps/web/supabase/schemas/04-roles.sql)
|
||||
5. Memberships [05-memberships.sql](mdc:apps/web/supabase/schemas/05-memberships.sql)
|
||||
6. Roles Permissions [06-roles-permissions.sql](mdc:apps/web/supabase/schemas/06-roles-permissions.sql)
|
||||
7. Invitations [07-invitations.sql](mdc:apps/web/supabase/schemas/07-invitations.sql)
|
||||
8. Billing Customers [08-billing-customers.sql](mdc:apps/web/supabase/schemas/08-billing-customers.sql)
|
||||
9. Subscriptions [09-subscriptions.sql](mdc:apps/web/supabase/schemas/09-subscriptions.sql)
|
||||
10. Orders [10-orders.sql](mdc:apps/web/supabase/schemas/10-orders.sql)
|
||||
11. Notifications [11-notifications.sql](mdc:apps/web/supabase/schemas/11-notifications.sql)
|
||||
12. One Time Tokens [12-one-time-tokens.sql](mdc:apps/web/supabase/schemas/12-one-time-tokens.sql)
|
||||
13. Multi Factor Auth [13-mfa.sql](mdc:apps/web/supabase/schemas/13-mfa.sql)
|
||||
14. Super Admin [14-super-admin.sql](mdc:apps/web/supabase/schemas/14-super-admin.sql)
|
||||
15. Account Views [15-account-views.sql](mdc:apps/web/supabase/schemas/15-account-views.sql)
|
||||
16. Storage [16-storage.sql](mdc:apps/web/supabase/schemas/16-storage.sql)
|
||||
|
||||
## Database Best Practices
|
||||
|
||||
### Inferring Database types
|
||||
|
||||
Fetch auto-generated data types using the `@kit/supabase/database` import. Do not write types manually if the shape is the same as the one from the database row.
|
||||
|
||||
```tsx
|
||||
import { Tables } from '@kit/supabase/database';
|
||||
|
||||
// public.accounts
|
||||
type Account = Tables<'accounts'>;
|
||||
|
||||
// public.subscriptions
|
||||
type Subscription = Tables<'subscriptions'>;
|
||||
|
||||
// public.notifications
|
||||
type Notification = Tables<'notifications'>;
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
- **Always enable RLS** on new tables unless explicitly instructed otherwise
|
||||
- **Create proper RLS policies** for all CRUD operations following existing patterns
|
||||
- **Always associate data with accounts** using a foreign key to ensure proper access control
|
||||
- **Use explicit schema references** (`public.table_name` not just `table_name`)
|
||||
- **Place internal functions in the `kit` schema**
|
||||
- **Private schema**: Place internal functions in the `kit` schema
|
||||
- **Search Path**: Always set search path to '' when defining functions
|
||||
- **Security Definer**: Do not use `security definer` functions unless stricly required
|
||||
|
||||
### Data Access Patterns
|
||||
|
||||
- Use `has_role_on_account(account_id, role?)` to check membership
|
||||
- Use `has_permission(user_id, account_id, permission)` for permission checks
|
||||
- Use `is_account_owner(account_id)` to identify account ownership
|
||||
- Use `public.has_role_on_account(account_id, role?)` to check membership
|
||||
- Use `public.has_permission(user_id, account_id, permission)` for permission checks
|
||||
- Use `public.is_account_owner(account_id)` to identify account ownership
|
||||
|
||||
### SQL Coding Style
|
||||
|
||||
@@ -136,12 +113,15 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
|
||||
|
||||
### 1. RLS Policy Management
|
||||
|
||||
- **Always Enable RLS**: Always enable RLS for your tables unless you have a specific reason not to.
|
||||
#### Always Enable RLS
|
||||
|
||||
Always enable RLS for your tables unless you have a specific reason not to.
|
||||
```sql
|
||||
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY;
|
||||
```
|
||||
|
||||
- **Follow the Standard Policies Pattern**: Use the existing structure for policies:
|
||||
#### Use Helper Functions to validate permissions and access control
|
||||
Use the existing structure for policies:
|
||||
```sql
|
||||
-- SELECT policy
|
||||
CREATE POLICY "my_table_read" ON public.my_table FOR SELECT
|
||||
@@ -153,11 +133,19 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
|
||||
-- INSERT/UPDATE/DELETE policies follow similar patterns
|
||||
```
|
||||
|
||||
When using RLS at team-account level, use `public.has_role_on_account(account_id)` for a generic check to understand if a user is part of a team.
|
||||
|
||||
When using RLS at user-account level, use `account_id = (select auth.uid())`.
|
||||
|
||||
When an entity can belong to both, use both.
|
||||
|
||||
When requiring a specific role, use the role parameter `public.has_role_on_account(account_id, 'owner')`
|
||||
|
||||
### 2. Account Association
|
||||
|
||||
- **Associate Data with Accounts**: Always link data to accounts using a foreign key:
|
||||
```sql
|
||||
CREATE TABLE public.my_data (
|
||||
CREATE TABLE if not exists public.my_data (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
account_id UUID REFERENCES public.accounts(id) ON DELETE CASCADE NOT NULL,
|
||||
/* other fields */
|
||||
@@ -201,14 +189,14 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
|
||||
```sql
|
||||
CREATE TYPE public.my_status AS ENUM('active', 'inactive', 'pending');
|
||||
|
||||
CREATE TABLE public.my_table (
|
||||
CREATE TABLE if not exists public.my_table (
|
||||
status public.my_status NOT NULL DEFAULT 'pending'
|
||||
);
|
||||
```
|
||||
|
||||
- **Apply Appropriate Constraints**: Use constraints to ensure data integrity:
|
||||
```sql
|
||||
CREATE TABLE public.my_table (
|
||||
CREATE TABLE if not exists public.my_table (
|
||||
email VARCHAR(255) NOT NULL CHECK (email ~* '^.+@.+\..+$'),
|
||||
count INTEGER NOT NULL CHECK (count >= 0),
|
||||
/* other fields */
|
||||
@@ -265,6 +253,8 @@ Makerkit implements a multi-tenant SaaS architecture through a robust account an
|
||||
SELECT ...
|
||||
```
|
||||
|
||||
You always must use `(security_invoker = true)` for views.
|
||||
|
||||
## Key Functions to Know
|
||||
|
||||
1. **Account Access**
|
||||
|
||||
@@ -3,16 +3,18 @@ description: Writing Forms with Shadcn UI, Server Actions, Zod
|
||||
globs: apps/**/*.tsx,packages/**/*.tsx
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Forms
|
||||
|
||||
- Use React Hook Form for form validation and submission.
|
||||
- Use Zod for form validation.
|
||||
- Use the `zodResolver` function to resolve the Zod schema to the form.
|
||||
- Use Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) for server-side code handling
|
||||
- Use Sonner for writing toasters for UI feedback
|
||||
|
||||
Follow the example below to create all forms:
|
||||
|
||||
## Define the schema
|
||||
|
||||
Zod schemas should be defined in the `schema` folder and exported, so we can reuse them across a Server Action and the client-side form:
|
||||
|
||||
```tsx
|
||||
@@ -27,19 +29,15 @@ export const CreateNoteSchema = z.object({
|
||||
|
||||
## Create the Server Action
|
||||
|
||||
Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) can help us create endpoints for our forms.
|
||||
|
||||
```tsx
|
||||
// _lib/server/server-actions.ts
|
||||
'use server';
|
||||
|
||||
import { z } from 'zod';
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
import { CreateNoteSchema } from '../schema/create-note.schema';
|
||||
|
||||
const CreateNoteSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
content: z.string().min(1),
|
||||
});
|
||||
|
||||
export const createNoteAction = enhanceAction(
|
||||
async function (data, user) {
|
||||
// 1. "data" has been validated against the Zod schema, and it's safe to use
|
||||
@@ -62,18 +60,22 @@ export const createNoteAction = enhanceAction(
|
||||
Then create a client component to handle the form submission:
|
||||
|
||||
```tsx
|
||||
// _components/create-note-form.tsx
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { Textarea } from '@kit/ui/textarea';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
|
||||
import { toast } from '@kit/ui/sonner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { CreateNoteSchema } from '../_lib/schema/create-note.schema';
|
||||
|
||||
export function CreateNoteForm() {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(CreateNoteSchema),
|
||||
@@ -85,58 +87,58 @@ export function CreateNoteForm() {
|
||||
|
||||
const onSubmit = (data) => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await createNoteAction(data);
|
||||
} catch {
|
||||
// handle error
|
||||
}
|
||||
await toast.promise(createNoteAction(data), {
|
||||
loading: t('notes:creatingNote`),
|
||||
success: t('notes:createNoteSuccess`),
|
||||
error: t('notes:createNoteError`)
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Form {...form}>
|
||||
<FormField name={'title'} render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className={'text-sm font-medium'}>Title</span>
|
||||
</FormLabel>
|
||||
<Form {...form}>
|
||||
<FormField name={'title'} render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className={'text-sm font-medium'}>Title</span>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<input
|
||||
type={'text'}
|
||||
className={'w-full'}
|
||||
placeholder={'Title'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'text'}
|
||||
className={'w-full'}
|
||||
placeholder={'Title'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<FormField name={'content'} render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className={'text-sm font-medium'}>Content</span>
|
||||
</FormLabel>
|
||||
<FormField name={'content'} render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className={'text-sm font-medium'}>Content</span>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<textarea
|
||||
className={'w-full'}
|
||||
placeholder={'Content'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className={'w-full'}
|
||||
placeholder={'Content'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<button disabled={pending} type={'submit'} className={'w-full'}>
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
<button disabled={pending} type={'submit'} className={'w-full'}>
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
206
.cursor/rules/jsx.mdc
Normal file
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/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Creating Pages
|
||||
|
||||
# Makerkit Page & Layout Guidelines
|
||||
@@ -310,7 +309,7 @@ async function getLayoutState() {
|
||||
- Define translation keys in the appropriate namespace in `apps/web/public/locales/<locale>/<namespace>.json`
|
||||
|
||||
5. **Metadata**:
|
||||
- Always include `generateMetadata` for SEO
|
||||
- Always include `generateMetadata` for SEO and UX
|
||||
- Use translations for page titles and descriptions
|
||||
|
||||
6. **Loading States**:
|
||||
|
||||
@@ -3,7 +3,6 @@ description: Permissions
|
||||
globs: apps/**,packages/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Access Control & Permissions Guidelines
|
||||
|
||||
This rule provides guidance for implementing access control, permissions, and subscription-related functionality in the application.
|
||||
@@ -66,4 +65,4 @@ This rule provides guidance for implementing access control, permissions, and su
|
||||
### Actions on Members
|
||||
- Higher roles can update/remove lower roles but not equal or higher roles
|
||||
- Primary owner cannot be removed from their account
|
||||
- Ownership transfer requires OTP verification and is limited to primary owners
|
||||
- Ownership transfer requires OTP verification and is limited to primary owners
|
||||
|
||||
@@ -3,7 +3,6 @@ description: Detailed Project Structure of the app
|
||||
globs: apps/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Project Structure
|
||||
|
||||
```
|
||||
@@ -219,7 +218,6 @@ apps/web/app/ # Root directory (apps/web/app)
|
||||
- `/lib/` - Global utilities (not shown)
|
||||
|
||||
4. **Data Fetching**
|
||||
- `*.loader.ts` files - Server-side data fetching functions
|
||||
- Use of React's `cache()` function for request deduplication
|
||||
|
||||
5. **Server Actions**
|
||||
@@ -234,5 +232,5 @@ apps/web/app/ # Root directory (apps/web/app)
|
||||
- `route.ts` - API route handlers
|
||||
|
||||
7. **Dynamic Routes**
|
||||
- `[account]` - Dynamic route for team accounts
|
||||
- `[account]` - Dynamic route for team accounts. The [account] property is the account slug in the table `public.accounts`.
|
||||
- `[slug]` - Dynamic route for blog posts and documentation
|
||||
224
.cursor/rules/react.mdc
Normal file
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}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Route Handler / API Routes
|
||||
|
||||
- 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 exported Server Actions suffixed as "Action", ex. "createPostAction"
|
||||
- Always use the `enhanceAction` function from the "@kit/supabase/actions" package [index.ts](mdc:packages/next/src/actions/index.ts)
|
||||
- Always use the 'use server' directive at the top of the file
|
||||
- Place the Zod schema in a separate file so it can be reused with `react-hook-form`
|
||||
|
||||
```tsx
|
||||
'use server';
|
||||
|
||||
import { z } from 'zod';
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
|
||||
const ZodSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(6),
|
||||
});
|
||||
import { EntitySchema } from '../entity.schema.ts`;
|
||||
|
||||
export const myServerAction = enhanceAction(
|
||||
async function (data, user) {
|
||||
// 1. "data" is already a valid ZodSchema and it's safe to use
|
||||
// 1. "data" is already a valid EntitySchema and it's safe to use
|
||||
// 2. "user" is the authenticated user
|
||||
|
||||
// ... your code here
|
||||
@@ -32,30 +31,7 @@ export const myServerAction = enhanceAction(
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
schema: ZodSchema,
|
||||
schema: EntitySchema,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Consider logging asynchronous requests using the `@kit/shared/logger` package in a structured way to provide context to the logs in both server actions and route handlers.
|
||||
|
||||
```tsx
|
||||
const ctx = {
|
||||
name: 'my-server-action', // use a meaningful name
|
||||
userId: user.id, // use the authenticated user's ID
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Request started...');
|
||||
|
||||
const { data, error } = await supabase.from('notes').select('*');
|
||||
|
||||
if (error) {
|
||||
logger.error(ctx, 'Request failed...');
|
||||
// handle error
|
||||
} else {
|
||||
logger.info(ctx, 'Request succeeded...');
|
||||
// use data
|
||||
}
|
||||
```
|
||||
@@ -3,6 +3,7 @@ description: Super Admin functionalities
|
||||
globs: apps/*/app/admin/**,packages/features/admin/**
|
||||
alwaysApply: false
|
||||
---
|
||||
## Super Admin
|
||||
|
||||
1. Page Authentication:
|
||||
- All pages in the admin section must be wrapped with the `AdminGuard` HOC
|
||||
@@ -90,6 +91,7 @@ The Super Admin section requires strict access control as it provides elevated p
|
||||
|
||||
export const loadYourAdminData = cache(async () => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { data, error } = await client.from('your_table').select('*');
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
@@ -3,7 +3,6 @@ description: Team Accounts context and functionality
|
||||
globs: apps/*/app/home/[account],packages/features/team-accounts/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
## Team Accounts
|
||||
|
||||
The team account context in the application lives under the path `app/home/[account]`. The `[account]` segment is the slug of the team account, from which we can identify the team.
|
||||
|
||||
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
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# UI Components
|
||||
|
||||
- Reusable UI components are defined in the "packages/ui" package named "@kit/ui".
|
||||
@@ -23,6 +22,7 @@ Makerkit leverages two sets of UI components:
|
||||
// Import Shadcn UI components
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Card } from '@kit/ui/card';
|
||||
import { toast } from '@kit/ui/sonner';
|
||||
|
||||
// Import Makerkit-specific components
|
||||
import { If } from '@kit/ui/if';
|
||||
@@ -34,57 +34,56 @@ import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
||||
|
||||
| Component | Description | Import Path |
|
||||
|-----------|-------------|-------------|
|
||||
| `Accordion` | Expandable/collapsible content sections | `@kit/ui/accordion` |
|
||||
| `AlertDialog` | Modal dialog for important actions | `@kit/ui/alert-dialog` |
|
||||
| `Alert` | Status/notification messages | `@kit/ui/alert` |
|
||||
| `Avatar` | User profile images with fallback | `@kit/ui/avatar` |
|
||||
| `Badge` | Small status indicators | `@kit/ui/badge` |
|
||||
| `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` |
|
||||
| `Button` | Clickable action elements | `@kit/ui/button` |
|
||||
| `Calendar` | Date picker and date display | `@kit/ui/calendar` |
|
||||
| `Card` | Container for grouped content | `@kit/ui/card` |
|
||||
| `Checkbox` | Selection input | `@kit/ui/checkbox` |
|
||||
| `Command` | Command palette interface | `@kit/ui/command` |
|
||||
| `DataTable` | Table with enhanced functionality | `@kit/ui/data-table` |
|
||||
| `Dialog` | Modal window for focused interactions | `@kit/ui/dialog` |
|
||||
| `DropdownMenu` | Menu triggered by a button | `@kit/ui/dropdown-menu` |
|
||||
| `Form` | Form components with validation | `@kit/ui/form` |
|
||||
| `Input` | Text input field | `@kit/ui/input` |
|
||||
| `Label` | Text label for form elements | `@kit/ui/label` |
|
||||
| `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` |
|
||||
| `Popover` | Floating content triggered by interaction | `@kit/ui/popover` |
|
||||
| `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` |
|
||||
| `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` |
|
||||
| `Select` | Dropdown selection menu | `@kit/ui/select` |
|
||||
| `Separator` | Visual divider between content | `@kit/ui/separator` |
|
||||
| `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` |
|
||||
| `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` |
|
||||
| `Skeleton` | Loading placeholder | `@kit/ui/skeleton` |
|
||||
| `Switch` | Toggle control | `@kit/ui/switch` |
|
||||
| `Table` | Data display in rows and columns | `@kit/ui/table` |
|
||||
| `Tabs` | Tab-based navigation | `@kit/ui/tabs` |
|
||||
| `Textarea` | Multi-line text input | `@kit/ui/textarea` |
|
||||
| `Tooltip` | Contextual information on hover | `@kit/ui/tooltip` |
|
||||
| `Accordion` | Expandable/collapsible content sections | `@kit/ui/accordion` [accordion.tsx](mdc:packages/ui/src/shadcn/accordion.tsx) |
|
||||
| `AlertDialog` | Modal dialog for important actions | `@kit/ui/alert-dialog` [alert-dialog.tsx](mdc:packages/ui/src/shadcn/alert-dialog.tsx) |
|
||||
| `Alert` | Status/notification messages | `@kit/ui/alert` [alert.tsx](mdc:packages/ui/src/shadcn/alert.tsx) |
|
||||
| `Avatar` | User profile images with fallback | `@kit/ui/avatar` [avatar.tsx](mdc:packages/ui/src/shadcn/avatar.tsx) |
|
||||
| `Badge` | Small status indicators | `@kit/ui/badge` [badge.tsx](mdc:packages/ui/src/shadcn/badge.tsx) |
|
||||
| `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` [breadcrumb.tsx](mdc:packages/ui/src/shadcn/breadcrumb.tsx) |
|
||||
| `Button` | Clickable action elements | `@kit/ui/button` [button.tsx](mdc:packages/ui/src/shadcn/button.tsx) |
|
||||
| `Calendar` | Date picker and date display | `@kit/ui/calendar` [calendar.tsx](mdc:packages/ui/src/shadcn/calendar.tsx) |
|
||||
| `Card` | Container for grouped content | `@kit/ui/card` [card.tsx](mdc:packages/ui/src/shadcn/card.tsx) |
|
||||
| `Checkbox` | Selection input | `@kit/ui/checkbox` [checkbox.tsx](mdc:packages/ui/src/shadcn/checkbox.tsx) |
|
||||
| `Command` | Command palette interface | `@kit/ui/command` [command.tsx](mdc:packages/ui/src/shadcn/command.tsx) |
|
||||
| `DataTable` | Table | `@kit/ui/data-table` [data-table.tsx](mdc:packages/ui/src/shadcn/data-table.tsx) |
|
||||
| `Dialog` | Modal window for focused interactions | `@kit/ui/dialog` [dialog.tsx](mdc:packages/ui/src/shadcn/dialog.tsx) |
|
||||
| `DropdownMenu` | Menu triggered by a button | `@kit/ui/dropdown-menu` [dropdown-menu.tsx](mdc:packages/ui/src/shadcn/dropdown-menu.tsx) |
|
||||
| `Form` | Form components with validation | `@kit/ui/form` [form.tsx](mdc:packages/ui/src/shadcn/form.tsx) |
|
||||
| `Input` | Text input field | `@kit/ui/input` [input.tsx](mdc:packages/ui/src/shadcn/input.tsx) |
|
||||
| `Input OTP` | OTP Text input field | `@kit/ui/input-otp` [input-otp.tsx](mdc:packages/ui/src/shadcn/input-otp.tsx) |
|
||||
| `Label` | Text label for form elements | `@kit/ui/label` [label.tsx](mdc:packages/ui/src/shadcn/label.tsx) |
|
||||
| `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` [navigation-menu.tsx](mdc:packages/ui/src/shadcn/navigation-menu.tsx) |
|
||||
| `Popover` | Floating content triggered by interaction | `@kit/ui/popover` [popover.tsx](mdc:packages/ui/src/shadcn/popover.tsx) |
|
||||
| `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` [radio-group.tsx](mdc:packages/ui/src/shadcn/radio-group.tsx) |
|
||||
| `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` [scroll-area.tsx](mdc:packages/ui/src/shadcn/scroll-area.tsx) |
|
||||
| `Select` | Dropdown selection menu | `@kit/ui/select` [select.tsx](mdc:packages/ui/src/shadcn/select.tsx) |
|
||||
| `Separator` | Visual divider between content | `@kit/ui/separator` [separator.tsx](mdc:packages/ui/src/shadcn/separator.tsx) |
|
||||
| `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` [sheet.tsx](mdc:packages/ui/src/shadcn/sheet.tsx) |
|
||||
| `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` [sidebar.tsx](mdc:packages/ui/src/shadcn/sidebar.tsx) |
|
||||
| `Skeleton` | Loading placeholder | `@kit/ui/skeleton` [skeleton.tsx](mdc:packages/ui/src/shadcn/skeleton.tsx) |
|
||||
| `Switch` | Toggle control | `@kit/ui/switch` [switch.tsx](mdc:packages/ui/src/shadcn/switch.tsx) |
|
||||
| `Toast` | Toaster | `@kit/ui/sonner` [sonner.tsx](mdc:packages/ui/src/shadcn/sonner.tsx) |
|
||||
| `Tabs` | Tab-based navigation | `@kit/ui/tabs` [tabs.tsx](mdc:packages/ui/src/shadcn/tabs.tsx) |
|
||||
| `Textarea` | Multi-line text input | `@kit/ui/textarea` [textarea.tsx](mdc:packages/ui/src/shadcn/textarea.tsx) |
|
||||
| `Tooltip` | Contextual information on hover | `@kit/ui/tooltip` [tooltip.tsx](mdc:packages/ui/src/shadcn/tooltip.tsx) |
|
||||
|
||||
## Makerkit-specific Components
|
||||
|
||||
| Component | Description | Import Path |
|
||||
|-----------|-------------|-------------|
|
||||
| `If` | Conditional rendering component | `@kit/ui/if` |
|
||||
| `Trans` | Internationalization text component | `@kit/ui/trans` |
|
||||
| `Sidebar` (Makerkit) | Navigation sidebar with context | `@kit/ui/sidebar` |
|
||||
| `Page` | Page layout with navigation | `@kit/ui/page` |
|
||||
| `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` |
|
||||
| `ImageUploader` | Image upload component | `@kit/ui/image-uploader` |
|
||||
| `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` |
|
||||
| `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` |
|
||||
| `Stepper` | Multi-step process indicator | `@kit/ui/stepper` |
|
||||
| `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` |
|
||||
| `CardButton` | Card-styled button | `@kit/ui/card-button` |
|
||||
| `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` |
|
||||
| `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` |
|
||||
| `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` |
|
||||
| `VersionUpdater` | App version update notifier | `@kit/ui/version-updater` |
|
||||
| `If` | Conditional rendering component | `@kit/ui/if` [if.tsx](mdc:packages/ui/src/makerkit/if.tsx) |
|
||||
| `Trans` | Internationalization text component | `@kit/ui/trans` [trans.tsx](mdc:packages/ui/src/makerkit/trans.tsx) |
|
||||
| `Page` | Page layout with navigation | `@kit/ui/page` [page.tsx](mdc:packages/ui/src/makerkit/page.tsx) |
|
||||
| `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` [global-loader.tsx](mdc:packages/ui/src/makerkit/global-loader.tsx) |
|
||||
| `ImageUploader` | Image upload component | `@kit/ui/image-uploader` [image-uploader.tsx](mdc:packages/ui/src/makerkit/image-uploader.tsx) |
|
||||
| `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` [profile-avatar.tsx](mdc:packages/ui/src/makerkit/profile-avatar.tsx) |
|
||||
| `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` [data-table.tsx](mdc:packages/ui/src/makerkit/data-table.tsx) |
|
||||
| `Stepper` | Multi-step process indicator | `@kit/ui/stepper` [stepper.tsx](mdc:packages/ui/src/makerkit/stepper.tsx) |
|
||||
| `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` [cookie-banner.tsx](mdc:packages/ui/src/makerkit/cookie-banner.tsx) |
|
||||
| `CardButton` | Card-styled button | `@kit/ui/card-button` [card-button.tsx](mdc:packages/ui/src/makerkit/card-button.tsx) |
|
||||
| `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` [multi-step-form.tsx](mdc:packages/ui/src/makerkit/multi-step-form.tsx) |
|
||||
| `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` [empty-state.tsx](mdc:packages/ui/src/makerkit/empty-state.tsx) |
|
||||
| `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` [app-breadcrumbs.tsx](mdc:packages/ui/src/makerkit/app-breadcrumbs.tsx) |
|
||||
|
||||
## Marketing Components
|
||||
|
||||
@@ -99,62 +98,10 @@ import {
|
||||
```
|
||||
|
||||
Key marketing components:
|
||||
- `Hero`, `SecondaryHero` - Hero sections
|
||||
- `FeatureCard`, `FeatureGrid` - Feature showcases
|
||||
- `Footer`, `Header` - Page structure
|
||||
- `NewsletterSignup` - Email collection
|
||||
- `ComingSoon` - Coming soon page template
|
||||
|
||||
## Utility Functions
|
||||
|
||||
```tsx
|
||||
import { cn } from '@kit/ui/utils';
|
||||
```
|
||||
|
||||
- `cn`: Utility for conditional class name joining using Tailwind
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Conditional Rendering with If
|
||||
```tsx
|
||||
<If condition={isLoading} fallback={<Content />}>
|
||||
<Spinner />
|
||||
</If>
|
||||
```
|
||||
|
||||
### Using Translations
|
||||
```tsx
|
||||
<Trans i18nKey="common:welcomeMessage" />
|
||||
```
|
||||
|
||||
### Page Layouts
|
||||
```tsx
|
||||
<Page style="sidebar">
|
||||
<PageNavigation>
|
||||
<SidebarNavigation config={navigationConfig} />
|
||||
</PageNavigation>
|
||||
<PageBody>
|
||||
{/* Page content */}
|
||||
</PageBody>
|
||||
</Page>
|
||||
```
|
||||
|
||||
### Form with Validation
|
||||
```tsx
|
||||
<Form {...form}>
|
||||
<FormField
|
||||
name="email"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
```
|
||||
- `Hero` - Hero sections [hero.tsx](mdc:packages/ui/src/makerkit/marketing/hero.tsx)
|
||||
- `SecondaryHero` [secondary-hero.tsx](mdc:packages/ui/src/makerkit/marketing/secondary-hero.tsx)
|
||||
- `FeatureCard`, `FeatureGrid` - Feature showcases [feature-card.tsx](mdc:packages/ui/src/makerkit/marketing/feature-card.tsx)
|
||||
- `Footer` - Page Footer [footer.tsx](mdc:packages/ui/src/makerkit/marketing/footer.tsx)
|
||||
- `Header` - Page Header [header.tsx](mdc:packages/ui/src/makerkit/marketing/header.tsx)
|
||||
- `NewsletterSignup` - Email collection [newsletter-signup-container.tsx](mdc:packages/ui/src/makerkit/marketing/newsletter-signup-container.tsx)
|
||||
- `ComingSoon` - Coming soon page template [coming-soon.tsx](mdc:packages/ui/src/makerkit/marketing/coming-soon.tsx)
|
||||
Reference in New Issue
Block a user