mercury-web/src/features/profile/ProfileView.tsx

170 lines
5.7 KiB
TypeScript

"use client";
import * as React from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Card, HideAmountsToggle, Icon, IconChip, MercuryButton, ThemeToggle } from "@/ds";
import type { IconName } from "@/ds";
import { useMe, useSettings } from "@/api/hooks/reads";
import { profileStrings } from "./strings";
import { SettingsForm } from "./SettingsForm";
import { ConnectedBanks } from "./ConnectedBanks";
/** The Миний (profile) tab hub: account summary, hide-amounts toggle,
* entries into Categories/Subscriptions (their own routes) and Settings
* (rendered inline, no dedicated route), the read-only connected-banks list,
* and logout. Ports `ProfileView.swift`'s layout minus bank connect/disconnect
* (mobile-only per the Task 13 brief) and the not-yet-wired static rows
* (Шинэ мэдээ / Түгээмэл асуултууд / Санал хүсэлт / Үйлчилгээний нөхцөл). */
export function ProfileView() {
const router = useRouter();
const { data: me } = useMe();
const { data: settings } = useSettings();
const [showSettings, setShowSettings] = React.useState(false);
const [loggingOut, setLoggingOut] = React.useState(false);
const emailLocal = me?.email ? me.email.split("@")[0] : "";
const displayName = settings?.holderName || emailLocal || me?.email || "";
const initial = (displayName || me?.email || "?").trim().charAt(0).toUpperCase() || "?";
async function handleLogout() {
setLoggingOut(true);
try {
await fetch("/api/auth/logout", { method: "POST", credentials: "same-origin" });
} finally {
router.push("/login");
}
}
if (showSettings) {
return <SettingsForm onBack={() => setShowSettings(false)} />;
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<h1 style={{ fontSize: 18, fontWeight: 600, margin: 0 }}>{profileStrings.header.title}</h1>
<HideAmountsToggle label={profileStrings.display.hideAmounts} />
</div>
<Card style={{ display: "flex", alignItems: "center", gap: 14 }}>
<span
aria-hidden
style={{
width: 56,
height: 56,
flexShrink: 0,
borderRadius: 28,
display: "grid",
placeItems: "center",
fontSize: 22,
fontWeight: 800,
color: "var(--mercury-on-brand)",
background: "var(--mercury-brand-yellow)",
}}
>
{initial}
</span>
<div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
<span
style={{
fontSize: 16,
fontWeight: 700,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{displayName}
</span>
{me?.email && (
<span
style={{
fontSize: 13,
color: "var(--seed-color-fg-muted, #6b7280)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{me.email}
</span>
)}
</div>
</Card>
<Card style={{ display: "flex", flexDirection: "column" }}>
<nav style={{ display: "flex", flexDirection: "column" }}>
<MenuRow icon="wrench" label={profileStrings.menu.settings} onClick={() => setShowSettings(true)} />
<MenuLink icon="list" label={profileStrings.menu.categories} href="/profile/categories" />
<MenuLink icon="sparkles" label={profileStrings.menu.subscriptions} href="/profile/subscriptions" />
</nav>
</Card>
<Card style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<span style={{ fontSize: 13, fontWeight: 600, color: "var(--seed-color-fg-muted, #6b7280)" }}>
{profileStrings.display.themeMode}
</span>
<ThemeToggle
labels={{
system: profileStrings.display.themeSystem,
light: profileStrings.display.themeLight,
dark: profileStrings.display.themeDark,
}}
/>
</Card>
<Card>
<ConnectedBanks />
</Card>
<MercuryButton variant="secondary" onClick={handleLogout} loading={loggingOut}>
{profileStrings.logout}
</MercuryButton>
</div>
);
}
const menuRowStyle: React.CSSProperties = {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
width: "100%",
padding: "10px 0",
background: "none",
border: "none",
cursor: "pointer",
font: "inherit",
color: "inherit",
textAlign: "left",
textDecoration: "none",
};
function MenuRowContent({ icon, label }: { icon: IconName; label: string }) {
return (
<>
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
<IconChip icon={icon} tint="var(--seed-color-bg-neutral-subtle, #eef0f2)" fg="var(--seed-color-fg-neutral)" />
<span style={{ fontSize: 15, fontWeight: 600 }}>{label}</span>
</div>
<Icon name="chevron-right" size={18} style={{ color: "var(--seed-color-fg-placeholder, #9ca3af)" }} />
</>
);
}
function MenuRow({ icon, label, onClick }: { icon: IconName; label: string; onClick: () => void }) {
return (
<button type="button" onClick={onClick} style={menuRowStyle}>
<MenuRowContent icon={icon} label={label} />
</button>
);
}
function MenuLink({ icon, label, href }: { icon: IconName; label: string; href: string }) {
return (
<Link href={href} style={menuRowStyle}>
<MenuRowContent icon={icon} label={label} />
</Link>
);
}