@webdevarif/dashui 0.3.13 → 0.5.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/dist/index.d.mts +180 -109
- package/dist/index.d.ts +180 -109
- package/dist/index.js +519 -291
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +504 -281
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/templates/README.md +74 -0
- package/templates/api-patterns/CRUD-route.pattern.ts +112 -0
- package/templates/hooks/useCreate.ts +46 -0
- package/templates/hooks/useDelete.ts +42 -0
- package/templates/hooks/useFetch.ts +30 -0
- package/templates/hooks/useUpdate.ts +46 -0
- package/templates/middleware/auth-middleware.ts +39 -0
- package/templates/nextauth/auth.config.ts +110 -0
- package/templates/nextauth/auth.ts +11 -0
- package/templates/nextauth/route-handlers/[auth].ts +10 -0
- package/templates/prisma/multi-tenant-schema.prisma +165 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webdevarif/dashui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Universal dashboard UI component library
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Universal dashboard UI component library — forms, inputs, media, tables, layouts. Modular categories: primitives, forms, dashboard, media, data, editors, ecommerce, cms.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dashboard",
|
|
7
7
|
"ui",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
37
|
+
"templates",
|
|
37
38
|
"README.md"
|
|
38
39
|
],
|
|
39
40
|
"scripts": {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# dashui Templates
|
|
2
|
+
|
|
3
|
+
Copy-paste starter code for your Next.js project. Customize to your API/database.
|
|
4
|
+
|
|
5
|
+
## What's included
|
|
6
|
+
|
|
7
|
+
- **nextauth/** — NextAuth v6 edge-safe configuration + route handlers
|
|
8
|
+
- **prisma/** — Base multi-tenant Prisma schemas (SaaS, E-commerce, CMS)
|
|
9
|
+
- **hooks/** — SWR data fetching patterns (useFetch, useCreate, useUpdate, useDelete)
|
|
10
|
+
- **middleware/** — Auth middleware, error handling, rate limiting
|
|
11
|
+
- **api-patterns/** — CRUD route templates, file uploads, webhooks
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
### 1. Copy NextAuth config
|
|
16
|
+
```bash
|
|
17
|
+
cp templates/nextauth/auth.config.ts ./
|
|
18
|
+
cp templates/nextauth/auth.ts ./
|
|
19
|
+
cp -r templates/nextauth/route-handlers ./app/api/auth/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then customize your provider credentials in `.env`.
|
|
23
|
+
|
|
24
|
+
### 2. Copy Prisma schema
|
|
25
|
+
```bash
|
|
26
|
+
cp templates/prisma/multi-tenant-schema.prisma ./prisma/schema.prisma
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Add your custom models.
|
|
30
|
+
|
|
31
|
+
### 3. Copy data fetching hooks
|
|
32
|
+
```bash
|
|
33
|
+
cp templates/hooks/*.ts ./lib/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use in components:
|
|
37
|
+
```tsx
|
|
38
|
+
const { data, error, isLoading } = useFetch('/api/products')
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 4. Copy middleware
|
|
42
|
+
```bash
|
|
43
|
+
cp templates/middleware/auth-middleware.ts ./lib/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use in API routes:
|
|
47
|
+
```tsx
|
|
48
|
+
import { requireAuth } from '@/lib/auth-middleware'
|
|
49
|
+
|
|
50
|
+
export async function POST(req) {
|
|
51
|
+
const user = await requireAuth(req)
|
|
52
|
+
// user is authenticated
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Before publishing
|
|
57
|
+
|
|
58
|
+
Each template is a starting point. **Customize to your needs:**
|
|
59
|
+
- Environment variables (OAuth credentials, database URL, API keys)
|
|
60
|
+
- Prisma relations (add your custom models)
|
|
61
|
+
- API endpoints (match your backend structure)
|
|
62
|
+
- Error handling (log to your service)
|
|
63
|
+
- Rate limits (adjust for your use case)
|
|
64
|
+
|
|
65
|
+
## FAQ
|
|
66
|
+
|
|
67
|
+
**Q: Can I modify these templates?**
|
|
68
|
+
A: Yes! They're in your project now. Make them yours.
|
|
69
|
+
|
|
70
|
+
**Q: Why not a generator/CLI?**
|
|
71
|
+
A: Copy-paste is simpler, more transparent, easier to customize. You see exactly what's installed.
|
|
72
|
+
|
|
73
|
+
**Q: How do I keep up with dashui updates?**
|
|
74
|
+
A: Components update → just re-export from dashui. Templates are static starter code — no updates needed unless you want them.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRUD API Route Pattern
|
|
3
|
+
*
|
|
4
|
+
* Copy and modify for your models.
|
|
5
|
+
* This example shows: GET, POST, PATCH, DELETE
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
9
|
+
import { requireAuth, requireRole } from '@/lib/auth-middleware'
|
|
10
|
+
import { prisma } from '@/lib/prisma'
|
|
11
|
+
|
|
12
|
+
// GET — List with pagination + filters
|
|
13
|
+
export async function GET(req: NextRequest) {
|
|
14
|
+
try {
|
|
15
|
+
const user = await requireAuth(req)
|
|
16
|
+
|
|
17
|
+
const url = new URL(req.url)
|
|
18
|
+
const page = parseInt(url.searchParams.get('page') || '1')
|
|
19
|
+
const limit = parseInt(url.searchParams.get('limit') || '20')
|
|
20
|
+
const skip = (page - 1) * limit
|
|
21
|
+
|
|
22
|
+
// TODO: Add your filters/search logic here
|
|
23
|
+
const items = await prisma.product.findMany({
|
|
24
|
+
skip,
|
|
25
|
+
take: limit,
|
|
26
|
+
// where: { storeId: user.storeId }, // filter by user's store if applicable
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const total = await prisma.product.count()
|
|
30
|
+
|
|
31
|
+
return NextResponse.json({
|
|
32
|
+
data: items,
|
|
33
|
+
pagination: { page, limit, total, pages: Math.ceil(total / limit) },
|
|
34
|
+
})
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error('GET error:', err)
|
|
37
|
+
return NextResponse.json({ error: 'Failed to fetch' }, { status: 500 })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// POST — Create
|
|
42
|
+
export async function POST(req: NextRequest) {
|
|
43
|
+
try {
|
|
44
|
+
const user = await requireAuth(req)
|
|
45
|
+
const body = await req.json()
|
|
46
|
+
|
|
47
|
+
// TODO: Validate body with zod or similar
|
|
48
|
+
if (!body.name) {
|
|
49
|
+
return NextResponse.json({ error: 'Name required' }, { status: 400 })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// TODO: Check permissions (user can create?)
|
|
53
|
+
const item = await prisma.product.create({
|
|
54
|
+
data: {
|
|
55
|
+
name: body.name,
|
|
56
|
+
description: body.description,
|
|
57
|
+
price: body.price,
|
|
58
|
+
// storeId: user.storeId, // if multi-tenant
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return NextResponse.json(item, { status: 201 })
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error('POST error:', err)
|
|
65
|
+
return NextResponse.json({ error: 'Failed to create' }, { status: 500 })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// PATCH — Update
|
|
70
|
+
export async function PATCH(req: NextRequest) {
|
|
71
|
+
try {
|
|
72
|
+
const user = await requireAuth(req)
|
|
73
|
+
const body = await req.json()
|
|
74
|
+
const { id, ...data } = body
|
|
75
|
+
|
|
76
|
+
if (!id) {
|
|
77
|
+
return NextResponse.json({ error: 'ID required' }, { status: 400 })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// TODO: Check permissions (user owns this item?)
|
|
81
|
+
const item = await prisma.product.update({
|
|
82
|
+
where: { id },
|
|
83
|
+
data,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return NextResponse.json(item)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('PATCH error:', err)
|
|
89
|
+
return NextResponse.json({ error: 'Failed to update' }, { status: 500 })
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// DELETE
|
|
94
|
+
export async function DELETE(req: NextRequest) {
|
|
95
|
+
try {
|
|
96
|
+
const user = await requireAuth(req)
|
|
97
|
+
const { searchParams } = new URL(req.url)
|
|
98
|
+
const id = searchParams.get('id')
|
|
99
|
+
|
|
100
|
+
if (!id) {
|
|
101
|
+
return NextResponse.json({ error: 'ID required' }, { status: 400 })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// TODO: Check permissions (user owns this item?)
|
|
105
|
+
await prisma.product.delete({ where: { id } })
|
|
106
|
+
|
|
107
|
+
return NextResponse.json({ success: true })
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error('DELETE error:', err)
|
|
110
|
+
return NextResponse.json({ error: 'Failed to delete' }, { status: 500 })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCreate — POST request with optimistic update
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./lib/hooks/useCreate.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const { mutate, isLoading, error } = useCreate('/api/products')
|
|
8
|
+
* await mutate({ name: 'New Product', price: 99.99 })
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState } from 'react'
|
|
12
|
+
|
|
13
|
+
export function useCreate<T = any>(endpoint: string) {
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
15
|
+
const [error, setError] = useState<Error | null>(null)
|
|
16
|
+
|
|
17
|
+
async function mutate(data: any) {
|
|
18
|
+
setIsLoading(true)
|
|
19
|
+
setError(null)
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(endpoint, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify(data),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Create failed: ${response.statusText}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = await response.json()
|
|
35
|
+
return result as T
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const error = err instanceof Error ? err : new Error('Unknown error')
|
|
38
|
+
setError(error)
|
|
39
|
+
throw error
|
|
40
|
+
} finally {
|
|
41
|
+
setIsLoading(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { mutate, isLoading, error }
|
|
46
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDelete — DELETE request
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./lib/hooks/useDelete.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const { mutate, isLoading, error } = useDelete('/api/products/123')
|
|
8
|
+
* await mutate()
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState } from 'react'
|
|
12
|
+
|
|
13
|
+
export function useDelete<T = any>(endpoint: string) {
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
15
|
+
const [error, setError] = useState<Error | null>(null)
|
|
16
|
+
|
|
17
|
+
async function mutate() {
|
|
18
|
+
setIsLoading(true)
|
|
19
|
+
setError(null)
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(endpoint, {
|
|
23
|
+
method: 'DELETE',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`Delete failed: ${response.statusText}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await response.json()
|
|
31
|
+
return result as T
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const error = err instanceof Error ? err : new Error('Unknown error')
|
|
34
|
+
setError(error)
|
|
35
|
+
throw error
|
|
36
|
+
} finally {
|
|
37
|
+
setIsLoading(false)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { mutate, isLoading, error }
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFetch — Generic SWR wrapper for GET requests
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./lib/hooks/useFetch.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const { data, error, isLoading } = useFetch('/api/products')
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import useSWR from 'swr'
|
|
11
|
+
|
|
12
|
+
const fetcher = (url: string) => fetch(url).then((r) => r.json())
|
|
13
|
+
|
|
14
|
+
export function useFetch<T = any>(endpoint: string | null) {
|
|
15
|
+
const { data, error, isLoading } = useSWR<T>(
|
|
16
|
+
endpoint, // null = skip fetching
|
|
17
|
+
fetcher,
|
|
18
|
+
{
|
|
19
|
+
revalidateOnFocus: false,
|
|
20
|
+
revalidateOnReconnect: false,
|
|
21
|
+
dedupingInterval: 60000,
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
data,
|
|
27
|
+
error,
|
|
28
|
+
isLoading,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useUpdate — PATCH request with optimistic update
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./lib/hooks/useUpdate.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const { mutate, isLoading, error } = useUpdate('/api/products/123')
|
|
8
|
+
* await mutate({ name: 'Updated Product' })
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState } from 'react'
|
|
12
|
+
|
|
13
|
+
export function useUpdate<T = any>(endpoint: string) {
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
15
|
+
const [error, setError] = useState<Error | null>(null)
|
|
16
|
+
|
|
17
|
+
async function mutate(data: any) {
|
|
18
|
+
setIsLoading(true)
|
|
19
|
+
setError(null)
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(endpoint, {
|
|
23
|
+
method: 'PATCH',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify(data),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Update failed: ${response.statusText}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = await response.json()
|
|
35
|
+
return result as T
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const error = err instanceof Error ? err : new Error('Unknown error')
|
|
38
|
+
setError(error)
|
|
39
|
+
throw error
|
|
40
|
+
} finally {
|
|
41
|
+
setIsLoading(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { mutate, isLoading, error }
|
|
46
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* requireAuth — API route middleware to enforce authentication
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./lib/auth-middleware.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage in API routes:
|
|
7
|
+
* export async function POST(req: Request) {
|
|
8
|
+
* const user = await requireAuth(req)
|
|
9
|
+
* // user is authenticated; access user.id, user.email, etc.
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { auth } from '@/auth'
|
|
14
|
+
import { NextRequest } from 'next/server'
|
|
15
|
+
|
|
16
|
+
export async function requireAuth(req: NextRequest) {
|
|
17
|
+
const session = await auth()
|
|
18
|
+
|
|
19
|
+
if (!session || !session.user) {
|
|
20
|
+
throw new Error('Unauthorized: Not authenticated', { cause: 'UNAUTHENTICATED' })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return session.user
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function requireRole(req: NextRequest, requiredRole: string) {
|
|
27
|
+
const user = await requireAuth(req)
|
|
28
|
+
|
|
29
|
+
const role = (user as any).role || 'user'
|
|
30
|
+
|
|
31
|
+
// Simple role check (admin can do everything)
|
|
32
|
+
if (role === 'admin') return user
|
|
33
|
+
|
|
34
|
+
if (role !== requiredRole) {
|
|
35
|
+
throw new Error(`Forbidden: Requires role ${requiredRole}`, { cause: 'FORBIDDEN' })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return user
|
|
39
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NextAuth v6 Edge-safe Config
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./auth.config.ts
|
|
5
|
+
* Customize: Add your OAuth provider credentials to .env
|
|
6
|
+
*
|
|
7
|
+
* Providers:
|
|
8
|
+
* - Credentials (username/password)
|
|
9
|
+
* - GitHub (OAuth)
|
|
10
|
+
* - Google (OAuth)
|
|
11
|
+
* - Add more as needed
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { NextAuthConfig } from 'next-auth'
|
|
15
|
+
import Credentials from 'next-auth/providers/credentials'
|
|
16
|
+
import GitHub from 'next-auth/providers/github'
|
|
17
|
+
import Google from 'next-auth/providers/google'
|
|
18
|
+
import { PrismaAdapter } from '@auth/prisma-adapter'
|
|
19
|
+
import { prisma } from '@/lib/prisma'
|
|
20
|
+
|
|
21
|
+
export const authConfig = {
|
|
22
|
+
providers: [
|
|
23
|
+
// Credentials provider (username/password)
|
|
24
|
+
Credentials({
|
|
25
|
+
async authorize(credentials) {
|
|
26
|
+
if (!credentials?.email || !credentials?.password) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// TODO: Implement your credential validation logic
|
|
31
|
+
// Example: find user, verify password hash, return user
|
|
32
|
+
const user = await prisma.user.findUnique({
|
|
33
|
+
where: { email: credentials.email as string },
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (!user) return null
|
|
37
|
+
|
|
38
|
+
// Verify password (use bcrypt or similar)
|
|
39
|
+
// const isValid = await bcrypt.compare(credentials.password, user.password)
|
|
40
|
+
// if (!isValid) return null
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
id: user.id,
|
|
44
|
+
email: user.email,
|
|
45
|
+
name: user.name,
|
|
46
|
+
image: user.image,
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
|
|
51
|
+
// GitHub OAuth (register app: https://github.com/settings/apps)
|
|
52
|
+
GitHub({
|
|
53
|
+
clientId: process.env.GITHUB_ID,
|
|
54
|
+
clientSecret: process.env.GITHUB_SECRET,
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
// Google OAuth (create: https://console.cloud.google.com/)
|
|
58
|
+
Google({
|
|
59
|
+
clientId: process.env.GOOGLE_ID,
|
|
60
|
+
clientSecret: process.env.GOOGLE_SECRET,
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
|
|
64
|
+
adapter: PrismaAdapter(prisma),
|
|
65
|
+
|
|
66
|
+
pages: {
|
|
67
|
+
signIn: '/login',
|
|
68
|
+
error: '/login',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
callbacks: {
|
|
72
|
+
// Add custom logic when user signs in
|
|
73
|
+
async signIn({ user, account, profile, email, credentials }) {
|
|
74
|
+
// Check if user is allowed, verify email domain, etc.
|
|
75
|
+
return true
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Add user role/permissions to session
|
|
79
|
+
async session({ session, user }) {
|
|
80
|
+
return {
|
|
81
|
+
...session,
|
|
82
|
+
user: {
|
|
83
|
+
...session.user,
|
|
84
|
+
id: user.id,
|
|
85
|
+
role: (user as any).role || 'user', // Adjust based on your schema
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Add user info to JWT token
|
|
91
|
+
async jwt({ token, user, account }) {
|
|
92
|
+
if (user) {
|
|
93
|
+
token.id = user.id
|
|
94
|
+
token.role = (user as any).role || 'user'
|
|
95
|
+
}
|
|
96
|
+
return token
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
events: {
|
|
101
|
+
// Log important events
|
|
102
|
+
async signIn({ user, account, profile, isNewUser }) {
|
|
103
|
+
console.log(`User signed in: ${user.email}`)
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
async signOut({ token }) {
|
|
107
|
+
console.log(`User signed out`)
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
} satisfies NextAuthConfig
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NextAuth v6 Instance
|
|
3
|
+
*
|
|
4
|
+
* Copy to: ./auth.ts
|
|
5
|
+
* Wraps auth.config.ts with handlers for API routes, middleware, client hooks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import NextAuth from 'next-auth'
|
|
9
|
+
import { authConfig } from './auth.config'
|
|
10
|
+
|
|
11
|
+
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig)
|