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.
- package/components/headerlogin.tsx +10 -28
- package/components/profile.tsx +302 -46
- package/components/slideshow.tsx +1 -9
- package/defaultapp/api/Auth/resend-verification/route.ts +3 -3
- package/defaultapp/api/Auth/route.ts +2 -2
- package/defaultapp/api/Auth/signup/route.ts +3 -3
- package/defaultapp/api/Auth/verify-email/route.ts +5 -5
- package/defaultapp/api/admin/users/route.ts +3 -3
- package/defaultapp/{db-users.ts → api/db-users.ts} +1 -1
- package/defaultapp/api/profile/route.ts +154 -0
- package/defaultapp/profile/page.tsx +6 -0
- package/dist/blueprintWriter.js.map +1 -1
- package/dist/components/headerlogin.tsx +10 -28
- package/dist/components/profile.tsx +302 -46
- package/dist/components/slideshow.tsx +1 -9
- package/dist/defaultapp/api/Auth/resend-verification/route.ts +3 -3
- package/dist/defaultapp/api/Auth/route.ts +2 -2
- package/dist/defaultapp/api/Auth/signup/route.ts +3 -3
- package/dist/defaultapp/api/Auth/verify-email/route.ts +5 -5
- package/dist/defaultapp/api/admin/users/route.ts +3 -3
- package/dist/defaultapp/api/csrf.ts +111 -0
- package/dist/defaultapp/api/db-mysql.ts +183 -0
- package/dist/defaultapp/api/db-password-auth.ts +362 -0
- package/dist/defaultapp/api/db-postgres.ts +189 -0
- package/dist/defaultapp/api/db-sqlite.ts +335 -0
- package/dist/defaultapp/api/db-users.ts +520 -0
- package/dist/defaultapp/api/db.ts +149 -0
- package/dist/defaultapp/api/profile/route.ts +154 -0
- package/dist/defaultapp/api/storage/email.ts +162 -0
- package/dist/defaultapp/api/storage/files.ts +160 -0
- package/dist/defaultapp/db-users.ts +1 -1
- package/dist/defaultapp/profile/page.tsx +6 -0
- package/dist/generators/env-example-template.txt +4 -3
- package/dist/generators/skeleton.js +3 -5
- package/dist/generators/skeleton.js.map +1 -1
- package/dist/generators/sql.js +60 -0
- package/dist/generators/sql.js.map +1 -1
- package/dist/sitepaige.js +2 -1
- package/dist/sitepaige.js.map +1 -1
- package/package.json +1 -1
- package/defaultapp/admin/page.tsx +0 -6
- package/defaultapp/api/example-secure/route.ts +0 -100
- package/defaultapp/migrate.ts +0 -142
- /package/defaultapp/{csrf.ts → api/csrf.ts} +0 -0
- /package/defaultapp/{db-mysql.ts → api/db-mysql.ts} +0 -0
- /package/defaultapp/{db-password-auth.ts → api/db-password-auth.ts} +0 -0
- /package/defaultapp/{db-postgres.ts → api/db-postgres.ts} +0 -0
- /package/defaultapp/{db-sqlite.ts → api/db-sqlite.ts} +0 -0
- /package/defaultapp/{db.ts → api/db.ts} +0 -0
- /package/defaultapp/{storage → api/storage}/email.ts +0 -0
- /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
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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(() => {
|
package/components/profile.tsx
CHANGED
|
@@ -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
|
|
12
|
-
|
|
7
|
+
interface UserData {
|
|
8
|
+
userid: string;
|
|
13
9
|
username: string;
|
|
14
|
-
avatarurl
|
|
10
|
+
avatarurl?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
userlevel: number;
|
|
13
|
+
source: string;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export default function Profile() {
|
|
18
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
throw new Error('Failed to fetch user data');
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
const data = await response.json();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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 (
|
|
62
|
-
return
|
|
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 (!
|
|
66
|
-
return
|
|
177
|
+
if (!userData) {
|
|
178
|
+
return null; // Will redirect to login
|
|
67
179
|
}
|
|
68
180
|
|
|
69
181
|
return (
|
|
70
|
-
<div className="
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
+
}
|
package/components/slideshow.tsx
CHANGED
|
@@ -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="
|
|
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 '
|
|
8
|
-
import { send_email } from '
|
|
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.
|
|
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 '
|
|
12
|
-
import { upsertUser, storeOAuthToken, validateSession, rotateSession } from '
|
|
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 '
|
|
8
|
-
import { send_email } from '
|
|
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.
|
|
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 '
|
|
8
|
-
import { upsertUser } from '
|
|
9
|
-
import { db_init, db_query } from '
|
|
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.
|
|
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.
|
|
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 '
|
|
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 '
|
|
16
|
-
import { validateCsrfToken } from '
|
|
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();
|