@digilogiclabs/create-saas-app 2.10.0 → 2.11.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 +153 -113
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +2 -6
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/templates/web/ai-platform/template/src/app/api/auth/route.ts +57 -0
- package/dist/templates/web/ai-platform/template/src/app/login/page.tsx +112 -0
- package/dist/templates/web/ai-platform/template/src/app/models/page.tsx +186 -0
- package/dist/templates/web/ai-platform/template/src/app/playground/page.tsx +251 -0
- package/dist/templates/web/ai-platform/template/src/app/settings/page.tsx +190 -0
- package/dist/templates/web/ai-platform/template/src/app/signup/page.tsx +133 -0
- package/dist/templates/web/ai-platform/template/src/lib/auth-session.ts +52 -0
- package/dist/templates/web/iot-dashboard/template/src/app/alerts/page.tsx +157 -0
- package/dist/templates/web/iot-dashboard/template/src/app/api/auth/route.ts +57 -0
- package/dist/templates/web/iot-dashboard/template/src/app/devices/[id]/page.tsx +204 -0
- package/dist/templates/web/iot-dashboard/template/src/app/devices/new/page.tsx +139 -0
- package/dist/templates/web/iot-dashboard/template/src/app/devices/page.tsx +171 -0
- package/dist/templates/web/iot-dashboard/template/src/app/login/page.tsx +112 -0
- package/dist/templates/web/iot-dashboard/template/src/app/settings/page.tsx +186 -0
- package/dist/templates/web/iot-dashboard/template/src/app/signup/page.tsx +133 -0
- package/dist/templates/web/iot-dashboard/template/src/lib/auth-session.ts +52 -0
- package/dist/templates/web/marketplace/template/src/app/api/auth/route.ts +57 -0
- package/dist/templates/web/marketplace/template/src/app/login/page.tsx +112 -0
- package/dist/templates/web/marketplace/template/src/app/orders/page.tsx +160 -0
- package/dist/templates/web/marketplace/template/src/app/products/[id]/page.tsx +218 -0
- package/dist/templates/web/marketplace/template/src/app/settings/page.tsx +150 -0
- package/dist/templates/web/marketplace/template/src/app/signup/page.tsx +133 -0
- package/dist/templates/web/marketplace/template/src/lib/auth-session.ts +52 -0
- package/dist/templates/web/micro-saas/template/src/app/api/auth/route.ts +57 -0
- package/dist/templates/web/micro-saas/template/src/app/login/page.tsx +14 -3
- package/dist/templates/web/micro-saas/template/src/app/signup/page.tsx +15 -4
- package/dist/templates/web/micro-saas/template/src/lib/auth-session.ts +52 -0
- package/package.json +1 -1
- package/src/templates/web/ai-platform/template/src/app/api/auth/route.ts +57 -0
- package/src/templates/web/ai-platform/template/src/app/login/page.tsx +112 -0
- package/src/templates/web/ai-platform/template/src/app/models/page.tsx +186 -0
- package/src/templates/web/ai-platform/template/src/app/playground/page.tsx +251 -0
- package/src/templates/web/ai-platform/template/src/app/settings/page.tsx +190 -0
- package/src/templates/web/ai-platform/template/src/app/signup/page.tsx +133 -0
- package/src/templates/web/ai-platform/template/src/lib/auth-session.ts +52 -0
- package/src/templates/web/iot-dashboard/template/src/app/alerts/page.tsx +157 -0
- package/src/templates/web/iot-dashboard/template/src/app/api/auth/route.ts +57 -0
- package/src/templates/web/iot-dashboard/template/src/app/devices/[id]/page.tsx +204 -0
- package/src/templates/web/iot-dashboard/template/src/app/devices/new/page.tsx +139 -0
- package/src/templates/web/iot-dashboard/template/src/app/devices/page.tsx +171 -0
- package/src/templates/web/iot-dashboard/template/src/app/login/page.tsx +112 -0
- package/src/templates/web/iot-dashboard/template/src/app/settings/page.tsx +186 -0
- package/src/templates/web/iot-dashboard/template/src/app/signup/page.tsx +133 -0
- package/src/templates/web/iot-dashboard/template/src/lib/auth-session.ts +52 -0
- package/src/templates/web/marketplace/template/src/app/api/auth/route.ts +57 -0
- package/src/templates/web/marketplace/template/src/app/login/page.tsx +112 -0
- package/src/templates/web/marketplace/template/src/app/orders/page.tsx +160 -0
- package/src/templates/web/marketplace/template/src/app/products/[id]/page.tsx +218 -0
- package/src/templates/web/marketplace/template/src/app/settings/page.tsx +150 -0
- package/src/templates/web/marketplace/template/src/app/signup/page.tsx +133 -0
- package/src/templates/web/marketplace/template/src/lib/auth-session.ts +52 -0
- package/src/templates/web/micro-saas/template/src/app/api/auth/route.ts +57 -0
- package/src/templates/web/micro-saas/template/src/app/login/page.tsx +14 -3
- package/src/templates/web/micro-saas/template/src/app/signup/page.tsx +15 -4
- package/src/templates/web/micro-saas/template/src/lib/auth-session.ts +52 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Button, Card } from '@digilogiclabs/saas-factory-ui'
|
|
2
|
+
import { User, Bell, CreditCard } from 'lucide-react'
|
|
3
|
+
import { requireAuth } from '@/lib/auth-server'
|
|
4
|
+
|
|
5
|
+
function ToggleSwitch({ defaultChecked = false }: { defaultChecked?: boolean }) {
|
|
6
|
+
return (
|
|
7
|
+
<label className="relative inline-flex items-center cursor-pointer">
|
|
8
|
+
<input type="checkbox" className="sr-only peer" defaultChecked={defaultChecked} />
|
|
9
|
+
<div className="w-11 h-6 bg-muted rounded-full peer peer-checked:bg-primary peer-focus:ring-2 peer-focus:ring-ring after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-background after:border after:border-border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full" />
|
|
10
|
+
</label>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default async function SettingsPage() {
|
|
15
|
+
const user = await requireAuth()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="min-h-screen bg-background">
|
|
19
|
+
<div className="max-w-3xl mx-auto px-4 py-8">
|
|
20
|
+
{/* Header */}
|
|
21
|
+
<div className="mb-8">
|
|
22
|
+
<h1 className="text-3xl font-bold text-foreground">Settings</h1>
|
|
23
|
+
<p className="text-muted-foreground mt-1">
|
|
24
|
+
Manage your account preferences
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div className="space-y-6">
|
|
29
|
+
{/* Profile Section */}
|
|
30
|
+
<Card className="p-6">
|
|
31
|
+
<div className="flex items-center gap-3 mb-6">
|
|
32
|
+
<User className="w-5 h-5 text-primary" />
|
|
33
|
+
<h2 className="text-lg font-semibold text-foreground">Profile</h2>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="space-y-4">
|
|
37
|
+
{/* Avatar */}
|
|
38
|
+
<div className="flex items-center gap-4 mb-6">
|
|
39
|
+
<div className="w-16 h-16 bg-muted rounded-full flex items-center justify-center border border-border">
|
|
40
|
+
<User className="w-8 h-8 text-muted-foreground" />
|
|
41
|
+
</div>
|
|
42
|
+
<Button variant="outline" size="sm">Change Avatar</Button>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Name */}
|
|
46
|
+
<div>
|
|
47
|
+
<label htmlFor="name" className="block text-sm font-medium text-foreground mb-1.5">
|
|
48
|
+
Display Name
|
|
49
|
+
</label>
|
|
50
|
+
<input
|
|
51
|
+
id="name"
|
|
52
|
+
type="text"
|
|
53
|
+
defaultValue={user.name || ''}
|
|
54
|
+
placeholder="Your name"
|
|
55
|
+
className="w-full px-4 py-2 border border-input rounded-lg focus:ring-2 focus:ring-ring bg-background text-foreground"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Email (read-only) */}
|
|
60
|
+
<div>
|
|
61
|
+
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1.5">
|
|
62
|
+
Email Address
|
|
63
|
+
</label>
|
|
64
|
+
<input
|
|
65
|
+
id="email"
|
|
66
|
+
type="email"
|
|
67
|
+
value={user.email || ''}
|
|
68
|
+
readOnly
|
|
69
|
+
className="w-full px-4 py-2 border border-input rounded-lg bg-muted text-muted-foreground cursor-not-allowed"
|
|
70
|
+
/>
|
|
71
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
72
|
+
Email cannot be changed. Contact support if you need to update it.
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</Card>
|
|
77
|
+
|
|
78
|
+
{/* Notification Preferences */}
|
|
79
|
+
<Card className="p-6">
|
|
80
|
+
<div className="flex items-center gap-3 mb-6">
|
|
81
|
+
<Bell className="w-5 h-5 text-primary" />
|
|
82
|
+
<h2 className="text-lg font-semibold text-foreground">Notifications</h2>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="space-y-4">
|
|
86
|
+
<div className="flex items-center justify-between py-2">
|
|
87
|
+
<div>
|
|
88
|
+
<p className="font-medium text-foreground">Order Updates</p>
|
|
89
|
+
<p className="text-sm text-muted-foreground">
|
|
90
|
+
Get notified when your order status changes
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
<ToggleSwitch defaultChecked />
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="border-t border-border" />
|
|
97
|
+
|
|
98
|
+
<div className="flex items-center justify-between py-2">
|
|
99
|
+
<div>
|
|
100
|
+
<p className="font-medium text-foreground">Marketing Emails</p>
|
|
101
|
+
<p className="text-sm text-muted-foreground">
|
|
102
|
+
Receive deals, promotions, and product recommendations
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
<ToggleSwitch />
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div className="border-t border-border" />
|
|
109
|
+
|
|
110
|
+
<div className="flex items-center justify-between py-2">
|
|
111
|
+
<div>
|
|
112
|
+
<p className="font-medium text-foreground">Vendor Messages</p>
|
|
113
|
+
<p className="text-sm text-muted-foreground">
|
|
114
|
+
Get notified when a vendor replies to your message
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
<ToggleSwitch defaultChecked />
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</Card>
|
|
121
|
+
|
|
122
|
+
{/* Payment Methods */}
|
|
123
|
+
<Card className="p-6">
|
|
124
|
+
<div className="flex items-center gap-3 mb-6">
|
|
125
|
+
<CreditCard className="w-5 h-5 text-primary" />
|
|
126
|
+
<h2 className="text-lg font-semibold text-foreground">Payment Methods</h2>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div className="text-center py-6">
|
|
130
|
+
<CreditCard className="w-12 h-12 mx-auto mb-3 text-muted-foreground/50" />
|
|
131
|
+
<p className="text-muted-foreground mb-4">
|
|
132
|
+
No payment methods saved yet
|
|
133
|
+
</p>
|
|
134
|
+
<Button variant="outline">
|
|
135
|
+
Add Payment Method
|
|
136
|
+
</Button>
|
|
137
|
+
</div>
|
|
138
|
+
</Card>
|
|
139
|
+
|
|
140
|
+
{/* Save Button */}
|
|
141
|
+
<div className="flex justify-end">
|
|
142
|
+
<Button size="lg">
|
|
143
|
+
Save Changes
|
|
144
|
+
</Button>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Button, Card, CardContent } from '@digilogiclabs/saas-factory-ui'
|
|
5
|
+
import { ShoppingBag } from 'lucide-react'
|
|
6
|
+
import { useRouter } from 'next/navigation'
|
|
7
|
+
import Link from 'next/link'
|
|
8
|
+
|
|
9
|
+
export default function SignupPage() {
|
|
10
|
+
const [email, setEmail] = useState('')
|
|
11
|
+
const [password, setPassword] = useState('')
|
|
12
|
+
const [confirmPassword, setConfirmPassword] = useState('')
|
|
13
|
+
const [error, setError] = useState('')
|
|
14
|
+
const [loading, setLoading] = useState(false)
|
|
15
|
+
const router = useRouter()
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
18
|
+
e.preventDefault()
|
|
19
|
+
setError('')
|
|
20
|
+
if (password !== confirmPassword) {
|
|
21
|
+
setError('Passwords do not match')
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
setLoading(true)
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch('/api/auth', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify({ email, password, action: 'signup' }),
|
|
30
|
+
})
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const data = await res.json()
|
|
33
|
+
throw new Error(data.error || 'Failed to create account')
|
|
34
|
+
}
|
|
35
|
+
router.push('/dashboard')
|
|
36
|
+
router.refresh()
|
|
37
|
+
} catch (err) {
|
|
38
|
+
setError(err instanceof Error ? err.message : 'Failed to create account')
|
|
39
|
+
} finally {
|
|
40
|
+
setLoading(false)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="min-h-screen flex items-center justify-center bg-background px-4">
|
|
46
|
+
<div className="w-full max-w-md">
|
|
47
|
+
{/* Logo */}
|
|
48
|
+
<Link href="/" className="flex items-center justify-center gap-2 mb-8 hover:opacity-80 transition-opacity">
|
|
49
|
+
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-brand-from via-brand-via to-brand-to flex items-center justify-center">
|
|
50
|
+
<ShoppingBag className="w-5 h-5 text-white" />
|
|
51
|
+
</div>
|
|
52
|
+
<span className="font-bold text-xl">Marketplace</span>
|
|
53
|
+
</Link>
|
|
54
|
+
|
|
55
|
+
<Card className="border border-border">
|
|
56
|
+
<CardContent className="p-8">
|
|
57
|
+
<div className="text-center mb-8">
|
|
58
|
+
<h1 className="text-2xl font-bold">Create your account</h1>
|
|
59
|
+
<p className="text-muted-foreground mt-2">Start buying and selling today</p>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{error && (
|
|
63
|
+
<div className="bg-destructive/10 text-destructive p-3 rounded-lg mb-6 text-sm" role="alert">
|
|
64
|
+
{error}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
69
|
+
<div>
|
|
70
|
+
<label htmlFor="email" className="block text-sm font-medium mb-1.5">
|
|
71
|
+
Email
|
|
72
|
+
</label>
|
|
73
|
+
<input
|
|
74
|
+
id="email"
|
|
75
|
+
type="email"
|
|
76
|
+
value={email}
|
|
77
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
78
|
+
className="w-full px-3 py-2.5 rounded-lg border border-input bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-shadow"
|
|
79
|
+
placeholder="you@example.com"
|
|
80
|
+
required
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div>
|
|
85
|
+
<label htmlFor="password" className="block text-sm font-medium mb-1.5">
|
|
86
|
+
Password
|
|
87
|
+
</label>
|
|
88
|
+
<input
|
|
89
|
+
id="password"
|
|
90
|
+
type="password"
|
|
91
|
+
value={password}
|
|
92
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
93
|
+
className="w-full px-3 py-2.5 rounded-lg border border-input bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-shadow"
|
|
94
|
+
placeholder="Minimum 8 characters"
|
|
95
|
+
required
|
|
96
|
+
minLength={8}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div>
|
|
101
|
+
<label htmlFor="confirmPassword" className="block text-sm font-medium mb-1.5">
|
|
102
|
+
Confirm Password
|
|
103
|
+
</label>
|
|
104
|
+
<input
|
|
105
|
+
id="confirmPassword"
|
|
106
|
+
type="password"
|
|
107
|
+
value={confirmPassword}
|
|
108
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
109
|
+
className="w-full px-3 py-2.5 rounded-lg border border-input bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-shadow"
|
|
110
|
+
placeholder="Re-enter your password"
|
|
111
|
+
required
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<Button type="submit" className="w-full h-11 text-base cursor-pointer hover:brightness-110 active:scale-[0.98] transition-all duration-200" disabled={loading}>
|
|
116
|
+
{loading ? 'Creating account...' : 'Create Account'}
|
|
117
|
+
</Button>
|
|
118
|
+
</form>
|
|
119
|
+
|
|
120
|
+
<div className="mt-6 text-center">
|
|
121
|
+
<p className="text-muted-foreground text-sm">
|
|
122
|
+
Already have an account?{' '}
|
|
123
|
+
<Link href="/login" className="text-primary hover:underline font-medium">
|
|
124
|
+
Sign in
|
|
125
|
+
</Link>
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
</CardContent>
|
|
129
|
+
</Card>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth session adapter — demo implementation.
|
|
3
|
+
*
|
|
4
|
+
* Replace this file with a real provider when configuring your app:
|
|
5
|
+
* - Keycloak: import { auth } from '@/auth' (Auth.js)
|
|
6
|
+
* - Supabase: import { createClient } from '@/lib/supabase/server'
|
|
7
|
+
* - Firebase: import { getServerSession } from 'next-auth'
|
|
8
|
+
*
|
|
9
|
+
* The getSession() and getUser() exports are consumed by auth-server.ts
|
|
10
|
+
* and must return the same shape regardless of provider.
|
|
11
|
+
*/
|
|
12
|
+
import 'server-only'
|
|
13
|
+
import { cookies } from 'next/headers'
|
|
14
|
+
|
|
15
|
+
type SessionUser = {
|
|
16
|
+
id?: string
|
|
17
|
+
email?: string | null
|
|
18
|
+
name?: string | null
|
|
19
|
+
roles?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Session = {
|
|
23
|
+
user?: SessionUser
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the current session from cookies.
|
|
28
|
+
* Demo: reads a JSON cookie set at login. Replace with your auth provider.
|
|
29
|
+
*/
|
|
30
|
+
export async function getSession(): Promise<Session | null> {
|
|
31
|
+
const cookieStore = await cookies()
|
|
32
|
+
const sessionCookie = cookieStore.get('session')
|
|
33
|
+
|
|
34
|
+
if (!sessionCookie?.value) {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const session = JSON.parse(sessionCookie.value) as Session
|
|
40
|
+
return session
|
|
41
|
+
} catch {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the current authenticated user, or null.
|
|
48
|
+
*/
|
|
49
|
+
export async function getUser(): Promise<SessionUser | null> {
|
|
50
|
+
const session = await getSession()
|
|
51
|
+
return session?.user ?? null
|
|
52
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo auth API routes.
|
|
3
|
+
*
|
|
4
|
+
* POST /api/auth — Login or Signup
|
|
5
|
+
* DELETE /api/auth — Logout
|
|
6
|
+
*
|
|
7
|
+
* Replace with your real auth provider (Auth.js, Supabase Auth, etc.)
|
|
8
|
+
* when configuring your app for production.
|
|
9
|
+
*/
|
|
10
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
11
|
+
import { cookies } from 'next/headers'
|
|
12
|
+
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
try {
|
|
15
|
+
const body = await request.json()
|
|
16
|
+
const { email, password, action } = body
|
|
17
|
+
|
|
18
|
+
if (!email || !password) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: 'Email and password are required' },
|
|
21
|
+
{ status: 400 }
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Demo: accept any email/password combination.
|
|
26
|
+
// Replace with real authentication (database lookup, bcrypt compare, etc.)
|
|
27
|
+
const user = {
|
|
28
|
+
id: crypto.randomUUID(),
|
|
29
|
+
email,
|
|
30
|
+
name: email.split('@')[0],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const session = JSON.stringify({ user })
|
|
34
|
+
const cookieStore = await cookies()
|
|
35
|
+
|
|
36
|
+
cookieStore.set('session', session, {
|
|
37
|
+
httpOnly: true,
|
|
38
|
+
secure: process.env.NODE_ENV === 'production',
|
|
39
|
+
sameSite: 'lax',
|
|
40
|
+
path: '/',
|
|
41
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return NextResponse.json({ user })
|
|
45
|
+
} catch {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: 'Authentication failed' },
|
|
48
|
+
{ status: 500 }
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function DELETE() {
|
|
54
|
+
const cookieStore = await cookies()
|
|
55
|
+
cookieStore.delete('session')
|
|
56
|
+
return NextResponse.json({ success: true })
|
|
57
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import { useAuth } from '@digilogiclabs/app-sdk'
|
|
5
4
|
import { Button, Card, CardContent } from '@digilogiclabs/saas-factory-ui'
|
|
6
5
|
import { Zap } from 'lucide-react'
|
|
7
6
|
import { useRouter } from 'next/navigation'
|
|
@@ -11,17 +10,29 @@ export default function LoginPage() {
|
|
|
11
10
|
const [email, setEmail] = useState('')
|
|
12
11
|
const [password, setPassword] = useState('')
|
|
13
12
|
const [error, setError] = useState('')
|
|
14
|
-
const
|
|
13
|
+
const [loading, setLoading] = useState(false)
|
|
15
14
|
const router = useRouter()
|
|
16
15
|
|
|
17
16
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
18
17
|
e.preventDefault()
|
|
19
18
|
setError('')
|
|
19
|
+
setLoading(true)
|
|
20
20
|
try {
|
|
21
|
-
await
|
|
21
|
+
const res = await fetch('/api/auth', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ email, password, action: 'login' }),
|
|
25
|
+
})
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
const data = await res.json()
|
|
28
|
+
throw new Error(data.error || 'Failed to sign in')
|
|
29
|
+
}
|
|
22
30
|
router.push('/dashboard')
|
|
31
|
+
router.refresh()
|
|
23
32
|
} catch (err) {
|
|
24
33
|
setError(err instanceof Error ? err.message : 'Failed to sign in')
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false)
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import { useAuth } from '@digilogiclabs/app-sdk'
|
|
5
4
|
import { Button, Card, CardContent } from '@digilogiclabs/saas-factory-ui'
|
|
6
5
|
import { Zap } from 'lucide-react'
|
|
7
6
|
import { useRouter } from 'next/navigation'
|
|
@@ -12,7 +11,7 @@ export default function SignupPage() {
|
|
|
12
11
|
const [password, setPassword] = useState('')
|
|
13
12
|
const [confirmPassword, setConfirmPassword] = useState('')
|
|
14
13
|
const [error, setError] = useState('')
|
|
15
|
-
const
|
|
14
|
+
const [loading, setLoading] = useState(false)
|
|
16
15
|
const router = useRouter()
|
|
17
16
|
|
|
18
17
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -22,11 +21,23 @@ export default function SignupPage() {
|
|
|
22
21
|
setError('Passwords do not match')
|
|
23
22
|
return
|
|
24
23
|
}
|
|
24
|
+
setLoading(true)
|
|
25
25
|
try {
|
|
26
|
-
await
|
|
26
|
+
const res = await fetch('/api/auth', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify({ email, password, action: 'signup' }),
|
|
30
|
+
})
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const data = await res.json()
|
|
33
|
+
throw new Error(data.error || 'Failed to create account')
|
|
34
|
+
}
|
|
27
35
|
router.push('/dashboard')
|
|
36
|
+
router.refresh()
|
|
28
37
|
} catch (err) {
|
|
29
|
-
setError(err instanceof Error ? err.message : 'Failed to
|
|
38
|
+
setError(err instanceof Error ? err.message : 'Failed to create account')
|
|
39
|
+
} finally {
|
|
40
|
+
setLoading(false)
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth session adapter — demo implementation.
|
|
3
|
+
*
|
|
4
|
+
* Replace this file with a real provider when configuring your app:
|
|
5
|
+
* - Keycloak: import { auth } from '@/auth' (Auth.js)
|
|
6
|
+
* - Supabase: import { createClient } from '@/lib/supabase/server'
|
|
7
|
+
* - Firebase: import { getServerSession } from 'next-auth'
|
|
8
|
+
*
|
|
9
|
+
* The getSession() and getUser() exports are consumed by auth-server.ts
|
|
10
|
+
* and must return the same shape regardless of provider.
|
|
11
|
+
*/
|
|
12
|
+
import 'server-only'
|
|
13
|
+
import { cookies } from 'next/headers'
|
|
14
|
+
|
|
15
|
+
type SessionUser = {
|
|
16
|
+
id?: string
|
|
17
|
+
email?: string | null
|
|
18
|
+
name?: string | null
|
|
19
|
+
roles?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Session = {
|
|
23
|
+
user?: SessionUser
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the current session from cookies.
|
|
28
|
+
* Demo: reads a JSON cookie set at login. Replace with your auth provider.
|
|
29
|
+
*/
|
|
30
|
+
export async function getSession(): Promise<Session | null> {
|
|
31
|
+
const cookieStore = await cookies()
|
|
32
|
+
const sessionCookie = cookieStore.get('session')
|
|
33
|
+
|
|
34
|
+
if (!sessionCookie?.value) {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const session = JSON.parse(sessionCookie.value) as Session
|
|
40
|
+
return session
|
|
41
|
+
} catch {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the current authenticated user, or null.
|
|
48
|
+
*/
|
|
49
|
+
export async function getUser(): Promise<SessionUser | null> {
|
|
50
|
+
const session = await getSession()
|
|
51
|
+
return session?.user ?? null
|
|
52
|
+
}
|