committed by
GitHub
parent
784682a0f5
commit
22f78b9a86
77
.cursor/rules/accounts-context.mdc
Normal file
77
.cursor/rules/accounts-context.mdc
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
description: Personal Accounts context and functionality
|
||||
globs: apps/*/app/home/(user),packages/features/accounts/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Personal Account Context
|
||||
|
||||
This rule provides guidance for working with personal account related components in the application.
|
||||
|
||||
The user/personal account context in the application lives under the path `app/home/(user)`. Under this context, we identify the user using Supabase Auth.
|
||||
|
||||
We can use the `requireUserInServerComponent` to retrieve the relative Supabase User object and identify the user. [require-user-in-server-component.ts](mdc:apps/web/lib/server/require-user-in-server-component.ts)
|
||||
|
||||
### Client Components
|
||||
|
||||
In a Client Component, we can access the `UserWorkspaceContext` and use the `user` object to identify the user.
|
||||
|
||||
We can use it like this:
|
||||
|
||||
```tsx
|
||||
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
|
||||
```
|
||||
|
||||
This utility only works in paths under `apps/*/app/home/(user)`.
|
||||
|
||||
## Guidelines
|
||||
|
||||
### Components and Structure
|
||||
- Personal account components are used in the `/home/(user)` route
|
||||
- Reusable components should be in `packages/features/accounts/src/components`
|
||||
- Settings-related components should be in `packages/features/accounts/src/components/personal-account-settings`
|
||||
|
||||
### State Management
|
||||
- Use the `UserWorkspaceContext` to access user workspace data
|
||||
- Personal account data can be fetched using `usePersonalAccountData` hook
|
||||
- Mutations should use React Query's `useMutation` hooks
|
||||
|
||||
### Authentication Flow
|
||||
- User authentication status is available via `useUser` hook
|
||||
- Account deletion requires OTP verification
|
||||
- Password updates may require reauthentication
|
||||
|
||||
### Feature Flags
|
||||
- Personal account features are controlled via `featureFlagsConfig` [feature-flags.config.ts](mdc:apps/web/config/feature-flags.config.ts)
|
||||
- Key flags:
|
||||
- `enableAccountDeletion`
|
||||
- `enablePasswordUpdate`
|
||||
- `enablePersonalAccountBilling`
|
||||
- `enableNotifications`
|
||||
|
||||
## Personal Account API
|
||||
|
||||
The API for the personal account is [api.ts](mdc:packages/features/accounts/src/server/api.ts)
|
||||
|
||||
A class that provides methods for interacting with account-related data in the database. Initializes a new instance of the `AccountsApi` class with a Supabase client.
|
||||
|
||||
### AccountsApi
|
||||
```typescript
|
||||
constructor(client: SupabaseClient<Database>)
|
||||
```
|
||||
|
||||
### Methods
|
||||
- `getAccount(id: string)` - Get account by ID
|
||||
- `getAccountWorkspace()` - Get current user's account workspace
|
||||
- `loadUserAccounts()` - Get all accounts for current user
|
||||
- `getSubscription(accountId: string)` - Get account subscription
|
||||
- `getOrder(accountId: string)` - Get account order
|
||||
- `getCustomerId(accountId: string)` - Get account customer ID
|
||||
|
||||
## Database
|
||||
|
||||
When applying Database rules [database.mdc](mdc:.cursor/rules/database.mdc) must ensure the authenticated user matches the account ID of the entity
|
||||
|
||||
```sql
|
||||
account_id = (select auth.uid())
|
||||
```
|
||||
91
.cursor/rules/data-fetching.mdc
Normal file
91
.cursor/rules/data-fetching.mdc
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
description: Fetch data from the Database using the Supabase Clients
|
||||
globs: apps/**,packages/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Data Fetching
|
||||
|
||||
## General Data Flow
|
||||
- In a Server Component context, please use the Supabase Client directly for data fetching
|
||||
- In a Client Component context, please use the `useQuery` hook from the "@tanstack/react-query" package
|
||||
|
||||
Data Flow works in the following way:
|
||||
|
||||
1. Server Component uses the Supabase Client to fetch data.
|
||||
2. Data is rendered in Server Components or passed down to Client Components when absolutely necessary to use a client component (e.g. when using React Hooks or any interaction with the DOM).
|
||||
|
||||
```tsx
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
async function ServerComponent() {
|
||||
const client = getSupabaseServerClient();
|
||||
const { data, error } = await client.from('notes').select('*');
|
||||
|
||||
// use data
|
||||
}
|
||||
```
|
||||
|
||||
or pass down the data to a Client Component:
|
||||
|
||||
```tsx
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
export default function ServerComponent() {
|
||||
const supabase = getSupabaseServerClient();
|
||||
const { data, error } = await supabase.from('notes').select('*');
|
||||
|
||||
if (error) {
|
||||
return <SomeErrorComponent error={error} />;
|
||||
}
|
||||
|
||||
return <SomeClientComponent data={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Supabase Clients
|
||||
- In a Server Component context, use the `getSupabaseServerClient` function from the "@kit/supabase/server-client" package [server-client.ts](mdc:packages/supabase/src/clients/server-client.ts)
|
||||
- In a Client Component context, use the `useSupabase` hook from the "@kit/supabase/hooks/use-supabase" package.
|
||||
|
||||
### Admin Actions
|
||||
|
||||
Only in rare cases suggest using the Admin client `getSupabaseServerAdminClient` when needing to bypass RLS from the package `@kit/supabase/server-admin-client` [server-admin-client.ts](mdc:packages/supabase/src/clients/server-admin-client.ts)
|
||||
|
||||
## React Query
|
||||
|
||||
When using `useQuery`, make sure to define the data fetching hook. Create two components: one that fetches the data and one that displays the data. For example a good usage is [roles-data-provider.tsx](mdc:packages/features/team-accounts/src/components/members/roles-data-provider.tsx) as shown in [update-member-role-dialog.tsx](mdc:packages/features/team-accounts/src/components/members/update-member-role-dialog.tsx)
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Logging using the `@kit/shared/logger` package [logger.ts](mdc:packages/shared/src/logger/logger.ts)
|
||||
- Don't swallow errors, always handle them appropriately
|
||||
- Handle promises and async/await gracefully
|
||||
- Consider the unhappy path and handle errors appropriately
|
||||
- Context without sensitive data
|
||||
|
||||
```tsx
|
||||
'use server';
|
||||
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
|
||||
export async function myServerAction() {
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info('Request started...');
|
||||
|
||||
try {
|
||||
// your code here
|
||||
await someAsyncFunction();
|
||||
|
||||
logger.info('Request succeeded...');
|
||||
} catch (error) {
|
||||
logger.error('Request failed...');
|
||||
|
||||
// handle error
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
```
|
||||
304
.cursor/rules/database.mdc
Normal file
304
.cursor/rules/database.mdc
Normal file
@@ -0,0 +1,304 @@
|
||||
---
|
||||
description: Detailed Database Schema and Architecture
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Database Rules
|
||||
|
||||
## Database Architecture
|
||||
- Supabase uses Postgres
|
||||
- We strive to create a safe, robust, performant schema
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
|
||||
## 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.
|
||||
- Unless specified, always enable RLS when creating a table. Propose the required RLS policies ensuring the safety of the data.
|
||||
- Always consider any required constraints and triggers are in place for data consistency
|
||||
- Always consider the compromises you need to make and explain them so I can make an educated decision. Follow up with the considerations make and explain them.
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
## Database Best Practices
|
||||
|
||||
### 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**
|
||||
|
||||
### 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
|
||||
|
||||
### SQL Coding Style
|
||||
|
||||
- Use explicit transactions for multi-step operations
|
||||
- Follow existing naming conventions:
|
||||
- Tables: snake_case, plural nouns (`accounts`, `subscriptions`)
|
||||
- Functions: snake_case, verb phrases (`create_team_account`, `verify_nonce`)
|
||||
- Triggers: descriptive action names (`set_slug_from_account_name`)
|
||||
- Document functions and complex SQL with comments
|
||||
- Use parameterized queries to prevent SQL injection
|
||||
|
||||
### Common Patterns
|
||||
|
||||
- **Account Lookup**: Typically by `id` (UUID) or `slug` (for team accounts)
|
||||
- **Permission Check**: Always verify proper permissions before mutations
|
||||
- **Timestamp Automation**: Use the `trigger_set_timestamps()` function
|
||||
- **User Tracking**: Use the `trigger_set_user_tracking()` function
|
||||
- **Configuration**: Use `is_set(field_name)` to check enabled features
|
||||
|
||||
## Best Practices for Database Code
|
||||
|
||||
### 1. RLS Policy Management
|
||||
|
||||
- **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:
|
||||
```sql
|
||||
-- SELECT policy
|
||||
CREATE POLICY "my_table_read" ON public.my_table FOR SELECT
|
||||
TO authenticated USING (
|
||||
account_id = (select auth.uid()) OR
|
||||
public.has_role_on_account(account_id)
|
||||
);
|
||||
|
||||
-- INSERT/UPDATE/DELETE policies follow similar patterns
|
||||
```
|
||||
|
||||
### 2. Account Association
|
||||
|
||||
- **Associate Data with Accounts**: Always link data to accounts using a foreign key:
|
||||
```sql
|
||||
CREATE TABLE 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 */
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Permission System
|
||||
|
||||
- **Use the Permission System**: Leverage the built-in permission system for access control:
|
||||
```sql
|
||||
-- Check if a user has a specific permission
|
||||
SELECT public.has_permission(
|
||||
auth.uid(),
|
||||
account_id,
|
||||
'my.permission'::public.app_permissions
|
||||
);
|
||||
```
|
||||
|
||||
### 4. Schema Organization
|
||||
|
||||
- **Use Schemas Explicitly**: Always use schema prefixes explicitly:
|
||||
```sql
|
||||
-- Good
|
||||
SELECT * FROM public.accounts;
|
||||
|
||||
-- Avoid
|
||||
SELECT * FROM accounts;
|
||||
```
|
||||
|
||||
- **Put Internal Functions in 'kit' Schema**: Use the 'kit' schema for internal helper functions
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION kit.my_helper_function()
|
||||
RETURNS void AS $$
|
||||
-- function body
|
||||
$$ LANGUAGE plpgsql;
|
||||
```
|
||||
|
||||
### 5. Types and Constraints
|
||||
|
||||
- **Use Enums for Constrained Values**: Create and use enum types for values with a fixed set:
|
||||
```sql
|
||||
CREATE TYPE public.my_status AS ENUM('active', 'inactive', 'pending');
|
||||
|
||||
CREATE TABLE 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 (
|
||||
email VARCHAR(255) NOT NULL CHECK (email ~* '^.+@.+\..+$'),
|
||||
count INTEGER NOT NULL CHECK (count >= 0),
|
||||
/* other fields */
|
||||
);
|
||||
```
|
||||
|
||||
### 6. Authentication and User Management
|
||||
|
||||
- **Use Supabase Auth**: Leverage auth.users for identity management
|
||||
- **Handle User Creation**: Use triggers like `kit.setup_new_user` to set up user data after registration
|
||||
|
||||
### 7. Function Security
|
||||
|
||||
- **Apply Security Definer Carefully**: For functions that need elevated privileges:
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION public.my_function()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = '' AS $$
|
||||
-- function body
|
||||
$$;
|
||||
```
|
||||
|
||||
- **Set Proper Function Permissions**:
|
||||
```sql
|
||||
GRANT EXECUTE ON FUNCTION public.my_function() TO authenticated, service_role;
|
||||
```
|
||||
|
||||
### 8. Error Handling and Validation
|
||||
|
||||
- **Use Custom Error Messages**: Return meaningful errors:
|
||||
```sql
|
||||
IF NOT validation_passed THEN
|
||||
RAISE EXCEPTION 'Validation failed: %', error_message;
|
||||
END IF;
|
||||
```
|
||||
|
||||
### 9. Triggers for Automation
|
||||
|
||||
- **Use Triggers for Derived Data**: Automate updates to derived fields:
|
||||
```sql
|
||||
CREATE TRIGGER update_timestamp
|
||||
BEFORE UPDATE ON public.my_table
|
||||
FOR EACH ROW EXECUTE FUNCTION public.trigger_set_timestamps();
|
||||
```
|
||||
|
||||
### 10. View Structure for Commonly Used Queries
|
||||
|
||||
- **Create Views for Complex Joins**: As done with `user_account_workspace`
|
||||
```sql
|
||||
CREATE OR REPLACE VIEW public.my_view
|
||||
WITH (security_invoker = true) AS
|
||||
SELECT ...
|
||||
```
|
||||
|
||||
## Key Functions to Know
|
||||
|
||||
1. **Account Access**
|
||||
- `public.has_role_on_account(account_id, account_role)`
|
||||
- `public.is_account_owner(account_id)`
|
||||
- `public.is_team_member(account_id, user_id)`
|
||||
|
||||
2. **Permissions**
|
||||
- `public.has_permission(user_id, account_id, permission_name)`
|
||||
- `public.has_more_elevated_role(target_user_id, target_account_id, role_name)`
|
||||
|
||||
3. **Team Management**
|
||||
- `public.create_team_account(account_name)`
|
||||
|
||||
4. **Billing & Subscriptions**
|
||||
- `public.has_active_subscription(target_account_id)`
|
||||
|
||||
5. **One-Time Tokens**
|
||||
- `public.create_nonce(...)`
|
||||
- `public.verify_nonce(...)`
|
||||
- `public.revoke_nonce(...)`
|
||||
|
||||
6. **Super Admins**
|
||||
- `public.is_super_admin()`
|
||||
|
||||
7. **MFA**:
|
||||
- `public.is_aal2()`
|
||||
- `public.is_mfa_compliant()`
|
||||
|
||||
## Configuration Control
|
||||
|
||||
- **Use the `config` Table**: The application has a central configuration table
|
||||
- **Check Features with `public.is_set(field_name)`**:
|
||||
```sql
|
||||
-- Check if team accounts are enabled
|
||||
SELECT public.is_set('enable_team_accounts');
|
||||
```
|
||||
145
.cursor/rules/forms.mdc
Normal file
145
.cursor/rules/forms.mdc
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
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.
|
||||
|
||||
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
|
||||
// _lib/schema/create-note.schema.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CreateNoteSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
content: z.string().min(1),
|
||||
});
|
||||
```
|
||||
|
||||
## Create the Server Action
|
||||
|
||||
```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
|
||||
// 2. "user" is the authenticated user
|
||||
|
||||
// ... your code here
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
schema: CreateNoteSchema,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Create the Form Component
|
||||
|
||||
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 { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
|
||||
|
||||
import { CreateNoteSchema } from '../_lib/schema/create-note.schema';
|
||||
|
||||
export function CreateNoteForm() {
|
||||
const [pending, startTransition] = useTransition();
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(CreateNoteSchema),
|
||||
defaultValues: {
|
||||
title: '',
|
||||
content: '',
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await createNoteAction(data);
|
||||
} catch {
|
||||
// handle error
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<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>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<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>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)} />
|
||||
|
||||
<button disabled={pending} type={'submit'} className={'w-full'}>
|
||||
Submit
|
||||
</button>
|
||||
</Form>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Always use `@kit/ui` for writing the UI of the form.
|
||||
322
.cursor/rules/page-creation.mdc
Normal file
322
.cursor/rules/page-creation.mdc
Normal file
@@ -0,0 +1,322 @@
|
||||
---
|
||||
description: Creating new Pages in the app
|
||||
globs: apps/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Creating Pages
|
||||
|
||||
# Makerkit Page & Layout Guidelines
|
||||
|
||||
## Page Structure Overview
|
||||
|
||||
Makerkit uses Next.js App Router architecture with a clear separation of concerns for layouts and pages. The application's structure reflects the multi-tenant approach with specific routing patterns:
|
||||
|
||||
```
|
||||
- app
|
||||
- home # protected routes
|
||||
- (user) # user workspace (personal account context)
|
||||
- [account] # team workspace (team account context)
|
||||
- (marketing) # marketing pages
|
||||
- auth # auth pages
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### Layouts
|
||||
|
||||
Layouts in Makerkit provide the structure for various parts of the application:
|
||||
|
||||
1. **Root Layout**: The base structure for the entire application
|
||||
2. **Workspace Layouts**:
|
||||
- User Workspace Layout (`app/home/(user)/layout.tsx`): For personal account context
|
||||
- Team Workspace Layout (`app/home/[account]/layout.tsx`): For team account context
|
||||
|
||||
Layouts handle:
|
||||
- Workspace context providers
|
||||
- Navigation components
|
||||
- Authentication requirements
|
||||
- UI structure (sidebar vs header style)
|
||||
|
||||
### Pages
|
||||
|
||||
Pages represent the actual content for each route and follow a consistent pattern:
|
||||
|
||||
1. **Metadata Generation**: Using `generateMetadata()` for SEO and page titles
|
||||
2. **Content Structure**:
|
||||
- Page headers with titles and descriptions
|
||||
- Page body containing the main content
|
||||
3. **i18n Implementation**: Wrapped with `withI18n` HOC
|
||||
|
||||
## Creating a New Page
|
||||
|
||||
### 1. Define the Page Structure
|
||||
|
||||
Create a new file within the appropriate route folder:
|
||||
|
||||
```tsx
|
||||
// app/home/(user)/my-feature/page.tsx
|
||||
import { PageBody } from '@kit/ui/page';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
// Import components from the _components folder if needed
|
||||
import { MyFeatureHeader } from './_components/my-feature-header';
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const i18n = await createI18nServerInstance();
|
||||
const title = i18n.t('account:myFeaturePage');
|
||||
|
||||
return {
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
function MyFeaturePage() {
|
||||
return (
|
||||
<>
|
||||
<MyFeatureHeader
|
||||
title={<Trans i18nKey={'common:routes.myFeature'} />}
|
||||
description={<Trans i18nKey={'common:myFeatureDescription'} />}
|
||||
/>
|
||||
|
||||
<PageBody>
|
||||
{/* Main page content */}
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(MyFeaturePage);
|
||||
```
|
||||
|
||||
- Authentication is enforced already in the middleware
|
||||
- Authorization is normally enforced by RLS at the database level
|
||||
- In the rare case you use the Supabase Admin client, you must enforce both authentication and authorization manually
|
||||
|
||||
### 2. Create a Loading State
|
||||
|
||||
```tsx
|
||||
// app/home/(user)/my-feature/loading.tsx
|
||||
import { GlobalLoader } from '@kit/ui/global-loader';
|
||||
|
||||
export default GlobalLoader;
|
||||
```
|
||||
|
||||
### 3. Create a Layout (if needed)
|
||||
|
||||
If the feature requires a specific layout, create a layout file:
|
||||
|
||||
```tsx
|
||||
// app/home/(user)/my-feature/layout.tsx
|
||||
import { use } from 'react';
|
||||
|
||||
import { UserWorkspaceContextProvider } from '@kit/accounts/components';
|
||||
import { Page, PageNavigation } from '@kit/ui/page';
|
||||
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
import { loadUserWorkspace } from '../_lib/server/load-user-workspace';
|
||||
|
||||
// Import components from the _components folder
|
||||
import { MyFeatureNavigation } from './_components/my-feature-navigation';
|
||||
|
||||
function MyFeatureLayout({ children }: React.PropsWithChildren) {
|
||||
const workspace = use(loadUserWorkspace());
|
||||
|
||||
return (
|
||||
<UserWorkspaceContextProvider value={workspace}>
|
||||
<Page>
|
||||
<PageNavigation>
|
||||
<MyFeatureNavigation workspace={workspace} />
|
||||
</PageNavigation>
|
||||
|
||||
{children}
|
||||
</Page>
|
||||
</UserWorkspaceContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(MyFeatureLayout);
|
||||
```
|
||||
|
||||
## Layout Patterns
|
||||
|
||||
### 1. User Workspace Layout
|
||||
|
||||
For pages in the personal account context, use the user workspace layout pattern:
|
||||
|
||||
```tsx
|
||||
import { use } from 'react';
|
||||
|
||||
import { UserWorkspaceContextProvider } from '@kit/accounts/components';
|
||||
import { Page } from '@kit/ui/page';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
import { loadUserWorkspace } from './_lib/server/load-user-workspace';
|
||||
|
||||
function MyLayout({ children }: React.PropsWithChildren) {
|
||||
const workspace = use(loadUserWorkspace());
|
||||
|
||||
return (
|
||||
<UserWorkspaceContextProvider value={workspace}>
|
||||
<Page>
|
||||
{/* Navigation components */}
|
||||
{children}
|
||||
</Page>
|
||||
</UserWorkspaceContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(MyLayout);
|
||||
```
|
||||
|
||||
### 2. Team Workspace Layout
|
||||
|
||||
For pages in the team account context, use the team workspace layout pattern:
|
||||
|
||||
```tsx
|
||||
import { use } from 'react';
|
||||
|
||||
import { TeamAccountWorkspaceContextProvider } from '@kit/team-accounts/components';
|
||||
import { Page } from '@kit/ui/page';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
import { loadTeamWorkspace } from './_lib/server/load-team-workspace';
|
||||
|
||||
function TeamLayout({ children, params }: LayoutParams) {
|
||||
const workspace = use(loadTeamWorkspace(params.account));
|
||||
|
||||
return (
|
||||
<TeamAccountWorkspaceContextProvider value={workspace}>
|
||||
<Page>
|
||||
{/* Navigation components */}
|
||||
{children}
|
||||
</Page>
|
||||
</TeamAccountWorkspaceContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(TeamLayout);
|
||||
```
|
||||
|
||||
## UI Components Structure
|
||||
|
||||
### Page Components
|
||||
|
||||
Break down pages into reusable components:
|
||||
|
||||
1. **Page Headers**: Create header components for consistent titling:
|
||||
```tsx
|
||||
// _components/my-feature-header.tsx
|
||||
import { PageHeader } from '@kit/ui/page-header';
|
||||
|
||||
export function MyFeatureHeader({
|
||||
title,
|
||||
description
|
||||
}: {
|
||||
title: React.ReactNode,
|
||||
description: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<PageHeader
|
||||
title={title}
|
||||
description={description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
2. **Feature Components**: Create components for feature-specific functionality:
|
||||
```tsx
|
||||
// _components/my-feature-component.tsx
|
||||
'use client';
|
||||
|
||||
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
|
||||
|
||||
export function MyFeatureComponent() {
|
||||
const { user } = useUserWorkspace();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Component content */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation Components
|
||||
|
||||
Create navigation components to handle sidebar or header navigation:
|
||||
|
||||
```tsx
|
||||
// _components/my-feature-navigation.tsx
|
||||
'use client';
|
||||
|
||||
import { NavigationMenu } from '@kit/ui/navigation-menu';
|
||||
|
||||
export function MyFeatureNavigation({
|
||||
workspace
|
||||
}: {
|
||||
workspace: UserWorkspace
|
||||
}) {
|
||||
return (
|
||||
<NavigationMenu>
|
||||
{/* Navigation items */}
|
||||
</NavigationMenu>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Layout Styles
|
||||
|
||||
Makerkit supports different layout styles that can be toggled by the user:
|
||||
|
||||
1. **Sidebar Layout**: A vertical sidebar navigation
|
||||
2. **Header Layout**: A horizontal header navigation
|
||||
|
||||
The layout style is stored in cookies and can be accessed server-side:
|
||||
|
||||
```tsx
|
||||
async function getLayoutState() {
|
||||
const cookieStore = await cookies();
|
||||
const layoutStyleCookie = cookieStore.get('layout-style');
|
||||
|
||||
return {
|
||||
style: layoutStyleCookie?.value ?? defaultStyle,
|
||||
// Other layout state properties
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Server vs. Client Components**:
|
||||
- Use Server Components for data fetching and initial rendering
|
||||
- Use Client Components ('use client') for interactive elements
|
||||
|
||||
2. **Data Loading**:
|
||||
- Load workspace data in layouts using server functions
|
||||
- Pass data down to components that need it
|
||||
- Use React Query for client-side data fetching
|
||||
|
||||
3. **Component Organization**:
|
||||
- Place feature-specific components in a `_components` folder
|
||||
- Place feature-specific server utilities in a `_lib/server` folder
|
||||
- Place feature-specific client utilities in a `_lib/client` folder
|
||||
|
||||
4. **i18n Support**:
|
||||
- Always use `withI18n` HOC for pages and layouts
|
||||
- Use `<Trans>` component for translated text
|
||||
- Define translation keys in the appropriate namespace in `apps/web/public/locales/<locale>/<namespace>.json`
|
||||
|
||||
5. **Metadata**:
|
||||
- Always include `generateMetadata` for SEO
|
||||
- Use translations for page titles and descriptions
|
||||
|
||||
6. **Loading States**:
|
||||
- Always provide a loading state for each route
|
||||
- Use the `GlobalLoader` or custom loading components
|
||||
|
||||
7. **Error Handling**:
|
||||
- Implement error.tsx files for route error boundaries
|
||||
- Handle data fetching errors gracefully
|
||||
69
.cursor/rules/permissions.mdc
Normal file
69
.cursor/rules/permissions.mdc
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
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.
|
||||
|
||||
## Role-Based Access Control
|
||||
|
||||
### Account Roles
|
||||
- Roles are defined in the `roles` table with hierarchy levels (lower number = higher privilege)
|
||||
- Default roles include `owner` (hierarchy_level=1) and `member` with specific permissions
|
||||
- Primary account owner has special privileges that cannot be revoked
|
||||
- Role hierarchy controls what actions users can perform on other members
|
||||
|
||||
### Role Permissions
|
||||
- Permissions are stored in `role_permissions` table mapping roles to specific permissions
|
||||
- Core permissions:
|
||||
- `roles.manage`: Manage roles of users with lower hierarchy
|
||||
- `billing.manage`: Access/update billing information
|
||||
- `settings.manage`: Update account settings
|
||||
- `members.manage`: Add/remove members
|
||||
- `invites.manage`: Create/update/delete invitations
|
||||
|
||||
### Permission Checking
|
||||
- Use `has_permission(user_id, account_id, permission_name)` to check specific permissions
|
||||
- Use `can_action_account_member(target_team_account_id, target_user_id)` to verify if a user can act on another
|
||||
- Use `is_account_owner(account_id)` to check if user is primary owner
|
||||
- Primary owners can perform any action regardless of explicit permissions
|
||||
|
||||
## Team Account Access
|
||||
|
||||
### Team Membership
|
||||
- Use `has_role_on_account(account_id, account_role)` to check if user is a member with specific role
|
||||
- Use `is_team_member(account_id, user_id)` to check if a specific user is a member
|
||||
- Use the authenticated user's `TeamAccountWorkspaceContext` to access current permissions array
|
||||
|
||||
### Invitations
|
||||
- Only users with `invites.manage` permission can create/manage invitations
|
||||
- Users can only invite others with the same or lower role hierarchy than they have
|
||||
- Invitations have expiry dates (default: 7 days)
|
||||
- Accept invitations using `accept_invitation` function with token
|
||||
|
||||
## Subscription Access
|
||||
|
||||
### Subscription Status Checking
|
||||
- Check active subscriptions with `has_active_subscription(account_id)`
|
||||
- Active status includes both `active` and `trialing` subscriptions
|
||||
- Guard premium features with subscription checks in both frontend and backend
|
||||
|
||||
### Billing Access
|
||||
- Only users with `billing.manage` permission can access billing functions
|
||||
- All billing operations should be guarded with permission checks
|
||||
- Per-seat billing automatically updates when members are added/removed
|
||||
|
||||
## Row Level Security
|
||||
|
||||
### Table RLS
|
||||
- Most tables have RLS policies restricting access based on team membership
|
||||
- Personal account data is only accessible by the account owner
|
||||
- Team account data is accessible by all team members based on their roles
|
||||
|
||||
### 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
|
||||
238
.cursor/rules/project-structure.mdc
Normal file
238
.cursor/rules/project-structure.mdc
Normal file
@@ -0,0 +1,238 @@
|
||||
---
|
||||
description: Detailed Project Structure of the app
|
||||
globs: apps/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Project Structure
|
||||
|
||||
```
|
||||
apps/web/app/ # Root directory (apps/web/app)
|
||||
│
|
||||
├── (marketing)/ # Marketing pages group
|
||||
│ ├── _components/ # Shared components for marketing routes
|
||||
│ │ ├── site-footer.tsx
|
||||
│ │ ├── site-header.tsx
|
||||
│ │ ├── site-navigation.tsx
|
||||
│ │ └── site-page-header.tsx
|
||||
│ │
|
||||
│ ├── (legal)/ # Legal pages subgroup
|
||||
│ │ ├── cookie-policy/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── privacy-policy/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ └── terms-of-service/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── blog/ # Blog section
|
||||
│ │ ├── _components/ # Blog-specific components
|
||||
│ │ │ ├── blog-pagination.tsx
|
||||
│ │ │ ├── post-header.tsx
|
||||
│ │ │ └── post-preview.tsx
|
||||
│ │ ├── [slug]/ # Dynamic route for blog posts
|
||||
│ │ │ └── page.tsx
|
||||
│ │ └── page.tsx # Blog listing page
|
||||
│ │
|
||||
│ ├── contact/ # Contact page
|
||||
│ │ ├── _components/
|
||||
│ │ │ └── contact-form.tsx
|
||||
│ │ ├── _lib/ # Contact page utilities
|
||||
│ │ │ ├── contact-email.schema.ts
|
||||
│ │ │ └── server/
|
||||
│ │ │ └── server-actions.ts
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── docs/ # Documentation pages
|
||||
│ │ ├── _components/
|
||||
│ │ ├── _lib/
|
||||
│ │ │ ├── server/
|
||||
│ │ │ │ └── docs.loader.ts
|
||||
│ │ │ └── utils.ts
|
||||
│ │ ├── [slug]/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── layout.tsx # Layout specific to docs section
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── faq/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── pricing/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── layout.tsx # Layout for all marketing pages
|
||||
│ ├── loading.tsx # Loading state for marketing pages
|
||||
│ └── page.tsx # Home/landing page
|
||||
│
|
||||
├── (auth)/ # Authentication pages group
|
||||
│ ├── callback/ # Auth callback routes
|
||||
│ │ ├── error/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ └── route.ts # API route handler for auth callback
|
||||
│ │
|
||||
│ ├── confirm/
|
||||
│ │ └── route.ts
|
||||
│ │
|
||||
│ ├── password-reset/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── sign-in/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── sign-up/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── verify/
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── layout.tsx # Layout for auth pages
|
||||
│ └── loading.tsx # Loading state for auth pages
|
||||
│
|
||||
├── admin/ # Admin section
|
||||
│ ├── _components/
|
||||
│ │ ├── admin-sidebar.tsx
|
||||
│ │ └── mobile-navigation.tsx
|
||||
│ │
|
||||
│ ├── accounts/
|
||||
│ │ ├── [id]/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── layout.tsx
|
||||
│ ├── loading.tsx
|
||||
│ └── page.tsx
|
||||
│
|
||||
├── api/ # API routes
|
||||
│ ├── billing/
|
||||
│ │ └── webhook/
|
||||
│ │ └── route.ts
|
||||
│ │
|
||||
│ └── db/
|
||||
│ └── webhook/
|
||||
│ └── route.ts
|
||||
│
|
||||
├── home/ # User dashboard area
|
||||
│ ├── (user)/ # Personal user routes
|
||||
│ │ ├── _components/ # User dashboard components
|
||||
│ │ │ ├── home-account-selector.tsx
|
||||
│ │ │ └── home-sidebar.tsx
|
||||
│ │ │
|
||||
│ │ ├── _lib/ # User dashboard utilities
|
||||
│ │ │ └── server/
|
||||
│ │ │ └── load-user-workspace.ts
|
||||
│ │ │
|
||||
│ │ ├── billing/ # Personal account billing
|
||||
│ │ │ ├── _components/
|
||||
│ │ │ ├── _lib/
|
||||
│ │ │ │ ├── schema/
|
||||
│ │ │ │ │ └── personal-account-checkout.schema.ts
|
||||
│ │ │ │ └── server/
|
||||
│ │ │ │ ├── personal-account-billing-page.loader.ts
|
||||
│ │ │ │ ├── server-actions.ts
|
||||
│ │ │ │ └── user-billing.service.ts
|
||||
│ │ │ │
|
||||
│ │ │ ├── error.tsx
|
||||
│ │ │ ├── layout.tsx
|
||||
│ │ │ ├── page.tsx
|
||||
│ │ │ └── return/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ │
|
||||
│ │ ├── settings/
|
||||
│ │ │ ├── layout.tsx
|
||||
│ │ │ └── page.tsx
|
||||
│ │ │
|
||||
│ │ ├── layout.tsx
|
||||
│ │ ├── loading.tsx
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── [account]/ # Team account routes (dynamic)
|
||||
│ │ ├── _components/ # Team account components
|
||||
│ │ │ ├── dashboard-demo.tsx
|
||||
│ │ │ ├── team-account-accounts-selector.tsx
|
||||
│ │ │ └── team-account-layout-sidebar.tsx
|
||||
│ │ │
|
||||
│ │ ├── _lib/ # Team account utilities
|
||||
│ │ │ └── server/
|
||||
│ │ │ ├── team-account-billing-page.loader.ts
|
||||
│ │ │ └── team-account-workspace.loader.ts
|
||||
│ │ │
|
||||
│ │ ├── billing/ # Team billing section
|
||||
│ │ │ ├── _components/
|
||||
│ │ │ ├── _lib/
|
||||
│ │ │ │ ├── schema/
|
||||
│ │ │ │ │ └── team-billing.schema.ts
|
||||
│ │ │ │ └── server/
|
||||
│ │ │ │ ├── server-actions.ts
|
||||
│ │ │ │ └── team-billing.service.ts
|
||||
│ │ │ │
|
||||
│ │ │ ├── error.tsx
|
||||
│ │ │ ├── layout.tsx
|
||||
│ │ │ ├── page.tsx
|
||||
│ │ │ └── return/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ │
|
||||
│ │ ├── members/ # Team members management
|
||||
│ │ │ ├── _lib/
|
||||
│ │ │ │ └── server/
|
||||
│ │ │ │ └── members-page.loader.ts
|
||||
│ │ │ └── page.tsx
|
||||
│ │ │
|
||||
│ │ ├── settings/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ │
|
||||
│ │ ├── layout.tsx
|
||||
│ │ ├── loading.tsx
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ └── loading.tsx
|
||||
│
|
||||
├── join/ # Team join page
|
||||
│ └── page.tsx
|
||||
│
|
||||
├── update-password/
|
||||
│ └── page.tsx
|
||||
│
|
||||
├── error.tsx # Global error page
|
||||
├── global-error.tsx # Global error component
|
||||
├── layout.tsx # Root layout
|
||||
├── not-found.tsx # 404 page
|
||||
├── robots.ts # Robots.txt config
|
||||
├── sitemap.xml/ # Sitemap generation
|
||||
│ └── route.ts
|
||||
└── version/ # Version info endpoint
|
||||
└── route.ts
|
||||
```
|
||||
|
||||
## Key Organization Patterns
|
||||
|
||||
1. **Route Groups**
|
||||
- `(marketing)` - Groups all marketing/public pages
|
||||
- `(auth)` - Groups all authentication related pages
|
||||
- `(user)` - Groups all personal user dashboard pages
|
||||
|
||||
2. **Component Organization**
|
||||
- `_components/` - Route-specific components
|
||||
- Global components are in the root `/components` directory (not shown)
|
||||
|
||||
3. **Utilities & Data**
|
||||
- `_lib/` - Route-specific utilities, types, and helpers
|
||||
- `_lib/server/` - Server-side utilities including data loaders
|
||||
- `/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**
|
||||
- `server-actions.ts` - Server-side actions for mutating data
|
||||
- Follows 'use server' directive pattern
|
||||
|
||||
6. **Special Files**
|
||||
- `layout.tsx` - Define layouts for routes
|
||||
- `loading.tsx` - Loading UI for routes
|
||||
- `error.tsx` - Error handling for routes
|
||||
- `page.tsx` - Page component for routes
|
||||
- `route.ts` - API route handlers
|
||||
|
||||
7. **Dynamic Routes**
|
||||
- `[account]` - Dynamic route for team accounts
|
||||
- `[slug]` - Dynamic route for blog posts and documentation
|
||||
51
.cursor/rules/route-handlers.mdc
Normal file
51
.cursor/rules/route-handlers.mdc
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
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
|
||||
- To create API routes (route.ts), always use the `enhanceRouteHandler` function from the "@kit/supabase/routes" package. [index.ts](mdc:packages/next/src/routes/index.ts)
|
||||
|
||||
```tsx
|
||||
import { z } from 'zod';
|
||||
import { enhanceRouteHandler } from '@kit/next/routes';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ZodSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(6),
|
||||
});
|
||||
|
||||
export const POST = enhanceRouteHandler(
|
||||
async function({ body, user, request }) {
|
||||
// 1. "body" is already a valid ZodSchema and it's safe to use
|
||||
// 2. "user" is the authenticated user
|
||||
// 3. "request" is NextRequest
|
||||
// ... your code here
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
});
|
||||
},
|
||||
{
|
||||
schema: ZodSchema,
|
||||
},
|
||||
);
|
||||
|
||||
// example of unauthenticated route (careful!)
|
||||
export const GET = enhanceRouteHandler(
|
||||
async function({ user, request }) {
|
||||
// 1. "user" is null, as "auth" is false and we don't require authentication
|
||||
// 2. "request" is NextRequest
|
||||
// ... your code here
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
});
|
||||
},
|
||||
{
|
||||
auth: false,
|
||||
},
|
||||
);
|
||||
```
|
||||
61
.cursor/rules/server-actions.mdc
Normal file
61
.cursor/rules/server-actions.mdc
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
description: Writing Server Actions for mutating data
|
||||
globs: apps/**","packages/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# Server Actions
|
||||
|
||||
- For Data Mutations from Client Components, always use Server Actions
|
||||
- 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)
|
||||
|
||||
```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),
|
||||
});
|
||||
|
||||
export const myServerAction = enhanceAction(
|
||||
async function (data, user) {
|
||||
// 1. "data" is already a valid ZodSchema and it's safe to use
|
||||
// 2. "user" is the authenticated user
|
||||
|
||||
// ... your code here
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
schema: ZodSchema,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## 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
|
||||
}
|
||||
```
|
||||
208
.cursor/rules/super-admin.mdc
Normal file
208
.cursor/rules/super-admin.mdc
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
description: Super Admin functionalities
|
||||
globs: apps/*/app/admin/**,packages/features/admin/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
1. Page Authentication:
|
||||
- All pages in the admin section must be wrapped with the `AdminGuard` HOC
|
||||
- This ensures only users with the 'super-admin' role and MFA enabled can access these pages
|
||||
- Example: `export default AdminGuard(AdminPageComponent);`
|
||||
|
||||
2. Server Actions:
|
||||
- Use the `adminAction` wrapper for all server actions in the admin section
|
||||
- This checks if the current user is a super admin before executing the action
|
||||
- Example:
|
||||
```typescript
|
||||
export const yourAdminAction = adminAction(
|
||||
enhanceAction(
|
||||
async (data) => {
|
||||
// Action implementation
|
||||
},
|
||||
{
|
||||
schema: YourActionSchema,
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
3. Authorization Functions:
|
||||
- Import and use `isSuperAdmin` from '@kit/admin' to check if the current user is a super admin [is-super-admin.ts](mdc:packages/features/admin/src/lib/server/utils/is-super-admin.ts)
|
||||
- This function returns a boolean indicating whether the user has the super-admin role and MFA enabled
|
||||
- Example:
|
||||
```typescript
|
||||
const isAdmin = await isSuperAdmin(getSupabaseServerClient());
|
||||
if (!isAdmin) {
|
||||
notFound(); // or redirect/throw error
|
||||
}
|
||||
```
|
||||
|
||||
4. Schema Validation:
|
||||
- Define Zod schemas for all admin actions in the 'schema' directory
|
||||
- Follow the pattern in [admin-actions.schema.ts](mdc:packages/features/admin/src/lib/server/schema/admin-actions.schema.ts)
|
||||
- Include appropriate validation for all fields
|
||||
|
||||
5. Data Fetching
|
||||
- Do not use `ServerDataLoader` unless the query is very simple
|
||||
- Use the authed Supabase Server Client such as [admin-dashboard.loader.ts](mdc:packages/features/admin/src/lib/server/loaders/admin-dashboard.loader.ts)
|
||||
|
||||
The Super Admin section requires strict access control as it provides elevated privileges. Always ensure the current user cannot perform destructive actions on their own account and properly validate input data."
|
||||
|
||||
## Writing Pages in the Admin Sections
|
||||
|
||||
1. Basic Page Structure:
|
||||
```typescript
|
||||
import { AdminGuard } from '@kit/admin/components/admin-guard';
|
||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
||||
|
||||
// Optional metadata export
|
||||
export const metadata = {
|
||||
title: `Page Title`,
|
||||
};
|
||||
|
||||
async function YourAdminPage() {
|
||||
// Load data using cached loaders
|
||||
const data = await loadYourAdminData();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader description={<AppBreadcrumbs />} />
|
||||
<PageBody>
|
||||
{/* Page content */}
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// IMPORTANT: Always wrap with AdminGuard
|
||||
export default AdminGuard(YourAdminPage);
|
||||
```
|
||||
|
||||
2. Data Loading:
|
||||
- Create a cached loader function in a server directory
|
||||
- Use the Supabase client for database operations
|
||||
- Example:
|
||||
```typescript
|
||||
// in _lib/server/loaders/your-loader.ts
|
||||
import { cache } from 'react';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
export const loadYourAdminData = cache(async () => {
|
||||
const client = getSupabaseServerClient();
|
||||
const { data, error } = await client.from('your_table').select('*');
|
||||
|
||||
if (error) throw error;
|
||||
return data;
|
||||
});
|
||||
```
|
||||
|
||||
3. Dynamic Routes:
|
||||
- For pages that need parameters (like `[id]`), handle them appropriately
|
||||
- For example:
|
||||
```typescript
|
||||
interface Params {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
async function AdminDetailPage({ params }: Params) {
|
||||
const { id } = await params;
|
||||
const item = await loadItemById(id);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
4. Updating Sidebar navigation at [admin-sidebar.tsx](mdc:apps/web/app/admin/_components/admin-sidebar.tsx) to include new pages
|
||||
|
||||
### Security Considerations:
|
||||
- Validate that the target is not the current super admin
|
||||
- Implement confirmation steps for destructive actions
|
||||
- Never expose sensitive error details to the client
|
||||
|
||||
### Services
|
||||
|
||||
1. Basic Service Structure:
|
||||
```typescript
|
||||
import 'server-only';
|
||||
import { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { Database } from '@kit/supabase/database';
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
|
||||
export function createYourAdminService(client: SupabaseClient<Database>) {
|
||||
return new YourAdminService(client);
|
||||
}
|
||||
|
||||
class YourAdminService {
|
||||
constructor(private readonly client: SupabaseClient<Database>) {}
|
||||
|
||||
async performAction(params: YourActionParams) {
|
||||
const logger = await getLogger();
|
||||
const ctx = { name: 'admin.yourService', ...params };
|
||||
|
||||
logger.info(ctx, 'Starting admin action');
|
||||
|
||||
// Perform the action
|
||||
const { data, error } = await this.client
|
||||
.from('your_table')
|
||||
.update({ some_field: params.value })
|
||||
.eq('id', params.id);
|
||||
|
||||
if (error) {
|
||||
logger.error({ ...ctx, error }, 'Admin action failed');
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info(ctx, 'Admin action completed successfully');
|
||||
return data;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Important Patterns:
|
||||
- Mark files with 'server-only' directive
|
||||
- Use factory functions to create service instances
|
||||
- Use class-based services with typed parameters
|
||||
- Properly type the Supabase client with the Database type
|
||||
- Use structured logging with context
|
||||
- Handle errors consistently
|
||||
|
||||
3. Security Checks:
|
||||
- Implement methods to verify the current user is not taking action on their own account
|
||||
- Example:
|
||||
```typescript
|
||||
private async assertUserIsNotCurrentSuperAdmin(targetId: string) {
|
||||
const { data } = await this.client.auth.getUser();
|
||||
const currentUserId = data.user?.id;
|
||||
|
||||
if (!currentUserId) {
|
||||
throw new Error(`Error fetching user`);
|
||||
}
|
||||
|
||||
if (currentUserId === targetId) {
|
||||
throw new Error(
|
||||
`You cannot perform a destructive action on your own account as a Super Admin`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Data Access:
|
||||
- Use the appropriate Supabase client (admin or regular)
|
||||
- For admin-only operations, use the admin client
|
||||
- For regular operations, use the standard client
|
||||
- Example:
|
||||
```typescript
|
||||
constructor(
|
||||
private readonly client: SupabaseClient<Database>,
|
||||
private readonly adminClient?: SupabaseClient<Database>
|
||||
) {}
|
||||
```
|
||||
|
||||
5. Error Handling:
|
||||
- Use structured error handling
|
||||
- Include appropriate context in error logs
|
||||
- Return typed error responses
|
||||
|
||||
Services should be focused on specific domains and follow the principle of single responsibility.
|
||||
80
.cursor/rules/team-account-context.mdc
Normal file
80
.cursor/rules/team-account-context.mdc
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
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.
|
||||
|
||||
### Accessing the Account Workspace Data in Client Components
|
||||
|
||||
The data fetched from the account workspace API is available in the team context. You can access this data using the `useTeamAccountWorkspace` hook [use-team-account-workspace.ts](mdc:packages/features/team-accounts/src/hooks/use-team-account-workspace.ts)
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
|
||||
|
||||
export default function SomeComponent() {
|
||||
const { account, user, accounts } = useTeamAccountWorkspace();
|
||||
// use account, user, and accounts
|
||||
}
|
||||
```
|
||||
|
||||
The `useTeamAccountWorkspace` hook returns the same data structure as the `loadTeamWorkspace` function.
|
||||
|
||||
NB: the hooks is not to be used is Server Components, only in Client Components. Additionally, this is only available in the pages under /home/[account] layout.
|
||||
|
||||
### Team Pages
|
||||
|
||||
These pages are dedicated to the team account, which means they are only accessible to team members. To access these pages, the user must be authenticated and belong to the team.
|
||||
|
||||
## Guidelines
|
||||
|
||||
### State Management
|
||||
- Use the `TeamAccountWorkspaceContext` to access account workspace data
|
||||
- Team account data can be accessed using `useTeamAccountWorkspace` hook
|
||||
- Server-side loading done with `loadTeamWorkspace` in `team-account-workspace.loader.ts` [team-account-workspace.loader.ts](mdc:apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts)
|
||||
|
||||
### Account Management Features
|
||||
- Role-based permissions control what users can do within a team
|
||||
- Team members can be invited, roles can be updated, and members can be removed
|
||||
- Primary account owner has special privileges (transfer ownership, delete team)
|
||||
- Account deletion requires OTP verification
|
||||
|
||||
### Billing Integration
|
||||
- Team account billing uses [team-billing.service.ts](mdc:apps/web/app/home/[account]/billing/_lib/server/team-billing.service.ts)
|
||||
- Per-seat billing handled by [account-per-seat-billing.service.ts](mdc:packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts)
|
||||
|
||||
## API
|
||||
|
||||
The API for the personal account is [api.ts](mdc:packages/features/team-accounts/src/server/api.ts)
|
||||
|
||||
### Factory
|
||||
```typescript
|
||||
createAccountsApi(client: SupabaseClient<Database>): AccountsApi
|
||||
```
|
||||
|
||||
### TeamAccountsApi
|
||||
```typescript
|
||||
constructor(client: SupabaseClient<Database>)
|
||||
```
|
||||
|
||||
### Methods
|
||||
- `getTeamAccount(slug: string)` - Get team by slug
|
||||
- `getTeamAccountById(accountId: string)` - Get team by ID
|
||||
- `getSubscription(accountId: string)` - Get team subscription
|
||||
- `getOrder(accountId: string)` - Get team order
|
||||
- `getAccountWorkspace(slug: string)` - Get team workspace
|
||||
- `hasPermission({accountId, userId, permission})` - Check user permission
|
||||
- `getMembersCount(accountId: string)` - Get team member count
|
||||
- `getCustomerId(accountId: string)` - Get team customer ID
|
||||
- `getInvitation(adminClient, token)` - Get invitation by token
|
||||
|
||||
## Feature Flags
|
||||
- Key flags at [feature-flags.config.ts](mdc:apps/web/config/feature-flags.config.ts)
|
||||
- `enableTeamAccountBilling`
|
||||
- `enableTeamDeletion`
|
||||
- `enableTeamCreation`
|
||||
- `enableNotifications`
|
||||
160
.cursor/rules/ui.mdc
Normal file
160
.cursor/rules/ui.mdc
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
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".
|
||||
- By exporting the component from the "exports" field, we can import it using the "@kit/ui/{component-name}" format.
|
||||
|
||||
## Styling
|
||||
- Styling is done using Tailwind CSS. We use the "cn" function from the "@kit/ui/utils" package to generate class names.
|
||||
- Avoid fixes classes such as "bg-gray-500". Instead, use Shadcn classes such as "bg-background", "text-secondary-foreground", "text-muted-foreground", etc.
|
||||
|
||||
Makerkit leverages two sets of UI components:
|
||||
1. **Shadcn UI Components**: Base components from the Shadcn UI library
|
||||
2. **Makerkit-specific Components**: Custom components built on top of Shadcn UI
|
||||
|
||||
## Importing Components
|
||||
|
||||
```tsx
|
||||
// Import Shadcn UI components
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Card } from '@kit/ui/card';
|
||||
|
||||
// Import Makerkit-specific components
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
||||
```
|
||||
|
||||
## Core Shadcn UI Components
|
||||
|
||||
| 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` |
|
||||
|
||||
## 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` |
|
||||
|
||||
## Marketing Components
|
||||
|
||||
Import all marketing components with:
|
||||
```tsx
|
||||
import {
|
||||
Hero,
|
||||
HeroTitle,
|
||||
GradientText,
|
||||
// etc.
|
||||
} from '@kit/ui/marketing';
|
||||
```
|
||||
|
||||
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>
|
||||
```
|
||||
Reference in New Issue
Block a user