sitepaige-mcp-server 1.1.0 → 1.2.1

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 (51) hide show
  1. package/components/headerlogin.tsx +10 -28
  2. package/components/profile.tsx +302 -46
  3. package/components/slideshow.tsx +1 -9
  4. package/defaultapp/api/Auth/resend-verification/route.ts +3 -3
  5. package/defaultapp/api/Auth/route.ts +2 -2
  6. package/defaultapp/api/Auth/signup/route.ts +3 -3
  7. package/defaultapp/api/Auth/verify-email/route.ts +5 -5
  8. package/defaultapp/api/admin/users/route.ts +3 -3
  9. package/defaultapp/{db-users.ts → api/db-users.ts} +1 -1
  10. package/defaultapp/api/profile/route.ts +154 -0
  11. package/defaultapp/profile/page.tsx +6 -0
  12. package/dist/blueprintWriter.js.map +1 -1
  13. package/dist/components/headerlogin.tsx +10 -28
  14. package/dist/components/profile.tsx +302 -46
  15. package/dist/components/slideshow.tsx +1 -9
  16. package/dist/defaultapp/api/Auth/resend-verification/route.ts +3 -3
  17. package/dist/defaultapp/api/Auth/route.ts +2 -2
  18. package/dist/defaultapp/api/Auth/signup/route.ts +3 -3
  19. package/dist/defaultapp/api/Auth/verify-email/route.ts +5 -5
  20. package/dist/defaultapp/api/admin/users/route.ts +3 -3
  21. package/dist/defaultapp/api/csrf.ts +111 -0
  22. package/dist/defaultapp/api/db-mysql.ts +183 -0
  23. package/dist/defaultapp/api/db-password-auth.ts +362 -0
  24. package/dist/defaultapp/api/db-postgres.ts +189 -0
  25. package/dist/defaultapp/api/db-sqlite.ts +335 -0
  26. package/dist/defaultapp/api/db-users.ts +520 -0
  27. package/dist/defaultapp/api/db.ts +149 -0
  28. package/dist/defaultapp/api/profile/route.ts +154 -0
  29. package/dist/defaultapp/api/storage/email.ts +162 -0
  30. package/dist/defaultapp/api/storage/files.ts +160 -0
  31. package/dist/defaultapp/db-users.ts +1 -1
  32. package/dist/defaultapp/profile/page.tsx +6 -0
  33. package/dist/generators/env-example-template.txt +4 -3
  34. package/dist/generators/skeleton.js +3 -5
  35. package/dist/generators/skeleton.js.map +1 -1
  36. package/dist/generators/sql.js +60 -0
  37. package/dist/generators/sql.js.map +1 -1
  38. package/dist/sitepaige.js +2 -1
  39. package/dist/sitepaige.js.map +1 -1
  40. package/package.json +1 -1
  41. package/defaultapp/admin/page.tsx +0 -6
  42. package/defaultapp/api/example-secure/route.ts +0 -100
  43. package/defaultapp/migrate.ts +0 -142
  44. /package/defaultapp/{csrf.ts → api/csrf.ts} +0 -0
  45. /package/defaultapp/{db-mysql.ts → api/db-mysql.ts} +0 -0
  46. /package/defaultapp/{db-password-auth.ts → api/db-password-auth.ts} +0 -0
  47. /package/defaultapp/{db-postgres.ts → api/db-postgres.ts} +0 -0
  48. /package/defaultapp/{db-sqlite.ts → api/db-sqlite.ts} +0 -0
  49. /package/defaultapp/{db.ts → api/db.ts} +0 -0
  50. /package/defaultapp/{storage → api/storage}/email.ts +0 -0
  51. /package/defaultapp/{storage → api/storage}/files.ts +0 -0
