feat: add mock page

This commit is contained in:
2026-08-22 08:24:06 +07:00
parent b44092153d
commit dd9ed73a02
23 changed files with 590 additions and 118 deletions
+97
View File
@@ -0,0 +1,97 @@
import { useState } from 'react';
import { Button } from '@/shared/components/Button';
import { Modal } from '@/shared/components/Modal';
import { useLogout } from '../hooks/useLogout';
import { useAuthStore } from '../store/authStore';
// ponytail: Gravatar's default-image endpoint seeds off whatever string sits in the
// path — no real MD5 needed for a mocked identicon. Swap in a proper md5(email) hash
// when real Gravatar photos (not just the generated identicon) are wanted.
function gravatarUrl(email: string, size: number) {
return `https://www.gravatar.com/avatar/${encodeURIComponent(email.trim().toLowerCase())}?d=identicon&s=${size}`;
}
const PLACEHOLDER = 'Not provided';
// ponytail: hardcoded until learner/progress feature exposes a real API; swap then.
const MOCK_PROGRESS = { level: 4, xp: 320, streak: 7 };
export function UserMenu() {
const user = useAuthStore((s) => s.user);
const logout = useLogout();
const [open, setOpen] = useState(false);
if (!user) return null;
const fields = [
{ label: 'Full name', value: user.name },
{ label: 'Date of birth', value: PLACEHOLDER },
{ label: 'Email', value: user.email },
{ label: 'Phone', value: PLACEHOLDER },
];
return (
<>
<button type="button" onClick={() => setOpen(true)} className="flex items-center gap-1.5">
<span className="flex h-10 items-center justify-center rounded-full bg-accent px-3 text-xs font-extrabold text-ink-inverse">
🔥{MOCK_PROGRESS.streak}
</span>
<span className="flex h-10 items-center justify-center rounded-full bg-secondary px-3 text-xs font-extrabold text-ink">
{MOCK_PROGRESS.xp}
</span>
<div className="relative h-10 w-10">
<img
src={gravatarUrl(user.email, 80)}
alt=""
className="h-10 w-10 rounded-lg border border-border"
/>
<span className="absolute -bottom-1 -right-1 inline-flex items-center rounded-full bg-primary px-1.5 py-0.5 text-xs font-extrabold text-ink-inverse">
{MOCK_PROGRESS.level}
</span>
</div>
</button>
<Modal open={open} onClose={() => setOpen(false)} title="User profile">
<div className="relative flex flex-col items-center gap-4 px-6 py-8">
<button
type="button"
onClick={() => setOpen(false)}
aria-label="Close"
className="absolute top-3 right-3 rounded-full p-1.5 text-ink-muted hover:bg-surface-soft hover:text-ink"
>
</button>
<img
src={gravatarUrl(user.email, 160)}
alt=""
className="h-20 w-20 rounded-full border-4 border-primary-light"
/>
<dl className="w-full space-y-3 text-sm">
{fields.map((f) => (
<div key={f.label} className="flex items-center justify-between gap-4 border-b border-border pb-2">
<dt className="text-ink-secondary">{f.label}</dt>
<dd className="truncate font-semibold text-ink">{f.value}</dd>
</div>
))}
</dl>
<Button
type="button"
variant="outline"
onClick={() => {
setOpen(false);
logout.mutate();
}}
disabled={logout.isPending}
className="min-h-11 w-full text-sm"
>
{logout.isPending ? 'Signing out…' : 'Sign out'}
</Button>
</div>
</Modal>
</>
);
}