sitepaige-mcp-server 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/components/IntegrationComponent.tsx +1 -0
  2. package/components/admin.tsx +30 -27
  3. package/components/auth.tsx +9 -9
  4. package/components/cta.tsx +3 -10
  5. package/components/headerlogin.tsx +9 -9
  6. package/components/login.tsx +90 -11
  7. package/components/logincallback.tsx +1 -0
  8. package/components/menu.tsx +0 -6
  9. package/components/profile.tsx +12 -11
  10. package/defaultapp/api/Auth/resend-verification/route.ts +130 -0
  11. package/defaultapp/api/Auth/route.ts +39 -49
  12. package/defaultapp/api/Auth/signup/route.ts +5 -15
  13. package/defaultapp/api/Auth/verify-email/route.ts +12 -5
  14. package/defaultapp/api/admin/users/route.ts +5 -3
  15. package/defaultapp/auth/auth.ts +9 -9
  16. package/defaultapp/db-mysql.ts +1 -1
  17. package/defaultapp/db-password-auth.ts +37 -0
  18. package/defaultapp/db-postgres.ts +1 -1
  19. package/defaultapp/db-sqlite.ts +1 -1
  20. package/defaultapp/db-users.ts +73 -73
  21. package/defaultapp/middleware.ts +15 -17
  22. package/dist/components/IntegrationComponent.tsx +1 -0
  23. package/dist/components/admin.tsx +30 -27
  24. package/dist/components/auth.tsx +9 -9
  25. package/dist/components/cta.tsx +3 -10
  26. package/dist/components/headerlogin.tsx +9 -9
  27. package/dist/components/login.tsx +90 -11
  28. package/dist/components/logincallback.tsx +1 -0
  29. package/dist/components/menu.tsx +0 -6
  30. package/dist/components/profile.tsx +12 -11
  31. package/dist/defaultapp/api/Auth/resend-verification/route.ts +130 -0
  32. package/dist/defaultapp/api/Auth/route.ts +39 -49
  33. package/dist/defaultapp/api/Auth/signup/route.ts +5 -15
  34. package/dist/defaultapp/api/Auth/verify-email/route.ts +12 -5
  35. package/dist/defaultapp/api/admin/users/route.ts +5 -3
  36. package/dist/defaultapp/auth/auth.ts +9 -9
  37. package/dist/defaultapp/db-mysql.ts +1 -1
  38. package/dist/defaultapp/db-password-auth.ts +37 -0
  39. package/dist/defaultapp/db-postgres.ts +1 -1
  40. package/dist/defaultapp/db-sqlite.ts +1 -1
  41. package/dist/defaultapp/db-users.ts +73 -73
  42. package/dist/defaultapp/middleware.ts +15 -17
  43. package/dist/generators/sql.js +11 -3
  44. package/dist/generators/sql.js.map +1 -1
  45. package/dist/generators/views.js +14 -14
  46. package/dist/generators/views.js.map +1 -1
  47. package/package.json +1 -1
