Test / components /studio /ProfileDialog.tsx
TrikozikGames's picture
Add source files
eddc354
Raw
History Blame Contribute Delete
6.84 kB
import { useEffect, useRef, useState } from "react";
import { Camera, FolderOpen, Save, UserRound } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { roleLabel, statusLabel, updateProfile, type StoredUser } from "@/lib/auth-service";
import { formatStamp, listProjects, type StoredProject } from "@/lib/studio-storage";
type Props = {
open: boolean;
onOpenChange: (v: boolean) => void;
user: StoredUser;
onUserChange: (u: StoredUser) => void;
onOpenProject: (p: StoredProject) => void;
};
export default function ProfileDialog({ open, onOpenChange, user, onUserChange, onOpenProject }: Props) {
const fileRef = useRef<HTMLInputElement | null>(null);
const [displayName, setDisplayName] = useState(user.displayName ?? user.username);
const [about, setAbout] = useState(user.about ?? "");
const [avatar, setAvatar] = useState(user.avatar ?? "");
const [items, setItems] = useState<StoredProject[]>([]);
const [savedAt, setSavedAt] = useState(0);
useEffect(() => {
if (!open) return;
setDisplayName(user.displayName ?? user.username);
setAbout(user.about ?? "");
setAvatar(user.avatar ?? "");
setItems(listProjects(user.id));
}, [open, user]);
const pickAvatar = (file: File) => {
const reader = new FileReader();
reader.onload = () => setAvatar(String(reader.result ?? ""));
reader.readAsDataURL(file);
};
const save = () => {
const next = updateProfile(user.id, { displayName: displayName.trim(), about: about.trim(), avatar });
if (next) onUserChange(next);
setSavedAt(Date.now());
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[88vh] max-w-3xl overflow-y-auto">
<DialogHeader>
<DialogTitle className="font-display text-2xl">Профиль</DialogTitle>
<DialogDescription>Данные аккаунта, аватар и сохранённые работы.</DialogDescription>
</DialogHeader>
<input
ref={fileRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) => {
const f = e.target.files?.[0];
if (f) pickAvatar(f);
e.target.value = "";
}}
/>
<div className="grid gap-4 sm:grid-cols-[auto_minmax(0,1fr)]">
<div className="flex flex-col items-center gap-2">
<div className="grid h-24 w-24 place-items-center overflow-hidden rounded-full border border-border bg-secondary">
{avatar ? (
<img src={avatar} alt="Аватар пользователя" className="h-full w-full object-cover" />
) : (
<UserRound className="h-10 w-10 text-muted-foreground" />
)}
</div>
<Button variant="outline" size="sm" className="h-9 gap-1.5" onClick={() => fileRef.current?.click()}>
<Camera className="h-4 w-4" /> Аватар
</Button>
</div>
<div className="space-y-3">
<div className="grid gap-2 sm:grid-cols-2">
<div className="space-y-1.5">
<Label htmlFor="pf-name">Отображаемое имя</Label>
<Input id="pf-name" value={displayName} onChange={(e) => setDisplayName(e.target.value)} />
</div>
<div className="space-y-1.5">
<Label>Логин</Label>
<Input value={user.username} readOnly className="text-muted-foreground" />
</div>
</div>
<div className="space-y-1.5">
<Label htmlFor="pf-about">О себе</Label>
<Textarea
id="pf-about"
rows={3}
value={about}
placeholder="Например: плету салфетки и мандалы"
onChange={(e) => setAbout(e.target.value)}
/>
</div>
<dl className="grid grid-cols-2 gap-2 text-xs sm:grid-cols-4">
{[
{ k: "Роль", v: roleLabel(user.role) },
{ k: "Статус", v: statusLabel(user.status) },
{ k: "Создан", v: formatStamp(user.createdAt) },
{ k: "Схем", v: String(items.length) },
].map((s) => (
<div key={s.k} className="rounded-lg border border-border bg-secondary px-2 py-1.5">
<dt className="text-muted-foreground">{s.k}</dt>
<dd className="truncate font-medium">{s.v}</dd>
</div>
))}
</dl>
<div className="flex items-center gap-2">
<Button size="sm" className="h-10 gap-1.5" onClick={save}>
<Save className="h-4 w-4" /> Сохранить профиль
</Button>
{savedAt > 0 && <span className="text-xs text-muted-foreground">Изменения сохранены</span>}
</div>
</div>
</div>
<div className="border-t border-border pt-3">
<h3 className="font-display text-lg">Мои работы</h3>
{items.length === 0 ? (
<p className="py-6 text-center text-sm text-muted-foreground">Пока нет сохранённых схем.</p>
) : (
<div className="mt-2 grid gap-3 sm:grid-cols-3">
{items.map((p) => (
<article key={p.id} className="overflow-hidden rounded-xl border border-border bg-card">
<img
src={p.thumb}
alt={`Превью схемы «${p.title}»`}
className="aspect-[4/3] w-full bg-secondary object-cover"
/>
<div className="space-y-1.5 p-2">
<h4 className="truncate text-sm font-medium">{p.title}</h4>
<p className="text-[11px] text-muted-foreground">{formatStamp(p.updatedAt)}</p>
<Button
size="sm"
className="h-8 w-full gap-1.5"
onClick={() => {
onOpenProject(p);
onOpenChange(false);
}}
>
<FolderOpen className="h-4 w-4" /> Открыть
</Button>
</div>
</article>
))}
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}