@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.
Files changed (133) hide show
  1. package/README.md +549 -549
  2. package/package.json +48 -48
  3. package/template/.claude/skills/anti-patterns.md +150 -0
  4. package/template/.claude/skills/drizzle-schema.md +178 -0
  5. package/template/.claude/skills/formatting.md +56 -0
  6. package/template/.claude/skills/module-architecture.md +143 -0
  7. package/template/.claude/skills/supabase-server-actions.md +199 -0
  8. package/template/.claude/skills/ui-patterns.md +161 -0
  9. package/template/CLAUDE.md +114 -1239
  10. package/template/drizzle.config.ts +12 -12
  11. package/template/eslint.config.mjs +16 -16
  12. package/template/gitignore +36 -36
  13. package/template/next.config.ts +7 -7
  14. package/template/package.json +86 -86
  15. package/template/postcss.config.mjs +7 -7
  16. package/template/proxy.ts +12 -12
  17. package/template/public/logolft.svg +11 -11
  18. package/template/src/app/(auth)/dashboard/dashboard-content.tsx +124 -124
  19. package/template/src/app/(auth)/dashboard/page.tsx +9 -9
  20. package/template/src/app/(auth)/layout.tsx +7 -7
  21. package/template/src/app/(auth)/users/page.tsx +9 -9
  22. package/template/src/app/(auth)/users/users-content.tsx +26 -26
  23. package/template/src/app/(public)/layout.tsx +7 -7
  24. package/template/src/app/(public)/login/page.tsx +17 -17
  25. package/template/src/app/api/webhooks/route.ts +20 -20
  26. package/template/src/app/globals.css +249 -249
  27. package/template/src/app/layout.tsx +37 -37
  28. package/template/src/app/page.tsx +5 -5
  29. package/template/src/app/providers.tsx +27 -27
  30. package/template/src/components/layout/main-content.tsx +28 -28
  31. package/template/src/components/layout/sidebar-context.tsx +33 -33
  32. package/template/src/components/layout/sidebar.tsx +141 -141
  33. package/template/src/components/tables/data-table-column-header.tsx +68 -68
  34. package/template/src/components/tables/data-table-date-filter.tsx +203 -203
  35. package/template/src/components/tables/data-table-faceted-filter.tsx +185 -185
  36. package/template/src/components/tables/data-table-filters-dropdown.tsx +130 -130
  37. package/template/src/components/tables/data-table-number-filter.tsx +295 -295
  38. package/template/src/components/tables/data-table-pagination.tsx +99 -99
  39. package/template/src/components/tables/data-table-toolbar.tsx +140 -140
  40. package/template/src/components/tables/data-table-view-options.tsx +63 -63
  41. package/template/src/components/tables/data-table.tsx +148 -148
  42. package/template/src/components/tables/index.ts +9 -9
  43. package/template/src/components/ui/accordion.tsx +58 -58
  44. package/template/src/components/ui/alert-dialog.tsx +165 -165
  45. package/template/src/components/ui/alert.tsx +66 -66
  46. package/template/src/components/ui/animations/index.ts +44 -44
  47. package/template/src/components/ui/avatar.tsx +55 -55
  48. package/template/src/components/ui/badge.tsx +50 -50
  49. package/template/src/components/ui/button.tsx +118 -118
  50. package/template/src/components/ui/calendar.tsx +220 -220
  51. package/template/src/components/ui/card.tsx +113 -113
  52. package/template/src/components/ui/checkbox.tsx +38 -38
  53. package/template/src/components/ui/collapsible.tsx +33 -33
  54. package/template/src/components/ui/command.tsx +196 -196
  55. package/template/src/components/ui/dialog.tsx +156 -156
  56. package/template/src/components/ui/dropdown-menu.tsx +280 -280
  57. package/template/src/components/ui/form.tsx +171 -171
  58. package/template/src/components/ui/icons.tsx +167 -167
  59. package/template/src/components/ui/input.tsx +28 -28
  60. package/template/src/components/ui/label.tsx +25 -25
  61. package/template/src/components/ui/motion.tsx +197 -197
  62. package/template/src/components/ui/page-transition.tsx +166 -166
  63. package/template/src/components/ui/popover.tsx +59 -59
  64. package/template/src/components/ui/progress.tsx +32 -32
  65. package/template/src/components/ui/radio-group.tsx +45 -45
  66. package/template/src/components/ui/scroll-area.tsx +63 -63
  67. package/template/src/components/ui/select.tsx +208 -208
  68. package/template/src/components/ui/separator.tsx +28 -28
  69. package/template/src/components/ui/sheet.tsx +170 -170
  70. package/template/src/components/ui/sidebar.tsx +726 -726
  71. package/template/src/components/ui/skeleton.tsx +15 -15
  72. package/template/src/components/ui/slider.tsx +58 -58
  73. package/template/src/components/ui/sonner.tsx +47 -47
  74. package/template/src/components/ui/spinner.tsx +27 -27
  75. package/template/src/components/ui/submit-button.tsx +47 -47
  76. package/template/src/components/ui/switch.tsx +31 -31
  77. package/template/src/components/ui/table.tsx +120 -120
  78. package/template/src/components/ui/tabs.tsx +75 -75
  79. package/template/src/components/ui/textarea.tsx +26 -26
  80. package/template/src/components/ui/tooltip.tsx +70 -70
  81. package/template/src/config/navigation.ts +59 -59
  82. package/template/src/config/roles.ts +27 -27
  83. package/template/src/config/site.ts +12 -12
  84. package/template/src/db/index.ts +12 -12
  85. package/template/src/db/schema/index.ts +1 -1
  86. package/template/src/db/schema/users.ts +16 -16
  87. package/template/src/db/seed.ts +39 -39
  88. package/template/src/hooks/index.ts +3 -3
  89. package/template/src/hooks/use-mobile.ts +21 -21
  90. package/template/src/hooks/useDataTable.ts +82 -82
  91. package/template/src/hooks/useDebounce.ts +49 -49
  92. package/template/src/hooks/useMediaQuery.ts +36 -36
  93. package/template/src/lib/date/config.ts +36 -36
  94. package/template/src/lib/date/formatters.ts +127 -127
  95. package/template/src/lib/date/index.ts +26 -26
  96. package/template/src/lib/excel/exporter.ts +89 -89
  97. package/template/src/lib/excel/index.ts +14 -14
  98. package/template/src/lib/excel/parser.ts +96 -96
  99. package/template/src/lib/query-client.ts +35 -35
  100. package/template/src/lib/supabase/admin.ts +23 -23
  101. package/template/src/lib/supabase/client.ts +11 -11
  102. package/template/src/lib/supabase/proxy.ts +67 -67
  103. package/template/src/lib/supabase/server.ts +38 -38
  104. package/template/src/lib/supabase/types.ts +53 -53
  105. package/template/src/lib/utils.ts +6 -6
  106. package/template/src/lib/validations/common.ts +75 -75
  107. package/template/src/lib/validations/index.ts +20 -20
  108. package/template/src/modules/auth/actions/auth-actions.ts +59 -59
  109. package/template/src/modules/auth/components/login-form.tsx +68 -68
  110. package/template/src/modules/auth/hooks/useAuth.ts +38 -38
  111. package/template/src/modules/auth/hooks/useAuthMutations.ts +43 -43
  112. package/template/src/modules/auth/hooks/useAuthQueries.ts +43 -43
  113. package/template/src/modules/auth/index.ts +12 -12
  114. package/template/src/modules/auth/schemas/auth.schema.ts +32 -32
  115. package/template/src/modules/auth/stores/useAuthStore.ts +37 -37
  116. package/template/src/modules/users/actions/users-actions.ts +166 -166
  117. package/template/src/modules/users/columns.tsx +106 -106
  118. package/template/src/modules/users/components/users-list.tsx +48 -48
  119. package/template/src/modules/users/hooks/useUsers.ts +39 -39
  120. package/template/src/modules/users/hooks/useUsersMutations.ts +55 -55
  121. package/template/src/modules/users/hooks/useUsersQueries.ts +35 -35
  122. package/template/src/modules/users/index.ts +30 -30
  123. package/template/src/modules/users/schemas/users.schema.ts +51 -51
  124. package/template/src/modules/users/stores/useUsersStore.ts +60 -60
  125. package/template/src/modules/users/types/auth-user.types.ts +42 -42
  126. package/template/src/modules/users/utils/user-mapper.ts +32 -32
  127. package/template/src/stores/index.ts +1 -1
  128. package/template/src/stores/useUiStore.ts +55 -55
  129. package/template/src/types/api.ts +28 -28
  130. package/template/src/types/index.ts +2 -2
  131. package/template/src/types/table.ts +34 -34
  132. package/template/supabase/config.toml +94 -94
  133. 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