@@ -59,6 +59,7 @@ export default function IntegrationComponent({
59
59
  headers: {
60
60
  'Content-Type': 'application/json',
61
61
  },
62
+ credentials: 'include',
62
63
  body: JSON.stringify({
63
64
  description: editedPrompt || 'Create integration code',
64
65
  projectId: projectId,
@@ -5,15 +5,15 @@ import { useRouter } from 'next/navigation';
5
5
 
6
6
  interface User {
7
7
  userid: string;
8
- OAuthID: string;
9
- Source: string;
10
- UserName: string;
11
- Email?: string;
12
- AvatarURL?: string;
13
- UserLevel: number;
14
- LastLoginDate: string;
15
- CreatedDate: string;
16
- IsActive: boolean;
8
+ oauthid: string;
9
+ source: string;
10
+ username: string;
11
+ email?: string;
12
+ avatarurl?: string;
13
+ userlevel: number;
14
+ lastlogindate: string;
15
+ createddate: string;
16
+ isactive: boolean;
17
17
  }
18
18
 
19
19
  interface UserStats {
@@ -46,7 +46,9 @@ export default function AdminPanel() {
46
46
  setError(null);
47
47
 
48
48
  // Fetch users - authorization is handled server-side via session cookie
49
- const response = await fetch('/api/admin/users');
49
+ const response = await fetch('/api/admin/users', {
50
+ credentials: 'include'
51
+ });
50
52
  if (!response.ok) {
51
53
  if (response.status === 401) {
52
54
  setError('Access denied. Admin privileges required.');
@@ -80,6 +82,7 @@ export default function AdminPanel() {
80
82
  headers: {
81
83
  'Content-Type': 'application/json',
82
84
  },
85
+ credentials: 'include',
83
86
  body: JSON.stringify({
84
87
  userId,
85
88
  permissionLevel: newLevel,
@@ -169,13 +172,13 @@ export default function AdminPanel() {
169
172
 
170
173
  // Filter users based on search and permission level
171
174
  const filteredUsers = users.filter(user => {
172
- const matchesSearch = user.UserName.toLowerCase().includes(searchTerm.toLowerCase()) ||
173
- (user.Email && user.Email.toLowerCase().includes(searchTerm.toLowerCase()));
175
+ const matchesSearch = user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
176
+ (user.email && user.email.toLowerCase().includes(searchTerm.toLowerCase()));
174
177
 
175
178
  const matchesFilter = filterLevel === 'all' ||
176
- (filterLevel === 'admin' && user.UserLevel === 2) ||
177
- (filterLevel === 'registered' && user.UserLevel === 1) ||
178
- (filterLevel === 'guest' && user.UserLevel === 0);
179
+ (filterLevel === 'admin' && user.userlevel === 2) ||
180
+ (filterLevel === 'registered' && user.userlevel === 1) ||
181
+ (filterLevel === 'guest' && user.userlevel === 0);
179
182
 
180
183
  return matchesSearch && matchesFilter;
181
184
  });
@@ -298,26 +301,26 @@ export default function AdminPanel() {
298
301
  <tr key={user.userid} className="hover:bg-gray-50">
299
302
  <td className="px-6 py-4 whitespace-nowrap">
300
303
  <div className="flex items-center">
301
- {user.AvatarURL ? (
304
+ {user.avatarurl ? (
302
305
  <img
303
306
  className="h-10 w-10 rounded-full"
304
- src={user.AvatarURL}
305
- alt={user.UserName}
307
+ src={user.avatarurl}
308
+ alt={user.username}
306
309
  />
307
310
  ) : (
308
311
  <div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
309
312
  <span className="text-gray-600 font-medium">
310
- {user.UserName.charAt(0).toUpperCase()}
313
+ {user.username.charAt(0).toUpperCase()}
311
314
  </span>
312
315
  </div>
313
316
  )}
314
317
  <div className="ml-4">
315
318
  <div className="text-sm font-medium text-gray-900">
316
- {user.UserName}
319
+ {user.username}
317
320
  </div>
318
- {user.Email && (
321
+ {user.email && (
319
322
  <div className="text-sm text-gray-500">
320
- {user.Email}
323
+ {user.email}
321
324
  </div>
322
325
  )}
323
326
  </div>
@@ -325,15 +328,15 @@ export default function AdminPanel() {
325
328
  </td>
326
329
  <td className="px-6 py-4 whitespace-nowrap">
327
330
  <span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">
328
- {user.Source}
331
+ {user.source}
329
332
  </span>
330
333
  </td>
331
334
  <td className="px-6 py-4 whitespace-nowrap">
332
335
  <select
333
- value={user.UserLevel}
336
+ value={user.userlevel}
334
337
  onChange={(e) => handlePermissionChange(user.userid, parseInt(e.target.value))}
335
338
  disabled={isUpdating === user.userid}
336
- className={`px-2 py-1 text-xs leading-5 font-semibold rounded-full ${getPermissionLevelColor(user.UserLevel)} cursor-pointer hover:opacity-80 disabled:opacity-50 disabled:cursor-not-allowed`}
339
+ className={`px-2 py-1 text-xs leading-5 font-semibold rounded-full ${getPermissionLevelColor(user.userlevel)} cursor-pointer hover:opacity-80 disabled:opacity-50 disabled:cursor-not-allowed`}
337
340
  >
338
341
  <option value={0}>Guest</option>
339
342
  <option value={1}>Registered User</option>
@@ -341,11 +344,11 @@ export default function AdminPanel() {
341
344
  </select>
342
345
  </td>
343
346
  <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
344
- {formatDate(user.LastLoginDate)}
347
+ {formatDate(user.lastlogindate)}
345
348
  </td>
346
349
  <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
347
350
  <button
348
- onClick={() => handleDeleteUser(user.userid, user.UserName)}
351
+ onClick={() => handleDeleteUser(user.userid, user.username)}
349
352
  disabled={isDeleting === user.userid}
350
353
  className="text-red-600 hover:text-red-900 disabled:opacity-50 disabled:cursor-not-allowed"
351
354
  >
@@ -11,11 +11,11 @@ import { useUserStore } from '@/store/user';
11
11
 
12
12
  interface AuthProps {
13
13
  auth: {
14
- ID: string;
15
- UserName: string;
16
- AvatarURL: string;
17
- UserLevel: string;
18
- IsAdmin: boolean;
14
+ id: string;
15
+ username: string;
16
+ avatarurl: string;
17
+ userlevel: string;
18
+ isadmin: boolean;
19
19
  } | null;
20
20
  }
21
21
 
@@ -25,10 +25,10 @@ export default function Auth({ auth }: AuthProps) {
25
25
  useEffect(() => {
26
26
  if (auth) {
27
27
  setIsAuthenticated(true);
28
- setUserLevel(auth.UserLevel);
29
- setIsAdmin(auth.IsAdmin);
30
- setUserName(auth.UserName);
31
- setAvatarURL(auth.AvatarURL);
28
+ setUserLevel(auth.userlevel);
29
+ setIsAdmin(auth.isadmin);
30
+ setUserName(auth.username);
31
+ setAvatarURL(auth.avatarurl);
32
32
  } else {
33
33
  setIsAuthenticated(false);
34
34
  setUserLevel('0');
@@ -28,10 +28,9 @@ interface CTAData {
28
28
  interface RCTAProps {
29
29
  custom_view_description: string;
30
30
  onNavigate: (pageId: string) => void;
31
- isPaigeLoading?: boolean;
32
31
  }
33
32
 
34
- const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate, isPaigeLoading = false }) => {
33
+ const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate }) => {
35
34
  let ctaData: CTAData;
36
35
 
37
36
  try {
@@ -101,10 +100,6 @@ const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate, isPaig
101
100
  };
102
101
 
103
102
  const handleButtonClick = (pageId: string) => {
104
- if (isPaigeLoading) {
105
- console.log('Navigation blocked: Paige is currently processing a request');
106
- return;
107
- }
108
103
  if (pageId) {
109
104
  onNavigate(pageId);
110
105
  }
@@ -175,11 +170,9 @@ const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate, isPaig
175
170
  <button
176
171
  key={index}
177
172
  onClick={() => handleButtonClick(button.page)}
178
- className={`px-6 py-3 rounded-lg transition-all ${hoverClass} ${
179
- isPaigeLoading ? 'opacity-50 cursor-not-allowed' : ''
180
- } ${!button.page ? 'opacity-75 cursor-not-allowed' : ''}`}
173
+ className={`px-6 py-3 rounded-lg transition-all ${hoverClass} ${!button.page ? 'opacity-75 cursor-not-allowed' : ''}`}
181
174
  style={buttonStyles}
182
- disabled={isPaigeLoading || !button.page}
175
+ disabled={!button.page}
183
176
  >
184
177
  {button.buttonTitle || 'Button'}
185
178
  </button>
@@ -17,11 +17,11 @@ interface MenuItem {
17
17
 
18
18
  interface UserData {
19
19
  userid: string;
20
- UserName: string;
21
- AvatarURL: string;
22
- Email: string;
23
- UserLevel: number;
24
- IsAdmin: boolean;
20
+ username: string;
21
+ avatarurl: string;
22
+ email: string;
23
+ userlevel: number;
24
+ isadmin: boolean;
25
25
  }
26
26
 
27
27
  const getLocalizedText = (text: string, language: string = 'English'): string => {
@@ -210,11 +210,11 @@ const LoginSection: React.FC<LoginSectionProps> = ({ websiteLanguage = 'English'
210
210
  className="flex items-center focus:outline-none relative group cursor-pointer"
211
211
  style={{ color: textColor }}
212
212
  >
213
- <span className="mr-2" style={{ color: textColor }}>{userData.UserName || getLocalizedText('Sample User', websiteLanguage)}</span>
213
+ <span className="mr-2" style={{ color: textColor }}>{userData.username || getLocalizedText('Sample User', websiteLanguage)}</span>
214
214
  <div className="relative">
215
- {userData.AvatarURL ? (
215
+ {userData.avatarurl ? (
216
216
  <img
217
- src={userData.AvatarURL}
217
+ src={userData.avatarurl}
218
218
  alt="User avatar"
219
219
  className="w-10 h-10 rounded-full"
220
220
  referrerPolicy="no-referrer"
@@ -222,7 +222,7 @@ const LoginSection: React.FC<LoginSectionProps> = ({ websiteLanguage = 'English'
222
222
  />
223
223
  ) : (
224
224
  <div className="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold">
225
- {userData.UserName ? userData.UserName.charAt(0).toUpperCase() : 'U'}
225
+ {userData.username ? userData.username.charAt(0).toUpperCase() : 'U'}
226
226
  </div>
227
227
  )}
228
228
  </div>
@@ -9,7 +9,7 @@ checked in the system build settings. It is safe to modify this file without it
9
9
  import React, { useState } from 'react';
10
10
 
11
11
  interface LoginProps {
12
- providers: ('apple' | 'facebook' | 'github' | 'google' | 'username')[];
12
+ providers: ('apple' | 'facebook' | 'github' | 'google' | 'userpass')[];
13
13
  }
14
14
 
15
15
  export default function Login({ providers }: LoginProps) {
@@ -20,6 +20,8 @@ export default function Login({ providers }: LoginProps) {
20
20
  const [confirmPassword, setConfirmPassword] = useState('');
21
21
  const [isLoading, setIsLoading] = useState(false);
22
22
  const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
23
+ const [showResendVerification, setShowResendVerification] = useState(false);
24
+ const [resendEmail, setResendEmail] = useState('');
23
25
 
24
26
 
25
27
  const handleProviderLogin = async (provider: string) => {
@@ -49,6 +51,37 @@ export default function Login({ providers }: LoginProps) {
49
51
 
50
52
  };
51
53
 
54
+ const handleResendVerification = async () => {
55
+ setError(null);
56
+ setMessage(null);
57
+ setIsLoading(true);
58
+
59
+ try {
60
+ const response = await fetch('/api/Auth/resend-verification', {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/json'
64
+ },
65
+ credentials: 'include', // Include cookies in request
66
+ body: JSON.stringify({ email: resendEmail || email })
67
+ });
68
+
69
+ const data = await response.json();
70
+
71
+ if (response.ok && data.success) {
72
+ setMessage({ type: 'success', text: data.message || 'Verification email sent! Please check your inbox.' });
73
+ setShowResendVerification(false);
74
+ setResendEmail('');
75
+ } else {
76
+ setError(data.error || 'Failed to send verification email');
77
+ }
78
+ } catch (err) {
79
+ setError('An unexpected error occurred');
80
+ } finally {
81
+ setIsLoading(false);
82
+ }
83
+ };
84
+
52
85
  const handleUsernamePasswordAuth = async (e: React.FormEvent) => {
53
86
  e.preventDefault();
54
87
  setError(null);
@@ -66,7 +99,10 @@ export default function Login({ providers }: LoginProps) {
66
99
 
67
100
  const response = await fetch('/api/Auth/signup', {
68
101
  method: 'POST',
69
- headers: { 'Content-Type': 'application/json' },
102
+ headers: {
103
+ 'Content-Type': 'application/json'
104
+ },
105
+ credentials: 'include', // Include cookies in request
70
106
  body: JSON.stringify({ email, password })
71
107
  });
72
108
 
@@ -84,8 +120,11 @@ export default function Login({ providers }: LoginProps) {
84
120
  // Handle login
85
121
  const response = await fetch('/api/Auth', {
86
122
  method: 'POST',
87
- headers: { 'Content-Type': 'application/json' },
88
- body: JSON.stringify({ email, password, provider: 'username' })
123
+ headers: {
124
+ 'Content-Type': 'application/json'
125
+ },
126
+ credentials: 'include', // Include cookies in request
127
+ body: JSON.stringify({ email, password, provider: 'userpass' })
89
128
  });
90
129
 
91
130
  const data = await response.json();
@@ -95,6 +134,11 @@ export default function Login({ providers }: LoginProps) {
95
134
  window.location.href = '/';
96
135
  } else {
97
136
  setError(data.error || 'Login failed');
137
+ // Check if error is about email verification
138
+ if (response.status === 403 || data.error?.toLowerCase().includes('verify')) {
139
+ setShowResendVerification(true);
140
+ setResendEmail(email);
141
+ }
98
142
  }
99
143
  }
100
144
  } catch (err) {
@@ -104,8 +148,8 @@ export default function Login({ providers }: LoginProps) {
104
148
  }
105
149
  };
106
150
 
107
- const showUsernamePasswordForm = providers?.includes('username');
108
- const oauthProviders = providers?.filter(p => p !== 'username') || [];
151
+ const showUsernamePasswordForm = providers?.includes('userpass');
152
+ const oauthProviders = providers?.filter(p => p !== 'userpass') || [];
109
153
 
110
154
  return (
111
155
  <div className="flex flex-col items-center justify-center min-h-[500px] p-4">
@@ -179,17 +223,18 @@ export default function Login({ providers }: LoginProps) {
179
223
  </div>
180
224
 
181
225
  <div className="text-center">
182
- <button
183
- type="button"
184
- onClick={() => {
226
+ <a
227
+ href="#"
228
+ onClick={(e) => {
229
+ e.preventDefault();
185
230
  setIsSignup(!isSignup);
186
231
  setError(null);
187
232
  setMessage(null);
188
233
  }}
189
- className="text-sm text-indigo-600 hover:text-indigo-500"
234
+ className="text-sm text-indigo-600 hover:text-indigo-500 underline"
190
235
  >
191
236
  {isSignup ? 'Already have an account? Sign in' : "Don't have an account? Sign up"}
192
- </button>
237
+ </a>
193
238
  </div>
194
239
  </form>
195
240
  )}
@@ -234,6 +279,40 @@ export default function Login({ providers }: LoginProps) {
234
279
  {error}
235
280
  </div>
236
281
  )}
282
+
283
+ {showResendVerification && (
284
+ <div className="mt-4 p-4 border border-gray-200 rounded-md bg-gray-50">
285
+ <p className="text-sm text-gray-700 mb-3">
286
+ Need a new verification email? Enter your email address and we'll send you a new link.
287
+ </p>
288
+ <div className="space-y-3">
289
+ <input
290
+ type="email"
291
+ value={resendEmail}
292
+ onChange={(e) => setResendEmail(e.target.value)}
293
+ placeholder="Email address"
294
+ className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 text-sm"
295
+ />
296
+ <button
297
+ onClick={handleResendVerification}
298
+ disabled={isLoading || !resendEmail}
299
+ className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
300
+ >
301
+ {isLoading ? 'Sending...' : 'Resend Verification Email'}
302
+ </button>
303
+ <button
304
+ type="button"
305
+ onClick={() => {
306
+ setShowResendVerification(false);
307
+ setResendEmail('');
308
+ }}
309
+ className="w-full text-sm text-gray-500 hover:text-gray-700 bg-transparent border-0 p-0 underline"
310
+ >
311
+ Cancel
312
+ </button>
313
+ </div>
314
+ </div>
315
+ )}
237
316
  </div>
238
317
  </div>
239
318
  );
@@ -44,6 +44,7 @@ export default function LoginCallback({ code, state }: LoginCallbackProps) {
44
44
  headers: {
45
45
  'Content-Type': 'application/json',
46
46
  },
47
+ credentials: 'include',
47
48
  body: JSON.stringify({
48
49
  code,
49
50
  provider
@@ -60,7 +60,6 @@ export default function Menu({ menu, onClick, pages = [] }: MenuProps) {
60
60
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
61
61
  const [isMobile, setIsMobile] = useState(false);
62
62
  const [selectedPage, setSelectedPage] = useState<string | null>(null);
63
- const [isPaigeLoading, setIsPaigeLoading] = useState(false);
64
63
 
65
64
  // Handle case where menu is undefined/null
66
65
  if (!menu) {
@@ -230,7 +229,6 @@ export default function Menu({ menu, onClick, pages = [] }: MenuProps) {
230
229
  items-center
231
230
  justify-center
232
231
  ${isSelected ? 'border-blue-600 bg-blue-50' : ''}
233
- ${isPaigeLoading ? 'opacity-50 cursor-not-allowed' : ''}
234
232
  `}>
235
233
  <h3
236
234
  className={`${isSelected ? 'font-bold' : 'font-medium'} text-gray-800`}
@@ -284,10 +282,6 @@ export default function Menu({ menu, onClick, pages = [] }: MenuProps) {
284
282
  href={linkUrl}
285
283
  onClick={(e) => {
286
284
  e.preventDefault();
287
- if (isPaigeLoading) {
288
- console.log('Navigation blocked: Paige is currently processing a request');
289
- return;
290
- }
291
285
  setSelectedPage(item.page);
292
286
  onClick?.();
293
287
  }}
@@ -9,9 +9,9 @@ checked in the system build settings. It is safe to modify this file without it
9
9
  import { useState, useEffect } from 'react';
10
10
 
11
11
  interface UserProfile {
12
- ID: string;
13
- UserName: string;
14
- AvatarURL: string | null;
12
+ id: string;
13
+ username: string;
14
+ avatarurl: string | null;
15
15
  }
16
16
 
17
17
  export default function Profile() {
@@ -23,7 +23,8 @@ export default function Profile() {
23
23
  const fetchProfile = async () => {
24
24
  try {
25
25
  const response = await fetch('/api/Auth', {
26
- method: 'GET'
26
+ method: 'GET',
27
+ credentials: 'include'
27
28
  });
28
29
 
29
30
  if (!response.ok) {
@@ -39,9 +40,9 @@ export default function Profile() {
39
40
  // Use the user object from the response
40
41
  const userData = data.user;
41
42
  setProfile({
42
- ID: userData.userid,
43
- UserName: userData.UserName,
44
- AvatarURL: userData.AvatarURL
43
+ id: userData.userid,
44
+ username: userData.username,
45
+ avatarurl: userData.avatarurl
45
46
  });
46
47
  } catch (err) {
47
48
  setError(err instanceof Error ? err.message : 'An error occurred');
@@ -68,17 +69,17 @@ export default function Profile() {
68
69
  return (
69
70
  <div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
70
71
  <div className="flex flex-col items-center">
71
- {profile.AvatarURL && (
72
+ {profile.avatarurl && (
72
73
  <img
73
- src={profile.AvatarURL}
74
+ src={profile.avatarurl}
74
75
  alt="Profile avatar"
75
76
  className="w-32 h-32 rounded-full mb-4"
76
77
  referrerPolicy="no-referrer"
77
78
  crossOrigin="anonymous"
78
79
  />
79
80
  )}
80
- <h2 className="text-2xl font-semibold text-gray-800">{profile.UserName}</h2>
81
- <p className="text-gray-500 mt-2">User ID: {profile.ID}</p>
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>
82
83
  </div>
83
84
  </div>
84
85
  );
@@ -0,0 +1,130 @@
1
+ /*
2
+ * Resend verification email endpoint
3
+ * Allows users to request a new verification email
4
+ */
5
+
6
+ import { NextResponse } from 'next/server';
7
+ import { regenerateVerificationToken } from '../../../db-password-auth';
8
+ import { send_email } from '../../../storage/email';
9
+
10
+ // Email validation regex
11
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
12
+
13
+ export async function POST(request: Request) {
14
+ try {
15
+ const { email } = await request.json();
16
+
17
+ // Validate email format
18
+ if (!email || !emailRegex.test(email)) {
19
+ return NextResponse.json(
20
+ { error: 'Invalid email address' },
21
+ { status: 400 }
22
+ );
23
+ }
24
+
25
+ // Regenerate verification token
26
+ const result = await regenerateVerificationToken(email);
27
+
28
+ if (!result) {
29
+ // Don't reveal whether the email exists or not
30
+ return NextResponse.json({
31
+ success: true,
32
+ message: 'If an unverified account exists with this email, a verification email has been sent.'
33
+ });
34
+ }
35
+
36
+ const { verificationToken } = result;
37
+
38
+ // Get site domain from environment or default
39
+ const siteDomain = process.env.SITE_DOMAIN || 'https://sitepaige.com';
40
+ const verificationUrl = `${siteDomain}/api/Auth/verify-email?token=${verificationToken}`;
41
+
42
+ // Send verification email
43
+ try {
44
+ const emailHtml = `
45
+ <!DOCTYPE html>
46
+ <html>
47
+ <head>
48
+ <style>
49
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
50
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
51
+ .button {
52
+ display: inline-block;
53
+ padding: 12px 24px;
54
+ background-color: #f0f0f0;
55
+ color: #000000;
56
+ text-decoration: none;
57
+ border: 1px solid #cccccc;
58
+ border-radius: 4px;
59
+ }
60
+ .footer { margin-top: 30px; font-size: 12px; color: #666; }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <div class="container">
65
+ <h2>Verify Your Email Address</h2>
66
+ <p>You requested a new verification email for your account at ${siteDomain}. Please verify your email address by clicking the button below:</p>
67
+ <p style="margin: 30px 0;">
68
+ <a href="${verificationUrl}" class="button">Verify Email Address</a>
69
+ </p>
70
+ <p>Or copy and paste this link into your browser:</p>
71
+ <p style="word-break: break-all; color: #0066cc;">${verificationUrl}</p>
72
+ <p>This link will expire in 24 hours.</p>
73
+ <div class="footer">
74
+ <p>If you didn't request this email, you can safely ignore it.</p>
75
+ </div>
76
+ </div>
77
+ </body>
78
+ </html>
79
+ `;
80
+
81
+ const emailText = `
82
+ Verify Your Email Address
83
+
84
+ You requested a new verification email for your account at ${siteDomain}. Please verify your email address by clicking the link below:
85
+
86
+ ${verificationUrl}
87
+
88
+ This link will expire in 24 hours.
89
+
90
+ If you didn't request this email, you can safely ignore it.
91
+ `;
92
+
93
+ await send_email({
94
+ to: email,
95
+ from: process.env.EMAIL_FROM || 'noreply@sitepaige.com',
96
+ subject: 'Verify your email address',
97
+ html: emailHtml,
98
+ text: emailText
99
+ });
100
+
101
+ return NextResponse.json({
102
+ success: true,
103
+ message: 'Verification email sent successfully! Please check your email.'
104
+ });
105
+
106
+ } catch (emailError) {
107
+ console.error('Failed to send verification email:', emailError);
108
+ return NextResponse.json({
109
+ success: false,
110
+ error: 'Failed to send verification email. Please try again later.'
111
+ }, { status: 500 });
112
+ }
113
+
114
+ } catch (error: any) {
115
+ console.error('Resend verification error:', error);
116
+
117
+ if (error.message === 'Email is already verified') {
118
+ return NextResponse.json(
119
+ { error: 'This email is already verified. Please sign in.' },
120
+ { status: 400 }
121
+ );
122
+ }
123
+
124
+ // Generic response to avoid revealing whether email exists
125
+ return NextResponse.json({
126
+ success: true,
127
+ message: 'If an unverified account exists with this email, a verification email has been sent.'
128
+ });
129
+ }
130
+ }