* 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
16 KiB
Project Agents.md Guide for OpenAI Codex
This Agents.md file provides comprehensive guidance for OpenAI Codex and other AI agents working with this codebase.
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`