@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,96 +1,96 @@
|
|
|
1
|
-
import * as XLSX from 'xlsx'
|
|
2
|
-
|
|
3
|
-
export interface ParseOptions {
|
|
4
|
-
sheet?: number | string
|
|
5
|
-
header?: number
|
|
6
|
-
range?: string
|
|
7
|
-
raw?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface ParseResult<T = Record<string, unknown>> {
|
|
11
|
-
data: T[]
|
|
12
|
-
headers: string[]
|
|
13
|
-
sheetNames: string[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Parse an Excel file buffer to JSON
|
|
18
|
-
*/
|
|
19
|
-
export function parseExcelBuffer<T = Record<string, unknown>>(
|
|
20
|
-
buffer: ArrayBuffer,
|
|
21
|
-
options: ParseOptions = {}
|
|
22
|
-
): ParseResult<T> {
|
|
23
|
-
const workbook = XLSX.read(buffer, { type: 'array' })
|
|
24
|
-
const sheetNames = workbook.SheetNames
|
|
25
|
-
|
|
26
|
-
const sheetName = typeof options.sheet === 'number'
|
|
27
|
-
? sheetNames[options.sheet]
|
|
28
|
-
: options.sheet ?? sheetNames[0]
|
|
29
|
-
|
|
30
|
-
const worksheet = workbook.Sheets[sheetName]
|
|
31
|
-
|
|
32
|
-
if (!worksheet) {
|
|
33
|
-
throw new Error(`Sheet "${sheetName}" not found`)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const jsonOptions: XLSX.Sheet2JSONOpts = {
|
|
37
|
-
header: options.header,
|
|
38
|
-
range: options.range,
|
|
39
|
-
raw: options.raw ?? false,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const data = XLSX.utils.sheet_to_json<T>(worksheet, jsonOptions)
|
|
43
|
-
|
|
44
|
-
// Get headers from the first row
|
|
45
|
-
const range = XLSX.utils.decode_range(worksheet['!ref'] ?? 'A1')
|
|
46
|
-
const headers: string[] = []
|
|
47
|
-
|
|
48
|
-
for (let col = range.s.c; col <= range.e.c; col++) {
|
|
49
|
-
const cell = worksheet[XLSX.utils.encode_cell({ r: 0, c: col })]
|
|
50
|
-
headers.push(cell?.v?.toString() ?? `Column${col + 1}`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
data,
|
|
55
|
-
headers,
|
|
56
|
-
sheetNames,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Parse an Excel file from a File object
|
|
62
|
-
*/
|
|
63
|
-
export async function parseExcelFile<T = Record<string, unknown>>(
|
|
64
|
-
file: File,
|
|
65
|
-
options: ParseOptions = {}
|
|
66
|
-
): Promise<ParseResult<T>> {
|
|
67
|
-
const buffer = await file.arrayBuffer()
|
|
68
|
-
return parseExcelBuffer<T>(buffer, options)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Validate Excel data against a schema
|
|
73
|
-
*/
|
|
74
|
-
export function validateExcelData<T>(
|
|
75
|
-
data: unknown[],
|
|
76
|
-
validator: (row: unknown, index: number) => T | null
|
|
77
|
-
): { valid: T[]; errors: { row: number; error: string }[] } {
|
|
78
|
-
const valid: T[] = []
|
|
79
|
-
const errors: { row: number; error: string }[] = []
|
|
80
|
-
|
|
81
|
-
data.forEach((row, index) => {
|
|
82
|
-
try {
|
|
83
|
-
const result = validator(row, index)
|
|
84
|
-
if (result !== null) {
|
|
85
|
-
valid.push(result)
|
|
86
|
-
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
errors.push({
|
|
89
|
-
row: index + 2, // +2 because Excel rows are 1-indexed and header is row 1
|
|
90
|
-
error: error instanceof Error ? error.message : 'Validation error',
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
return { valid, errors }
|
|
96
|
-
}
|
|
1
|
+
import * as XLSX from 'xlsx'
|
|
2
|
+
|
|
3
|
+
export interface ParseOptions {
|
|
4
|
+
sheet?: number | string
|
|
5
|
+
header?: number
|
|
6
|
+
range?: string
|
|
7
|
+
raw?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ParseResult<T = Record<string, unknown>> {
|
|
11
|
+
data: T[]
|
|
12
|
+
headers: string[]
|
|
13
|
+
sheetNames: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse an Excel file buffer to JSON
|
|
18
|
+
*/
|
|
19
|
+
export function parseExcelBuffer<T = Record<string, unknown>>(
|
|
20
|
+
buffer: ArrayBuffer,
|
|
21
|
+
options: ParseOptions = {}
|
|
22
|
+
): ParseResult<T> {
|
|
23
|
+
const workbook = XLSX.read(buffer, { type: 'array' })
|
|
24
|
+
const sheetNames = workbook.SheetNames
|
|
25
|
+
|
|
26
|
+
const sheetName = typeof options.sheet === 'number'
|
|
27
|
+
? sheetNames[options.sheet]
|
|
28
|
+
: options.sheet ?? sheetNames[0]
|
|
29
|
+
|
|
30
|
+
const worksheet = workbook.Sheets[sheetName]
|
|
31
|
+
|
|
32
|
+
if (!worksheet) {
|
|
33
|
+
throw new Error(`Sheet "${sheetName}" not found`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const jsonOptions: XLSX.Sheet2JSONOpts = {
|
|
37
|
+
header: options.header,
|
|
38
|
+
range: options.range,
|
|
39
|
+
raw: options.raw ?? false,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = XLSX.utils.sheet_to_json<T>(worksheet, jsonOptions)
|
|
43
|
+
|
|
44
|
+
// Get headers from the first row
|
|
45
|
+
const range = XLSX.utils.decode_range(worksheet['!ref'] ?? 'A1')
|
|
46
|
+
const headers: string[] = []
|
|
47
|
+
|
|
48
|
+
for (let col = range.s.c; col <= range.e.c; col++) {
|
|
49
|
+
const cell = worksheet[XLSX.utils.encode_cell({ r: 0, c: col })]
|
|
50
|
+
headers.push(cell?.v?.toString() ?? `Column${col + 1}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
data,
|
|
55
|
+
headers,
|
|
56
|
+
sheetNames,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Parse an Excel file from a File object
|
|
62
|
+
*/
|
|
63
|
+
export async function parseExcelFile<T = Record<string, unknown>>(
|
|
64
|
+
file: File,
|
|
65
|
+
options: ParseOptions = {}
|
|
66
|
+
): Promise<ParseResult<T>> {
|
|
67
|
+
const buffer = await file.arrayBuffer()
|
|
68
|
+
return parseExcelBuffer<T>(buffer, options)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validate Excel data against a schema
|
|
73
|
+
*/
|
|
74
|
+
export function validateExcelData<T>(
|
|
75
|
+
data: unknown[],
|
|
76
|
+
validator: (row: unknown, index: number) => T | null
|
|
77
|
+
): { valid: T[]; errors: { row: number; error: string }[] } {
|
|
78
|
+
const valid: T[] = []
|
|
79
|
+
const errors: { row: number; error: string }[] = []
|
|
80
|
+
|
|
81
|
+
data.forEach((row, index) => {
|
|
82
|
+
try {
|
|
83
|
+
const result = validator(row, index)
|
|
84
|
+
if (result !== null) {
|
|
85
|
+
valid.push(result)
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
errors.push({
|
|
89
|
+
row: index + 2, // +2 because Excel rows are 1-indexed and header is row 1
|
|
90
|
+
error: error instanceof Error ? error.message : 'Validation error',
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
return { valid, errors }
|
|
96
|
+
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import { QueryClient, defaultShouldDehydrateQuery, isServer } from '@tanstack/react-query'
|
|
2
|
-
|
|
3
|
-
function makeQueryClient() {
|
|
4
|
-
return new QueryClient({
|
|
5
|
-
defaultOptions: {
|
|
6
|
-
queries: {
|
|
7
|
-
staleTime: 60 * 1000, // 1 minute
|
|
8
|
-
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
9
|
-
retry: 1,
|
|
10
|
-
refetchOnWindowFocus: false,
|
|
11
|
-
},
|
|
12
|
-
dehydrate: {
|
|
13
|
-
shouldDehydrateQuery: (query) =>
|
|
14
|
-
defaultShouldDehydrateQuery(query) ||
|
|
15
|
-
query.state.status === 'pending',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let browserQueryClient: QueryClient | undefined = undefined
|
|
22
|
-
|
|
23
|
-
export function getQueryClient() {
|
|
24
|
-
if (isServer) {
|
|
25
|
-
// Server: always make a new query client
|
|
26
|
-
return makeQueryClient()
|
|
27
|
-
} else {
|
|
28
|
-
// Browser: make a new query client if we don't already have one
|
|
29
|
-
// This is very important, so we don't re-make a new client if React
|
|
30
|
-
// suspends during the initial render. This may not be needed if we
|
|
31
|
-
// have a suspense boundary BELOW the creation of the query client
|
|
32
|
-
if (!browserQueryClient) browserQueryClient = makeQueryClient()
|
|
33
|
-
return browserQueryClient
|
|
34
|
-
}
|
|
35
|
-
}
|
|
1
|
+
import { QueryClient, defaultShouldDehydrateQuery, isServer } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
function makeQueryClient() {
|
|
4
|
+
return new QueryClient({
|
|
5
|
+
defaultOptions: {
|
|
6
|
+
queries: {
|
|
7
|
+
staleTime: 60 * 1000, // 1 minute
|
|
8
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
9
|
+
retry: 1,
|
|
10
|
+
refetchOnWindowFocus: false,
|
|
11
|
+
},
|
|
12
|
+
dehydrate: {
|
|
13
|
+
shouldDehydrateQuery: (query) =>
|
|
14
|
+
defaultShouldDehydrateQuery(query) ||
|
|
15
|
+
query.state.status === 'pending',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let browserQueryClient: QueryClient | undefined = undefined
|
|
22
|
+
|
|
23
|
+
export function getQueryClient() {
|
|
24
|
+
if (isServer) {
|
|
25
|
+
// Server: always make a new query client
|
|
26
|
+
return makeQueryClient()
|
|
27
|
+
} else {
|
|
28
|
+
// Browser: make a new query client if we don't already have one
|
|
29
|
+
// This is very important, so we don't re-make a new client if React
|
|
30
|
+
// suspends during the initial render. This may not be needed if we
|
|
31
|
+
// have a suspense boundary BELOW the creation of the query client
|
|
32
|
+
if (!browserQueryClient) browserQueryClient = makeQueryClient()
|
|
33
|
+
return browserQueryClient
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { createClient } from '@supabase/supabase-js'
|
|
2
|
-
|
|
3
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
|
4
|
-
const secretKey = process.env.SUPABASE_SECRET_DEFAULT_KEY!
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Cliente admin para operaciones que requieren privilegios elevados.
|
|
8
|
-
* Usa la nueva Secret Key de Supabase (sb_secret_...).
|
|
9
|
-
* SOLO usar en server-side (actions, API routes).
|
|
10
|
-
* NUNCA exponer en el cliente.
|
|
11
|
-
*/
|
|
12
|
-
export function createAdminClient() {
|
|
13
|
-
if (!secretKey) {
|
|
14
|
-
throw new Error('SUPABASE_SECRET_DEFAULT_KEY is not defined')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return createClient(supabaseUrl, secretKey, {
|
|
18
|
-
auth: {
|
|
19
|
-
autoRefreshToken: false,
|
|
20
|
-
persistSession: false,
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
}
|
|
1
|
+
import { createClient } from '@supabase/supabase-js'
|
|
2
|
+
|
|
3
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
|
4
|
+
const secretKey = process.env.SUPABASE_SECRET_DEFAULT_KEY!
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cliente admin para operaciones que requieren privilegios elevados.
|
|
8
|
+
* Usa la nueva Secret Key de Supabase (sb_secret_...).
|
|
9
|
+
* SOLO usar en server-side (actions, API routes).
|
|
10
|
+
* NUNCA exponer en el cliente.
|
|
11
|
+
*/
|
|
12
|
+
export function createAdminClient() {
|
|
13
|
+
if (!secretKey) {
|
|
14
|
+
throw new Error('SUPABASE_SECRET_DEFAULT_KEY is not defined')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return createClient(supabaseUrl, secretKey, {
|
|
18
|
+
auth: {
|
|
19
|
+
autoRefreshToken: false,
|
|
20
|
+
persistSession: false,
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { createBrowserClient } from '@supabase/ssr'
|
|
2
|
-
|
|
3
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
4
|
-
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
|
5
|
-
|
|
6
|
-
export function createClient() {
|
|
7
|
-
return createBrowserClient(
|
|
8
|
-
supabaseUrl!,
|
|
9
|
-
supabaseKey!
|
|
10
|
-
)
|
|
11
|
-
}
|
|
1
|
+
import { createBrowserClient } from '@supabase/ssr'
|
|
2
|
+
|
|
3
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
4
|
+
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
|
5
|
+
|
|
6
|
+
export function createClient() {
|
|
7
|
+
return createBrowserClient(
|
|
8
|
+
supabaseUrl!,
|
|
9
|
+
supabaseKey!
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import { createServerClient, type CookieOptions } from '@supabase/ssr'
|
|
2
|
-
import { NextResponse, type NextRequest } from 'next/server'
|
|
3
|
-
|
|
4
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
5
|
-
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
|
6
|
-
|
|
7
|
-
type CookieToSet = {
|
|
8
|
-
name: string
|
|
9
|
-
value: string
|
|
10
|
-
options: CookieOptions
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function updateSession(request: NextRequest) {
|
|
14
|
-
let supabaseResponse = NextResponse.next({
|
|
15
|
-
request: {
|
|
16
|
-
headers: request.headers,
|
|
17
|
-
},
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const supabase = createServerClient(
|
|
21
|
-
supabaseUrl!,
|
|
22
|
-
supabaseKey!,
|
|
23
|
-
{
|
|
24
|
-
cookies: {
|
|
25
|
-
getAll() {
|
|
26
|
-
return request.cookies.getAll()
|
|
27
|
-
},
|
|
28
|
-
setAll(cookiesToSet: CookieToSet[]) {
|
|
29
|
-
cookiesToSet.forEach(({ name, value }) =>
|
|
30
|
-
request.cookies.set(name, value)
|
|
31
|
-
)
|
|
32
|
-
supabaseResponse = NextResponse.next({
|
|
33
|
-
request,
|
|
34
|
-
})
|
|
35
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
36
|
-
supabaseResponse.cookies.set(name, value, options)
|
|
37
|
-
)
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
// Refresh session if expired
|
|
44
|
-
const { data: { user } } = await supabase.auth.getUser()
|
|
45
|
-
|
|
46
|
-
// Define route protection
|
|
47
|
-
const pathname = request.nextUrl.pathname
|
|
48
|
-
|
|
49
|
-
const protectedPaths = ['/dashboard', '/users', '/reports']
|
|
50
|
-
const publicOnlyPaths = ['/login', '/register']
|
|
51
|
-
|
|
52
|
-
const isProtectedPath = protectedPaths.some((path) => pathname.startsWith(path))
|
|
53
|
-
const isPublicOnlyPath = publicOnlyPaths.some((path) => pathname.startsWith(path))
|
|
54
|
-
|
|
55
|
-
// Redirect logic
|
|
56
|
-
if (!user && isProtectedPath) {
|
|
57
|
-
const redirectUrl = new URL('/login', request.url)
|
|
58
|
-
redirectUrl.searchParams.set('redirectTo', pathname)
|
|
59
|
-
return NextResponse.redirect(redirectUrl)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (user && isPublicOnlyPath) {
|
|
63
|
-
return NextResponse.redirect(new URL('/dashboard', request.url))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return supabaseResponse
|
|
67
|
-
}
|
|
1
|
+
import { createServerClient, type CookieOptions } from '@supabase/ssr'
|
|
2
|
+
import { NextResponse, type NextRequest } from 'next/server'
|
|
3
|
+
|
|
4
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
5
|
+
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
|
6
|
+
|
|
7
|
+
type CookieToSet = {
|
|
8
|
+
name: string
|
|
9
|
+
value: string
|
|
10
|
+
options: CookieOptions
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function updateSession(request: NextRequest) {
|
|
14
|
+
let supabaseResponse = NextResponse.next({
|
|
15
|
+
request: {
|
|
16
|
+
headers: request.headers,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const supabase = createServerClient(
|
|
21
|
+
supabaseUrl!,
|
|
22
|
+
supabaseKey!,
|
|
23
|
+
{
|
|
24
|
+
cookies: {
|
|
25
|
+
getAll() {
|
|
26
|
+
return request.cookies.getAll()
|
|
27
|
+
},
|
|
28
|
+
setAll(cookiesToSet: CookieToSet[]) {
|
|
29
|
+
cookiesToSet.forEach(({ name, value }) =>
|
|
30
|
+
request.cookies.set(name, value)
|
|
31
|
+
)
|
|
32
|
+
supabaseResponse = NextResponse.next({
|
|
33
|
+
request,
|
|
34
|
+
})
|
|
35
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
36
|
+
supabaseResponse.cookies.set(name, value, options)
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// Refresh session if expired
|
|
44
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
45
|
+
|
|
46
|
+
// Define route protection
|
|
47
|
+
const pathname = request.nextUrl.pathname
|
|
48
|
+
|
|
49
|
+
const protectedPaths = ['/dashboard', '/users', '/reports']
|
|
50
|
+
const publicOnlyPaths = ['/login', '/register']
|
|
51
|
+
|
|
52
|
+
const isProtectedPath = protectedPaths.some((path) => pathname.startsWith(path))
|
|
53
|
+
const isPublicOnlyPath = publicOnlyPaths.some((path) => pathname.startsWith(path))
|
|
54
|
+
|
|
55
|
+
// Redirect logic
|
|
56
|
+
if (!user && isProtectedPath) {
|
|
57
|
+
const redirectUrl = new URL('/login', request.url)
|
|
58
|
+
redirectUrl.searchParams.set('redirectTo', pathname)
|
|
59
|
+
return NextResponse.redirect(redirectUrl)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (user && isPublicOnlyPath) {
|
|
63
|
+
return NextResponse.redirect(new URL('/dashboard', request.url))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return supabaseResponse
|
|
67
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { createServerClient, type CookieOptions } from '@supabase/ssr'
|
|
2
|
-
import { cookies } from 'next/headers'
|
|
3
|
-
|
|
4
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
5
|
-
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
|
6
|
-
|
|
7
|
-
type CookieToSet = {
|
|
8
|
-
name: string
|
|
9
|
-
value: string
|
|
10
|
-
options: CookieOptions
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function createClient() {
|
|
14
|
-
const cookieStore = await cookies()
|
|
15
|
-
|
|
16
|
-
return createServerClient(
|
|
17
|
-
supabaseUrl!,
|
|
18
|
-
supabaseKey!,
|
|
19
|
-
{
|
|
20
|
-
cookies: {
|
|
21
|
-
getAll() {
|
|
22
|
-
return cookieStore.getAll()
|
|
23
|
-
},
|
|
24
|
-
setAll(cookiesToSet: CookieToSet[]) {
|
|
25
|
-
try {
|
|
26
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
27
|
-
cookieStore.set(name, value, options)
|
|
28
|
-
)
|
|
29
|
-
} catch {
|
|
30
|
-
// The `setAll` method was called from a Server Component.
|
|
31
|
-
// This can be ignored if you have middleware refreshing
|
|
32
|
-
// user sessions.
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
)
|
|
38
|
-
}
|
|
1
|
+
import { createServerClient, type CookieOptions } from '@supabase/ssr'
|
|
2
|
+
import { cookies } from 'next/headers'
|
|
3
|
+
|
|
4
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
5
|
+
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
|
6
|
+
|
|
7
|
+
type CookieToSet = {
|
|
8
|
+
name: string
|
|
9
|
+
value: string
|
|
10
|
+
options: CookieOptions
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function createClient() {
|
|
14
|
+
const cookieStore = await cookies()
|
|
15
|
+
|
|
16
|
+
return createServerClient(
|
|
17
|
+
supabaseUrl!,
|
|
18
|
+
supabaseKey!,
|
|
19
|
+
{
|
|
20
|
+
cookies: {
|
|
21
|
+
getAll() {
|
|
22
|
+
return cookieStore.getAll()
|
|
23
|
+
},
|
|
24
|
+
setAll(cookiesToSet: CookieToSet[]) {
|
|
25
|
+
try {
|
|
26
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
27
|
+
cookieStore.set(name, value, options)
|
|
28
|
+
)
|
|
29
|
+
} catch {
|
|
30
|
+
// The `setAll` method was called from a Server Component.
|
|
31
|
+
// This can be ignored if you have middleware refreshing
|
|
32
|
+
// user sessions.
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
export type Json =
|
|
2
|
-
| string
|
|
3
|
-
| number
|
|
4
|
-
| boolean
|
|
5
|
-
| null
|
|
6
|
-
| { [key: string]: Json | undefined }
|
|
7
|
-
| Json[]
|
|
8
|
-
|
|
9
|
-
export interface Database {
|
|
10
|
-
public: {
|
|
11
|
-
Tables: {
|
|
12
|
-
users: {
|
|
13
|
-
Row: {
|
|
14
|
-
id: string
|
|
15
|
-
email: string
|
|
16
|
-
name: string
|
|
17
|
-
role: 'admin' | 'user' | 'viewer'
|
|
18
|
-
avatar_url: string | null
|
|
19
|
-
created_at: string
|
|
20
|
-
updated_at: string
|
|
21
|
-
}
|
|
22
|
-
Insert: {
|
|
23
|
-
id?: string
|
|
24
|
-
email: string
|
|
25
|
-
name: string
|
|
26
|
-
role?: 'admin' | 'user' | 'viewer'
|
|
27
|
-
avatar_url?: string | null
|
|
28
|
-
created_at?: string
|
|
29
|
-
updated_at?: string
|
|
30
|
-
}
|
|
31
|
-
Update: {
|
|
32
|
-
id?: string
|
|
33
|
-
email?: string
|
|
34
|
-
name?: string
|
|
35
|
-
role?: 'admin' | 'user' | 'viewer'
|
|
36
|
-
avatar_url?: string | null
|
|
37
|
-
created_at?: string
|
|
38
|
-
updated_at?: string
|
|
39
|
-
}
|
|
40
|
-
Relationships: []
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
Views: {
|
|
44
|
-
[_ in never]: never
|
|
45
|
-
}
|
|
46
|
-
Functions: {
|
|
47
|
-
[_ in never]: never
|
|
48
|
-
}
|
|
49
|
-
Enums: {
|
|
50
|
-
user_role: 'admin' | 'user' | 'viewer'
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
export type Json =
|
|
2
|
+
| string
|
|
3
|
+
| number
|
|
4
|
+
| boolean
|
|
5
|
+
| null
|
|
6
|
+
| { [key: string]: Json | undefined }
|
|
7
|
+
| Json[]
|
|
8
|
+
|
|
9
|
+
export interface Database {
|
|
10
|
+
public: {
|
|
11
|
+
Tables: {
|
|
12
|
+
users: {
|
|
13
|
+
Row: {
|
|
14
|
+
id: string
|
|
15
|
+
email: string
|
|
16
|
+
name: string
|
|
17
|
+
role: 'admin' | 'user' | 'viewer'
|
|
18
|
+
avatar_url: string | null
|
|
19
|
+
created_at: string
|
|
20
|
+
updated_at: string
|
|
21
|
+
}
|
|
22
|
+
Insert: {
|
|
23
|
+
id?: string
|
|
24
|
+
email: string
|
|
25
|
+
name: string
|
|
26
|
+
role?: 'admin' | 'user' | 'viewer'
|
|
27
|
+
avatar_url?: string | null
|
|
28
|
+
created_at?: string
|
|
29
|
+
updated_at?: string
|
|
30
|
+
}
|
|
31
|
+
Update: {
|
|
32
|
+
id?: string
|
|
33
|
+
email?: string
|
|
34
|
+
name?: string
|
|
35
|
+
role?: 'admin' | 'user' | 'viewer'
|
|
36
|
+
avatar_url?: string | null
|
|
37
|
+
created_at?: string
|
|
38
|
+
updated_at?: string
|
|
39
|
+
}
|
|
40
|
+
Relationships: []
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
Views: {
|
|
44
|
+
[_ in never]: never
|
|
45
|
+
}
|
|
46
|
+
Functions: {
|
|
47
|
+
[_ in never]: never
|
|
48
|
+
}
|
|
49
|
+
Enums: {
|
|
50
|
+
user_role: 'admin' | 'user' | 'viewer'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx, type ClassValue } from "clsx"
|
|
2
|
-
import { twMerge } from "tailwind-merge"
|
|
3
|
-
|
|
4
|
-
export function cn(...inputs: ClassValue[]) {
|
|
5
|
-
return twMerge(clsx(inputs))
|
|
6
|
-
}
|
|
1
|
+
import { clsx, type ClassValue } from "clsx"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs))
|
|
6
|
+
}
|