@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
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import { USER_ROLES, DEFAULT_ROLE } from '@/config/roles'
|
|
3
|
-
|
|
4
|
-
// Schema de rol reutilizable
|
|
5
|
-
const roleSchema = z.enum(USER_ROLES)
|
|
6
|
-
|
|
7
|
-
// Schema base del usuario (estructura que devuelve el mapper)
|
|
8
|
-
export const userSchema = z.object({
|
|
9
|
-
id: z.string().uuid(),
|
|
10
|
-
email: z.string().email(),
|
|
11
|
-
name: z.string().min(1, 'El nombre es requerido'),
|
|
12
|
-
role: roleSchema.default(DEFAULT_ROLE),
|
|
13
|
-
avatar_url: z.string().url().nullable().optional(),
|
|
14
|
-
created_at: z.string().datetime(),
|
|
15
|
-
updated_at: z.string().datetime(),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
// Schema para crear usuario via Supabase Auth Admin
|
|
19
|
-
export const createAuthUserSchema = z.object({
|
|
20
|
-
email: z.string().email('Email inválido'),
|
|
21
|
-
password: z.string().min(8, 'Mínimo 8 caracteres').optional(),
|
|
22
|
-
name: z.string().min(1, 'El nombre es requerido'),
|
|
23
|
-
role: roleSchema.default(DEFAULT_ROLE),
|
|
24
|
-
avatar_url: z.string().url().nullable().optional(),
|
|
25
|
-
send_invite: z.boolean().default(true),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// Schema para actualizar usuario via Supabase Auth Admin
|
|
29
|
-
export const updateAuthUserSchema = z.object({
|
|
30
|
-
email: z.string().email('Email inválido').optional(),
|
|
31
|
-
password: z.string().min(8, 'Mínimo 8 caracteres').optional(),
|
|
32
|
-
name: z.string().min(1, 'El nombre es requerido').optional(),
|
|
33
|
-
role: roleSchema.optional(),
|
|
34
|
-
avatar_url: z.string().url().nullable().optional(),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
// Schemas legacy (mantener compatibilidad)
|
|
38
|
-
export const createUserSchema = userSchema.omit({
|
|
39
|
-
id: true,
|
|
40
|
-
created_at: true,
|
|
41
|
-
updated_at: true,
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
export const updateUserSchema = createUserSchema.partial()
|
|
45
|
-
|
|
46
|
-
// Types
|
|
47
|
-
export type User = z.infer<typeof userSchema>
|
|
48
|
-
export type CreateUserInput = z.infer<typeof createUserSchema>
|
|
49
|
-
export type UpdateUserInput = z.infer<typeof updateUserSchema>
|
|
50
|
-
export type CreateAuthUserInput = z.infer<typeof createAuthUserSchema>
|
|
51
|
-
export type UpdateAuthUserInput = z.infer<typeof updateAuthUserSchema>
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { USER_ROLES, DEFAULT_ROLE } from '@/config/roles'
|
|
3
|
+
|
|
4
|
+
// Schema de rol reutilizable
|
|
5
|
+
const roleSchema = z.enum(USER_ROLES)
|
|
6
|
+
|
|
7
|
+
// Schema base del usuario (estructura que devuelve el mapper)
|
|
8
|
+
export const userSchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
email: z.string().email(),
|
|
11
|
+
name: z.string().min(1, 'El nombre es requerido'),
|
|
12
|
+
role: roleSchema.default(DEFAULT_ROLE),
|
|
13
|
+
avatar_url: z.string().url().nullable().optional(),
|
|
14
|
+
created_at: z.string().datetime(),
|
|
15
|
+
updated_at: z.string().datetime(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// Schema para crear usuario via Supabase Auth Admin
|
|
19
|
+
export const createAuthUserSchema = z.object({
|
|
20
|
+
email: z.string().email('Email inválido'),
|
|
21
|
+
password: z.string().min(8, 'Mínimo 8 caracteres').optional(),
|
|
22
|
+
name: z.string().min(1, 'El nombre es requerido'),
|
|
23
|
+
role: roleSchema.default(DEFAULT_ROLE),
|
|
24
|
+
avatar_url: z.string().url().nullable().optional(),
|
|
25
|
+
send_invite: z.boolean().default(true),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Schema para actualizar usuario via Supabase Auth Admin
|
|
29
|
+
export const updateAuthUserSchema = z.object({
|
|
30
|
+
email: z.string().email('Email inválido').optional(),
|
|
31
|
+
password: z.string().min(8, 'Mínimo 8 caracteres').optional(),
|
|
32
|
+
name: z.string().min(1, 'El nombre es requerido').optional(),
|
|
33
|
+
role: roleSchema.optional(),
|
|
34
|
+
avatar_url: z.string().url().nullable().optional(),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Schemas legacy (mantener compatibilidad)
|
|
38
|
+
export const createUserSchema = userSchema.omit({
|
|
39
|
+
id: true,
|
|
40
|
+
created_at: true,
|
|
41
|
+
updated_at: true,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const updateUserSchema = createUserSchema.partial()
|
|
45
|
+
|
|
46
|
+
// Types
|
|
47
|
+
export type User = z.infer<typeof userSchema>
|
|
48
|
+
export type CreateUserInput = z.infer<typeof createUserSchema>
|
|
49
|
+
export type UpdateUserInput = z.infer<typeof updateUserSchema>
|
|
50
|
+
export type CreateAuthUserInput = z.infer<typeof createAuthUserSchema>
|
|
51
|
+
export type UpdateAuthUserInput = z.infer<typeof updateAuthUserSchema>
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { create } from 'zustand'
|
|
2
|
-
|
|
3
|
-
interface UsersState {
|
|
4
|
-
state: {
|
|
5
|
-
selectedUserId: string | null
|
|
6
|
-
isFormOpen: boolean
|
|
7
|
-
isDeleteDialogOpen: boolean
|
|
8
|
-
searchQuery: string
|
|
9
|
-
}
|
|
10
|
-
actions: {
|
|
11
|
-
setSelectedUserId: (id: string | null) => void
|
|
12
|
-
openForm: () => void
|
|
13
|
-
closeForm: () => void
|
|
14
|
-
openDeleteDialog: (id: string) => void
|
|
15
|
-
closeDeleteDialog: () => void
|
|
16
|
-
setSearchQuery: (query: string) => void
|
|
17
|
-
reset: () => void
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const initialState = {
|
|
22
|
-
selectedUserId: null,
|
|
23
|
-
isFormOpen: false,
|
|
24
|
-
isDeleteDialogOpen: false,
|
|
25
|
-
searchQuery: '',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const useUsersStore = create<UsersState>()((set) => ({
|
|
29
|
-
state: initialState,
|
|
30
|
-
actions: {
|
|
31
|
-
setSelectedUserId: (id) =>
|
|
32
|
-
set((state) => ({
|
|
33
|
-
state: { ...state.state, selectedUserId: id },
|
|
34
|
-
})),
|
|
35
|
-
openForm: () =>
|
|
36
|
-
set((state) => ({
|
|
37
|
-
state: { ...state.state, isFormOpen: true },
|
|
38
|
-
})),
|
|
39
|
-
closeForm: () =>
|
|
40
|
-
set((state) => ({
|
|
41
|
-
state: { ...state.state, isFormOpen: false, selectedUserId: null },
|
|
42
|
-
})),
|
|
43
|
-
openDeleteDialog: (id) =>
|
|
44
|
-
set((state) => ({
|
|
45
|
-
state: { ...state.state, isDeleteDialogOpen: true, selectedUserId: id },
|
|
46
|
-
})),
|
|
47
|
-
closeDeleteDialog: () =>
|
|
48
|
-
set((state) => ({
|
|
49
|
-
state: { ...state.state, isDeleteDialogOpen: false, selectedUserId: null },
|
|
50
|
-
})),
|
|
51
|
-
setSearchQuery: (query) =>
|
|
52
|
-
set((state) => ({
|
|
53
|
-
state: { ...state.state, searchQuery: query },
|
|
54
|
-
})),
|
|
55
|
-
reset: () =>
|
|
56
|
-
set(() => ({
|
|
57
|
-
state: initialState,
|
|
58
|
-
})),
|
|
59
|
-
},
|
|
60
|
-
}))
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
|
|
3
|
+
interface UsersState {
|
|
4
|
+
state: {
|
|
5
|
+
selectedUserId: string | null
|
|
6
|
+
isFormOpen: boolean
|
|
7
|
+
isDeleteDialogOpen: boolean
|
|
8
|
+
searchQuery: string
|
|
9
|
+
}
|
|
10
|
+
actions: {
|
|
11
|
+
setSelectedUserId: (id: string | null) => void
|
|
12
|
+
openForm: () => void
|
|
13
|
+
closeForm: () => void
|
|
14
|
+
openDeleteDialog: (id: string) => void
|
|
15
|
+
closeDeleteDialog: () => void
|
|
16
|
+
setSearchQuery: (query: string) => void
|
|
17
|
+
reset: () => void
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const initialState = {
|
|
22
|
+
selectedUserId: null,
|
|
23
|
+
isFormOpen: false,
|
|
24
|
+
isDeleteDialogOpen: false,
|
|
25
|
+
searchQuery: '',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useUsersStore = create<UsersState>()((set) => ({
|
|
29
|
+
state: initialState,
|
|
30
|
+
actions: {
|
|
31
|
+
setSelectedUserId: (id) =>
|
|
32
|
+
set((state) => ({
|
|
33
|
+
state: { ...state.state, selectedUserId: id },
|
|
34
|
+
})),
|
|
35
|
+
openForm: () =>
|
|
36
|
+
set((state) => ({
|
|
37
|
+
state: { ...state.state, isFormOpen: true },
|
|
38
|
+
})),
|
|
39
|
+
closeForm: () =>
|
|
40
|
+
set((state) => ({
|
|
41
|
+
state: { ...state.state, isFormOpen: false, selectedUserId: null },
|
|
42
|
+
})),
|
|
43
|
+
openDeleteDialog: (id) =>
|
|
44
|
+
set((state) => ({
|
|
45
|
+
state: { ...state.state, isDeleteDialogOpen: true, selectedUserId: id },
|
|
46
|
+
})),
|
|
47
|
+
closeDeleteDialog: () =>
|
|
48
|
+
set((state) => ({
|
|
49
|
+
state: { ...state.state, isDeleteDialogOpen: false, selectedUserId: null },
|
|
50
|
+
})),
|
|
51
|
+
setSearchQuery: (query) =>
|
|
52
|
+
set((state) => ({
|
|
53
|
+
state: { ...state.state, searchQuery: query },
|
|
54
|
+
})),
|
|
55
|
+
reset: () =>
|
|
56
|
+
set(() => ({
|
|
57
|
+
state: initialState,
|
|
58
|
+
})),
|
|
59
|
+
},
|
|
60
|
+
}))
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import type { User as SupabaseAuthUser } from '@supabase/supabase-js'
|
|
2
|
-
import type { UserRole } from '@/config/roles'
|
|
3
|
-
|
|
4
|
-
// Re-exportar desde config para mantener compatibilidad
|
|
5
|
-
export type { UserRole } from '@/config/roles'
|
|
6
|
-
|
|
7
|
-
export interface UserAppMetadata {
|
|
8
|
-
role: UserRole
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface UserMetadata {
|
|
12
|
-
name: string
|
|
13
|
-
avatar_url?: string | null
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface AuthUserWithMetadata extends SupabaseAuthUser {
|
|
17
|
-
app_metadata: UserAppMetadata
|
|
18
|
-
user_metadata: UserMetadata
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Input para crear usuario via auth.admin.createUser
|
|
23
|
-
*/
|
|
24
|
-
export interface CreateAuthUserInput {
|
|
25
|
-
email: string
|
|
26
|
-
password?: string
|
|
27
|
-
name: string
|
|
28
|
-
role?: UserRole
|
|
29
|
-
avatar_url?: string | null
|
|
30
|
-
send_invite?: boolean
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Input para actualizar usuario via auth.admin.updateUserById
|
|
35
|
-
*/
|
|
36
|
-
export interface UpdateAuthUserInput {
|
|
37
|
-
email?: string
|
|
38
|
-
password?: string
|
|
39
|
-
name?: string
|
|
40
|
-
role?: UserRole
|
|
41
|
-
avatar_url?: string | null
|
|
42
|
-
}
|
|
1
|
+
import type { User as SupabaseAuthUser } from '@supabase/supabase-js'
|
|
2
|
+
import type { UserRole } from '@/config/roles'
|
|
3
|
+
|
|
4
|
+
// Re-exportar desde config para mantener compatibilidad
|
|
5
|
+
export type { UserRole } from '@/config/roles'
|
|
6
|
+
|
|
7
|
+
export interface UserAppMetadata {
|
|
8
|
+
role: UserRole
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UserMetadata {
|
|
12
|
+
name: string
|
|
13
|
+
avatar_url?: string | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AuthUserWithMetadata extends SupabaseAuthUser {
|
|
17
|
+
app_metadata: UserAppMetadata
|
|
18
|
+
user_metadata: UserMetadata
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Input para crear usuario via auth.admin.createUser
|
|
23
|
+
*/
|
|
24
|
+
export interface CreateAuthUserInput {
|
|
25
|
+
email: string
|
|
26
|
+
password?: string
|
|
27
|
+
name: string
|
|
28
|
+
role?: UserRole
|
|
29
|
+
avatar_url?: string | null
|
|
30
|
+
send_invite?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Input para actualizar usuario via auth.admin.updateUserById
|
|
35
|
+
*/
|
|
36
|
+
export interface UpdateAuthUserInput {
|
|
37
|
+
email?: string
|
|
38
|
+
password?: string
|
|
39
|
+
name?: string
|
|
40
|
+
role?: UserRole
|
|
41
|
+
avatar_url?: string | null
|
|
42
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import type { User as SupabaseAuthUser } from '@supabase/supabase-js'
|
|
2
|
-
import type { User } from '../schemas/users.schema'
|
|
3
|
-
import { DEFAULT_ROLE, type UserRole } from '@/config/roles'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Mapea un usuario de auth.users al tipo User de la aplicación.
|
|
7
|
-
* Extrae datos de app_metadata (role) y user_metadata (name, avatar_url).
|
|
8
|
-
*/
|
|
9
|
-
export function mapAuthUserToUser(authUser: SupabaseAuthUser): User {
|
|
10
|
-
const appMetadata = authUser.app_metadata as { role?: UserRole } | undefined
|
|
11
|
-
const userMetadata = authUser.user_metadata as {
|
|
12
|
-
name?: string
|
|
13
|
-
avatar_url?: string | null
|
|
14
|
-
} | undefined
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
id: authUser.id,
|
|
18
|
-
email: authUser.email ?? '',
|
|
19
|
-
name: userMetadata?.name ?? '',
|
|
20
|
-
role: appMetadata?.role ?? DEFAULT_ROLE,
|
|
21
|
-
avatar_url: userMetadata?.avatar_url ?? null,
|
|
22
|
-
created_at: authUser.created_at ?? new Date().toISOString(),
|
|
23
|
-
updated_at: authUser.updated_at ?? new Date().toISOString(),
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Mapea array de usuarios de auth.users al tipo User[]
|
|
29
|
-
*/
|
|
30
|
-
export function mapAuthUsersToUsers(authUsers: SupabaseAuthUser[]): User[] {
|
|
31
|
-
return authUsers.map(mapAuthUserToUser)
|
|
32
|
-
}
|
|
1
|
+
import type { User as SupabaseAuthUser } from '@supabase/supabase-js'
|
|
2
|
+
import type { User } from '../schemas/users.schema'
|
|
3
|
+
import { DEFAULT_ROLE, type UserRole } from '@/config/roles'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mapea un usuario de auth.users al tipo User de la aplicación.
|
|
7
|
+
* Extrae datos de app_metadata (role) y user_metadata (name, avatar_url).
|
|
8
|
+
*/
|
|
9
|
+
export function mapAuthUserToUser(authUser: SupabaseAuthUser): User {
|
|
10
|
+
const appMetadata = authUser.app_metadata as { role?: UserRole } | undefined
|
|
11
|
+
const userMetadata = authUser.user_metadata as {
|
|
12
|
+
name?: string
|
|
13
|
+
avatar_url?: string | null
|
|
14
|
+
} | undefined
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
id: authUser.id,
|
|
18
|
+
email: authUser.email ?? '',
|
|
19
|
+
name: userMetadata?.name ?? '',
|
|
20
|
+
role: appMetadata?.role ?? DEFAULT_ROLE,
|
|
21
|
+
avatar_url: userMetadata?.avatar_url ?? null,
|
|
22
|
+
created_at: authUser.created_at ?? new Date().toISOString(),
|
|
23
|
+
updated_at: authUser.updated_at ?? new Date().toISOString(),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Mapea array de usuarios de auth.users al tipo User[]
|
|
29
|
+
*/
|
|
30
|
+
export function mapAuthUsersToUsers(authUsers: SupabaseAuthUser[]): User[] {
|
|
31
|
+
return authUsers.map(mapAuthUserToUser)
|
|
32
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useUiStore } from './useUiStore'
|
|
1
|
+
export { useUiStore } from './useUiStore'
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import { create } from 'zustand'
|
|
2
|
-
import { persist } from 'zustand/middleware'
|
|
3
|
-
|
|
4
|
-
interface UiState {
|
|
5
|
-
state: {
|
|
6
|
-
sidebarOpen: boolean
|
|
7
|
-
sidebarCollapsed: boolean
|
|
8
|
-
theme: 'light' | 'dark' | 'system'
|
|
9
|
-
}
|
|
10
|
-
actions: {
|
|
11
|
-
toggleSidebar: () => void
|
|
12
|
-
setSidebarOpen: (open: boolean) => void
|
|
13
|
-
toggleSidebarCollapsed: () => void
|
|
14
|
-
setSidebarCollapsed: (collapsed: boolean) => void
|
|
15
|
-
setTheme: (theme: 'light' | 'dark' | 'system') => void
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const useUiStore = create<UiState>()(
|
|
20
|
-
persist(
|
|
21
|
-
(set) => ({
|
|
22
|
-
state: {
|
|
23
|
-
sidebarOpen: true,
|
|
24
|
-
sidebarCollapsed: false,
|
|
25
|
-
theme: 'system',
|
|
26
|
-
},
|
|
27
|
-
actions: {
|
|
28
|
-
toggleSidebar: () =>
|
|
29
|
-
set((state) => ({
|
|
30
|
-
state: { ...state.state, sidebarOpen: !state.state.sidebarOpen },
|
|
31
|
-
})),
|
|
32
|
-
setSidebarOpen: (open) =>
|
|
33
|
-
set((state) => ({
|
|
34
|
-
state: { ...state.state, sidebarOpen: open },
|
|
35
|
-
})),
|
|
36
|
-
toggleSidebarCollapsed: () =>
|
|
37
|
-
set((state) => ({
|
|
38
|
-
state: { ...state.state, sidebarCollapsed: !state.state.sidebarCollapsed },
|
|
39
|
-
})),
|
|
40
|
-
setSidebarCollapsed: (collapsed) =>
|
|
41
|
-
set((state) => ({
|
|
42
|
-
state: { ...state.state, sidebarCollapsed: collapsed },
|
|
43
|
-
})),
|
|
44
|
-
setTheme: (theme) =>
|
|
45
|
-
set((state) => ({
|
|
46
|
-
state: { ...state.state, theme },
|
|
47
|
-
})),
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
{
|
|
51
|
-
name: 'ui-storage',
|
|
52
|
-
partialize: (state) => ({ state: state.state }),
|
|
53
|
-
}
|
|
54
|
-
)
|
|
55
|
-
)
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import { persist } from 'zustand/middleware'
|
|
3
|
+
|
|
4
|
+
interface UiState {
|
|
5
|
+
state: {
|
|
6
|
+
sidebarOpen: boolean
|
|
7
|
+
sidebarCollapsed: boolean
|
|
8
|
+
theme: 'light' | 'dark' | 'system'
|
|
9
|
+
}
|
|
10
|
+
actions: {
|
|
11
|
+
toggleSidebar: () => void
|
|
12
|
+
setSidebarOpen: (open: boolean) => void
|
|
13
|
+
toggleSidebarCollapsed: () => void
|
|
14
|
+
setSidebarCollapsed: (collapsed: boolean) => void
|
|
15
|
+
setTheme: (theme: 'light' | 'dark' | 'system') => void
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useUiStore = create<UiState>()(
|
|
20
|
+
persist(
|
|
21
|
+
(set) => ({
|
|
22
|
+
state: {
|
|
23
|
+
sidebarOpen: true,
|
|
24
|
+
sidebarCollapsed: false,
|
|
25
|
+
theme: 'system',
|
|
26
|
+
},
|
|
27
|
+
actions: {
|
|
28
|
+
toggleSidebar: () =>
|
|
29
|
+
set((state) => ({
|
|
30
|
+
state: { ...state.state, sidebarOpen: !state.state.sidebarOpen },
|
|
31
|
+
})),
|
|
32
|
+
setSidebarOpen: (open) =>
|
|
33
|
+
set((state) => ({
|
|
34
|
+
state: { ...state.state, sidebarOpen: open },
|
|
35
|
+
})),
|
|
36
|
+
toggleSidebarCollapsed: () =>
|
|
37
|
+
set((state) => ({
|
|
38
|
+
state: { ...state.state, sidebarCollapsed: !state.state.sidebarCollapsed },
|
|
39
|
+
})),
|
|
40
|
+
setSidebarCollapsed: (collapsed) =>
|
|
41
|
+
set((state) => ({
|
|
42
|
+
state: { ...state.state, sidebarCollapsed: collapsed },
|
|
43
|
+
})),
|
|
44
|
+
setTheme: (theme) =>
|
|
45
|
+
set((state) => ({
|
|
46
|
+
state: { ...state.state, theme },
|
|
47
|
+
})),
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
{
|
|
51
|
+
name: 'ui-storage',
|
|
52
|
+
partialize: (state) => ({ state: state.state }),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
)
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
export interface ApiResponse<T = unknown> {
|
|
2
|
-
data: T
|
|
3
|
-
success: boolean
|
|
4
|
-
message?: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface ApiError {
|
|
8
|
-
message: string
|
|
9
|
-
code?: string
|
|
10
|
-
status?: number
|
|
11
|
-
details?: Record<string, unknown>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface PaginatedResponse<T> {
|
|
15
|
-
data: T[]
|
|
16
|
-
pagination: {
|
|
17
|
-
page: number
|
|
18
|
-
pageSize: number
|
|
19
|
-
total: number
|
|
20
|
-
totalPages: number
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ActionResult<T = void> {
|
|
25
|
-
success: boolean
|
|
26
|
-
data?: T
|
|
27
|
-
error?: string
|
|
28
|
-
}
|
|
1
|
+
export interface ApiResponse<T = unknown> {
|
|
2
|
+
data: T
|
|
3
|
+
success: boolean
|
|
4
|
+
message?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ApiError {
|
|
8
|
+
message: string
|
|
9
|
+
code?: string
|
|
10
|
+
status?: number
|
|
11
|
+
details?: Record<string, unknown>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PaginatedResponse<T> {
|
|
15
|
+
data: T[]
|
|
16
|
+
pagination: {
|
|
17
|
+
page: number
|
|
18
|
+
pageSize: number
|
|
19
|
+
total: number
|
|
20
|
+
totalPages: number
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ActionResult<T = void> {
|
|
25
|
+
success: boolean
|
|
26
|
+
data?: T
|
|
27
|
+
error?: string
|
|
28
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './api'
|
|
2
|
-
export * from './table'
|
|
1
|
+
export * from './api'
|
|
2
|
+
export * from './table'
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import type { ColumnDef, Table, Row } from '@tanstack/react-table'
|
|
2
|
-
|
|
3
|
-
export type DataTableColumn<TData, TValue = unknown> = ColumnDef<TData, TValue>
|
|
4
|
-
|
|
5
|
-
export interface DataTableProps<TData, TValue> {
|
|
6
|
-
columns: DataTableColumn<TData, TValue>[]
|
|
7
|
-
data: TData[]
|
|
8
|
-
searchKey?: string
|
|
9
|
-
searchPlaceholder?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface DataTablePaginationProps<TData> {
|
|
13
|
-
table: Table<TData>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DataTableToolbarProps<TData> {
|
|
17
|
-
table: Table<TData>
|
|
18
|
-
searchKey?: string
|
|
19
|
-
searchPlaceholder?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface DataTableRowActionsProps<TData> {
|
|
23
|
-
row: Row<TData>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface DataTableFacetedFilterProps<TData, TValue> {
|
|
27
|
-
column?: import('@tanstack/react-table').Column<TData, TValue>
|
|
28
|
-
title?: string
|
|
29
|
-
options: {
|
|
30
|
-
label: string
|
|
31
|
-
value: string
|
|
32
|
-
icon?: React.ComponentType<{ className?: string }>
|
|
33
|
-
}[]
|
|
34
|
-
}
|
|
1
|
+
import type { ColumnDef, Table, Row } from '@tanstack/react-table'
|
|
2
|
+
|
|
3
|
+
export type DataTableColumn<TData, TValue = unknown> = ColumnDef<TData, TValue>
|
|
4
|
+
|
|
5
|
+
export interface DataTableProps<TData, TValue> {
|
|
6
|
+
columns: DataTableColumn<TData, TValue>[]
|
|
7
|
+
data: TData[]
|
|
8
|
+
searchKey?: string
|
|
9
|
+
searchPlaceholder?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DataTablePaginationProps<TData> {
|
|
13
|
+
table: Table<TData>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DataTableToolbarProps<TData> {
|
|
17
|
+
table: Table<TData>
|
|
18
|
+
searchKey?: string
|
|
19
|
+
searchPlaceholder?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DataTableRowActionsProps<TData> {
|
|
23
|
+
row: Row<TData>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DataTableFacetedFilterProps<TData, TValue> {
|
|
27
|
+
column?: import('@tanstack/react-table').Column<TData, TValue>
|
|
28
|
+
title?: string
|
|
29
|
+
options: {
|
|
30
|
+
label: string
|
|
31
|
+
value: string
|
|
32
|
+
icon?: React.ComponentType<{ className?: string }>
|
|
33
|
+
}[]
|
|
34
|
+
}
|