@digilogiclabs/create-saas-app 1.5.1 → 1.5.3

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 (115) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cli/commands/add.d.ts +6 -0
  3. package/dist/cli/commands/add.d.ts.map +1 -0
  4. package/dist/cli/commands/add.js +39 -0
  5. package/dist/cli/commands/add.js.map +1 -0
  6. package/dist/cli/commands/create.d.ts +28 -0
  7. package/dist/cli/commands/create.d.ts.map +1 -0
  8. package/dist/cli/commands/create.js +130 -0
  9. package/dist/cli/commands/create.js.map +1 -0
  10. package/dist/cli/commands/index.d.ts +4 -0
  11. package/dist/cli/commands/index.d.ts.map +1 -0
  12. package/dist/cli/commands/index.js +20 -0
  13. package/dist/cli/commands/index.js.map +1 -0
  14. package/dist/cli/commands/update.d.ts +6 -0
  15. package/dist/cli/commands/update.d.ts.map +1 -0
  16. package/dist/cli/commands/update.js +68 -0
  17. package/dist/cli/commands/update.js.map +1 -0
  18. package/dist/{index.d.ts → cli/index.d.ts} +2 -3
  19. package/dist/cli/index.d.ts.map +1 -0
  20. package/dist/cli/index.js +59 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/prompts/index.d.ts +2 -0
  23. package/dist/cli/prompts/index.d.ts.map +1 -0
  24. package/dist/cli/prompts/index.js +18 -0
  25. package/dist/cli/prompts/index.js.map +1 -0
  26. package/dist/cli/prompts/project-setup.d.ts +5 -0
  27. package/dist/cli/prompts/project-setup.d.ts.map +1 -0
  28. package/dist/cli/prompts/project-setup.js +251 -0
  29. package/dist/cli/prompts/project-setup.js.map +1 -0
  30. package/dist/cli/utils/git.d.ts +9 -0
  31. package/dist/cli/utils/git.d.ts.map +1 -0
  32. package/dist/cli/utils/git.js +77 -0
  33. package/dist/cli/utils/git.js.map +1 -0
  34. package/dist/cli/utils/index.d.ts +5 -0
  35. package/dist/cli/utils/index.d.ts.map +1 -0
  36. package/dist/cli/utils/index.js +21 -0
  37. package/dist/cli/utils/index.js.map +1 -0
  38. package/dist/cli/utils/logger.d.ts +16 -0
  39. package/dist/cli/utils/logger.d.ts.map +1 -0
  40. package/dist/cli/utils/logger.js +55 -0
  41. package/dist/cli/utils/logger.js.map +1 -0
  42. package/dist/cli/utils/package-manager.d.ts +8 -0
  43. package/dist/cli/utils/package-manager.d.ts.map +1 -0
  44. package/dist/cli/utils/package-manager.js +92 -0
  45. package/dist/cli/utils/package-manager.js.map +1 -0
  46. package/dist/cli/utils/spinner.d.ts +7 -0
  47. package/dist/cli/utils/spinner.d.ts.map +1 -0
  48. package/dist/cli/utils/spinner.js +48 -0
  49. package/dist/cli/utils/spinner.js.map +1 -0
  50. package/dist/cli/validators/dependencies.d.ts +15 -0
  51. package/dist/cli/validators/dependencies.d.ts.map +1 -0
  52. package/dist/cli/validators/dependencies.js +108 -0
  53. package/dist/cli/validators/dependencies.js.map +1 -0
  54. package/dist/cli/validators/index.d.ts +3 -0
  55. package/dist/cli/validators/index.d.ts.map +1 -0
  56. package/dist/cli/validators/index.js +19 -0
  57. package/dist/cli/validators/index.js.map +1 -0
  58. package/dist/cli/validators/project-name.d.ts +5 -0
  59. package/dist/cli/validators/project-name.d.ts.map +1 -0
  60. package/dist/cli/validators/project-name.js +151 -0
  61. package/dist/cli/validators/project-name.js.map +1 -0
  62. package/dist/generators/file-processor.d.ts +28 -0
  63. package/dist/generators/file-processor.d.ts.map +1 -0
  64. package/dist/generators/file-processor.js +203 -0
  65. package/dist/generators/file-processor.js.map +1 -0
  66. package/dist/generators/index.d.ts +4 -0
  67. package/dist/generators/index.d.ts.map +1 -0
  68. package/dist/generators/index.js +20 -0
  69. package/dist/generators/index.js.map +1 -0
  70. package/dist/generators/package-installer.d.ts +29 -0
  71. package/dist/generators/package-installer.d.ts.map +1 -0
  72. package/dist/generators/package-installer.js +167 -0
  73. package/dist/generators/package-installer.js.map +1 -0
  74. package/dist/generators/template-generator.d.ts +48 -0
  75. package/dist/generators/template-generator.d.ts.map +1 -0
  76. package/dist/generators/template-generator.js +276 -0
  77. package/dist/generators/template-generator.js.map +1 -0
  78. package/dist/index.js +3 -3
  79. package/dist/index.js.map +1 -1
  80. package/dist/templates/web/ui-auth-payments/template/package-lock.json +12240 -0
  81. package/dist/templates/web/ui-auth-payments/template/src/components/client/login-form.tsx +1 -1
  82. package/dist/templates/web/ui-auth-payments/template/src/components/client/signup-form.tsx +3 -3
  83. package/dist/templates/web/ui-auth-payments/template/tsconfig.tsbuildinfo +1 -0
  84. package/dist/templates/web/ui-auth-payments-audio/template/middleware.ts +68 -0
  85. package/dist/templates/web/ui-auth-payments-audio/template/package-lock.json +12241 -0
  86. package/dist/templates/web/ui-auth-payments-audio/template/src/app/dashboard/layout.tsx +22 -0
  87. package/dist/templates/web/ui-auth-payments-audio/template/src/app/dashboard/page.tsx +183 -0
  88. package/dist/templates/web/ui-auth-payments-audio/template/src/app/login/page.tsx +2 -105
  89. package/dist/templates/web/ui-auth-payments-audio/template/src/app/signup/page.tsx +2 -124
  90. package/dist/templates/web/ui-auth-payments-audio/template/src/components/client/auth-status.tsx +52 -0
  91. package/dist/templates/web/ui-auth-payments-audio/template/src/components/client/login-form.tsx +144 -0
  92. package/dist/templates/web/ui-auth-payments-audio/template/src/components/client/signup-form.tsx +185 -0
  93. package/dist/templates/web/ui-auth-payments-audio/template/src/lib/actions/auth.ts +246 -0
  94. package/dist/templates/web/ui-auth-payments-audio/template/src/lib/actions/index.ts +14 -0
  95. package/dist/templates/web/ui-auth-payments-audio/template/src/lib/auth-server.ts +177 -0
  96. package/dist/templates/web/ui-auth-payments-audio/template/tsconfig.tsbuildinfo +1 -0
  97. package/dist/templates/web/ui-package-test/template/next-env.d.ts +5 -0
  98. package/package.json +4 -3
  99. package/src/templates/web/ui-auth-payments/template/package-lock.json +12240 -0
  100. package/src/templates/web/ui-auth-payments/template/src/components/client/login-form.tsx +1 -1
  101. package/src/templates/web/ui-auth-payments/template/src/components/client/signup-form.tsx +3 -3
  102. package/src/templates/web/ui-auth-payments/template/tsconfig.tsbuildinfo +1 -0
  103. package/src/templates/web/ui-auth-payments-audio/template/middleware.ts +68 -0
  104. package/src/templates/web/ui-auth-payments-audio/template/package-lock.json +12241 -0
  105. package/src/templates/web/ui-auth-payments-audio/template/src/app/dashboard/layout.tsx +22 -0
  106. package/src/templates/web/ui-auth-payments-audio/template/src/app/dashboard/page.tsx +183 -0
  107. package/src/templates/web/ui-auth-payments-audio/template/src/app/login/page.tsx +2 -105
  108. package/src/templates/web/ui-auth-payments-audio/template/src/app/signup/page.tsx +2 -124
  109. package/src/templates/web/ui-auth-payments-audio/template/src/components/client/auth-status.tsx +52 -0
  110. package/src/templates/web/ui-auth-payments-audio/template/src/components/client/login-form.tsx +144 -0
  111. package/src/templates/web/ui-auth-payments-audio/template/src/components/client/signup-form.tsx +185 -0
  112. package/src/templates/web/ui-auth-payments-audio/template/src/lib/actions/auth.ts +246 -0
  113. package/src/templates/web/ui-auth-payments-audio/template/src/lib/actions/index.ts +14 -0
  114. package/src/templates/web/ui-auth-payments-audio/template/src/lib/auth-server.ts +177 -0
  115. package/src/templates/web/ui-auth-payments-audio/template/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,185 @@
