* Update Calendar component and update dependencies * Refactor AGENTS.md and CLAUDE.md for clarity and consistency - Streamlined project overview and architecture sections in both AGENTS.md and CLAUDE.md to enhance readability. - Updated command sections to reflect essential commands and improved formatting for better usability. - Clarified multi-tenant architecture details and database operations, ensuring accurate representation of the project's structure. - Enhanced component organization and security guidelines for better developer onboarding and adherence to best practices. * Refactor code for consistency and readability - Improved formatting in `env-variables-model.ts` for better description clarity. - Enhanced readability of `current-plan-alert.tsx` and `current-plan-badge.tsx` by adjusting the status badge mapping. - Standardized object syntax in `lemon-squeezy-webhook-handler.service.ts` for consistency. - Refined className ordering in `account-selector.tsx` for better maintainability. - Ensured proper syntax in `console.ts` with consistent object export. - Reorganized imports in `calendar.tsx` for clarity and structure. - Improved className formatting in `sidebar.tsx` for better readability. * Update package version to 2.10.0 in package.json
15 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Makerkit is a multi-tenant SaaS application using a Turborepo monorepo structure with distinct apps for the main web application, development tools, and e2e testing.
Monorepo Structure
/apps/web- Main Next.js SaaS application/apps/dev-tool- Development utilities (port 3010)/apps/e2e- Playwright end-to-end tests/packages/- Shared packages and utilities/tooling/- Build tools and development scripts
Core Technologies
- Next.js 15 with App Router and Turbopack
- Supabase for database, auth, and storage
- React 19 with React Compiler
- TypeScript with strict configuration
- Tailwind CSS 4 for styling
- Turborepo for monorepo management
Multi-Tenant Architecture
Uses a dual account model:
- Personal Accounts: Individual user accounts (
auth.users.id = accounts.id) - Team Accounts: Shared workspaces with members, roles, and permissions
- Data associates with accounts via foreign keys for proper access control
Essential Commands
Development
pnpm dev # Start all apps
pnpm --filter web dev # Main app (port 3000)
pnpm --filter dev-tool dev # Dev tools (port 3010)
pnpm build # Build all apps
Database Operations
pnpm supabase:web:start # Start Supabase locally
pnpm supabase:web:reset # Reset with latest schema
pnpm supabase:web:typegen # Generate TypeScript types
pnpm --filter web supabase:db:diff # Create migration
Code Quality
pnpm lint && pnpm format # Lint and format
pnpm typecheck # Type checking
pnpm test # Run tests
Application Structure
Route Organization
app/
├── (marketing)/ # Public pages (landing, blog, docs)
├── (auth)/ # Authentication pages
├── home/
│ ├── (user)/ # Personal account context
│ └── [account]/ # Team account context ([account] = team slug)
├── admin/ # Super admin section
└── api/ # API routes
See complete structure in @apps/web/app/ with examples like:
- Marketing layout: @apps/web/app/(marketing)/layout.tsx
- Personal dashboard: @apps/web/app/home/(user)/page.tsx
- Team workspace: @apps/web/app/home/[account]/page.tsx
- Admin section: @apps/web/app/admin/page.tsx
Component Organization
- Route-specific: Use
_components/directories - Route utilities: Use
_lib/for client,_lib/server/for server-side - Global components: Root-level directories
Example organization:
- Team components: @apps/web/app/home/[account]/_components/
- Team server utils: @apps/web/app/home/[account]/_lib/server/
- Marketing components: @apps/web/app/(marketing)/_components/
Database Guidelines
Security & RLS
- Always enable RLS on new tables unless explicitly instructed otherwise
- Use helper functions for access control:
public.has_role_on_account(account_id, role?)- Check team membershippublic.has_permission(user_id, account_id, permission)- Check permissionspublic.is_account_owner(account_id)- Verify ownership
See RLS examples in database schemas: @apps/web/supabase/schemas/
Schema Management
- Schemas in
apps/web/supabase/schemas/ - Create as
<number>-<name>.sql - After changes:
pnpm --filter web supabase:db:diffthenpnpm supabase:web:reset
Key schema files:
- Accounts:
apps/web/supabase/schemas/03-accounts.sql - Memberships:
apps/web/supabase/schemas/05-memberships.sql - Permissions:
apps/web/supabase/schemas/06-roles-permissions.sql
Type Generation
Import auto-generated types from @packages/supabase/src/types/database.ts:
import { Tables } from '@kit/supabase/database';
type Account = Tables<'accounts'>;
Development Patterns
Data Fetching
- Server Components: Use
getSupabaseServerClient()from @packages/supabase/src/clients/server-client.ts - Client Components: Use
useSupabase()hook + React Query'suseQuery - Admin Operations: Use
getSupabaseServerAdminClient()from @packages/supabase/src/clients/server-admin-client.ts (rare cases only - bypasses RLS, use with caution!) - Prefer Server Components and pass data down when needed
Use the Container/Presenter pattern for complex data components:
// Container: handles data fetching
function UserProfileContainer() {
const userData = useUserData();
return <UserProfilePresenter data={userData.data} />;
}
// Presenter: handles UI rendering
function UserProfilePresenter({ data }: { data: UserData }) {
return <div>{data.name}</div>;
}
Example server-side data loading:
- User workspace loader: @apps/web/app/home/(user)/_lib/server/load-user-workspace.ts
- Team workspace loader: @apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts
- Data provider pattern: @packages/features/team-accounts/src/components/members/roles-data-provider.tsx
Server Actions
Always use enhanceAction from @packages/next/src/actions/index.ts:
'use server';
import { enhanceAction } from '@kit/next/actions';
export const createNoteAction = enhanceAction(
async function (data, user) {
// data is validated, user is authenticated
return { success: true };
},
{
auth: true,
schema: CreateNoteSchema,
},
);
Example server actions:
- Team billing: @apps/web/app/home/[account]/billing/_lib/server/server-actions.ts
- Personal settings: @apps/web/app/home/(user)/settings/_lib/server/server-actions.ts
Forms with React Hook Form & Zod
// 1. Define schema in separate file
export const CreateNoteSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});
// 2. Client component with form
('use client');
const form = useForm({
resolver: zodResolver(CreateNoteSchema),
});
const onSubmit = (data) => {
startTransition(async () => {
await toast.promise(createNoteAction(data), {
loading: 'Creating...',
success: 'Created!',
error: 'Failed!',
});
});
};
See form examples:
- Contact form: @apps/web/app/(marketing)/contact/_components/contact-form.tsx
- Verify OTP form: @packages/otp/src/components/verify-otp-form.tsx
Route Handlers (API Routes)
Use enhanceRouteHandler from @packages/next/src/routes/index.ts:
import { enhanceRouteHandler } from '@kit/next/routes';
export const POST = enhanceRouteHandler(
async function ({ body, user, request }) {
// body is validated, user available if auth: true
return NextResponse.json({ success: true });
},
{
auth: true,
schema: ZodSchema,
},
);
Example API routes:
- Billing webhook: @apps/web/app/api/billing/webhook/route.ts
- Database webhook: @apps/web/app/api/db/webhook/route.ts
React & TypeScript Best Practices
Components
- Use functional components with TypeScript
- Always use
'use client'directive for client components - Destructure props with proper TypeScript interfaces
Conditional Rendering
Use the If component from @packages/ui/src/makerkit/if.tsx:
import { If } from '@kit/ui/if';
import { Spinner } '@kit/ui/spinner';
<If condition={isLoading} fallback={<Content />}>
<Spinner />
</If>
// With type inference
<If condition={error}>
{(err) => <ErrorMessage error={err} />}
</If>
Testing Attributes
Add data attributes for testing:
<button data-test="submit-button">Submit</button>
<div data-test="user-profile" data-user-id={user.id}>Profile</div>
<form data-test="signup-form">Form content</form>
Internationalization
Always use Trans component from @packages/ui/src/makerkit/trans.tsx:
import { Trans } from '@kit/ui/trans';
// Basic usage
<Trans
i18nKey="user:welcomeMessage"
values={{ name: user.name }}
defaults="Welcome, {name}!"
/>
// With HTML elements
<Trans
i18nKey="terms:agreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
}}
defaults="I agree to the <TermsLink>Terms</TermsLink>."
/>
// Pluralization
<Trans
i18nKey="notifications:count"
count={notifications.length}
defaults="{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
/>
Use LanguageSelector component from @packages/ui/src/makerkit/language-selector.tsx:
import { LanguageSelector } from '@kit/ui/language-selector';
<LanguageSelector />;
Adding new languages:
- Add language code to @apps/web/lib/i18n/i18n.settings.ts
- Create translation files in @apps/web/public/locales/[new-language]/
- Copy structure from English files as template
Adding new namespaces:
- Add namespace to
defaultI18nNamespacesin @apps/web/lib/i18n/i18n.settings.ts - Create corresponding translation files for all supported languages
Translation files located in @apps/web/public/locales//.json:
- Common translations: @apps/web/public/locales/en/common.json
- Auth translations: @apps/web/public/locales/en/auth.json
- Team translations: @apps/web/public/locales/en/teams.json
Security Guidelines
Authentication & Authorization
- Authentication enforced by middleware
- Authorization typically handled by RLS at database level, unless using the admin client
- For rare admin client usage, enforce both manually
- User authentication helper: @apps/web/lib/server/require-user-in-server-component.ts if required or to obtain the authed user
Data Passing
- Never pass sensitive data to Client Components
- Never expose server environment variables to client (unless
NEXT_PUBLIC_) - Always validate user input before processing
OTP for Sensitive Operations
Use one-time tokens from @packages/otp/src/api/index.ts for destructive operations:
import { VerifyOtpForm } from '@kit/otp/components';
<VerifyOtpForm
purpose="account-deletion"
email={user.email}
onSuccess={(otp) => {
// Proceed with verified operation
}}
/>;
OTP schema and functions: @apps/web/supabase/schemas/12-one-time-tokens.sql
Super Admin Protection
For admin routes, use AdminGuard from @packages/features/admin/src/components/admin-guard.tsx:
import { AdminGuard } from '@kit/admin/components/admin-guard';
export default AdminGuard(AdminPageComponent);
For admin server actions, use adminAction wrapper:
import { adminAction } from '@kit/admin';
export const yourAdminAction = adminAction(
enhanceAction(
async (data) => {
// Action implementation
},
{ schema: YourActionSchema },
),
);
Admin service security pattern:
private async assertUserIsNotCurrentSuperAdmin(targetId: string) {
const { data } = await this.client.auth.getUser();
const currentUserId = data.user?.id;
if (currentUserId === targetId) {
throw new Error('Cannot perform destructive action on your own account');
}
}
UI Components
Core UI Library
Import from @packages/ui/src/:
// Shadcn components
import { Button } from '@kit/ui/button';
// @packages/ui/src/shadcn/button.tsx
import { Card } from '@kit/ui/card';
// @packages/ui/src/shadcn/sonner.tsx
// Makerkit components
import { If } from '@kit/ui/if';
// @packages/ui/src/makerkit/trans.tsx
import { ProfileAvatar } from '@kit/ui/profile-avatar';
// @packages/ui/src/shadcn/card.tsx
import { toast } from '@kit/ui/sonner';
// @packages/ui/src/makerkit/if.tsx
import { Trans } from '@kit/ui/trans';
// @packages/ui/src/makerkit/profile-avatar.tsx
Key Component Categories
- Forms: Form components in @packages/ui/src/shadcn/form.tsx
- Navigation: Navigation menu in @packages/ui/src/shadcn/navigation-menu.tsx
- Data Display: Data table in @packages/ui/src/makerkit/data-table.tsx
- Marketing: Marketing components in @packages/ui/src/makerkit/marketing/
Styling
- Use Tailwind CSS with semantic classes
- Prefer
bg-background,text-muted-foregroundover fixed colors - Use
cn()utility from @packages/ui/src/lib/utils.ts for class merging
Workspace Contexts
Personal Account Context (/home/(user))
Use hook from packages/features/accounts/src/hooks/use-user-workspace.ts:
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
function PersonalComponent() {
const { user, account } = useUserWorkspace();
// Personal account data
}
Context provider: packages/features/accounts/src/components/user-workspace-context-provider.tsx
Team Account Context (/home/[account])
Use hook from packages/features/team-accounts/src/hooks/use-team-account-workspace.ts:
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
function TeamComponent() {
const { account, user, accounts } = useTeamAccountWorkspace();
// Team account data with permissions
}
Context provider: packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx
Error Handling & Logging
Structured Logging
Use logger from packages/shared/src/logger/logger.ts:
import { getLogger } from '@kit/shared/logger';
const logger = await getLogger();
const ctx = { name: 'myOperation', userId: user.id };
logger.info(ctx, 'Operation started');
// ... operation
logger.error({ ...ctx, error }, 'Operation failed');
Error Boundaries
Use proper error handling with meaningful user messages:
try {
await operation();
} catch (error) {
logger.error({ error, context }, 'Operation failed');
return { error: 'Unable to complete operation' }; // Generic message
}
Feature Development Workflow
Creating New Pages
- Create page component in appropriate route group
- Add
withI18n()HOC fromapps/web/lib/i18n/with-i18n.tsx - Implement
generateMetadata()for SEO - Add loading state with
loading.tsx - Create components in
_components/directory - Add server utilities in
_lib/server/
Example page structure:
- Marketing page:
apps/web/app/(marketing)/pricing/page.tsx - Dashboard page:
apps/web/app/home/(user)/page.tsx - Team page:
apps/web/app/home/[account]/members/page.tsx
Permission Patterns
- Check permissions before data operations
- Guard premium features with
public.has_active_subscription - Use role hierarchy for member management
- Primary account owners have special privileges
Permission helpers in database: apps/web/supabase/schemas/06-roles-permissions.sql
Database Development
- Create schema file:
apps/web/supabase/schemas/<number>-<name>.sql - Enable RLS and create policies
- Generate migration:
pnpm --filter web supabase:db:diff - Reset database:
pnpm supabase:web:reset - Generate types:
pnpm supabase:web:typegen
API Services
Account Services
- Personal accounts API:
packages/features/accounts/src/server/api.ts - Team accounts API:
packages/features/team-accounts/src/server/api.ts - Admin service:
packages/features/admin/src/lib/server/services/admin.service.ts
Billing Services
- Personal billing:
apps/web/app/home/(user)/billing/_lib/server/user-billing.service.ts - Team billing:
apps/web/app/home/[account]/billing/_lib/server/team-billing.service.ts - Per-seat billing:
packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts
Key Configuration Files
- Feature flags: @apps/web/config/feature-flags.config.ts
- i18n settings: @apps/web/lib/i18n/i18n.settings.ts
- Supabase local config: @apps/web/supabase/config.toml@
- Middleware: @apps/web/middleware.ts`