SarahXia0405 commited on
Commit
31386d4
·
verified ·
1 Parent(s): 20e2a55

Update web/src/components/ProfileEditor.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ProfileEditor.tsx +205 -198
web/src/components/ProfileEditor.tsx CHANGED
@@ -1,18 +1,12 @@
1
- import React, { useState, useRef } from 'react';
2
- import { Button } from './ui/button';
3
- import { Input } from './ui/input';
4
- import { Label } from './ui/label';
5
- import { Textarea } from './ui/textarea';
6
- import { Dialog, DialogContent, DialogTitle } from './ui/dialog';
7
- import type { User as UserType } from '../App';
8
- import { toast } from 'sonner';
9
- import {
10
- Select,
11
- SelectContent,
12
- SelectItem,
13
- SelectTrigger,
14
- SelectValue,
15
- } from './ui/select';
16
 
17
  interface ProfileEditorProps {
18
  user: UserType;
@@ -23,45 +17,63 @@ interface ProfileEditorProps {
23
  export function ProfileEditor({ user, onSave, onClose }: ProfileEditorProps) {
24
  const [name, setName] = useState(user.name);
25
  const [email, setEmail] = useState(user.email);
26
- const [studentId, setStudentId] = useState('S12345678');
27
- const [department, setDepartment] = useState('Computer Science');
28
- const [year, setYear] = useState('3rd Year');
29
- const [major, setMajor] = useState('Artificial Intelligence');
30
- const [bio, setBio] = useState('Passionate about AI and machine learning');
 
 
 
 
 
31
  const [photoPreview, setPhotoPreview] = useState<string | null>(null);
32
  const fileInputRef = useRef<HTMLInputElement>(null);
33
 
 
 
 
 
 
 
 
34
  const handleSave = () => {
35
- if (name.trim() && email.trim()) {
36
- onSave({ name: name.trim(), email: email.trim() });
37
- toast.success('Profile updated successfully!');
38
- onClose();
39
- } else {
40
- toast.error('Please fill in all required fields');
41
  }
 
 
 
 
 
 
 
 
 
 
 
42
  };
43
 
44
  const handlePhotoSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
45
  const file = e.target.files?.[0];
46
- if (file) {
47
- // Validate file type
48
- if (!file.type.startsWith('image/')) {
49
- toast.error('Please select an image file');
50
- return;
51
- }
52
- // Validate file size (2MB)
53
- if (file.size > 2 * 1024 * 1024) {
54
- toast.error('File size must be less than 2MB');
55
- return;
56
- }
57
- // Create preview
58
- const reader = new FileReader();
59
- reader.onload = (e) => {
60
- setPhotoPreview(e.target?.result as string);
61
- toast.success('Photo updated successfully!');
62
- };
63
- reader.readAsDataURL(file);
64
  }
 
 
 
 
 
 
 
65
  };
66
 
67
  const handleChangePhotoClick = () => {
@@ -69,18 +81,23 @@ export function ProfileEditor({ user, onSave, onClose }: ProfileEditorProps) {
69
  };
70
 
71
  return (
72
- <Dialog open onOpenChange={(open) => { if (!open) onClose(); }}>
73
- <DialogContent
 
 
 
 
 
74
  className="sm:max-w-[800px] p-0 gap-0 max-h-[90vh] overflow-hidden"
75
- style={{ zIndex: 1001, maxWidth: '800px', width: '800px' }}
76
  overlayClassName="!top-16 !left-0 !right-0 !bottom-0 !z-[99]"
77
- overlayStyle={{
78
- top: '64px',
79
- left: 0,
80
- right: 0,
81
- bottom: 0,
82
  zIndex: 99,
83
- position: 'fixed'
84
  }}
85
  >
86
  <div className="flex flex-col max-h-[90vh]">
@@ -91,170 +108,160 @@ export function ProfileEditor({ user, onSave, onClose }: ProfileEditorProps) {
91
 
92
  {/* Content */}
93
  <div className="p-6 space-y-6 overflow-y-auto flex-1">
94
- {/* Profile Picture */}
95
- <div className="flex items-center gap-4">
96
- <div className="w-20 h-20 rounded-full bg-gradient-to-br from-red-500 to-orange-500 flex items-center justify-center text-white text-2xl overflow-hidden">
97
- {photoPreview ? (
98
- <img src={photoPreview} alt="Profile" className="w-full h-full object-cover" />
99
- ) : (
100
- name.charAt(0).toUpperCase()
101
- )}
102
- </div>
103
- <div>
104
- <input
105
- ref={fileInputRef}
106
- type="file"
107
- accept="image/jpeg,image/png,image/gif,image/webp"
108
- onChange={handlePhotoSelect}
109
- className="hidden"
110
- />
111
- <Button
112
- variant="outline"
113
- size="sm"
114
- onClick={handleChangePhotoClick}
115
- >
116
- Change Photo
117
- </Button>
118
- <p className="text-xs text-muted-foreground mt-1">
119
- JPG, PNG or GIF. Max size 2MB
120
- </p>
121
- </div>
122
- </div>
123
-
124
- {/* Basic Information */}
125
- <div className="space-y-4">
126
- <h3 className="text-sm font-medium">Basic Information</h3>
127
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
128
- <div className="space-y-2">
129
- <Label htmlFor="edit-name">Full Name *</Label>
130
- <Input
131
- id="edit-name"
132
- value={name}
133
- onChange={(e) => setName(e.target.value)}
134
- placeholder="Enter your full name"
135
- />
136
  </div>
137
- <div className="space-y-2">
138
- <Label htmlFor="edit-email">Email *</Label>
139
- <Input
140
- id="edit-email"
141
- type="email"
142
- value={email}
143
- onChange={(e) => setEmail(e.target.value)}
144
- placeholder="Enter your email"
145
- />
146
- </div>
147
- <div className="space-y-2">
148
- <Label htmlFor="edit-student-id">Student ID</Label>
149
- <Input
150
- id="edit-student-id"
151
- value={studentId}
152
- onChange={(e) => setStudentId(e.target.value)}
153
- placeholder="Enter your student ID"
154
- />
155
- </div>
156
- <div className="space-y-2">
157
- <Label htmlFor="edit-department">Department</Label>
158
- <Input
159
- id="edit-department"
160
- value={department}
161
- onChange={(e) => setDepartment(e.target.value)}
162
- placeholder="Enter your department"
163
  />
 
 
 
 
164
  </div>
165
  </div>
166
- </div>
167
 
168
- {/* Academic Background */}
169
- <div className="space-y-4">
170
- <h3 className="text-sm font-medium">Academic Background</h3>
171
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
172
- <div className="space-y-2">
173
- <Label htmlFor="edit-year">Year Level</Label>
174
- <Select value={year} onValueChange={setYear}>
175
- <SelectTrigger id="edit-year">
176
- <SelectValue />
177
- </SelectTrigger>
178
- <SelectContent>
179
- <SelectItem value="1st Year">1st Year</SelectItem>
180
- <SelectItem value="2nd Year">2nd Year</SelectItem>
181
- <SelectItem value="3rd Year">3rd Year</SelectItem>
182
- <SelectItem value="4th Year">4th Year</SelectItem>
183
- <SelectItem value="Graduate">Graduate</SelectItem>
184
- </SelectContent>
185
- </Select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  </div>
187
- <div className="space-y-2">
188
- <Label htmlFor="edit-major">Major</Label>
189
- <Input
190
- id="edit-major"
191
- value={major}
192
- onChange={(e) => setMajor(e.target.value)}
193
- placeholder="Enter your major"
194
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  </div>
196
  </div>
197
- </div>
198
 
199
- {/* Bio */}
200
- <div className="space-y-2">
201
- <Label htmlFor="edit-bio">Bio</Label>
202
- <Textarea
203
- id="edit-bio"
204
- value={bio}
205
- onChange={(e) => setBio(e.target.value)}
206
- placeholder="Tell us about yourself..."
207
- className="min-h-[100px] resize-none"
208
- />
209
- <p className="text-xs text-muted-foreground">
210
- Brief description for your profile. Max 200 characters.
211
- </p>
212
- </div>
213
 
214
- {/* Learning Preferences */}
215
- <div className="space-y-4">
216
- <h3 className="text-sm font-medium">Learning Preferences</h3>
217
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
218
- <div className="space-y-2">
219
- <Label htmlFor="edit-learning-style">Preferred Learning Style</Label>
220
- <Select defaultValue="visual">
221
- <SelectTrigger id="edit-learning-style">
222
- <SelectValue />
223
- </SelectTrigger>
224
- <SelectContent>
225
- <SelectItem value="visual">Visual</SelectItem>
226
- <SelectItem value="auditory">Auditory</SelectItem>
227
- <SelectItem value="reading">Reading/Writing</SelectItem>
228
- <SelectItem value="kinesthetic">Kinesthetic</SelectItem>
229
- </SelectContent>
230
- </Select>
231
- </div>
232
- <div className="space-y-2">
233
- <Label htmlFor="edit-pace">Learning Pace</Label>
234
- <Select defaultValue="moderate">
235
- <SelectTrigger id="edit-pace">
236
- <SelectValue />
237
- </SelectTrigger>
238
- <SelectContent>
239
- <SelectItem value="slow">Slow & Steady</SelectItem>
240
- <SelectItem value="moderate">Moderate</SelectItem>
241
- <SelectItem value="fast">Fast-paced</SelectItem>
242
- </SelectContent>
243
- </Select>
 
244
  </div>
245
  </div>
246
  </div>
247
 
248
- </div>
249
-
250
  {/* Footer */}
251
  <div className="border-t border-border p-4 flex justify-end gap-2 flex-shrink-0">
252
  <Button variant="outline" onClick={onClose}>
253
  Cancel
254
  </Button>
255
- <Button onClick={handleSave}>
256
- Save Changes
257
- </Button>
258
  </div>
259
  </div>
260
  </DialogContent>
 
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import { Button } from "./ui/button";
3
+ import { Input } from "./ui/input";
4
+ import { Label } from "./ui/label";
5
+ import { Textarea } from "./ui/textarea";
6
+ import { Dialog, DialogContent, DialogTitle } from "./ui/dialog";
7
+ import type { User as UserType } from "../App";
8
+ import { toast } from "sonner";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
 
 
 
 
 
 
10
 
11
  interface ProfileEditorProps {
12
  user: UserType;
 
17
  export function ProfileEditor({ user, onSave, onClose }: ProfileEditorProps) {
18
  const [name, setName] = useState(user.name);
19
  const [email, setEmail] = useState(user.email);
20
+
21
+ // these can remain local-only for now
22
+ const [studentId, setStudentId] = useState("S12345678");
23
+ const [department, setDepartment] = useState("Computer Science");
24
+ const [year, setYear] = useState("3rd Year");
25
+ const [major, setMajor] = useState("Artificial Intelligence");
26
+
27
+ // ✅ IMPORTANT: initialize from user.bio (so it reflects Clare-generated bio)
28
+ const [bio, setBio] = useState(user.bio ?? "");
29
+
30
  const [photoPreview, setPhotoPreview] = useState<string | null>(null);
31
  const fileInputRef = useRef<HTMLInputElement>(null);
32
 
33
+ // ✅ If user changes while dialog is open, keep fields in sync
34
+ useEffect(() => {
35
+ setName(user.name);
36
+ setEmail(user.email);
37
+ setBio(user.bio ?? "");
38
+ }, [user.name, user.email, user.bio]);
39
+
40
  const handleSave = () => {
41
+ if (!name.trim() || !email.trim()) {
42
+ toast.error("Please fill in all required fields");
43
+ return;
 
 
 
44
  }
45
+
46
+ const next: UserType = {
47
+ ...user,
48
+ name: name.trim(),
49
+ email: email.trim(),
50
+ bio: (bio ?? "").slice(0, 200), // enforce 200 chars UI limit
51
+ };
52
+
53
+ onSave(next);
54
+ toast.success("Profile updated successfully!");
55
+ onClose();
56
  };
57
 
58
  const handlePhotoSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
59
  const file = e.target.files?.[0];
60
+ if (!file) return;
61
+
62
+ if (!file.type.startsWith("image/")) {
63
+ toast.error("Please select an image file");
64
+ return;
65
+ }
66
+ if (file.size > 2 * 1024 * 1024) {
67
+ toast.error("File size must be less than 2MB");
68
+ return;
 
 
 
 
 
 
 
 
 
69
  }
70
+
71
+ const reader = new FileReader();
72
+ reader.onload = (ev) => {
73
+ setPhotoPreview(ev.target?.result as string);
74
+ toast.success("Photo updated successfully!");
75
+ };
76
+ reader.readAsDataURL(file);
77
  };
78
 
79
  const handleChangePhotoClick = () => {
 
81
  };
82
 
83
  return (
84
+ <Dialog
85
+ open
86
+ onOpenChange={(open) => {
87
+ if (!open) onClose();
88
+ }}
89
+ >
90
+ <DialogContent
91
  className="sm:max-w-[800px] p-0 gap-0 max-h-[90vh] overflow-hidden"
92
+ style={{ zIndex: 1001, maxWidth: "800px", width: "800px" }}
93
  overlayClassName="!top-16 !left-0 !right-0 !bottom-0 !z-[99]"
94
+ overlayStyle={{
95
+ top: "64px",
96
+ left: 0,
97
+ right: 0,
98
+ bottom: 0,
99
  zIndex: 99,
100
+ position: "fixed",
101
  }}
102
  >
103
  <div className="flex flex-col max-h-[90vh]">
 
108
 
109
  {/* Content */}
110
  <div className="p-6 space-y-6 overflow-y-auto flex-1">
111
+ {/* Profile Picture */}
112
+ <div className="flex items-center gap-4">
113
+ <div className="w-20 h-20 rounded-full bg-gradient-to-br from-red-500 to-orange-500 flex items-center justify-center text-white text-2xl overflow-hidden">
114
+ {photoPreview ? (
115
+ <img src={photoPreview} alt="Profile" className="w-full h-full object-cover" />
116
+ ) : (
117
+ name.charAt(0).toUpperCase()
118
+ )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  </div>
120
+ <div>
121
+ <input
122
+ ref={fileInputRef}
123
+ type="file"
124
+ accept="image/jpeg,image/png,image/gif,image/webp"
125
+ onChange={handlePhotoSelect}
126
+ className="hidden"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  />
128
+ <Button variant="outline" size="sm" onClick={handleChangePhotoClick}>
129
+ Change Photo
130
+ </Button>
131
+ <p className="text-xs text-muted-foreground mt-1">JPG, PNG or GIF. Max size 2MB</p>
132
  </div>
133
  </div>
 
134
 
135
+ {/* Basic Information */}
136
+ <div className="space-y-4">
137
+ <h3 className="text-sm font-medium">Basic Information</h3>
138
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
139
+ <div className="space-y-2">
140
+ <Label htmlFor="edit-name">Full Name *</Label>
141
+ <Input
142
+ id="edit-name"
143
+ value={name}
144
+ onChange={(e) => setName(e.target.value)}
145
+ placeholder="Enter your full name"
146
+ />
147
+ </div>
148
+ <div className="space-y-2">
149
+ <Label htmlFor="edit-email">Email *</Label>
150
+ <Input
151
+ id="edit-email"
152
+ type="email"
153
+ value={email}
154
+ onChange={(e) => setEmail(e.target.value)}
155
+ placeholder="Enter your email"
156
+ />
157
+ </div>
158
+ <div className="space-y-2">
159
+ <Label htmlFor="edit-student-id">Student ID</Label>
160
+ <Input
161
+ id="edit-student-id"
162
+ value={studentId}
163
+ onChange={(e) => setStudentId(e.target.value)}
164
+ placeholder="Enter your student ID"
165
+ />
166
+ </div>
167
+ <div className="space-y-2">
168
+ <Label htmlFor="edit-department">Department</Label>
169
+ <Input
170
+ id="edit-department"
171
+ value={department}
172
+ onChange={(e) => setDepartment(e.target.value)}
173
+ placeholder="Enter your department"
174
+ />
175
+ </div>
176
  </div>
177
+ </div>
178
+
179
+ {/* Academic Background */}
180
+ <div className="space-y-4">
181
+ <h3 className="text-sm font-medium">Academic Background</h3>
182
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
183
+ <div className="space-y-2">
184
+ <Label htmlFor="edit-year">Year Level</Label>
185
+ <Select value={year} onValueChange={setYear}>
186
+ <SelectTrigger id="edit-year">
187
+ <SelectValue />
188
+ </SelectTrigger>
189
+ <SelectContent>
190
+ <SelectItem value="1st Year">1st Year</SelectItem>
191
+ <SelectItem value="2nd Year">2nd Year</SelectItem>
192
+ <SelectItem value="3rd Year">3rd Year</SelectItem>
193
+ <SelectItem value="4th Year">4th Year</SelectItem>
194
+ <SelectItem value="Graduate">Graduate</SelectItem>
195
+ </SelectContent>
196
+ </Select>
197
+ </div>
198
+ <div className="space-y-2">
199
+ <Label htmlFor="edit-major">Major</Label>
200
+ <Input
201
+ id="edit-major"
202
+ value={major}
203
+ onChange={(e) => setMajor(e.target.value)}
204
+ placeholder="Enter your major"
205
+ />
206
+ </div>
207
  </div>
208
  </div>
 
209
 
210
+ {/* Bio */}
211
+ <div className="space-y-2">
212
+ <Label htmlFor="edit-bio">Bio</Label>
213
+ <Textarea
214
+ id="edit-bio"
215
+ value={bio}
216
+ onChange={(e) => setBio(e.target.value)}
217
+ placeholder="Tell us about yourself..."
218
+ className="min-h-[100px] resize-none"
219
+ maxLength={200}
220
+ />
221
+ <p className="text-xs text-muted-foreground">Brief description for your profile. Max 200 characters.</p>
222
+ </div>
 
223
 
224
+ {/* Learning Preferences */}
225
+ <div className="space-y-4">
226
+ <h3 className="text-sm font-medium">Learning Preferences</h3>
227
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
228
+ <div className="space-y-2">
229
+ <Label htmlFor="edit-learning-style">Preferred Learning Style</Label>
230
+ <Select defaultValue="visual">
231
+ <SelectTrigger id="edit-learning-style">
232
+ <SelectValue />
233
+ </SelectTrigger>
234
+ <SelectContent>
235
+ <SelectItem value="visual">Visual</SelectItem>
236
+ <SelectItem value="auditory">Auditory</SelectItem>
237
+ <SelectItem value="reading">Reading/Writing</SelectItem>
238
+ <SelectItem value="kinesthetic">Kinesthetic</SelectItem>
239
+ </SelectContent>
240
+ </Select>
241
+ </div>
242
+ <div className="space-y-2">
243
+ <Label htmlFor="edit-pace">Learning Pace</Label>
244
+ <Select defaultValue="moderate">
245
+ <SelectTrigger id="edit-pace">
246
+ <SelectValue />
247
+ </SelectTrigger>
248
+ <SelectContent>
249
+ <SelectItem value="slow">Slow & Steady</SelectItem>
250
+ <SelectItem value="moderate">Moderate</SelectItem>
251
+ <SelectItem value="fast">Fast-paced</SelectItem>
252
+ </SelectContent>
253
+ </Select>
254
+ </div>
255
  </div>
256
  </div>
257
  </div>
258
 
 
 
259
  {/* Footer */}
260
  <div className="border-t border-border p-4 flex justify-end gap-2 flex-shrink-0">
261
  <Button variant="outline" onClick={onClose}>
262
  Cancel
263
  </Button>
264
+ <Button onClick={handleSave}>Save Changes</Button>
 
 
265
  </div>
266
  </div>
267
  </DialogContent>