ui/src/pages/ProfilePage.tsx (view raw)
1import { useState, useEffect } from 'react';
2import { useAppContext } from '../context/AppContext';
3import type { User } from '../types/models';
4import { FaUser, FaSave, FaTimes } from 'react-icons/fa';
5
6function parseBirthDate(dateString: string) {
7 return new Date(dateString).toISOString().split('T')[0]; // Format to YYYY-MM-DD
8}
9
10const ProfilePage = () => {
11 const { user, updateUser, isLoading, error: contextError } = useAppContext();
12 const [formData, setFormData] = useState<User | null>(null);
13 const [isEditing, setIsEditing] = useState(false);
14 const [error, setError] = useState<string | null>(null);
15 const [successMessage, setSuccessMessage] = useState<string | null>(null);
16
17 useEffect(() => {
18 if (user && !formData) {
19 setFormData({ ...user });
20 }
21 }, [user, formData]);
22
23 const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
24 const { name, value } = e.target;
25
26 setFormData(prev => {
27 if (!prev) return prev;
28 return { ...prev, [name]: value };
29 });
30 };
31
32 const handleEditToggle = () => {
33 if (isEditing) {
34 // Cancel edit - revert changes
35 setFormData(user ? { ...user } : null);
36 }
37 setIsEditing(!isEditing);
38 setError(null);
39 setSuccessMessage(null);
40 };
41
42 const handleSubmit = async (e: React.FormEvent) => {
43 e.preventDefault();
44
45 if (!formData) return;
46
47 try {
48 setError(null);
49 await updateUser(formData);
50 setSuccessMessage('Profile updated successfully!');
51 setIsEditing(false);
52
53 // Clear success message after a few seconds
54 setTimeout(() => {
55 setSuccessMessage(null);
56 }, 3000);
57 } catch {
58 setError('Failed to update profile. Please try again.');
59 }
60 };
61
62 if (isLoading) {
63 return (
64 <div className="page profile-page">
65 <h1>Profile</h1>
66 <div className="loading">Loading profile data...</div>
67 </div>
68 );
69 }
70
71 if (!formData) {
72 return (
73 <div className="page profile-page">
74 <h1>Profile</h1>
75 <div className="error-message">Could not load profile data.</div>
76 </div>
77 );
78 }
79
80 return (
81 <div className="page profile-page">
82 <div className="profile-header">
83 <h1>Your Profile</h1>
84
85 <button
86 onClick={handleEditToggle}
87 className={`btn ${isEditing ? 'btn-danger' : 'btn-secondary'}`}
88 >
89 {isEditing ? (
90 <>
91 <FaTimes /> Cancel
92 </>
93 ) : (
94 <>Edit Profile</>
95 )}
96 </button>
97 </div>
98
99 {contextError && <div className="error-message">{contextError}</div>}
100 {error && <div className="error-message">{error}</div>}
101 {successMessage && <div className="success-message">{successMessage}</div>}
102
103 <div className="card">
104 <form onSubmit={handleSubmit}>
105 <div className="profile-avatar">
106 <div className="avatar-circle">
107 <FaUser size={40} />
108 </div>
109 </div>
110
111 <div className="form-group">
112 <label htmlFor="name">Name</label>
113 <input
114 type="text"
115 id="name"
116 name="name"
117 value={formData.name}
118 onChange={handleInputChange}
119 disabled={!isEditing}
120 required
121 />
122 </div>
123
124 <div className="form-group">
125 <label htmlFor="isFemale">Gender</label>
126 <select
127 id="isFemale"
128 name="isFemale"
129 value={formData.isFemale.toString()}
130 onChange={handleInputChange}
131 disabled={!isEditing}
132 required
133 >
134 <option value="false">Male</option>
135 <option value="true">Female</option>
136 </select>
137 </div>
138
139 <div className="form-group">
140 <label htmlFor="weight">Weight (kg)</label>
141 <input
142 type="number"
143 id="weight"
144 name="weight"
145 min="20"
146 max="300"
147 step="1"
148 value={formData.weight ?? 0}
149 onChange={handleInputChange}
150 disabled={!isEditing}
151 required
152 />
153 </div>
154
155 <div className="form-group">
156 <label htmlFor="height">Height (cm)</label>
157 <input
158 type="number"
159 id="height"
160 name="height"
161 min="0"
162 max="300"
163 step="1"
164 value={formData.height ?? 0}
165 onChange={handleInputChange}
166 disabled={!isEditing}
167 required
168 />
169 </div>
170
171 <div className="form-group">
172 <label htmlFor="birthDate">Date of Birth</label>
173 <input
174 type="date"
175 id="birthDate"
176 name="birthDate"
177 value={parseBirthDate(formData.birthDate ?? '')}
178 onChange={handleInputChange}
179 disabled={!isEditing}
180 required
181 />
182 </div>
183
184 {isEditing && (
185 <div className="form-actions">
186 <button type="submit" className="btn btn-primary btn-block">
187 <FaSave /> Save Changes
188 </button>
189 </div>
190 )}
191 </form>
192 </div>
193
194 <style>{`
195 .profile-header {
196 display: flex;
197 justify-content: space-between;
198 align-items: center;
199 margin-bottom: var(--spacing-lg);
200 }
201
202 .profile-avatar {
203 display: flex;
204 justify-content: center;
205 margin-bottom: var(--spacing-xl);
206 }
207
208 .avatar-circle {
209 width: 100px;
210 height: 100px;
211 border-radius: 50%;
212 background-color: var(--light-gray);
213 display: flex;
214 align-items: center;
215 justify-content: center;
216 color: var(--dark-gray);
217 }
218
219 .form-actions {
220 margin-top: var(--spacing-lg);
221 }
222
223 .error-message {
224 background-color: rgba(255, 59, 48, 0.1);
225 color: var(--danger-color);
226 padding: var(--spacing-md);
227 border-radius: var(--border-radius);
228 margin-bottom: var(--spacing-lg);
229 }
230
231 .success-message {
232 background-color: rgba(52, 199, 89, 0.1);
233 color: var(--success-color);
234 padding: var(--spacing-md);
235 border-radius: var(--border-radius);
236 margin-bottom: var(--spacing-lg);
237 }
238 `}</style>
239 </div>
240 );
241};
242
243export default ProfilePage;