AI_Agent_Final_V2 / web /src /components /ProfileEditor.tsx
SarahXia0405's picture
Update web/src/components/ProfileEditor.tsx
bba3a81 verified
import React, { useEffect, useRef, useState } from "react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { Textarea } from "./ui/textarea";
import { Dialog, DialogContent, DialogTitle } from "./ui/dialog";
import type { User as UserType } from "../App";
import { toast } from "sonner";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
interface ProfileEditorProps {
user: UserType;
onSave: (user: UserType) => void;
onClose: () => void;
}
export function ProfileEditor({ user, onSave, onClose }: ProfileEditorProps) {
const [name, setName] = useState(user.name ?? "");
const [email, setEmail] = useState(user.email ?? "");
const [studentId, setStudentId] = useState(user.studentId ?? "");
const [department, setDepartment] = useState(user.department ?? "");
const [yearLevel, setYearLevel] = useState(user.yearLevel ?? "");
const [major, setMajor] = useState(user.major ?? "");
// ✅ bio is editable here (user can override Clare-generated bio)
const [bio, setBio] = useState(user.bio ?? "");
const [learningStyle, setLearningStyle] = useState(user.learningStyle ?? "visual");
const [learningPace, setLearningPace] = useState(user.learningPace ?? "moderate");
const [photoPreview, setPhotoPreview] = useState<string | null>(user.avatarUrl ?? null);
const fileInputRef = useRef<HTMLInputElement>(null);
// ✅ Keep fields in sync if user changes while dialog is open
useEffect(() => {
setName(user.name ?? "");
setEmail(user.email ?? "");
setStudentId(user.studentId ?? "");
setDepartment(user.department ?? "");
setYearLevel(user.yearLevel ?? "");
setMajor(user.major ?? "");
setBio(user.bio ?? "");
setLearningStyle(user.learningStyle ?? "visual");
setLearningPace(user.learningPace ?? "moderate");
setPhotoPreview(user.avatarUrl ?? null);
}, [
user.name,
user.email,
user.studentId,
user.department,
user.yearLevel,
user.major,
user.bio,
user.learningStyle,
user.learningPace,
user.avatarUrl,
]);
const handleSave = () => {
if (!name.trim() || !email.trim()) {
toast.error("Please fill in all required fields");
return;
}
const next: UserType = {
...user,
name: name.trim(),
email: email.trim(),
studentId: studentId.trim() || undefined,
department: department.trim() || undefined,
yearLevel: yearLevel || undefined,
major: major.trim() || undefined,
// ✅ allow user edit
bio: (bio ?? "").slice(0, 200),
learningStyle: learningStyle || undefined,
learningPace: learningPace || undefined,
avatarUrl: photoPreview || undefined,
};
onSave(next);
toast.success("Profile updated successfully!");
onClose();
};
const handlePhotoSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith("image/")) {
toast.error("Please select an image file");
return;
}
if (file.size > 2 * 1024 * 1024) {
toast.error("File size must be less than 2MB");
return;
}
const reader = new FileReader();
reader.onload = (ev) => {
setPhotoPreview(ev.target?.result as string);
toast.success("Photo updated successfully!");
};
reader.readAsDataURL(file);
};
const handleChangePhotoClick = () => fileInputRef.current?.click();
return (
<Dialog
open
onOpenChange={(open) => {
if (!open) onClose();
}}
>
<DialogContent
className="sm:max-w-[800px] p-0 gap-0 max-h-[90vh] overflow-hidden"
style={{ zIndex: 1001, maxWidth: "800px", width: "800px" }}
overlayClassName="!top-16 !left-0 !right-0 !bottom-0 !z-[99]"
overlayStyle={{ top: "64px", left: 0, right: 0, bottom: 0, zIndex: 99, position: "fixed" }}
>
<div className="flex flex-col max-h-[90vh]">
{/* Header */}
<div className="p-4 flex items-center justify-between flex-shrink-0">
<DialogTitle className="text-xl font-medium">Edit Profile</DialogTitle>
</div>
{/* Content */}
<div className="p-6 space-y-6 overflow-y-auto flex-1">
{/* Profile Picture */}
<div className="flex items-center gap-4">
<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">
{photoPreview ? (
<img src={photoPreview} alt="Profile" className="w-full h-full object-cover" />
) : (
(name?.charAt(0) || "U").toUpperCase()
)}
</div>
<div>
<input
ref={fileInputRef}
type="file"
accept="image/jpeg,image/png,image/gif,image/webp"
onChange={handlePhotoSelect}
className="hidden"
/>
<Button variant="outline" size="sm" onClick={handleChangePhotoClick}>
Change Photo
</Button>
<p className="text-xs text-muted-foreground mt-1">JPG, PNG or GIF. Max size 2MB</p>
</div>
</div>
{/* Basic Information */}
<div className="space-y-4">
<h3 className="text-sm font-medium">Basic Information</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-name">Full Name *</Label>
<Input
id="edit-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your full name"
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-email">Email *</Label>
<Input
id="edit-email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-student-id">Student ID</Label>
<Input
id="edit-student-id"
value={studentId}
onChange={(e) => setStudentId(e.target.value)}
placeholder="Enter your student ID"
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-department">Department</Label>
<Input
id="edit-department"
value={department}
onChange={(e) => setDepartment(e.target.value)}
placeholder="Enter your department"
/>
</div>
</div>
</div>
{/* Academic Background */}
<div className="space-y-4">
<h3 className="text-sm font-medium">Academic Background</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-year">Year Level</Label>
<Select value={yearLevel} onValueChange={setYearLevel}>
<SelectTrigger id="edit-year">
<SelectValue placeholder="Select year level" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1st Year">1st Year</SelectItem>
<SelectItem value="2nd Year">2nd Year</SelectItem>
<SelectItem value="3rd Year">3rd Year</SelectItem>
<SelectItem value="4th Year">4th Year</SelectItem>
<SelectItem value="Graduate">Graduate</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="edit-major">Major</Label>
<Input
id="edit-major"
value={major}
onChange={(e) => setMajor(e.target.value)}
placeholder="Enter your major"
/>
</div>
</div>
</div>
{/* Bio (Editable) */}
<div className="space-y-2">
<Label htmlFor="edit-bio">Bio</Label>
<Textarea
id="edit-bio"
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="Tell us about yourself..."
className="min-h-[100px] resize-none"
maxLength={200}
/>
<p className="text-xs text-muted-foreground">Max 200 characters. You can edit this anytime.</p>
</div>
{/* Learning Preferences */}
<div className="space-y-4">
<h3 className="text-sm font-medium">Learning Preferences</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-learning-style">Preferred Learning Style</Label>
<Select value={learningStyle} onValueChange={setLearningStyle}>
<SelectTrigger id="edit-learning-style">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="visual">Visual</SelectItem>
<SelectItem value="auditory">Auditory</SelectItem>
<SelectItem value="reading">Reading/Writing</SelectItem>
<SelectItem value="kinesthetic">Kinesthetic</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="edit-pace">Learning Pace</Label>
<Select value={learningPace} onValueChange={setLearningPace}>
<SelectTrigger id="edit-pace">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="slow">Slow & Steady</SelectItem>
<SelectItem value="moderate">Moderate</SelectItem>
<SelectItem value="fast">Fast-paced</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="border-t border-border p-4 flex justify-end gap-2 flex-shrink-0">
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button onClick={handleSave}>Save Changes</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}