1
+ 'use client'
2
+
3
+ import React, { useState, useActionState } from 'react'
4
+ import { Button, Card, Input, Label } from '@digilogiclabs/saas-factory-ui'
5
+ import { useAuth } from '@digilogiclabs/saas-factory-auth'
6
+ import { useRouter } from 'next/navigation'
7
+ import { signUpAction } from '@/lib/actions/auth'
8
+
9
+ export function SignupForm() {
10
+ const [name, setName] = useState('')
11
+ const [email, setEmail] = useState('')
12
+ const [password, setPassword] = useState('')
13
+ const [confirmPassword, setConfirmPassword] = useState('')
14
+ const { signUp, signInWithOAuth, loading, error, user } = useAuth()
15
+ const [actionState, formAction] = useActionState(signUpAction, { success: false })
16
+ const router = useRouter()
17
+
18
+ // Redirect if already logged in
19
+ React.useEffect(() => {
20
+ if (user) {
21
+ router.push('/')
22
+ }
23
+ }, [user, router])
24
+
25
+ const handleSignUp = async (e: React.FormEvent) => {
26
+ e.preventDefault()
27
+
28
+ if (password !== confirmPassword) {
29
+ console.error('Passwords do not match')
30
+ return
31
+ }
32
+
33
+ try {
34
+ await signUp(email, password)
35
+ router.push('/')
36
+ } catch (err) {
37
+ console.error('Sign up error:', err)
38
+ }
39
+ }
40
+
41
+ const handleGoogleSignUp = async () => {
42
+ try {
43
+ await signInWithOAuth('google', window.location.origin)
44
+ } catch (err) {
45
+ console.error('Google sign up error:', err)
46
+ }
47
+ }
48
+
49
+ // Enhanced form action that uses both server action validation and client auth
50
+ const handleServerAction = async (formData: FormData) => {
51
+ const result = await formAction(formData)
52
+
53
+ if (result?.success) {
54
+ // If server validation passes, proceed with client auth
55
+ const emailValue = formData.get('email') as string
56
+ const passwordValue = formData.get('password') as string
57
+ const nameValue = formData.get('name') as string
58
+
59
+ try {
60
+ await signUp(emailValue, passwordValue)
61
+ router.push('/')
62
+ } catch (err) {
63
+ console.error('Sign up error:', err)
64
+ }
65
+ }
66
+ }
67
+
68
+ if (loading) {
69
+ return (
70
+ <div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
71
+ <div>Loading...</div>
72
+ </div>
73
+ )
74
+ }
75
+
76
+ return (
77
+ <div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
78
+ <Card className="w-full max-w-md p-8">
79
+ <h1 className="text-2xl font-bold text-center mb-6">Sign Up</h1>
80
+
81
+ {/* Server action errors */}
82
+ {actionState?.error && (
83
+ <div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
84
+ {actionState.error}
85
+ </div>
86
+ )}
87
+
88
+ {/* Client auth errors */}
89
+ {error && (
90
+ <div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
91
+ {error.message}
92
+ </div>
93
+ )}
94
+
95
+ <form action={handleServerAction} className="space-y-4">
96
+ <div>
97
+ <Label htmlFor="name">Name</Label>
98
+ <Input
99
+ id="name"
100
+ name="name"
101
+ type="text"
102
+ value={name}
103
+ onChange={(e) => setName(e.target.value)}
104
+ placeholder="Enter your name"
105
+ required
106
+ disabled={loading}
107
+ />
108
+ {actionState?.fieldErrors?.name && (
109
+ <p className="text-sm text-red-600 mt-1">{actionState.fieldErrors.name[0]}</p>
110
+ )}
111
+ </div>
112
+ <div>
113
+ <Label htmlFor="email">Email</Label>
114
+ <Input
115
+ id="email"
116
+ name="email"
117
+ type="email"
118
+ value={email}
119
+ onChange={(e) => setEmail(e.target.value)}
120
+ placeholder="Enter your email"
121
+ required
122
+ disabled={loading}
123
+ />
124
+ {actionState?.fieldErrors?.email && (
125
+ <p className="text-sm text-red-600 mt-1">{actionState.fieldErrors.email[0]}</p>
126
+ )}
127
+ </div>
128
+ <div>
129
+ <Label htmlFor="password">Password</Label>
130
+ <Input
131
+ id="password"
132
+ name="password"
133
+ type="password"
134
+ value={password}
135
+ onChange={(e) => setPassword(e.target.value)}
136
+ placeholder="Enter your password"
137
+ required
138
+ disabled={loading}
139
+ />
140
+ {actionState?.fieldErrors?.password && (
141
+ <p className="text-sm text-red-600 mt-1">{actionState.fieldErrors.password[0]}</p>
142
+ )}
143
+ </div>
144
+ <div>
145
+ <Label htmlFor="confirmPassword">Confirm Password</Label>
146
+ <Input
147
+ id="confirmPassword"
148
+ name="confirmPassword"
149
+ type="password"
150
+ value={confirmPassword}
151
+ onChange={(e) => setConfirmPassword(e.target.value)}
152
+ placeholder="Confirm your password"
153
+ required
154
+ disabled={loading}
155
+ />
156
+ {actionState?.fieldErrors?.confirmPassword && (
157
+ <p className="text-sm text-red-600 mt-1">{actionState.fieldErrors.confirmPassword[0]}</p>
158
+ )}
159
+ </div>
160
+ <Button type="submit" className="w-full" disabled={loading}>
161
+ {loading ? 'Signing Up...' : 'Sign Up'}
162
+ </Button>
163
+ <Button
164
+ type="button"
165
+ variant="outline"
166
+ className="w-full"
167
+ onClick={handleGoogleSignUp}
168
+ disabled={loading}
169
+ >
170
+ Sign up with Google
171
+ </Button>
172
+ </form>
173
+
174
+ <div className="mt-4 text-center">
175
+ <p className="text-sm text-gray-600 dark:text-gray-300">
176
+ Already have an account?{' '}
177
+ <a href="/login" className="text-blue-600 hover:underline dark:text-blue-400">
178
+ Sign in
179
+ </a>
180
+ </p>
181
+ </div>
182
+ </Card>
183
+ </div>
184
+ )
185
+ }
@@ -0,0 +1,246 @@
1
+ 'use server'
2
+
3
+ import { redirect } from 'next/navigation'
4
+ import { cookies } from 'next/headers'
5
+ import { z } from 'zod'
6
+
7
+ // Schema for form validation
8
+ const signInSchema = z.object({
9
+ email: z.string().email('Invalid email address'),
10
+ password: z.string().min(6, 'Password must be at least 6 characters'),
11
+ })
12
+
13
+ const signUpSchema = z.object({
14
+ email: z.string().email('Invalid email address'),
15
+ password: z.string().min(6, 'Password must be at least 6 characters'),
16
+ confirmPassword: z.string(),
17
+ name: z.string().min(2, 'Name must be at least 2 characters').optional(),
18
+ }).refine((data) => data.password === data.confirmPassword, {
19
+ message: "Passwords don't match",
20
+ path: ["confirmPassword"],
21
+ })
22
+
23
+ // Response types
24
+ type ActionResult = {
25
+ success: boolean
26
+ error?: string
27
+ fieldErrors?: Record<string, string[]>
28
+ }
29
+
30
+ /**
31
+ * Server action for signing in
32
+ * This works alongside the client-side @digilogiclabs/saas-factory-auth
33
+ */
34
+ export async function signInAction(prevState: any, formData: FormData): Promise<ActionResult> {
35
+ try {
36
+ const rawData = {
37
+ email: formData.get('email') as string,
38
+ password: formData.get('password') as string,
39
+ }
40
+
41
+ // Validate form data
42
+ const validationResult = signInSchema.safeParse(rawData)
43
+
44
+ if (!validationResult.success) {
45
+ return {
46
+ success: false,
47
+ error: 'Please check your input',
48
+ fieldErrors: validationResult.error.formErrors.fieldErrors,
49
+ }
50
+ }
51
+
52
+ const { email, password } = validationResult.data
53
+
54
+ // Note: The actual authentication is handled by the client-side library
55
+ // This server action is mainly for form validation and server-side processing
56
+ // You might want to add additional server-side logic here like:
57
+ // - Rate limiting
58
+ // - Logging
59
+ // - Analytics
60
+
61
+ // For now, we'll return success and let the client handle the actual auth
62
+ return {
63
+ success: true,
64
+ }
65
+ } catch (error) {
66
+ console.error('Sign in error:', error)
67
+ return {
68
+ success: false,
69
+ error: 'An unexpected error occurred. Please try again.',
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Server action for signing up
76
+ */
77
+ export async function signUpAction(prevState: any, formData: FormData): Promise<ActionResult> {
78
+ try {
79
+ const rawData = {
80
+ email: formData.get('email') as string,
81
+ password: formData.get('password') as string,
82
+ confirmPassword: formData.get('confirmPassword') as string,
83
+ name: formData.get('name') as string,
84
+ }
85
+
86
+ // Validate form data
87
+ const validationResult = signUpSchema.safeParse(rawData)
88
+
89
+ if (!validationResult.success) {
90
+ return {
91
+ success: false,
92
+ error: 'Please check your input',
93
+ fieldErrors: validationResult.error.formErrors.fieldErrors,
94
+ }
95
+ }
96
+
97
+ const { email, password, name } = validationResult.data
98
+
99
+ // Additional server-side processing can go here
100
+ // - User existence check
101
+ // - Welcome email preparation
102
+ // - User analytics setup
103
+
104
+ return {
105
+ success: true,
106
+ }
107
+ } catch (error) {
108
+ console.error('Sign up error:', error)
109
+ return {
110
+ success: false,
111
+ error: 'An unexpected error occurred. Please try again.',
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Server action for signing out
118
+ */
119
+ export async function signOutAction(): Promise<void> {
120
+ try {
121
+ const cookieStore = await cookies()
122
+
123
+ // Clear auth cookies (adjust cookie names based on the auth library)
124
+ cookieStore.delete('saas-factory-auth-token')
125
+ cookieStore.delete('saas-factory-auth-user')
126
+
127
+ // Additional cleanup can go here
128
+ // - Clear session data
129
+ // - Log sign out event
130
+ // - Clear cache
131
+
132
+ } catch (error) {
133
+ console.error('Sign out error:', error)
134
+ }
135
+
136
+ redirect('/')
137
+ }
138
+
139
+ /**
140
+ * Server action for OAuth redirect handling
141
+ */
142
+ export async function handleOAuthCallback(provider: string, code: string): Promise<ActionResult> {
143
+ try {
144
+ // This would handle OAuth callback processing
145
+ // For now, we'll delegate to the client-side library
146
+
147
+ return {
148
+ success: true,
149
+ }
150
+ } catch (error) {
151
+ console.error('OAuth callback error:', error)
152
+ return {
153
+ success: false,
154
+ error: 'Authentication failed. Please try again.',
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Server action for password reset request
161
+ */
162
+ export async function requestPasswordReset(prevState: any, formData: FormData): Promise<ActionResult> {
163
+ try {
164
+ const email = formData.get('email') as string
165
+
166
+ if (!email) {
167
+ return {
168
+ success: false,
169
+ error: 'Email is required',
170
+ }
171
+ }
172
+
173
+ const emailSchema = z.string().email()
174
+ const validationResult = emailSchema.safeParse(email)
175
+
176
+ if (!validationResult.success) {
177
+ return {
178
+ success: false,
179
+ error: 'Please enter a valid email address',
180
+ }
181
+ }
182
+
183
+ // Password reset logic would go here
184
+ // - Generate reset token
185
+ // - Send reset email
186
+ // - Log reset request
187
+
188
+ return {
189
+ success: true,
190
+ }
191
+ } catch (error) {
192
+ console.error('Password reset error:', error)
193
+ return {
194
+ success: false,
195
+ error: 'An unexpected error occurred. Please try again.',
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Server action for updating user profile
202
+ */
203
+ export async function updateProfileAction(prevState: any, formData: FormData): Promise<ActionResult> {
204
+ try {
205
+ // This would require authentication check
206
+ const cookieStore = await cookies()
207
+ const authToken = cookieStore.get('saas-factory-auth-token')?.value
208
+
209
+ if (!authToken) {
210
+ redirect('/login')
211
+ }
212
+
213
+ const name = formData.get('name') as string
214
+ const email = formData.get('email') as string
215
+
216
+ const updateSchema = z.object({
217
+ name: z.string().min(2, 'Name must be at least 2 characters').optional(),
218
+ email: z.string().email('Invalid email address').optional(),
219
+ })
220
+
221
+ const validationResult = updateSchema.safeParse({ name, email })
222
+
223
+ if (!validationResult.success) {
224
+ return {
225
+ success: false,
226
+ error: 'Please check your input',
227
+ fieldErrors: validationResult.error.formErrors.fieldErrors,
228
+ }
229
+ }
230
+
231
+ // Profile update logic would go here
232
+ // - Update user in database
233
+ // - Update auth cookies
234
+ // - Send confirmation email if email changed
235
+
236
+ return {
237
+ success: true,
238
+ }
239
+ } catch (error) {
240
+ console.error('Profile update error:', error)
241
+ return {
242
+ success: false,
243
+ error: 'An unexpected error occurred. Please try again.',
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,14 @@
1
+ // Re-export all server actions for easier imports
2
+ export {
3
+ signInAction,
4
+ signUpAction,
5
+ signOutAction,
6
+ handleOAuthCallback,
7
+ requestPasswordReset,
8
+ updateProfileAction,
9
+ } from './auth'
10
+
11
+ // Add other action exports here as needed
12
+ // export * from './posts'
13
+ // export * from './profile'
14
+ // export * from './settings'
@@ -0,0 +1,177 @@
1
+ import { cookies } from 'next/headers'
2
+ import { redirect } from 'next/navigation'
3
+
4
+ // Server-side auth utilities that work with @digilogiclabs/saas-factory-auth
5
+ // These functions provide server-side access to auth state
6
+
7
+ export type User = {
8
+ id: string
9
+ email: string
10
+ name?: string
11
+ avatar?: string
12
+ [key: string]: any
13
+ }
14
+
15
+ /**
16
+ * Get the current user from server-side cookies
17
+ * This function should be called in Server Components, Server Actions, or Route Handlers
18
+ */
19
+ export async function getCurrentUser(): Promise<User | null> {
20
+ try {
21
+ const cookieStore = await cookies()
22
+
23
+ // Check for auth token in cookies set by @digilogiclabs/saas-factory-auth
24
+ const authToken = cookieStore.get('saas-factory-auth-token')?.value
25
+ const userCookie = cookieStore.get('saas-factory-auth-user')?.value
26
+
27
+ if (!authToken || !userCookie) {
28
+ return null
29
+ }
30
+
31
+ // Parse user data from cookie
32
+ try {
33
+ const userData = JSON.parse(userCookie)
34
+ return userData
35
+ } catch {
36
+ return null
37
+ }
38
+ } catch (error) {
39
+ console.error('Error getting current user:', error)
40
+ return null
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Require authentication - redirects to login if not authenticated
46
+ * Use this in Server Components that require authentication
47
+ */
48
+ export async function requireAuth(): Promise<User> {
49
+ const user = await getCurrentUser()
50
+
51
+ if (!user) {
52
+ redirect('/login')
53
+ }
54
+
55
+ return user
56
+ }
57
+
58
+ /**
59
+ * Check if user is authenticated (without redirecting)
60
+ * Returns boolean for conditional rendering
61
+ */
62
+ export async function isAuthenticated(): Promise<boolean> {
63
+ const user = await getCurrentUser()
64
+ return !!user
65
+ }
66
+
67
+ /**
68
+ * Redirect if already authenticated
69
+ * Use this on login/signup pages
70
+ */
71
+ export async function redirectIfAuthenticated(redirectTo: string = '/') {
72
+ const user = await getCurrentUser()
73
+
74
+ if (user) {
75
+ redirect(redirectTo)
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get user session data for server components
81
+ * Returns both user and loading state info
82
+ */
83
+ export async function getServerSession() {
84
+ const user = await getCurrentUser()
85
+
86
+ return {
87
+ user,
88
+ isAuthenticated: !!user,
89
+ isLoading: false, // Server-side is never loading
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Server-side auth check for API routes
95
+ */
96
+ export async function getAuthFromRequest(request?: Request): Promise<User | null> {
97
+ try {
98
+ // If request is provided, extract cookies from it
99
+ if (request) {
100
+ const cookie = request.headers.get('cookie')
101
+ if (!cookie) return null
102
+
103
+ // Parse cookies manually from request
104
+ const cookies = cookie.split(';').reduce((acc, cookie) => {
105
+ const [key, value] = cookie.trim().split('=')
106
+ acc[key] = value
107
+ return acc
108
+ }, {} as Record<string, string>)
109
+
110
+ const authToken = cookies['saas-factory-auth-token']
111
+ const userCookie = cookies['saas-factory-auth-user']
112
+
113
+ if (!authToken || !userCookie) {
114
+ return null
115
+ }
116
+
117
+ try {
118
+ const userData = JSON.parse(decodeURIComponent(userCookie))
119
+ return userData
120
+ } catch {
121
+ return null
122
+ }
123
+ }
124
+
125
+ // Fallback to cookies() API
126
+ return await getCurrentUser()
127
+ } catch (error) {
128
+ console.error('Error getting auth from request:', error)
129
+ return null
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Create server action wrapper that requires authentication
135
+ */
136
+ export function withAuth<T extends any[], R>(
137
+ action: (user: User, ...args: T) => Promise<R>
138
+ ) {
139
+ return async (...args: T): Promise<R> => {
140
+ const user = await requireAuth()
141
+ return action(user, ...args)
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Server-side utility to check specific roles/permissions
147
+ * Extend this based on your user data structure
148
+ */
149
+ export async function hasRole(role: string): Promise<boolean> {
150
+ const user = await getCurrentUser()
151
+
152
+ if (!user) return false
153
+
154
+ // Assuming role is stored in user.role or user.roles
155
+ if (Array.isArray(user.roles)) {
156
+ return user.roles.includes(role)
157
+ }
158
+
159
+ return user.role === role
160
+ }
161
+
162
+ /**
163
+ * Server-side utility to require specific roles
164
+ */
165
+ export async function requireRole(role: string): Promise<User> {
166
+ const user = await requireAuth()
167
+
168
+ const hasRequiredRole = Array.isArray(user.roles)
169
+ ? user.roles.includes(role)
170
+ : user.role === role
171
+
172
+ if (!hasRequiredRole) {
173
+ redirect('/unauthorized')
174
+ }
175
+
176
+ return user
177
+ }