@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
@@ -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
+ }