Spaces:
Sleeping
Sleeping
| import { useState, useEffect, useMemo } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogDescription, | |
| DialogFooter, | |
| DialogHeader, | |
| DialogTitle, | |
| } from "@/components/ui/dialog"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Label } from "@/components/ui/label"; | |
| import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; | |
| import { Switch } from "@/components/ui/switch"; | |
| import { User, Mail, Shield, LogOut } from "lucide-react"; | |
| import { Separator } from "@/components/ui/separator"; | |
| import { toast } from "@/components/ui/sonner"; | |
| import { storage, STORAGE_KEYS } from "@/lib/storage"; | |
| import { | |
| AlertDialog, | |
| AlertDialogAction, | |
| AlertDialogCancel, | |
| AlertDialogContent, | |
| AlertDialogDescription, | |
| AlertDialogFooter, | |
| AlertDialogHeader, | |
| AlertDialogTitle, | |
| } from "@/components/ui/alert-dialog"; | |
| // Use the ESM build of multiavatar to generate SVG strings | |
| import multiavatar from "@multiavatar/multiavatar/esm"; | |
| interface ProfileModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| } | |
| interface ProfileSettings { | |
| name: string; | |
| email: string; | |
| avatarUrl?: string; // now optional | |
| saveHistory: boolean; | |
| } | |
| const PROFILE_STORAGE_KEY = STORAGE_KEYS.PROFILE_STORAGE_KEY; | |
| export const ProfileModal = ({ isOpen, onClose }: ProfileModalProps) => { | |
| const [signOutDialogOpen, setSignOutDialogOpen] = useState(false); | |
| const [profileSettings, setProfileSettings] = useState<ProfileSettings>(() => { | |
| // load, or default (no external URL here) | |
| return ( | |
| storage.get<ProfileSettings>(PROFILE_STORAGE_KEY) || { | |
| name: "John Doe", | |
| email: "john.doe@example.com", | |
| avatarUrl: undefined, | |
| saveHistory: true, | |
| } | |
| ); | |
| }); | |
| // reload from storage whenever modal re-opens | |
| useEffect(() => { | |
| if (isOpen) { | |
| const saved = storage.get<ProfileSettings>(PROFILE_STORAGE_KEY); | |
| if (saved) setProfileSettings(saved); | |
| } | |
| }, [isOpen]); | |
| // memoize the SVG data URI so it only regenerates when the name changes | |
| const defaultAvatarDataUri = useMemo(() => { | |
| const svg = multiavatar(profileSettings.name); | |
| return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; | |
| }, [profileSettings.name]); | |
| // pick either the custom URL or the inline SVG | |
| const avatarSrc = profileSettings.avatarUrl || defaultAvatarDataUri; | |
| const handleSaveChanges = () => { | |
| storage.set(PROFILE_STORAGE_KEY, profileSettings); | |
| toast.success("Profile settings saved successfully"); | |
| onClose(); | |
| }; | |
| const handleSignOut = () => { | |
| toast.success("Signed out successfully"); | |
| setSignOutDialogOpen(false); | |
| onClose(); | |
| // clear auth tokens, etc. | |
| }; | |
| return ( | |
| <> | |
| <Dialog open={isOpen} onOpenChange={onClose}> | |
| <DialogContent className="sm:max-w-[500px] max-h-[85vh] overflow-y-auto"> | |
| <DialogHeader> | |
| <DialogTitle className="text-xl flex items-center"> | |
| <User className="mr-2 h-5 w-5" /> | |
| Profile Settings | |
| </DialogTitle> | |
| <DialogDescription> | |
| Manage your profile information and preferences. | |
| </DialogDescription> | |
| </DialogHeader> | |
| <Separator /> | |
| <div className="flex flex-col space-y-6 py-4"> | |
| {/* Profile section */} | |
| <div className="flex flex-col space-y-4"> | |
| <div className="flex items-center space-x-4"> | |
| <Avatar className="h-20 w-20"> | |
| <AvatarImage src={avatarSrc} alt="User avatar" /> | |
| <AvatarFallback className="bg-primary/10 text-lg"> | |
| <User className="h-8 w-8 text-primary" /> | |
| </AvatarFallback> | |
| </Avatar> | |
| <div className="space-y-1"> | |
| <h3 className="font-medium text-lg">{profileSettings.name}</h3> | |
| <p className="text-sm text-muted-foreground">{profileSettings.email}</p> | |
| <Button variant="outline" size="sm" className="mt-2"> | |
| Change picture | |
| </Button> | |
| </div> | |
| </div> | |
| <Separator /> | |
| <div className="grid gap-4"> | |
| {/* Name */} | |
| <div className="grid grid-cols-4 items-center gap-4"> | |
| <Label htmlFor="name" className="text-right"> | |
| Name | |
| </Label> | |
| <Input | |
| id="name" | |
| value={profileSettings.name} | |
| onChange={(e) => | |
| setProfileSettings({ ...profileSettings, name: e.target.value }) | |
| } | |
| className="col-span-3" | |
| /> | |
| </div> | |
| {/* Email */} | |
| <div className="grid grid-cols-4 items-center gap-4"> | |
| <Label htmlFor="email" className="text-right"> | |
| </Label> | |
| <Input | |
| id="email" | |
| value={profileSettings.email} | |
| onChange={(e) => | |
| setProfileSettings({ ...profileSettings, email: e.target.value }) | |
| } | |
| className="col-span-3" | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Privacy */} | |
| <div className="space-y-4"> | |
| <h3 className="text-lg font-medium flex items-center gap-2"> | |
| <Shield className="h-5 w-5" /> | |
| Privacy and Data | |
| </h3> | |
| <Separator /> | |
| <div className="flex items-center justify-between"> | |
| <div className="space-y-0.5"> | |
| <Label htmlFor="chat-history">Save Chat History</Label> | |
| <p className="text-sm text-muted-foreground"> | |
| Keep your chat history saved | |
| </p> | |
| </div> | |
| <Switch | |
| id="chat-history" | |
| checked={profileSettings.saveHistory} | |
| onCheckedChange={(saveHistory) => | |
| setProfileSettings({ ...profileSettings, saveHistory }) | |
| } | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| <DialogFooter className="flex items-center justify-between mt-6"> | |
| <Button | |
| variant="destructive" | |
| size="sm" | |
| onClick={() => setSignOutDialogOpen(true)} | |
| > | |
| <LogOut className="mr-2 h-4 w-4" /> | |
| Sign Out | |
| </Button> | |
| <Button onClick={handleSaveChanges}>Save changes</Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| {/* Sign-out confirmation */} | |
| <AlertDialog open={signOutDialogOpen} onOpenChange={setSignOutDialogOpen}> | |
| <AlertDialogContent> | |
| <AlertDialogHeader> | |
| <AlertDialogTitle>Sign Out</AlertDialogTitle> | |
| <AlertDialogDescription> | |
| Are you sure you want to sign out? You will need to sign in again to | |
| access your account. | |
| </AlertDialogDescription> | |
| </AlertDialogHeader> | |
| <AlertDialogFooter> | |
| <AlertDialogCancel>Cancel</AlertDialogCancel> | |
| <AlertDialogAction | |
| onClick={handleSignOut} | |
| className="bg-destructive text-destructive-foreground hover:bg-destructive/90" | |
| > | |
| Sign Out | |
| </AlertDialogAction> | |
| </AlertDialogFooter> | |
| </AlertDialogContent> | |
| </AlertDialog> | |
| </> | |
| ); | |
| }; | |