Files
myeasycms-v2/.claude/commands/feature-builder.md
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

213 lines
6.6 KiB
Markdown

---
description: End-to-end feature implementation following Makerkit patterns across database, API, and UI layers
---
# Feature Builder
You are an expert at implementing complete features in Makerkit following established patterns across all layers.
You MUST use the specialized skills for each phase while building the feature.
- Database Schema: `postgres-supabase-expert`
- Server Layer: `server-action-builder`
- Forms: `forms-builder`
## Implementation Phases
### Phase 1: Database Schema
Use `postgres-supabase-expert` skill.
1. Create schema file in `apps/web/supabase/schemas/`
2. Enable RLS and create policies using helper functions
3. Generate migration: `pnpm --filter web supabase:db:diff -f feature_name`
4. Apply: `pnpm --filter web supabase migrations up`
5. Generate types: `pnpm supabase:web:typegen`
```sql
-- Example: apps/web/supabase/schemas/20-projects.sql
create table if not exists public.projects (
id uuid unique not null default extensions.uuid_generate_v4(),
account_id uuid references public.accounts(id) on delete cascade not null,
name varchar(255) not null,
created_at timestamp with time zone default now(),
updated_at timestamp with time zone default now(),
primary key (id)
);
alter table "public"."projects" enable row level security;
revoke all on public.projects from authenticated, service_role;
grant select, insert, update, delete on table public.projects to authenticated;
create policy "projects_read" on public.projects for select
to authenticated using (
account_id = (select auth.uid()) or
public.has_role_on_account(account_id)
);
create policy "projects_write" on public.projects for all
to authenticated using (
public.has_permission(auth.uid(), account_id, 'projects.manage'::app_permissions)
);
```
### Phase 2: Server Layer
Use `server-action-builder` skill for detailed patterns.
**Rule: Services are decoupled from interfaces.** The service is pure logic that receives dependencies (database client,
etc.) as arguments — it never imports framework-specific modules. The server action is a thin adapter that resolves
dependencies and calls the service. This means the same service can be called from a server action, an MCP tool, a CLI
command, or a unit test with zero changes.
Create in route's `_lib/server/` directory:
1. **Schema** (`_lib/schemas/feature.schema.ts`)
2. **Service** (`_lib/server/feature.service.ts`) — pure logic, dependencies injected, testable in isolation
3. **Actions** (`_lib/server/server-actions.ts`) — thin adapter, no business logic
### Phase 3: UI Components
Use `form-builder` skill for form patterns.
Create in route's `_components/` directory:
1. **List component** - Display items with loading states
2. **Form component** - Create/edit with validation
3. **Detail component** - Single item view
### Phase 4: Page Integration
Create page in appropriate route group:
- Personal: `apps/web/app/home/(user)/feature/`
- Team: `apps/web/app/home/[account]/feature/`
```typescript
// apps/web/app/home/[account]/projects/page.tsx
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { PageBody, PageHeader } from '@kit/ui/page';
import { ProjectsList } from './_components/projects-list';
interface Props {
params: Promise<{ account: string }>;
}
export default async function ProjectsPage({ params }: Props) {
const { account } = await params;
const client = getSupabaseServerClient();
const { data: projects } = await client
.from('projects')
.select('*')
.order('created_at', { ascending: false });
return (
<>
<PageHeader title="Projects" />
<PageBody>
<ProjectsList projects={projects ?? []} accountSlug={account} />
</PageBody>
</>
);
}
```
### Phase 5: Navigation
Add routes to sidebar navigation in `apps/web/config/team-account-navigation.config.tsx` or `apps/web/config/personal-account-navigation.config.tsx`.
## File Structure
```
apps/web/app/home/[account]/projects/
├── page.tsx # List page
├── [projectId]/
│ └── page.tsx # Detail page
├── _components/
│ ├── projects-list.tsx
│ ├── project-form.tsx
│ └── project-card.tsx
└── _lib/
├── schemas/
│ └── project.schema.ts
└── server/
├── project.service.ts
└── server-actions.ts
```
## Verification Checklist
### Database Layer
- [ ] Schema file created in `apps/web/supabase/schemas/`
- [ ] RLS enabled on table
- [ ] Default permissions revoked
- [ ] Specific permissions granted to `authenticated`
- [ ] RLS policies use helper functions (`has_role_on_account`, `has_permission`)
- [ ] Indexes added for foreign keys and common queries
- [ ] Timestamps triggers added if applicable
- [ ] Migration generated and applied
- [ ] TypeScript types regenerated
### Server Layer
- [ ] Zod schema in `_lib/schemas/`
- [ ] Service class in `_lib/server/` with dependencies injected (not imported)
- [ ] Service contains all business logic — testable with mock dependencies
- [ ] Server actions are thin adapters — resolve dependencies, call service, handle revalidation
- [ ] Server actions use `enhanceAction`
- [ ] Actions have `auth: true` and `schema` options
- [ ] Logging added for operations
- [ ] `revalidatePath` called after mutations
- [ ] Error handling with `isRedirectError` check if applicable (i.e. when using redirect() in a server action)
### UI Layer
- [ ] Components in `_components/` directory
- [ ] Forms use `react-hook-form` with `zodResolver`
- [ ] Loading states with `useTransition`
- [ ] Error display with `Alert` component
- [ ] `data-test` attributes for E2E testing
- [ ] `Trans` component for all user-facing strings
- [ ] Toast notifications for success/error if applicable
### Page Layer
- [ ] Page in correct route group (user vs team)
- [ ] Async params handled with `await params`
- [ ] Server-side data fetching
- [ ] `PageHeader` and `PageBody` components used
- [ ] Proper error boundaries
### Navigation
- [ ] Path added to `config/paths.config.ts`
- [ ] Menu item added to navigation config
- [ ] Translation key added to `public/locales/en/common.json`
### Testing
- [ ] Page Object created for E2E tests
- [ ] Basic CRUD operations tested
- [ ] Error states tested
- [ ] `data-test` selectors used in tests
### Final Verification
```bash
# Type check
pnpm typecheck
# Lint
pnpm lint:fix
# Format
pnpm format:fix
# Test (if tests exist)
pnpm --filter web-e2e exec playwright test feature-name --workers=1
```
When you are done, run the code quality reviewer agent to verify the code quality.