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