From 93ff331bfba7543230a11b8dc92b1fa859642c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Sun, 23 Nov 2025 22:13:56 +0100 Subject: [PATCH] refactor(frontend): extract ProfileForm and PasswordChangeForm from ProfilePage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create components/profile/ProfileForm.jsx (192 lines) - Create components/profile/PasswordChangeForm.jsx (99 lines) - Create components/profile/index.js barrel export - Reduce ProfilePage.jsx from 394 → 84 lines (-79%) --- .../components/profile/PasswordChangeForm.jsx | 107 ++++++ .../src/components/profile/ProfileForm.jsx | 213 ++++++++++++ frontend/src/components/profile/index.js | 2 + frontend/src/pages/ProfilePage.jsx | 322 +----------------- 4 files changed, 328 insertions(+), 316 deletions(-) create mode 100644 frontend/src/components/profile/PasswordChangeForm.jsx create mode 100644 frontend/src/components/profile/ProfileForm.jsx create mode 100644 frontend/src/components/profile/index.js diff --git a/frontend/src/components/profile/PasswordChangeForm.jsx b/frontend/src/components/profile/PasswordChangeForm.jsx new file mode 100644 index 0000000..d949518 --- /dev/null +++ b/frontend/src/components/profile/PasswordChangeForm.jsx @@ -0,0 +1,107 @@ +import { useState } from 'react'; +import { authAPI } from '../../services/api'; +import Alert from '../common/Alert'; +import FormInput from '../common/FormInput'; +import LoadingButton from '../common/LoadingButton'; +import { Lock } from 'lucide-react'; + +const PasswordChangeForm = () => { + const [passwordData, setPasswordData] = useState({ + currentPassword: '', + newPassword: '', + confirmPassword: '', + }); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + const handleChange = (e) => { + setPasswordData({ ...passwordData, [e.target.name]: e.target.value }); + setMessage(''); + setError(''); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setMessage(''); + setError(''); + + if (passwordData.newPassword !== passwordData.confirmPassword) { + setError('New passwords do not match'); + setLoading(false); + return; + } + + try { + const response = await authAPI.changePassword( + passwordData.currentPassword, + passwordData.newPassword + ); + + if (response.success) { + setMessage(response.message); + setPasswordData({ + currentPassword: '', + newPassword: '', + confirmPassword: '', + }); + } + } catch (err) { + setError(err.data?.error || 'Failed to change password'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Change Password

+ + + + + + + + + + + + + Change Password + + + ); +}; + +export default PasswordChangeForm; diff --git a/frontend/src/components/profile/ProfileForm.jsx b/frontend/src/components/profile/ProfileForm.jsx new file mode 100644 index 0000000..caa412f --- /dev/null +++ b/frontend/src/components/profile/ProfileForm.jsx @@ -0,0 +1,213 @@ +import { useState, useEffect } from 'react'; +import { authAPI } from '../../services/api'; +import Alert from '../common/Alert'; +import FormInput from '../common/FormInput'; +import FormSelect from '../common/FormSelect'; +import LoadingButton from '../common/LoadingButton'; +import { Mail, Save, Hash, Youtube, Instagram, Facebook, MapPin, Globe } from 'lucide-react'; +import { COUNTRIES } from '../../data/countries'; + +const TikTokIcon = () => ( + + + +); + +const ProfileForm = ({ user, onUserUpdate }) => { + const [profileData, setProfileData] = useState({ + firstName: '', + lastName: '', + email: '', + wsdcId: '', + youtubeUrl: '', + instagramUrl: '', + facebookUrl: '', + tiktokUrl: '', + country: '', + city: '', + }); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + useEffect(() => { + if (user) { + setProfileData({ + firstName: user.firstName || '', + lastName: user.lastName || '', + email: user.email || '', + wsdcId: user.wsdcId || '', + youtubeUrl: user.youtubeUrl || '', + instagramUrl: user.instagramUrl || '', + facebookUrl: user.facebookUrl || '', + tiktokUrl: user.tiktokUrl || '', + country: user.country || '', + city: user.city || '', + }); + } + }, [user]); + + const handleChange = (e) => { + setProfileData({ ...profileData, [e.target.name]: e.target.value }); + setMessage(''); + setError(''); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setMessage(''); + setError(''); + + try { + const response = await authAPI.updateProfile(profileData); + + if (response.success) { + if (response.data.user) { + onUserUpdate(response.data.user); + } + + setMessage(response.message); + + if (response.data.emailChanged) { + setMessage( + 'Profile updated! Please check your new email address to verify it.' + ); + } + } + } catch (err) { + setError(err.data?.error || 'Failed to update profile'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Edit Profile

+ + + + +
+ + + +
+ +
+ + + +
+ + { + const value = e.target.value.replace(/\D/g, ''); + setProfileData({ ...profileData, wsdcId: value }); + }} + icon={Hash} + placeholder="12345" + maxLength={10} + helperText="Your World Swing Dance Council ID (optional)" + /> + + + +
+

Social Media Links

+ +
+ + + + + + + +
+
+ + + + Save Changes + + + ); +}; + +export default ProfileForm; diff --git a/frontend/src/components/profile/index.js b/frontend/src/components/profile/index.js new file mode 100644 index 0000000..f543e13 --- /dev/null +++ b/frontend/src/components/profile/index.js @@ -0,0 +1,2 @@ +export { default as ProfileForm } from './ProfileForm'; +export { default as PasswordChangeForm } from './PasswordChangeForm'; diff --git a/frontend/src/pages/ProfilePage.jsx b/frontend/src/pages/ProfilePage.jsx index 23c20c1..6ca2753 100644 --- a/frontend/src/pages/ProfilePage.jsx +++ b/frontend/src/pages/ProfilePage.jsx @@ -1,142 +1,14 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useAuth } from '../contexts/AuthContext'; -import { authAPI } from '../services/api'; import Layout from '../components/layout/Layout'; -import Alert from '../components/common/Alert'; -import FormInput from '../components/common/FormInput'; -import FormSelect from '../components/common/FormSelect'; -import LoadingButton from '../components/common/LoadingButton'; import Avatar from '../components/common/Avatar'; -import { User, Mail, Lock, Save, Hash, Youtube, Instagram, Facebook, MapPin, Globe } from 'lucide-react'; -import { COUNTRIES } from '../data/countries'; +import { ProfileForm, PasswordChangeForm } from '../components/profile'; +import { User, Lock } from 'lucide-react'; const ProfilePage = () => { const { user, updateUser } = useAuth(); const [activeTab, setActiveTab] = useState('profile'); - // Profile edit state - const [profileData, setProfileData] = useState({ - firstName: '', - lastName: '', - email: '', - wsdcId: '', - youtubeUrl: '', - instagramUrl: '', - facebookUrl: '', - tiktokUrl: '', - country: '', - city: '', - }); - - // Load user data when component mounts or user changes - useEffect(() => { - if (user) { - setProfileData({ - firstName: user.firstName || '', - lastName: user.lastName || '', - email: user.email || '', - wsdcId: user.wsdcId || '', - youtubeUrl: user.youtubeUrl || '', - instagramUrl: user.instagramUrl || '', - facebookUrl: user.facebookUrl || '', - tiktokUrl: user.tiktokUrl || '', - country: user.country || '', - city: user.city || '', - }); - } - }, [user]); - - const [profileLoading, setProfileLoading] = useState(false); - const [profileMessage, setProfileMessage] = useState(''); - const [profileError, setProfileError] = useState(''); - - // Password change state - const [passwordData, setPasswordData] = useState({ - currentPassword: '', - newPassword: '', - confirmPassword: '', - }); - const [passwordLoading, setPasswordLoading] = useState(false); - const [passwordMessage, setPasswordMessage] = useState(''); - const [passwordError, setPasswordError] = useState(''); - - const handleProfileChange = (e) => { - setProfileData({ ...profileData, [e.target.name]: e.target.value }); - setProfileMessage(''); - setProfileError(''); - }; - - const handlePasswordChange = (e) => { - setPasswordData({ ...passwordData, [e.target.name]: e.target.value }); - setPasswordMessage(''); - setPasswordError(''); - }; - - const handleProfileSubmit = async (e) => { - e.preventDefault(); - setProfileLoading(true); - setProfileMessage(''); - setProfileError(''); - - try { - const response = await authAPI.updateProfile(profileData); - - if (response.success) { - // Update context with new user data - if (response.data.user) { - updateUser(response.data.user); - } - - setProfileMessage(response.message); - - if (response.data.emailChanged) { - setProfileMessage( - 'Profile updated! Please check your new email address to verify it.' - ); - } - } - } catch (error) { - setProfileError(error.data?.error || 'Failed to update profile'); - } finally { - setProfileLoading(false); - } - }; - - const handlePasswordSubmit = async (e) => { - e.preventDefault(); - setPasswordLoading(true); - setPasswordMessage(''); - setPasswordError(''); - - // Validate passwords match - if (passwordData.newPassword !== passwordData.confirmPassword) { - setPasswordError('New passwords do not match'); - setPasswordLoading(false); - return; - } - - try { - const response = await authAPI.changePassword( - passwordData.currentPassword, - passwordData.newPassword - ); - - if (response.success) { - setPasswordMessage(response.message); - // Clear form - setPasswordData({ - currentPassword: '', - newPassword: '', - confirmPassword: '', - }); - } - } catch (error) { - setPasswordError(error.data?.error || 'Failed to change password'); - } finally { - setPasswordLoading(false); - } - }; - return (
@@ -159,7 +31,7 @@ const ProfilePage = () => {

@{user?.username}

{!user?.emailVerified && (

- ⚠️ Email not verified + Email not verified

)}
@@ -196,193 +68,11 @@ const ProfilePage = () => {
- {/* Profile Tab */} {activeTab === 'profile' && ( -
-

Edit Profile

- - - - -
- - - -
- - {/* Location Section */} -
- - - -
- - {/* WSDC ID */} - { - const value = e.target.value.replace(/\D/g, ''); - setProfileData({ ...profileData, wsdcId: value }); - }} - icon={Hash} - placeholder="12345" - maxLength={10} - helperText="Your World Swing Dance Council ID (optional)" - /> - - {/* Email */} - - - {/* Social Media Links Section */} -
-

Social Media Links

- -
- - - - - - - ( - - - - )} - placeholder="https://tiktok.com/@yourhandle" - /> -
-
- - {/* Submit Button */} - - - Save Changes - - + )} - {/* Password Tab */} - {activeTab === 'password' && ( -
-

Change Password

- - - - - - - - - - - {/* Submit Button */} - - - Change Password - - - )} + {activeTab === 'password' && }