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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
import { validateSession } from '../../db-users';
|
|
4
|
+
import { updatePassword, getPasswordAuthByEmail } from '../../db-password-auth';
|
|
5
|
+
import { deleteUser } from '../../db-users';
|
|
6
|
+
|
|
7
|
+
// Handle password change
|
|
8
|
+
export async function PUT(request: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
// Get session token from cookies
|
|
11
|
+
const sessionCookie = await cookies();
|
|
12
|
+
const sessionToken = sessionCookie.get('session_id')?.value;
|
|
13
|
+
|
|
14
|
+
if (!sessionToken) {
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: 'Not authenticated' },
|
|
17
|
+
{ status: 401 }
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Validate session and get user details
|
|
22
|
+
const sessionData = await validateSession(sessionToken);
|
|
23
|
+
|
|
24
|
+
if (!sessionData.valid || !sessionData.user) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Invalid session' },
|
|
27
|
+
{ status: 401 }
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { currentPassword, newPassword } = await request.json();
|
|
32
|
+
|
|
33
|
+
if (!currentPassword || !newPassword) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: 'Current password and new password are required' },
|
|
36
|
+
{ status: 400 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if user is using password authentication
|
|
41
|
+
if (sessionData.user.source !== 'userpass') {
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: 'Password changes are only available for email/password accounts' },
|
|
44
|
+
{ status: 400 }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate password requirements
|
|
49
|
+
if (newPassword.length < 8) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
{ error: 'Password must be at least 8 characters long' },
|
|
52
|
+
{ status: 400 }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if user has an email (required for password auth)
|
|
57
|
+
if (!sessionData.user.email) {
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ error: 'No email associated with this account' },
|
|
60
|
+
{ status: 400 }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Update the password
|
|
65
|
+
const success = await updatePassword(
|
|
66
|
+
sessionData.user.email,
|
|
67
|
+
currentPassword,
|
|
68
|
+
newPassword
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!success) {
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: 'Current password is incorrect' },
|
|
74
|
+
{ status: 401 }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return NextResponse.json({
|
|
79
|
+
success: true,
|
|
80
|
+
message: 'Password updated successfully'
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Password update error:', error);
|
|
85
|
+
return NextResponse.json(
|
|
86
|
+
{ error: error instanceof Error ? error.message : 'Failed to update password' },
|
|
87
|
+
{ status: 500 }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle account deletion
|
|
93
|
+
export async function DELETE(request: NextRequest) {
|
|
94
|
+
try {
|
|
95
|
+
// Get session token from cookies
|
|
96
|
+
const sessionCookie = await cookies();
|
|
97
|
+
const sessionToken = sessionCookie.get('session_id')?.value;
|
|
98
|
+
|
|
99
|
+
if (!sessionToken) {
|
|
100
|
+
return NextResponse.json(
|
|
101
|
+
{ error: 'Not authenticated' },
|
|
102
|
+
{ status: 401 }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate session and get user details
|
|
107
|
+
const sessionData = await validateSession(sessionToken);
|
|
108
|
+
|
|
109
|
+
if (!sessionData.valid || !sessionData.user) {
|
|
110
|
+
return NextResponse.json(
|
|
111
|
+
{ error: 'Invalid session' },
|
|
112
|
+
{ status: 401 }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { confirmDelete } = await request.json();
|
|
117
|
+
|
|
118
|
+
if (confirmDelete !== true) {
|
|
119
|
+
return NextResponse.json(
|
|
120
|
+
{ error: 'Please confirm account deletion' },
|
|
121
|
+
{ status: 400 }
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Delete the user account
|
|
126
|
+
try {
|
|
127
|
+
await deleteUser(sessionData.user.userid);
|
|
128
|
+
} catch (error: any) {
|
|
129
|
+
if (error.message && error.message.includes('last admin')) {
|
|
130
|
+
return NextResponse.json(
|
|
131
|
+
{ error: 'Cannot delete the last admin account' },
|
|
132
|
+
{ status: 400 }
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Clear the session cookie
|
|
139
|
+
const response = NextResponse.json({
|
|
140
|
+
success: true,
|
|
141
|
+
message: 'Account deleted successfully'
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
response.cookies.delete('session_id');
|
|
145
|
+
return response;
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Account deletion error:', error);
|
|
149
|
+
return NextResponse.json(
|
|
150
|
+
{ error: error instanceof Error ? error.message : 'Failed to delete account' },
|
|
151
|
+
{ status: 500 }
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blueprintWriter.js","sourceRoot":"","sources":["../src/blueprintWriter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,OAAO,GAAG,MAAM,kBAAkB,CAAC;AAEnC,uCAAuC;AACvC,KAAK,UAAU,QAAQ,CAAC,OAAe;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC7C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAChG,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,qBAAqB,CAAC,CAAC;YAChE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,SAAS,KAAK,OAAO,IAAI,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC;
|
|
1
|
+
{"version":3,"file":"blueprintWriter.js","sourceRoot":"","sources":["../src/blueprintWriter.ts"],"names":[],"mappings":"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"}
|
|
@@ -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(() => {
|
|
@@ -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
|
+
}
|
|
@@ -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) => (
|