1
0
Fork 0

feat(banking): inline currency emblem display with optional label hiding

This commit is contained in:
Jason Fraley 2026-09-22 14:52:44 -04:00
parent ae4ed8402d
commit 213a8c106f
4 changed files with 24 additions and 16 deletions

View file

@ -3,7 +3,7 @@ import { getPayload } from "payload";
import { notFound } from "next/navigation";
import Link from "next/link";
import { headers as nextHeaders } from "next/headers";
import type { BankAccount, Campaign, Faction, Media, Mission, Resource } from "@/payload-types";
import type { BankAccount, Campaign, Faction, Media, Mission } from "@/payload-types";
import { RichText } from "@payloadcms/richtext-lexical/react";
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
import {
@ -16,7 +16,8 @@ import {
} from "lucide-react";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { MissionCard } from "@/components/frontend/intelligence/MissionCard";
import { formatAmount } from "@/lib/banking/format";
import { getCurrencyConfig } from "@/lib/banking";
import { CurrencyAmount } from "@/components/frontend/banking/CurrencyAmount";
interface FactionDetailPageProps {
params: Promise<{ id: string }>;
@ -118,10 +119,9 @@ export default async function FactionDetailPage({ params }: FactionDetailPagePro
})
.catch(() => ({ docs: [] }));
const bankAccount = (bankRes.docs[0] as BankAccount | undefined) ?? null;
const bankCurrency =
bankAccount && typeof bankAccount.currency === "object"
? (bankAccount.currency as Resource)
: null;
// Currency emblem/label is shared across all accounts (from Game Rules), so
// the bank balance renders the same emblem the banking surfaces show.
const currencyConfig = await getCurrencyConfig(payload);
// Missions involving this faction
const missionsRes = await payload
@ -365,7 +365,7 @@ export default async function FactionDetailPage({ params }: FactionDetailPagePro
<ItemContent>
<div className="flex flex-col gap-1">
<span className="text-lg font-semibold font-mono">
{formatAmount(bankAccount.balance ?? 0, bankCurrency)}
<CurrencyAmount value={bankAccount.balance ?? 0} currency={currencyConfig} />
</span>
<span className="text-xs text-muted-foreground">{bankAccount.name}</span>
{bankAccount.status !== "open" && (

View file

@ -1,11 +1,13 @@
import { formatAmount } from "@/lib/banking/format";
import { formatAmount, isCurrencyConfig } from "@/lib/banking/format";
import type { CurrencyDisplay } from "@/lib/banking/format";
import { CurrencySymbol } from "./CurrencySymbol";
/**
* Renders the currency emblem inline with the formatted amount. Delegates
* symbol rendering to <CurrencySymbol> and formatting to formatAmount.
* Renders nothing when currency is null/undefined/number (no emblem to show).
* symbol rendering to <CurrencySymbol> and formatting to formatAmount. When an
* emblem is configured the unit label (e.g. "credits") is dropped so the icon
* sits beside the number without a trailing word; renders nothing when no
* emblem is configured.
*/
export function CurrencyAmount({
value,
@ -14,10 +16,12 @@ export function CurrencyAmount({
readonly value: number;
readonly currency?: CurrencyDisplay | null;
}) {
const showSymbol =
currency != null && typeof currency === "object" && isCurrencyConfig(currency) && !!currency.symbol;
return (
<>
<div className="flex items-center gap-1.5">
<CurrencySymbol currency={currency} />
{formatAmount(value, currency)}
</>
<span>{formatAmount(value, currency, showSymbol ? { hideLabel: true } : undefined)}</span>
</div>
);
}

View file

@ -35,7 +35,7 @@ export function CurrencySymbol({
aria-hidden={ariaHidden}
loading="lazy"
className={cn(
"inline-block h-[0.85em] align-text-bottom max-h-[1.15em] object-contain",
"inline-block h-[0.85em] align-middle max-h-[1.15em] object-contain",
className,
)}
/>

View file

@ -44,9 +44,13 @@ export function currencyLabel(currency?: CurrencyDisplay, amount = 2): string {
);
}
export function formatAmount(amount: number, currency?: CurrencyDisplay): string {
export function formatAmount(
amount: number,
currency?: CurrencyDisplay,
options?: { readonly hideLabel?: boolean },
): string {
const n = amount.toLocaleString();
const label = currencyLabel(currency, amount);
const label = options?.hideLabel ? "" : currencyLabel(currency, amount);
return label ? `${n} ${label}` : n;
}