@@ -45,7 +45,6 @@ const LoginSection: React.FC<LoginSectionProps> = ({ websiteLanguage = 'English'
45
45
  const [userData, setUserData] = useState<UserData | null>(null);
46
46
  const [isLoading, setIsLoading] = useState(true);
47
47
  const [isAuthenticated, setIsAuthenticated] = useState(false);
48
- const [profileMenuItems, setProfileMenuItems] = useState<MenuItem[]>([]);
49
48
  const userMenuRef = useRef<HTMLDivElement>(null);
50
49
 
51
50
  // Hardcoded values
@@ -53,33 +52,16 @@ const LoginSection: React.FC<LoginSectionProps> = ({ websiteLanguage = 'English'
53
52
  const align = 'Right' as "Left" | "Center" | "Right";
54
53
  const mobileViewMode = 'desktop' as 'desktop' | 'phone' | 'sm-tablet' | 'lg-tablet';
55
54
 
56
- // Load profile menu items from localStorage
57
- useEffect(() => {
58
- const loadProfileMenuItems = () => {
59
- try {
60
- const storedMenuData = localStorage.getItem('blueprint_profile_menu');
61
- if (storedMenuData) {
62
- const menuData = JSON.parse(storedMenuData);
63
- setProfileMenuItems(menuData);
64
- } else {
65
- // Default fallback menu items
66
- setProfileMenuItems([
67
- { label: 'Profile', link: '/profile' },
68
- { label: 'Settings', link: '/settings' }
69
- ]);
70
- }
71
- } catch (error) {
72
- console.error('Failed to load profile menu from localStorage:', error);
73
- // Default fallback menu items
74
- setProfileMenuItems([
75
- { label: 'Profile', link: '/profile' },
76
- { label: 'Settings', link: '/settings' }
77
- ]);
78
- }
79
- };
80
-
81
- loadProfileMenuItems();
82
- }, []);
55
+ // Build menu items based on user level
56
+ const profileMenuItems: MenuItem[] = [
57
+ { label: 'Dashboard', link: '/dashboard' },
58
+ { label: 'Profile', link: '/profile' }
59
+ ];
60
+
61
+ // Add Admin Dashboard only if user is an admin (userlevel == 2)
62
+ if (userData?.userlevel === 2) {
63
+ profileMenuItems.push({ label: 'Admin Dashboard', link: '/admin_dashboard' });
64
+ }
83
65
 
84
66
  // Fetch user authentication status and data
85
67
  useEffect(() => {
@@ -1,86 +1,342 @@
1
- /*
2
- Sitepaige Components v1.0.0
3
- Sitepaige components are automatically added to your project the first time it is built, and are only added again if the "Build Components" button is
4
- checked in the system build settings. It is safe to modify this file without it being overwritten unless that setting is selected.
5
- */
6
-
7
1
  'use client';
8
2
 
9
3
  import { useState, useEffect } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ // import { useCsrfToken } from './csrf-provider'; // Disabled for now
10
6
 
11
- interface UserProfile {
12
- id: string;
7
+ interface UserData {
8
+ userid: string;
13
9
  username: string;
14
- avatarurl: string | null;
10
+ avatarurl?: string;
11
+ email?: string;
12
+ userlevel: number;
13
+ source: string;
15
14
  }
16
15
 
17
16
  export default function Profile() {
18
- const [profile, setProfile] = useState<UserProfile | null>(null);
17
+ const router = useRouter();
18
+ // const csrfToken = useCsrfToken(); // Disabled for now
19
+ const [userData, setUserData] = useState<UserData | null>(null);
19
20
  const [loading, setLoading] = useState(true);
20
21
  const [error, setError] = useState<string | null>(null);
22
+ const [success, setSuccess] = useState<string | null>(null);
23
+
24
+ // Password change state
25
+ const [currentPassword, setCurrentPassword] = useState('');
26
+ const [newPassword, setNewPassword] = useState('');
27
+ const [confirmPassword, setConfirmPassword] = useState('');
28
+ const [isChangingPassword, setIsChangingPassword] = useState(false);
29
+
30
+ // Account deletion state
31
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
32
+ const [deleteConfirmText, setDeleteConfirmText] = useState('');
33
+ const [isDeleting, setIsDeleting] = useState(false);
21
34
 
22
35
  useEffect(() => {
23
- const fetchProfile = async () => {
36
+ fetchUserData();
37
+ }, []);
38
+
39
+ const fetchUserData = async () => {
24
40
  try {
41
+ setLoading(true);
25
42
  const response = await fetch('/api/Auth', {
26
- method: 'GET',
27
43
  credentials: 'include'
28
44
  });
29
45
 
30
46
  if (!response.ok) {
31
- throw new Error('Failed to fetch profile');
47
+ throw new Error('Failed to fetch user data');
32
48
  }
33
49
 
34
50
  const data = await response.json();
35
-
36
- if (!data.user) {
37
- throw new Error('User not authenticated');
38
- }
39
-
40
- // Use the user object from the response
41
- const userData = data.user;
42
- setProfile({
43
- id: userData.userid,
44
- username: userData.username,
45
- avatarurl: userData.avatarurl
46
- });
51
+ if (data.user) {
52
+ setUserData(data.user);
53
+ } else {
54
+ // Not authenticated, redirect to login
55
+ router.push('/login');
56
+ }
47
57
  } catch (err) {
48
- setError(err instanceof Error ? err.message : 'An error occurred');
58
+ setError(err instanceof Error ? err.message : 'Failed to load profile');
49
59
  } finally {
50
60
  setLoading(false);
51
61
  }
52
62
  };
53
63
 
54
- fetchProfile();
55
- }, []);
64
+ const handlePasswordChange = async (e: React.FormEvent) => {
65
+ e.preventDefault();
66
+ setError(null);
67
+ setSuccess(null);
68
+
69
+ // Validate passwords
70
+ if (newPassword !== confirmPassword) {
71
+ setError('Passwords do not match');
72
+ return;
73
+ }
74
+
75
+ if (newPassword.length < 8) {
76
+ setError('Password must be at least 8 characters long');
77
+ return;
78
+ }
79
+
80
+ setIsChangingPassword(true);
81
+
82
+ try {
83
+ const response = await fetch('/api/profile', {
84
+ method: 'PUT',
85
+ headers: {
86
+ 'Content-Type': 'application/json'
87
+ // 'X-CSRF-Token': csrfToken || '' // Disabled for now
88
+ },
89
+ credentials: 'include',
90
+ body: JSON.stringify({
91
+ currentPassword,
92
+ newPassword
93
+ })
94
+ });
95
+
96
+ const data = await response.json();
97
+
98
+ if (!response.ok) {
99
+ throw new Error(data.error || 'Failed to update password');
100
+ }
101
+
102
+ setSuccess('Password updated successfully');
103
+ setCurrentPassword('');
104
+ setNewPassword('');
105
+ setConfirmPassword('');
106
+ } catch (err) {
107
+ setError(err instanceof Error ? err.message : 'Failed to update password');
108
+ } finally {
109
+ setIsChangingPassword(false);
110
+ }
111
+ };
56
112
 
57
- if (loading) {
58
- return <div className="flex justify-center items-center min-h-[200px]">Loading...</div>;
59
- }
113
+ const handleDeleteAccount = async () => {
114
+ if (deleteConfirmText !== 'DELETE') {
115
+ setError('Please type DELETE to confirm account deletion');
116
+ return;
117
+ }
118
+
119
+ setIsDeleting(true);
120
+ setError(null);
121
+
122
+ try {
123
+ const response = await fetch('/api/profile', {
124
+ method: 'DELETE',
125
+ headers: {
126
+ 'Content-Type': 'application/json'
127
+ // 'X-CSRF-Token': csrfToken || '' // Disabled for now
128
+ },
129
+ credentials: 'include',
130
+ body: JSON.stringify({
131
+ confirmDelete: true
132
+ })
133
+ });
134
+
135
+ const data = await response.json();
136
+
137
+ if (!response.ok) {
138
+ throw new Error(data.error || 'Failed to delete account');
139
+ }
140
+
141
+ // Redirect to home page after successful deletion
142
+ window.location.href = '/';
143
+ } catch (err) {
144
+ setError(err instanceof Error ? err.message : 'Failed to delete account');
145
+ setIsDeleting(false);
146
+ }
147
+ };
148
+
149
+ const getUserLevelName = (level: number) => {
150
+ switch (level) {
151
+ case 0: return 'Guest';
152
+ case 1: return 'Registered User';
153
+ case 2: return 'Admin';
154
+ default: return 'Unknown';
155
+ }
156
+ };
157
+
158
+ const getAuthMethodName = (source: string) => {
159
+ switch (source) {
160
+ case 'userpass': return 'Email/Password';
161
+ case 'google': return 'Google';
162
+ case 'facebook': return 'Facebook';
163
+ case 'apple': return 'Apple';
164
+ case 'github': return 'GitHub';
165
+ default: return source;
166
+ }
167
+ };
60
168
 
61
- if (error) {
62
- return <div className="text-red-500 text-center">{error}</div>;
169
+ if (loading) {
170
+ return (
171
+ <div className="flex items-center justify-center min-h-screen">
172
+ <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-600"></div>
173
+ </div>
174
+ );
63
175
  }
64
176
 
65
- if (!profile) {
66
- return <div className="text-center">No profile found</div>;
177
+ if (!userData) {
178
+ return null; // Will redirect to login
67
179
  }
68
180
 
69
181
  return (
70
- <div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
71
- <div className="flex flex-col items-center">
72
- {profile.avatarurl && (
73
- <img
74
- src={profile.avatarurl}
75
- alt="Profile avatar"
76
- className="w-32 h-32 rounded-full mb-4"
182
+ <div className="container mx-auto px-4 py-8 max-w-4xl">
183
+ <h1 className="text-3xl font-bold mb-8">Profile Settings</h1>
184
+
185
+ {/* User Information */}
186
+ <div className="bg-white rounded-lg shadow p-6 mb-8">
187
+ <h2 className="text-xl font-semibold mb-4">User Information</h2>
188
+ <div className="grid md:grid-cols-2 gap-4">
189
+ <div className="flex items-center space-x-4">
190
+ {userData.avatarurl ? (
191
+ <img
192
+ src={userData.avatarurl}
193
+ alt={userData.username}
194
+ className="w-16 h-16 rounded-full"
77
195
  referrerPolicy="no-referrer"
78
196
  crossOrigin="anonymous"
79
197
  />
80
- )}
81
- <h2 className="text-2xl font-semibold text-gray-800">{profile.username}</h2>
82
- <p className="text-gray-500 mt-2">User ID: {profile.id}</p>
198
+ ) : (
199
+ <div className="w-16 h-16 rounded-full bg-gray-300 flex items-center justify-center">
200
+ <span className="text-2xl text-gray-600">
201
+ {userData.username.charAt(0).toUpperCase()}
202
+ </span>
203
+ </div>
204
+ )}
205
+ <div>
206
+ <p className="font-semibold">{userData.username}</p>
207
+ {userData.email && (
208
+ <p className="text-sm text-gray-600">{userData.email}</p>
209
+ )}
210
+ </div>
211
+ </div>
212
+ <div className="space-y-2">
213
+ <p><span className="font-semibold">User Level:</span> {getUserLevelName(userData.userlevel)}</p>
214
+ <p><span className="font-semibold">Auth Method:</span> {getAuthMethodName(userData.source)}</p>
215
+ </div>
216
+ </div>
217
+ </div>
218
+
219
+ {/* Error and Success Messages */}
220
+ {error && (
221
+ <div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded">
222
+ {error}
223
+ </div>
224
+ )}
225
+ {success && (
226
+ <div className="mb-4 p-4 bg-green-50 border border-green-200 text-green-700 rounded">
227
+ {success}
228
+ </div>
229
+ )}
230
+
231
+ {/* Password Change Section - Only for email/password users */}
232
+ {userData.source === 'userpass' && (
233
+ <div className="bg-white rounded-lg shadow p-6 mb-8">
234
+ <h2 className="text-xl font-semibold mb-4">Change Password</h2>
235
+ <form onSubmit={handlePasswordChange} className="space-y-4">
236
+ <div>
237
+ <label htmlFor="currentPassword" className="block text-sm font-medium text-gray-700 mb-1">
238
+ Current Password
239
+ </label>
240
+ <input
241
+ type="password"
242
+ id="currentPassword"
243
+ value={currentPassword}
244
+ onChange={(e) => setCurrentPassword(e.target.value)}
245
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
246
+ required
247
+ />
248
+ </div>
249
+ <div>
250
+ <label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1">
251
+ New Password
252
+ </label>
253
+ <input
254
+ type="password"
255
+ id="newPassword"
256
+ value={newPassword}
257
+ onChange={(e) => setNewPassword(e.target.value)}
258
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
259
+ required
260
+ minLength={8}
261
+ />
262
+ <p className="text-xs text-gray-500 mt-1">Must be at least 8 characters long</p>
263
+ </div>
264
+ <div>
265
+ <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
266
+ Confirm New Password
267
+ </label>
268
+ <input
269
+ type="password"
270
+ id="confirmPassword"
271
+ value={confirmPassword}
272
+ onChange={(e) => setConfirmPassword(e.target.value)}
273
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
274
+ required
275
+ />
276
+ </div>
277
+ <button
278
+ type="submit"
279
+ disabled={isChangingPassword}
280
+ className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
281
+ >
282
+ {isChangingPassword ? 'Updating...' : 'Update Password'}
283
+ </button>
284
+ </form>
285
+ </div>
286
+ )}
287
+
288
+ {/* Delete Account Section */}
289
+ <div className="bg-white rounded-lg shadow p-6">
290
+ <h2 className="text-xl font-semibold mb-4 text-red-600">Danger Zone</h2>
291
+ <div className="border border-red-200 rounded-md p-4">
292
+ <h3 className="font-semibold mb-2">Delete Account</h3>
293
+ <p className="text-sm text-gray-600 mb-4">
294
+ Once you delete your account, there is no going back. Please be certain.
295
+ </p>
296
+
297
+ {!showDeleteConfirm ? (
298
+ <button
299
+ onClick={() => setShowDeleteConfirm(true)}
300
+ className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700"
301
+ >
302
+ Delete Account
303
+ </button>
304
+ ) : (
305
+ <div className="space-y-4">
306
+ <p className="text-sm font-semibold">
307
+ To confirm deletion, please type <span className="font-mono bg-gray-100 px-1">DELETE</span> below:
308
+ </p>
309
+ <input
310
+ type="text"
311
+ value={deleteConfirmText}
312
+ onChange={(e) => setDeleteConfirmText(e.target.value)}
313
+ placeholder="Type DELETE to confirm"
314
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500"
315
+ />
316
+ <div className="flex space-x-4">
317
+ <button
318
+ onClick={handleDeleteAccount}
319
+ disabled={isDeleting || deleteConfirmText !== 'DELETE'}
320
+ className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
321
+ >
322
+ {isDeleting ? 'Deleting...' : 'Confirm Delete'}
323
+ </button>
324
+ <button
325
+ onClick={() => {
326
+ setShowDeleteConfirm(false);
327
+ setDeleteConfirmText('');
328
+ setError(null);
329
+ }}
330
+ disabled={isDeleting}
331
+ className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400"
332
+ >
333
+ Cancel
334
+ </button>
335
+ </div>
336
+ </div>
337
+ )}
338
+ </div>
83
339
  </div>
84
340
  </div>
85
341
  );
86
- }
342
+ }
@@ -80,10 +80,6 @@ export default function Slideshow({
80
80
  return (
81
81
  <div
82
82
  className="relative w-full flex items-center justify-center bg-black rounded-lg"
83
- style={{
84
- height: height || 400,
85
- maxHeight: '1000px'
86
- }}
87
83
  >
88
84
  <p className="text-gray-400">No images in slideshow</p>
89
85
  </div>
@@ -102,15 +98,11 @@ export default function Slideshow({
102
98
  return (
103
99
  <div
104
100
  className="relative w-full overflow-hidden rounded-lg bg-black"
105
- style={{
106
- height: height || 600,
107
- maxHeight: '1000px'
108
- }}
109
101
  onMouseEnter={() => setIsHovered(true)}
110
102
  onMouseLeave={() => setIsHovered(false)}
111
103
  >
112
104
  {/* Main image display */}
113
- <div className="absolute inset-0 flex items-center justify-center">
105
+ <div className="flex items-center justify-center">
114
106
  {animationType === 'fade' ? (
115
107
  // Fade animation - render all images with opacity transitions
116
108
  images.map((imageId, index) => (
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { NextResponse } from 'next/server';
7
- import { regenerateVerificationToken } from '../../../db-password-auth';
8
- import { send_email } from '../../../storage/email';
7
+ import { regenerateVerificationToken } from '../../db-password-auth';
8
+ import { send_email } from '../../storage/email';
9
9
 
10
10
  // Email validation regex
11
11
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -36,7 +36,7 @@ export async function POST(request: Request) {
36
36
  const { verificationToken } = result;
37
37
 
38
38
  // Get site domain from environment or default
39
- const siteDomain = process.env.SITE_DOMAIN || 'https://sitepaige.com';
39
+ const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
40
40
  const verificationUrl = `${siteDomain}/api/Auth/verify-email?token=${verificationToken}`;
41
41
 
42
42
  // Send verification email
@@ -8,8 +8,8 @@ import { cookies } from 'next/headers';
8
8
  import { NextResponse } from 'next/server';
9
9
  import * as crypto from 'node:crypto';
10
10
 
11
- import { db_init, db_query } from '../../db';
12
- import { upsertUser, storeOAuthToken, validateSession, rotateSession } from '../../db-users';
11
+ import { db_init, db_query } from '../db';
12
+ import { upsertUser, storeOAuthToken, validateSession, rotateSession } from '../db-users';
13
13
 
14
14
  type OAuthProvider = 'google' | 'facebook' | 'apple' | 'github';
15
15
 
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { NextResponse } from 'next/server';
7
- import { createPasswordAuth } from '../../../db-password-auth';
8
- import { send_email } from '../../../storage/email';
7
+ import { createPasswordAuth } from '../../db-password-auth';
8
+ import { send_email } from '../../storage/email';
9
9
 
10
10
  // Email validation regex
11
11
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -37,7 +37,7 @@ export async function POST(request: Request) {
37
37
  const { passwordAuth, verificationToken } = await createPasswordAuth(email, password);
38
38
 
39
39
  // Get site domain from environment or default
40
- const siteDomain = process.env.SITE_DOMAIN || 'https://sitepaige.com';
40
+ const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
41
41
  const verificationUrl = `${siteDomain}/api/Auth/verify-email?token=${verificationToken}`;
42
42
 
43
43
  // Send verification email
@@ -4,9 +4,9 @@
4
4
  */
5
5
 
6
6
  import { NextResponse } from 'next/server';
7
- import { verifyEmailWithToken } from '../../../db-password-auth';
8
- import { upsertUser } from '../../../db-users';
9
- import { db_init, db_query } from '../../../db';
7
+ import { verifyEmailWithToken } from '../../db-password-auth';
8
+ import { upsertUser } from '../../db-users';
9
+ import { db_init, db_query } from '../../db';
10
10
  import { cookies } from 'next/headers';
11
11
  import * as crypto from 'node:crypto';
12
12
 
@@ -86,7 +86,7 @@ export async function GET(request: Request) {
86
86
  });
87
87
 
88
88
  // Redirect to home page or dashboard with success message
89
- const siteDomain = process.env.SITE_DOMAIN || 'https://sitepaige.com';
89
+ const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
90
90
  const redirectUrl = new URL('/', siteDomain);
91
91
  redirectUrl.searchParams.set('verified', 'true');
92
92
 
@@ -96,7 +96,7 @@ export async function GET(request: Request) {
96
96
  console.error('Email verification error:', error);
97
97
 
98
98
  // Redirect to home with error
99
- const siteDomain = process.env.SITE_DOMAIN || 'https://sitepaige.com';
99
+ const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
100
100
  const redirectUrl = new URL('/', siteDomain);
101
101
  redirectUrl.searchParams.set('error', 'verification_failed');
102
102
 
@@ -5,15 +5,15 @@
5
5
 
6
6
  import { NextRequest, NextResponse } from 'next/server';
7
7
  import { cookies } from 'next/headers';
8
- import { db_init, db_query } from '../../../db';
8
+ import { db_init, db_query } from '../../db';
9
9
  import {
10
10
  getAllUsers,
11
11
  updateUserPermission,
12
12
  deleteUser,
13
13
  getUserStats,
14
14
  getUserByID
15
- } from '../../../db-users';
16
- import { validateCsrfToken } from '../../../csrf';
15
+ } from '../../db-users';
16
+ import { validateCsrfToken } from '../../csrf';
17
17
 
18
18
  // Helper function to check if the current user is an admin
19
19
  async function checkAdminAuth(): Promise<{ isAdmin: boolean; userId?: string }> {
@@ -111,7 +111,7 @@ export async function upsertUser(
111
111
  } else {
112
112
  // Check if this is the first user (should be admin)
113
113
  const allUsers = await db_query(client, "SELECT COUNT(*) as count FROM users");
114
- const isFirstUser = allUsers[0].count === 0;
114
+ const isFirstUser = Number(allUsers[0].count) === 0;
115
115
 
116
116
  // Create new user
117
117
  const userId = crypto.randomUUID();