feat(web): rework profile + auth visuals to reference bar

Elevate the Profile (Миний) tab and login/register/forgot screens to match
TransactionList's IconChip + Card row pattern:

- Profile: avatar medallion (initial + brand tint) + name/email header,
  IconChip menu rows (Тохиргоо/Ангилал/Захиалга) with chevrons, IconChip +
  EmptyState connected-banks list.
- Categories/Subscriptions: IconChip rows via categoryStyle, EmptyState for
  empty lists, chevron-left back buttons.
- Auth: shared AuthHeader with a tinted monogram + wordmark lockup, more
  comfortable field/CTA spacing in AuthForm.

Visual only — no changes to hooks, mutations, or auth actions.
This commit is contained in:
Munkherdene 2026-08-22 22:18:18 +08:00
parent e0c07be232
commit 059b7e13d5
10 changed files with 336 additions and 277 deletions

View file

@ -1,45 +1,15 @@
"use client";
import { useRouter } from "next/navigation";
import { AuthForm } from "@/features/auth/AuthForm";
import { authStrings } from "@/features/auth/strings";
import { AuthHeader } from "@/features/auth/AuthHeader";
// Ports ios/Mercury/Features/Auth/ForgotPasswordView.swift: back chevron,
// MERCURY wordmark, explanatory subtitle (rendered by AuthForm), the email
// field, and the "Сэргээх холбоос авах" CTA.
export default function ForgotPasswordPage() {
const router = useRouter();
return (
<>
<button
type="button"
onClick={() => router.back()}
aria-label="Буцах"
style={{
background: "none",
border: "none",
alignSelf: "flex-start",
marginTop: 24,
fontSize: 20,
cursor: "pointer",
}}
>
</button>
<div style={{ height: 72 }} />
<h1
style={{
textAlign: "center",
fontWeight: 700,
letterSpacing: 1,
fontSize: 24,
}}
>
{authStrings.wordmark}
</h1>
<AuthHeader />
<div style={{ flex: 1, minHeight: 40 }} />

View file

@ -2,7 +2,7 @@
import { useRouter } from "next/navigation";
import { AuthForm } from "@/features/auth/AuthForm";
import { authStrings } from "@/features/auth/strings";
import { AuthHeader } from "@/features/auth/AuthHeader";
// Ports ios/Mercury/Features/Auth/LoginView.swift: back chevron, MERCURY
// wordmark, then the login form (email + password + forgot link + CTAs).
@ -11,34 +11,7 @@ export default function LoginPage() {
return (
<>
<button
type="button"
onClick={() => router.back()}
aria-label="Буцах"
style={{
background: "none",
border: "none",
alignSelf: "flex-start",
marginTop: 24,
fontSize: 20,
cursor: "pointer",
}}
>
</button>
<div style={{ height: 72 }} />
<h1
style={{
textAlign: "center",
fontWeight: 700,
letterSpacing: 1,
fontSize: 24,
}}
>
{authStrings.wordmark}
</h1>
<AuthHeader />
<div style={{ flex: 1, minHeight: 40 }} />

View file

@ -2,7 +2,7 @@
import { useRouter } from "next/navigation";
import { AuthForm } from "@/features/auth/AuthForm";
import { authStrings } from "@/features/auth/strings";
import { AuthHeader } from "@/features/auth/AuthHeader";
// Ports ios/Mercury/Features/Auth/RegisterView.swift: back chevron, MERCURY
// wordmark, then the registration form (email + password + confirm + CTAs).
@ -11,34 +11,7 @@ export default function RegisterPage() {
return (
<>
<button
type="button"
onClick={() => router.back()}
aria-label="Буцах"
style={{
background: "none",
border: "none",
alignSelf: "flex-start",
marginTop: 24,
fontSize: 20,
cursor: "pointer",
}}
>
</button>
<div style={{ height: 72 }} />
<h1
style={{
textAlign: "center",
fontWeight: 700,
letterSpacing: 1,
fontSize: 24,
}}
>
{authStrings.wordmark}
</h1>
<AuthHeader />
<div style={{ flex: 1, minHeight: 40 }} />

View file

@ -137,8 +137,8 @@ export function AuthForm({
if (step === "twoFactor") {
return (
<form onSubmit={handleCodeSubmit} className="flex flex-col gap-3">
<p className="text-center text-sm" style={{ color: "var(--mercury-subtle, #6b7280)" }}>
<form onSubmit={handleCodeSubmit} className="flex flex-col gap-5">
<p className="text-center text-sm" style={{ color: "var(--mercury-subtle, #6b7280)", margin: 0 }}>
{authStrings.twoFactor.subtitle}
</p>
@ -162,84 +162,88 @@ export function AuthForm({
}
return (
<form onSubmit={handleFormSubmit} className="flex flex-col gap-3">
<form onSubmit={handleFormSubmit} className="flex flex-col gap-5">
{mode === "forgot" && (
<p className="text-center text-sm" style={{ color: "var(--mercury-subtle, #6b7280)" }}>
<p className="text-center text-sm" style={{ color: "var(--mercury-subtle, #6b7280)", margin: 0 }}>
{authStrings.forgot.subtitle}
</p>
)}
<TextFieldRoot value={email} onValueChange={setEmail} name="email">
<TextFieldInput
type="email"
autoComplete="email"
placeholder={authStrings.login.email}
aria-label={authStrings.login.email}
/>
</TextFieldRoot>
{mode !== "forgot" && (
<TextFieldRoot value={password} onValueChange={setPassword} name="password">
<div className="flex flex-col gap-3">
<TextFieldRoot value={email} onValueChange={setEmail} name="email">
<TextFieldInput
type="password"
autoComplete={mode === "register" ? "new-password" : "current-password"}
placeholder={authStrings.login.password}
aria-label={authStrings.login.password}
type="email"
autoComplete="email"
placeholder={authStrings.login.email}
aria-label={authStrings.login.email}
/>
</TextFieldRoot>
)}
{mode === "register" && (
<TextFieldRoot
value={confirmPassword}
onValueChange={setConfirmPassword}
name="confirmPassword"
>
<TextFieldInput
type="password"
autoComplete="new-password"
placeholder={authStrings.register.confirmPassword}
aria-label={authStrings.register.confirmPassword}
/>
</TextFieldRoot>
)}
{mode !== "forgot" && (
<TextFieldRoot value={password} onValueChange={setPassword} name="password">
<TextFieldInput
type="password"
autoComplete={mode === "register" ? "new-password" : "current-password"}
placeholder={authStrings.login.password}
aria-label={authStrings.login.password}
/>
</TextFieldRoot>
)}
{mode === "login" && (
<button
type="button"
onClick={onNavigateForgot}
className="self-end text-sm"
style={{ color: "var(--mercury-subtle, #6b7280)", background: "none", border: "none" }}
>
{authStrings.login.forgot}
</button>
)}
{mode === "register" && (
<TextFieldRoot
value={confirmPassword}
onValueChange={setConfirmPassword}
name="confirmPassword"
>
<TextFieldInput
type="password"
autoComplete="new-password"
placeholder={authStrings.register.confirmPassword}
aria-label={authStrings.register.confirmPassword}
/>
</TextFieldRoot>
)}
{mode === "login" && (
<button
type="button"
onClick={onNavigateForgot}
className="self-end text-sm"
style={{ color: "var(--mercury-subtle, #6b7280)", background: "none", border: "none", padding: 0 }}
>
{authStrings.login.forgot}
</button>
)}
</div>
<FormError message={errorMessage} />
{infoMessage && (
<p className="text-sm" style={{ color: "var(--mercury-subtle, #6b7280)" }}>
<p className="text-sm" style={{ color: "var(--mercury-subtle, #6b7280)", margin: 0 }}>
{infoMessage}
</p>
)}
<MercuryButton type="submit" variant="primary" loading={isSubmitting}>
{mode === "login"
? authStrings.login.submit
: mode === "register"
? authStrings.register.submit
: authStrings.forgot.submit}
</MercuryButton>
<div className="flex flex-col gap-3" style={{ marginTop: 4 }}>
<MercuryButton type="submit" variant="primary" loading={isSubmitting}>
{mode === "login"
? authStrings.login.submit
: mode === "register"
? authStrings.register.submit
: authStrings.forgot.submit}
</MercuryButton>
{mode === "login" && (
<MercuryButton type="button" variant="ghost" onClick={onNavigateRegister}>
{authStrings.login.registerLink}
</MercuryButton>
)}
{mode === "register" && (
<MercuryButton type="button" variant="ghost" onClick={onNavigateLogin}>
{authStrings.register.loginLink}
</MercuryButton>
)}
{mode === "login" && (
<MercuryButton type="button" variant="ghost" onClick={onNavigateRegister}>
{authStrings.login.registerLink}
</MercuryButton>
)}
{mode === "register" && (
<MercuryButton type="button" variant="ghost" onClick={onNavigateLogin}>
{authStrings.register.loginLink}
</MercuryButton>
)}
</div>
</form>
);
}
@ -247,7 +251,7 @@ export function AuthForm({
function FormError({ message }: { message?: string }) {
if (!message) return null;
return (
<p role="alert" className="text-sm" style={{ color: "var(--seed-color-fg-critical)" }}>
<p role="alert" className="text-sm" style={{ color: "var(--seed-color-fg-critical)", margin: 0 }}>
{message}
</p>
);

View file

@ -0,0 +1,71 @@
"use client";
import { useRouter } from "next/navigation";
import { Icon } from "@/ds";
import { authStrings } from "./strings";
/**
* Shared header for the three auth screens (login / register / forgot): a
* back chevron pinned top-left, then a MERCURY wordmark lockup a tinted
* monogram badge stacked above the bold, letter-spaced wordmark centered
* below it. Purely presentational; navigation still goes through
* `router.back()`, unchanged from the markup this replaces in each page.
*/
export function AuthHeader() {
const router = useRouter();
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<button
type="button"
onClick={() => router.back()}
aria-label="Буцах"
style={{
alignSelf: "flex-start",
marginTop: 24,
display: "grid",
placeItems: "center",
width: 36,
height: 36,
borderRadius: 12,
background: "none",
border: "none",
cursor: "pointer",
color: "var(--seed-color-fg-neutral)",
}}
>
<Icon name="chevron-left" size={22} />
</button>
<div style={{ marginTop: 48, display: "flex", flexDirection: "column", alignItems: "center", gap: 14 }}>
<span
aria-hidden
style={{
width: 52,
height: 52,
borderRadius: 16,
display: "grid",
placeItems: "center",
fontSize: 22,
fontWeight: 800,
color: "var(--mercury-on-brand)",
background: "var(--mercury-brand-yellow)",
}}
>
M
</span>
<h1
style={{
textAlign: "center",
fontWeight: 800,
letterSpacing: 3,
fontSize: 24,
margin: 0,
}}
>
{authStrings.wordmark}
</h1>
</div>
</div>
);
}

View file

@ -15,6 +15,8 @@ import {
Skeleton,
} from "@seed-design/react";
import { MercuryButton } from "@/ds/MercuryButton";
import { EmptyState, Icon, IconChip } from "@/ds";
import { categoryStyle } from "@/ds/categoryStyle";
import { useCategories } from "@/api/hooks/reads";
import { useCategoryMutations } from "@/api/hooks/mutations";
import type { Category } from "@/api/schemas";
@ -105,9 +107,9 @@ export function CategoriesManager({ onBack }: CategoriesManagerProps) {
type="button"
onClick={onBack}
aria-label={profileStrings.categories.back}
style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer" }}
style={{ display: "grid", placeItems: "center", background: "none", border: "none", cursor: "pointer", color: "var(--seed-color-fg-neutral)" }}
>
<Icon name="chevron-left" size={22} />
</button>
)}
<h1 style={{ fontSize: 16, fontWeight: 600, margin: 0, flex: 1 }}>{profileStrings.categories.title}</h1>
@ -124,7 +126,7 @@ export function CategoriesManager({ onBack }: CategoriesManagerProps) {
)}
{!isLoading && groups.length === 0 && (
<p style={{ fontSize: 14, color: "var(--seed-color-fg-muted, #6b7280)" }}>{profileStrings.categories.empty}</p>
<EmptyState icon="list" title={profileStrings.categories.empty} />
)}
{groups.map((group) => (
@ -132,6 +134,7 @@ export function CategoriesManager({ onBack }: CategoriesManagerProps) {
<CategoryRow
icon={group.icon}
name={group.name}
kind="expense"
heading
onEdit={() => setSheet({ mode: "edit", category: { name: group.name, kind: "expense", depth: 0, icon: group.icon } })}
onDelete={() => setDeleteTarget(group.name)}
@ -204,20 +207,23 @@ export function CategoriesManager({ onBack }: CategoriesManagerProps) {
function CategoryRow({
icon,
name,
kind,
heading,
onEdit,
onDelete,
}: {
icon?: string | null;
name: string;
kind?: string;
heading?: boolean;
onEdit: () => void;
onDelete: () => void;
}) {
const style = categoryStyle(name, kind === "income");
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span aria-hidden style={{ fontSize: heading ? 20 : 16 }}>{icon || "🏷️"}</span>
<span style={{ fontSize: heading ? 16 : 14, fontWeight: heading ? 600 : 400, flex: 1 }}>{name}</span>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<IconChip icon={style.icon} tint={style.tint} fg={style.fg} size={heading ? 40 : 32} />
<span style={{ fontSize: heading ? 16 : 14, fontWeight: heading ? 700 : 500, flex: 1 }}>{style.name}</span>
<button
type="button"
onClick={onEdit}
@ -247,24 +253,26 @@ function CategoryChip({
onEdit: () => void;
onDelete: () => void;
}) {
const style = categoryStyle(category.name, category.kind === "income");
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: 6,
padding: "6px 10px",
padding: "6px 10px 6px 6px",
borderRadius: 999,
background: "var(--seed-color-bg-layer-default)",
background: style.tint,
color: style.fg,
}}
>
<span aria-hidden>{category.icon || "🏷️"}</span>
<span style={{ fontSize: 13 }}>{category.name}</span>
<IconChip icon={style.icon} tint="transparent" fg={style.fg} size={22} />
<span style={{ fontSize: 13, fontWeight: 600 }}>{style.name}</span>
<button
type="button"
onClick={onEdit}
aria-label={`${profileStrings.categories.editAction}: ${category.name}`}
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)" }}
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 12, color: "inherit", opacity: 0.7 }}
>
</button>
@ -315,9 +323,9 @@ function CategoryEditor({
type="button"
onClick={onCancel}
aria-label={profileStrings.categories.cancel}
style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer" }}
style={{ display: "grid", placeItems: "center", background: "none", border: "none", cursor: "pointer", color: "var(--seed-color-fg-neutral)" }}
>
<Icon name="chevron-left" size={22} />
</button>
<h1 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>
{editing ? profileStrings.categories.edit : profileStrings.categories.add}

View file

@ -2,6 +2,7 @@
import { useConnections } from "@/api/hooks/reads";
import { Skeleton } from "@seed-design/react";
import { EmptyState, IconChip, SectionHeader } from "@/ds";
import { profileStrings } from "./strings";
/**
@ -11,68 +12,79 @@ import { profileStrings } from "./strings";
*/
export function ConnectedBanks() {
const { data: connections, isLoading } = useConnections();
const empty = !isLoading && (!connections || connections.length === 0);
return (
<section aria-labelledby="connected-banks-heading">
<h2
id="connected-banks-heading"
style={{ fontSize: 12, color: "var(--seed-color-fg-subtle, #6b7280)", margin: "0 0 12px" }}
>
{profileStrings.banks.title}
</h2>
<div id="connected-banks-heading">
<SectionHeader title={profileStrings.banks.title} />
</div>
{isLoading && (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 12 }}>
<Skeleton height="44px" />
<Skeleton height="44px" />
</div>
)}
{!isLoading && (!connections || connections.length === 0) && (
<p style={{ fontSize: 14, color: "var(--seed-color-fg-muted, #6b7280)" }}>
{profileStrings.banks.empty}
</p>
{empty && (
<EmptyState icon="bank" title={profileStrings.banks.empty} hint={profileStrings.banks.manageNote} compact />
)}
{!isLoading && connections && connections.length > 0 && (
<ul style={{ listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 4 }}>
{connections.map((c) => (
<li
key={c.bank}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "10px 0",
}}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<span style={{ fontSize: 14, fontWeight: 600 }}>{c.bank}</span>
<span style={{ fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)" }}>
{c.username}
</span>
</div>
{c.courierManaged && (
<span
style={{
fontSize: 11,
padding: "4px 8px",
borderRadius: 999,
background: "var(--seed-color-bg-layer-default)",
color: "var(--seed-color-fg-muted, #6b7280)",
}}
>
{profileStrings.banks.courierManaged}
</span>
)}
</li>
))}
</ul>
)}
<>
<ul style={{ listStyle: "none", margin: "8px 0 0", padding: 0, display: "flex", flexDirection: "column" }}>
{connections.map((c) => (
<li
key={c.bank}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
padding: "10px 0",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
<IconChip icon="bank" tint="var(--seed-color-bg-neutral-subtle, #eef0f2)" fg="var(--seed-color-fg-neutral)" />
<div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
<span
style={{
fontSize: 15,
fontWeight: 700,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{c.bank}
</span>
<span style={{ fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)" }}>{c.username}</span>
</div>
</div>
{c.courierManaged && (
<span
style={{
fontSize: 11,
padding: "4px 8px",
borderRadius: 999,
flexShrink: 0,
background: "var(--seed-color-bg-layer-default)",
color: "var(--seed-color-fg-muted, #6b7280)",
}}
>
{profileStrings.banks.courierManaged}
</span>
)}
</li>
))}
</ul>
<p style={{ fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)", marginTop: 8 }}>
{profileStrings.banks.manageNote}
</p>
<p style={{ fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)", marginTop: 8 }}>
{profileStrings.banks.manageNote}
</p>
</>
)}
</section>
);
}

View file

@ -3,7 +3,8 @@
import * as React from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Card, HideAmountsToggle, MercuryButton } from "@/ds";
import { Card, HideAmountsToggle, Icon, IconChip, MercuryButton } from "@/ds";
import type { IconName } from "@/ds";
import { useMe, useSettings } from "@/api/hooks/reads";
import { profileStrings } from "./strings";
import { SettingsForm } from "./SettingsForm";
@ -24,6 +25,7 @@ export function ProfileView() {
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);
@ -45,23 +47,57 @@ export function ProfileView() {
<HideAmountsToggle label={profileStrings.display.hideAmounts} />
</div>
<Card>
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<span style={{ fontSize: 14, fontWeight: 600 }}>{displayName}</span>
<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: 12, color: "var(--seed-color-fg-muted, #6b7280)" }}>
{profileStrings.account.handlePrefix}
{emailLocal}
<span
style={{
fontSize: 13,
color: "var(--seed-color-fg-muted, #6b7280)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{me.email}
</span>
)}
</div>
</Card>
<Card>
<nav style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<MenuRow label={profileStrings.menu.settings} onClick={() => setShowSettings(true)} />
<MenuLink label={profileStrings.menu.categories} href="/profile/categories" />
<MenuLink label={profileStrings.menu.subscriptions} href="/profile/subscriptions" />
<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>
@ -76,49 +112,46 @@ export function ProfileView() {
);
}
function MenuRow({ label, onClick }: { label: string; onClick: () => void }) {
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 (
<button
type="button"
onClick={onClick}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
background: "none",
border: "none",
cursor: "pointer",
font: "inherit",
color: "inherit",
textAlign: "left",
}}
>
<span style={{ fontSize: 14 }}>{label}</span>
<span aria-hidden style={{ color: "var(--seed-color-fg-placeholder, #9ca3af)" }}>
</span>
<>
<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({ label, href }: { label: string; href: string }) {
function MenuLink({ icon, label, href }: { icon: IconName; label: string; href: string }) {
return (
<Link
href={href}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 0",
color: "inherit",
textDecoration: "none",
}}
>
<span style={{ fontSize: 14 }}>{label}</span>
<span aria-hidden style={{ color: "var(--seed-color-fg-placeholder, #9ca3af)" }}>
</span>
<Link href={href} style={menuRowStyle}>
<MenuRowContent icon={icon} label={label} />
</Link>
);
}

View file

@ -12,6 +12,7 @@ import {
Skeleton,
} from "@seed-design/react";
import { MercuryButton } from "@/ds/MercuryButton";
import { Icon } from "@/ds";
import { useSettings } from "@/api/hooks/reads";
import { useSaveSettings } from "@/api/hooks/mutations";
import type { Settings } from "@/api/schemas";
@ -126,9 +127,9 @@ export function SettingsForm({ onBack }: SettingsFormProps) {
type="button"
onClick={onBack}
aria-label={profileStrings.settings.back}
style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer" }}
style={{ display: "grid", placeItems: "center", background: "none", border: "none", cursor: "pointer", color: "var(--seed-color-fg-neutral)" }}
>
<Icon name="chevron-left" size={22} />
</button>
)}
<h1 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>{profileStrings.settings.title}</h1>

View file

@ -3,6 +3,7 @@
import * as React from "react";
import { TextFieldRoot, TextFieldInput, Skeleton } from "@seed-design/react";
import { MercuryButton } from "@/ds/MercuryButton";
import { EmptyState, Icon, IconChip } from "@/ds";
import { tugrik } from "@/ds/money";
import { useSubscriptions } from "@/api/hooks/reads";
import { useSubscriptionMutations } from "@/api/hooks/mutations";
@ -58,9 +59,9 @@ export function SubscriptionsView({ onBack }: SubscriptionsViewProps) {
type="button"
onClick={onBack}
aria-label={profileStrings.subscriptions.back}
style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer" }}
style={{ display: "grid", placeItems: "center", background: "none", border: "none", cursor: "pointer", color: "var(--seed-color-fg-neutral)" }}
>
<Icon name="chevron-left" size={22} />
</button>
)}
<h1 style={{ fontSize: 16, fontWeight: 600, margin: 0, flex: 1 }}>{profileStrings.subscriptions.title}</h1>
@ -77,7 +78,7 @@ export function SubscriptionsView({ onBack }: SubscriptionsViewProps) {
)}
{!isLoading && all.length === 0 && (
<p style={{ fontSize: 14, color: "var(--seed-color-fg-muted, #6b7280)" }}>{profileStrings.subscriptions.empty}</p>
<EmptyState icon="sparkles" title={profileStrings.subscriptions.empty} />
)}
{!isLoading && detected.length > 0 && (
@ -136,13 +137,26 @@ function SubscriptionRow({
destructive?: boolean;
}) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 0" }}>
<div style={{ display: "flex", flexDirection: "column" }}>
<span style={{ fontSize: 14, fontWeight: 600 }}>{sub.label}</span>
<span style={{ fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)" }}>
{tugrik(sub.monthly)} / {sub.cadence}
{sub.nextDue ? ` · ${sub.nextDue}` : ""}
</span>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: "8px 0" }}>
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
<IconChip icon="sparkles" tint="var(--seed-color-bg-neutral-subtle, #eef0f2)" fg="var(--seed-color-fg-neutral)" size={36} />
<div style={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
<span
style={{
fontSize: 14,
fontWeight: 700,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{sub.label}
</span>
<span style={{ fontSize: 12, color: "var(--seed-color-fg-muted, #6b7280)" }}>
{tugrik(sub.monthly)} / {sub.cadence}
{sub.nextDue ? ` · ${sub.nextDue}` : ""}
</span>
</div>
</div>
<button
type="button"
@ -204,9 +218,9 @@ function ManualSubscriptionForm({
type="button"
onClick={onCancel}
aria-label={profileStrings.subscriptions.cancel}
style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer" }}
style={{ display: "grid", placeItems: "center", background: "none", border: "none", cursor: "pointer", color: "var(--seed-color-fg-neutral)" }}
>
<Icon name="chevron-left" size={22} />
</button>
<h1 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>{profileStrings.subscriptions.addManual}</h1>
</div>