all repos — go-lift @ 387721fc5e90ba268efbae885c9baf8e0a543f28

Lightweight workout tracker prototype..

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;