@create-lft-app/nextjs 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +549 -549
- package/package.json +48 -48
- package/template/.claude/skills/anti-patterns.md +150 -0
- package/template/.claude/skills/drizzle-schema.md +178 -0
- package/template/.claude/skills/formatting.md +56 -0
- package/template/.claude/skills/module-architecture.md +143 -0
- package/template/.claude/skills/supabase-server-actions.md +199 -0
- package/template/.claude/skills/ui-patterns.md +161 -0
- package/template/CLAUDE.md +114 -1239
- package/template/drizzle.config.ts +12 -12
- package/template/eslint.config.mjs +16 -16
- package/template/gitignore +36 -36
- package/template/next.config.ts +7 -7
- package/template/package.json +86 -86
- package/template/postcss.config.mjs +7 -7
- package/template/proxy.ts +12 -12
- package/template/public/logolft.svg +11 -11
- package/template/src/app/(auth)/dashboard/dashboard-content.tsx +124 -124
- package/template/src/app/(auth)/dashboard/page.tsx +9 -9
- package/template/src/app/(auth)/layout.tsx +7 -7
- package/template/src/app/(auth)/users/page.tsx +9 -9
- package/template/src/app/(auth)/users/users-content.tsx +26 -26
- package/template/src/app/(public)/layout.tsx +7 -7
- package/template/src/app/(public)/login/page.tsx +17 -17
- package/template/src/app/api/webhooks/route.ts +20 -20
- package/template/src/app/globals.css +249 -249
- package/template/src/app/layout.tsx +37 -37
- package/template/src/app/page.tsx +5 -5
- package/template/src/app/providers.tsx +27 -27
- package/template/src/components/layout/main-content.tsx +28 -28
- package/template/src/components/layout/sidebar-context.tsx +33 -33
- package/template/src/components/layout/sidebar.tsx +141 -141
- package/template/src/components/tables/data-table-column-header.tsx +68 -68
- package/template/src/components/tables/data-table-date-filter.tsx +203 -203
- package/template/src/components/tables/data-table-faceted-filter.tsx +185 -185
- package/template/src/components/tables/data-table-filters-dropdown.tsx +130 -130
- package/template/src/components/tables/data-table-number-filter.tsx +295 -295
- package/template/src/components/tables/data-table-pagination.tsx +99 -99
- package/template/src/components/tables/data-table-toolbar.tsx +140 -140
- package/template/src/components/tables/data-table-view-options.tsx +63 -63
- package/template/src/components/tables/data-table.tsx +148 -148
- package/template/src/components/tables/index.ts +9 -9
- package/template/src/components/ui/accordion.tsx +58 -58
- package/template/src/components/ui/alert-dialog.tsx +165 -165
- package/template/src/components/ui/alert.tsx +66 -66
- package/template/src/components/ui/animations/index.ts +44 -44
- package/template/src/components/ui/avatar.tsx +55 -55
- package/template/src/components/ui/badge.tsx +50 -50
- package/template/src/components/ui/button.tsx +118 -118
- package/template/src/components/ui/calendar.tsx +220 -220
- package/template/src/components/ui/card.tsx +113 -113
- package/template/src/components/ui/checkbox.tsx +38 -38
- package/template/src/components/ui/collapsible.tsx +33 -33
- package/template/src/components/ui/command.tsx +196 -196
- package/template/src/components/ui/dialog.tsx +156 -156
- package/template/src/components/ui/dropdown-menu.tsx +280 -280
- package/template/src/components/ui/form.tsx +171 -171
- package/template/src/components/ui/icons.tsx +167 -167
- package/template/src/components/ui/input.tsx +28 -28
- package/template/src/components/ui/label.tsx +25 -25
- package/template/src/components/ui/motion.tsx +197 -197
- package/template/src/components/ui/page-transition.tsx +166 -166
- package/template/src/components/ui/popover.tsx +59 -59
- package/template/src/components/ui/progress.tsx +32 -32
- package/template/src/components/ui/radio-group.tsx +45 -45
- package/template/src/components/ui/scroll-area.tsx +63 -63
- package/template/src/components/ui/select.tsx +208 -208
- package/template/src/components/ui/separator.tsx +28 -28
- package/template/src/components/ui/sheet.tsx +170 -170
- package/template/src/components/ui/sidebar.tsx +726 -726
- package/template/src/components/ui/skeleton.tsx +15 -15
- package/template/src/components/ui/slider.tsx +58 -58
- package/template/src/components/ui/sonner.tsx +47 -47
- package/template/src/components/ui/spinner.tsx +27 -27
- package/template/src/components/ui/submit-button.tsx +47 -47
- package/template/src/components/ui/switch.tsx +31 -31
- package/template/src/components/ui/table.tsx +120 -120
- package/template/src/components/ui/tabs.tsx +75 -75
- package/template/src/components/ui/textarea.tsx +26 -26
- package/template/src/components/ui/tooltip.tsx +70 -70
- package/template/src/config/navigation.ts +59 -59
- package/template/src/config/roles.ts +27 -27
- package/template/src/config/site.ts +12 -12
- package/template/src/db/index.ts +12 -12
- package/template/src/db/schema/index.ts +1 -1
- package/template/src/db/schema/users.ts +16 -16
- package/template/src/db/seed.ts +39 -39
- package/template/src/hooks/index.ts +3 -3
- package/template/src/hooks/use-mobile.ts +21 -21
- package/template/src/hooks/useDataTable.ts +82 -82
- package/template/src/hooks/useDebounce.ts +49 -49
- package/template/src/hooks/useMediaQuery.ts +36 -36
- package/template/src/lib/date/config.ts +36 -36
- package/template/src/lib/date/formatters.ts +127 -127
- package/template/src/lib/date/index.ts +26 -26
- package/template/src/lib/excel/exporter.ts +89 -89
- package/template/src/lib/excel/index.ts +14 -14
- package/template/src/lib/excel/parser.ts +96 -96
- package/template/src/lib/query-client.ts +35 -35
- package/template/src/lib/supabase/admin.ts +23 -23
- package/template/src/lib/supabase/client.ts +11 -11
- package/template/src/lib/supabase/proxy.ts +67 -67
- package/template/src/lib/supabase/server.ts +38 -38
- package/template/src/lib/supabase/types.ts +53 -53
- package/template/src/lib/utils.ts +6 -6
- package/template/src/lib/validations/common.ts +75 -75
- package/template/src/lib/validations/index.ts +20 -20
- package/template/src/modules/auth/actions/auth-actions.ts +59 -59
- package/template/src/modules/auth/components/login-form.tsx +68 -68
- package/template/src/modules/auth/hooks/useAuth.ts +38 -38
- package/template/src/modules/auth/hooks/useAuthMutations.ts +43 -43
- package/template/src/modules/auth/hooks/useAuthQueries.ts +43 -43
- package/template/src/modules/auth/index.ts +12 -12
- package/template/src/modules/auth/schemas/auth.schema.ts +32 -32
- package/template/src/modules/auth/stores/useAuthStore.ts +37 -37
- package/template/src/modules/users/actions/users-actions.ts +166 -166
- package/template/src/modules/users/columns.tsx +106 -106
- package/template/src/modules/users/components/users-list.tsx +48 -48
- package/template/src/modules/users/hooks/useUsers.ts +39 -39
- package/template/src/modules/users/hooks/useUsersMutations.ts +55 -55
- package/template/src/modules/users/hooks/useUsersQueries.ts +35 -35
- package/template/src/modules/users/index.ts +30 -30
- package/template/src/modules/users/schemas/users.schema.ts +51 -51
- package/template/src/modules/users/stores/useUsersStore.ts +60 -60
- package/template/src/modules/users/types/auth-user.types.ts +42 -42
- package/template/src/modules/users/utils/user-mapper.ts +32 -32
- package/template/src/stores/index.ts +1 -1
- package/template/src/stores/useUiStore.ts +55 -55
- package/template/src/types/api.ts +28 -28
- package/template/src/types/index.ts +2 -2
- package/template/src/types/table.ts +34 -34
- package/template/supabase/config.toml +94 -94
- package/template/tsconfig.json +42 -42
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Supabase Server Actions & Auth
|
|
2
|
+
|
|
3
|
+
## Supabase Clients
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
lib/supabase/
|
|
7
|
+
├── client.ts → Browser client (anon key) - for UI
|
|
8
|
+
├── server.ts → Server client (anon key + cookies) - for verifying auth
|
|
9
|
+
└── admin.ts → Admin client (secret key) - for managing users
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### When to use each client
|
|
13
|
+
|
|
14
|
+
| Operation | Client | Reason |
|
|
15
|
+
|-----------|--------|--------|
|
|
16
|
+
| Login/Logout | `server.ts` | Needs cookies |
|
|
17
|
+
| Verify session | `server.ts` | Needs cookies |
|
|
18
|
+
| Create user | `admin.ts` | Requires admin privileges |
|
|
19
|
+
| List/Update/Delete users | `admin.ts` | Requires admin privileges |
|
|
20
|
+
| Queries on own tables | `server.ts` | RLS based on user |
|
|
21
|
+
|
|
22
|
+
## Standard Server Action Template (Own Tables)
|
|
23
|
+
|
|
24
|
+
For entities with their own database tables, server actions use the repository pattern:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
'use server'
|
|
28
|
+
|
|
29
|
+
import { createClient } from '@/lib/supabase/server'
|
|
30
|
+
import { entityRepository } from '../repositories/entity.repository'
|
|
31
|
+
import { entityInsertSchema, entityUpdateSchema } from '../schemas/entity.schema'
|
|
32
|
+
|
|
33
|
+
// Helper: verify authenticated user or throw
|
|
34
|
+
async function requireAuth() {
|
|
35
|
+
const supabase = await createClient()
|
|
36
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
37
|
+
if (!user) throw new Error('Unauthorized')
|
|
38
|
+
return user
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// CREATE
|
|
42
|
+
export async function createEntity(raw: unknown) {
|
|
43
|
+
await requireAuth()
|
|
44
|
+
const data = entityInsertSchema.parse(raw)
|
|
45
|
+
return entityRepository.create(data)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// READ
|
|
49
|
+
export async function getEntities() {
|
|
50
|
+
await requireAuth()
|
|
51
|
+
return entityRepository.findAll()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// UPDATE
|
|
55
|
+
export async function updateEntity(id: string, raw: unknown) {
|
|
56
|
+
await requireAuth()
|
|
57
|
+
const data = entityUpdateSchema.parse(raw)
|
|
58
|
+
return entityRepository.update(id, data)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// DELETE
|
|
62
|
+
export async function deleteEntity(id: string) {
|
|
63
|
+
await requireAuth()
|
|
64
|
+
return entityRepository.delete(id)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Server Action Template (auth.users with Admin API)
|
|
69
|
+
|
|
70
|
+
For managing Supabase Auth users (not own tables):
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
'use server'
|
|
74
|
+
|
|
75
|
+
import { createClient } from '@/lib/supabase/server'
|
|
76
|
+
import { createAdminClient } from '@/lib/supabase/admin'
|
|
77
|
+
import { mapAuthUserToUser } from '../utils/user-mapper'
|
|
78
|
+
|
|
79
|
+
export async function createUser(input: CreateAuthUserInput) {
|
|
80
|
+
// 1. Verify current user is authenticated
|
|
81
|
+
const supabase = await createClient()
|
|
82
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
83
|
+
if (!user) throw new Error('Unauthorized')
|
|
84
|
+
|
|
85
|
+
// 2. Use admin client for privileged operations
|
|
86
|
+
const adminClient = createAdminClient()
|
|
87
|
+
const { data, error } = await adminClient.auth.admin.createUser({
|
|
88
|
+
email: input.email,
|
|
89
|
+
app_metadata: { role: input.role },
|
|
90
|
+
user_metadata: { name: input.name },
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (error) throw error
|
|
94
|
+
return mapAuthUserToUser(data.user)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Roles Configuration
|
|
99
|
+
|
|
100
|
+
Roles are centralized in `config/roles.ts`. Single source of truth:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// config/roles.ts
|
|
104
|
+
export const USER_ROLES = ['admin', 'user', 'viewer'] as const
|
|
105
|
+
export type UserRole = (typeof USER_ROLES)[number]
|
|
106
|
+
export const DEFAULT_ROLE: UserRole = 'user'
|
|
107
|
+
|
|
108
|
+
export const ROLE_LABELS: Record<UserRole, string> = {
|
|
109
|
+
admin: 'Administrador',
|
|
110
|
+
user: 'Usuario',
|
|
111
|
+
viewer: 'Visualizador',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const ROLE_OPTIONS = USER_ROLES.map((role) => ({
|
|
115
|
+
value: role,
|
|
116
|
+
label: ROLE_LABELS[role],
|
|
117
|
+
}))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Usage:
|
|
121
|
+
- Zod schemas: `import { USER_ROLES, DEFAULT_ROLE } from '@/config/roles'`
|
|
122
|
+
- Columns/UI: `import { ROLE_LABELS, ROLE_OPTIONS } from '@/config/roles'`
|
|
123
|
+
- Types: `import type { UserRole } from '@/config/roles'`
|
|
124
|
+
|
|
125
|
+
If the system does NOT need roles, simplify to `['user']` or remove `config/roles.ts` entirely.
|
|
126
|
+
|
|
127
|
+
## User Architecture
|
|
128
|
+
|
|
129
|
+
Users live in `auth.users` (Supabase Auth), NOT in a `public.users` table:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
auth.users (Supabase Auth)
|
|
133
|
+
├── id: uuid
|
|
134
|
+
├── email: string
|
|
135
|
+
├── app_metadata: { role: UserRole } ← Role here
|
|
136
|
+
├── user_metadata: { name, avatar_url } ← Extra data here
|
|
137
|
+
├── created_at: timestamp
|
|
138
|
+
└── updated_at: timestamp
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The users module includes a mapper: `modules/users/utils/user-mapper.ts`.
|
|
142
|
+
|
|
143
|
+
## Integration with TanStack Mutations
|
|
144
|
+
|
|
145
|
+
Server actions are called from the mutations hook, which handles cache invalidation:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// modules/[entity]/hooks/use[Entity]Mutations.ts
|
|
149
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
150
|
+
import { createEntity, updateEntity, deleteEntity } from '../actions/entity-actions'
|
|
151
|
+
|
|
152
|
+
export function useEntityMutations() {
|
|
153
|
+
const qc = useQueryClient()
|
|
154
|
+
|
|
155
|
+
const create = useMutation({
|
|
156
|
+
mutationFn: createEntity,
|
|
157
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['entities'] }),
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const update = useMutation({
|
|
161
|
+
mutationFn: ({ id, data }: { id: string; data: EntityUpdate }) =>
|
|
162
|
+
updateEntity(id, data),
|
|
163
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['entities'] }),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const remove = useMutation({
|
|
167
|
+
mutationFn: deleteEntity,
|
|
168
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['entities'] }),
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
createEntity: create.mutateAsync,
|
|
173
|
+
updateEntity: update.mutateAsync,
|
|
174
|
+
deleteEntity: remove.mutateAsync,
|
|
175
|
+
isCreating: create.isPending,
|
|
176
|
+
isUpdating: update.isPending,
|
|
177
|
+
isDeleting: remove.isPending,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Key Rules
|
|
183
|
+
|
|
184
|
+
1. **Always `'use server'`** at the top of the file.
|
|
185
|
+
2. **Always verify the user** with `requireAuth()` before any operation.
|
|
186
|
+
3. **Never expose the service-role key** to server actions called from the client.
|
|
187
|
+
4. **Let RLS do the authorization** — after verifying the user exists, RLS policies handle what they can access.
|
|
188
|
+
5. **Validate inputs with Zod** — always `schema.parse(raw)` before passing data to the repository.
|
|
189
|
+
6. **Do NOT use `revalidatePath`** — cache invalidation is handled by TanStack Query's `invalidateQueries` in the mutations hook. Only add `revalidatePath` if a Server Component also consumes the same data outside TanStack Query.
|
|
190
|
+
7. **Use repository for own tables, Admin API for auth.users** — never mix them.
|
|
191
|
+
|
|
192
|
+
## Environment Variables
|
|
193
|
+
|
|
194
|
+
```env
|
|
195
|
+
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
|
196
|
+
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=sb_publishable_...
|
|
197
|
+
SUPABASE_SECRET_DEFAULT_KEY=sb_secret_... # Required for admin client
|
|
198
|
+
DATABASE_URL=postgresql://user:pass@host:5432/db # Required for Drizzle
|
|
199
|
+
```
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# UI Patterns & Page Architecture
|
|
2
|
+
|
|
3
|
+
## Design Philosophy (Midday style)
|
|
4
|
+
|
|
5
|
+
- **Minimalism**: Wide spacing, clear typography
|
|
6
|
+
- **Dark mode first**: Dark theme as primary experience
|
|
7
|
+
- **No modals for CRUD**: Use full pages instead of dialogs
|
|
8
|
+
- **Hierarchical navigation**: Sidebar > SecondaryMenu (tabs) > Content
|
|
9
|
+
- **Constrained containers**: `max-w-[800px]` for forms
|
|
10
|
+
|
|
11
|
+
## Pages vs Modals
|
|
12
|
+
|
|
13
|
+
| Action | DO NOT use | MUST use |
|
|
14
|
+
|--------|-----------|----------|
|
|
15
|
+
| Create entity | Dialog with form | `/entity/new` (full page) |
|
|
16
|
+
| Edit entity | Sheet with form | `/entity/[id]/edit` (full page) |
|
|
17
|
+
| View detail | Expand row | `/entity/[id]` (full page) |
|
|
18
|
+
| Confirm deletion | — | AlertDialog (only exception) |
|
|
19
|
+
| Complex filters | Modal | Collapsible panel or page |
|
|
20
|
+
|
|
21
|
+
## Route Structure Pattern
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
app/(auth)/
|
|
25
|
+
├── layout.tsx # Layout with Sidebar
|
|
26
|
+
├── dashboard/
|
|
27
|
+
│ └── page.tsx
|
|
28
|
+
├── [entity]/
|
|
29
|
+
│ ├── layout.tsx # SecondaryMenu for this section
|
|
30
|
+
│ ├── page.tsx # List
|
|
31
|
+
│ ├── new/
|
|
32
|
+
│ │ └── page.tsx # Create (full page)
|
|
33
|
+
│ ├── [id]/
|
|
34
|
+
│ │ ├── page.tsx # Detail
|
|
35
|
+
│ │ └── edit/
|
|
36
|
+
│ │ └── page.tsx # Edit (full page)
|
|
37
|
+
│ └── [sub-section]/
|
|
38
|
+
│ └── page.tsx # Additional section pages
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## SecondaryMenu Pattern
|
|
42
|
+
|
|
43
|
+
When a section has subpages, MUST add a `layout.tsx` with SecondaryMenu:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// app/(auth)/settings/layout.tsx
|
|
47
|
+
import { SecondaryMenu } from '@/components/layout/secondary-menu'
|
|
48
|
+
|
|
49
|
+
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="max-w-[800px]">
|
|
52
|
+
<SecondaryMenu
|
|
53
|
+
items={[
|
|
54
|
+
{ path: '/settings', label: 'General' },
|
|
55
|
+
{ path: '/settings/billing', label: 'Billing' },
|
|
56
|
+
{ path: '/settings/members', label: 'Members' },
|
|
57
|
+
{ path: '/settings/notifications', label: 'Notifications' },
|
|
58
|
+
]}
|
|
59
|
+
/>
|
|
60
|
+
<main className="mt-8">{children}</main>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Navigation Configuration
|
|
67
|
+
|
|
68
|
+
Sidebar navigation is defined in `config/navigation.ts` with support for children (submenus):
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// config/navigation.ts
|
|
72
|
+
export type NavItem = {
|
|
73
|
+
title: string
|
|
74
|
+
href: string
|
|
75
|
+
icon?: keyof typeof Icons
|
|
76
|
+
disabled?: boolean
|
|
77
|
+
badge?: string
|
|
78
|
+
children?: NavItem[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type NavSection = {
|
|
82
|
+
title?: string
|
|
83
|
+
items: NavItem[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const sidebarNav: NavSection[] = [
|
|
87
|
+
{
|
|
88
|
+
items: [
|
|
89
|
+
{ title: 'Dashboard', href: '/dashboard', icon: 'dashboard' },
|
|
90
|
+
{
|
|
91
|
+
title: 'Users', href: '/users', icon: 'users',
|
|
92
|
+
children: [
|
|
93
|
+
{ title: 'All Users', href: '/users' },
|
|
94
|
+
{ title: 'Create New', href: '/users/new' },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Container Widths
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Forms and settings
|
|
106
|
+
<div className="max-w-[800px]">
|
|
107
|
+
|
|
108
|
+
// Tables and lists
|
|
109
|
+
<div className="w-full">
|
|
110
|
+
|
|
111
|
+
// Dashboards with metrics
|
|
112
|
+
<div className="max-w-[1200px]">
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Spacing
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Between form sections
|
|
119
|
+
<div className="space-y-12">
|
|
120
|
+
|
|
121
|
+
// Between form fields
|
|
122
|
+
<div className="space-y-6">
|
|
123
|
+
|
|
124
|
+
// Between small elements
|
|
125
|
+
<div className="space-y-4">
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Page Header
|
|
129
|
+
|
|
130
|
+
Always use the `PageHeader` component for page headers:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { PageHeader } from '@/components/layout/page-header'
|
|
134
|
+
|
|
135
|
+
<PageHeader
|
|
136
|
+
title="Products"
|
|
137
|
+
description="Manage your product catalog"
|
|
138
|
+
actions={<Button onClick={() => router.push('/products/new')}>Create</Button>}
|
|
139
|
+
/>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Table Row Actions
|
|
143
|
+
|
|
144
|
+
Row actions MUST navigate to pages, NOT open modals:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const actions = [
|
|
148
|
+
{ label: 'Edit', onClick: (row) => router.push(`/users/${row.id}/edit`) },
|
|
149
|
+
{ label: 'View', onClick: (row) => router.push(`/users/${row.id}`) },
|
|
150
|
+
{ label: 'Delete', onClick: (row) => setDeleteId(row.id) }, // AlertDialog only
|
|
151
|
+
]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Checklist for New Pages
|
|
155
|
+
|
|
156
|
+
- Has subpages? Create `layout.tsx` with SecondaryMenu
|
|
157
|
+
- Is a form? Use `max-w-[800px]`
|
|
158
|
+
- Is a list/table? Use `w-full`
|
|
159
|
+
- Needs create/edit? Create `/new` and `/[id]/edit` routes
|
|
160
|
+
- Has parent navigation? Add to `navigation.ts` with children
|
|
161
|
+
- Sidebar needs update? Add icon and route
|