Spaces:
Running
Running
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: "[email protected]", | |
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> | |
</> | |
); | |
}; | |