diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx new file mode 100644 index 0000000..a99a0fc --- /dev/null +++ b/frontend/src/pages/Settings.tsx @@ -0,0 +1,309 @@ +import React, { useState, useEffect } from 'react'; +import { useAuth } from '../contexts/AuthContext'; +import api from '../api/axios'; + +interface UserProfile { + id: number; + username: string; + email: string; + full_name: string; + discord_id?: string; + profile_picture?: string; + is_admin: boolean; +} + +interface UpdateProfileData { + email?: string; + full_name?: string; + discord_id?: string; + profile_picture?: string; + password?: string; +} + +const Settings: React.FC = () => { + const { user } = useAuth(); + const [profile, setProfile] = useState(null); + const [formData, setFormData] = useState({}); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + const [allUsers, setAllUsers] = useState([]); + const [selectedUserId, setSelectedUserId] = useState(null); + + useEffect(() => { + loadProfile(); + if (user?.is_admin) { + loadAllUsers(); + } + }, [user]); + + const loadProfile = async () => { + try { + const response = await api.get('/api/v1/auth/me'); + setProfile(response.data); + setFormData({ + email: response.data.email, + full_name: response.data.full_name, + discord_id: response.data.discord_id || '', + profile_picture: response.data.profile_picture || '', + }); + } catch (err) { + console.error('Failed to load profile:', err); + setError('Failed to load profile'); + } + }; + + const loadAllUsers = async () => { + try { + const response = await api.get('/api/v1/users'); + setAllUsers(response.data); + } catch (err) { + console.error('Failed to load users:', err); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setSuccess(''); + + // Validate passwords match if changing password + if (formData.password && formData.password !== confirmPassword) { + setError('Passwords do not match'); + return; + } + + setIsLoading(true); + + try { + const updateData: UpdateProfileData = {}; + + // Only include changed fields + if (formData.email !== profile?.email) updateData.email = formData.email; + if (formData.full_name !== profile?.full_name) updateData.full_name = formData.full_name; + if (formData.discord_id !== profile?.discord_id) updateData.discord_id = formData.discord_id; + if (formData.profile_picture !== profile?.profile_picture) updateData.profile_picture = formData.profile_picture; + if (formData.password) updateData.password = formData.password; + + await api.put('/api/v1/auth/me', updateData); + setSuccess('Profile updated successfully!'); + setFormData({ ...formData, password: '' }); + setConfirmPassword(''); + loadProfile(); + } catch (err: any) { + setError(err.response?.data?.detail || 'Failed to update profile'); + } finally { + setIsLoading(false); + } + }; + + const handleAdminUpdateUser = async (userId: number, updateData: Partial) => { + try { + await api.put(`/api/v1/auth/users/${userId}`, updateData); + setSuccess('User updated successfully!'); + loadAllUsers(); + } catch (err: any) { + setError(err.response?.data?.detail || 'Failed to update user'); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; + + if (!profile) { + return
Loading...
; + } + + return ( +
+

Settings

+ + {/* Personal Profile Section */} +
+

My Profile

+ + {error && ( +
+ {error} +
+ )} + + {success && ( +
+ {success} +
+ )} + +
+
+ + +

Username cannot be changed

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+

Change Password

+ +
+
+ + +
+ +
+ + setConfirmPassword(e.target.value)} + placeholder="Confirm new password" + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900" + /> +
+
+
+ +
+ +
+
+
+ + {/* Admin Section */} + {user?.is_admin && ( +
+

+ User Management (Admin) +

+ +
+ {allUsers.map((u) => ( +
+
+
+

{u.full_name}

+

@{u.username}

+

{u.email}

+ {u.discord_id && ( +

Discord: {u.discord_id}

+ )} +
+
+ + {u.id !== user.id && ( + + )} +
+
+
+ ))} +
+
+ )} +
+ ); +}; + +export default Settings;