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 { notFound } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { headers as nextHeaders } from "next/headers"; 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 { RichText } from "@payloadcms/richtext-lexical/react";
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext"; import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
import { import {
@ -16,7 +16,8 @@ import {
} from "lucide-react"; } from "lucide-react";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item"; import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { MissionCard } from "@/components/frontend/intelligence/MissionCard"; 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 { interface FactionDetailPageProps {
params: Promise<{ id: string }>; params: Promise<{ id: string }>;
@ -118,10 +119,9 @@ export default async function FactionDetailPage({ params }: FactionDetailPagePro
}) })
.catch(() => ({ docs: [] })); .catch(() => ({ docs: [] }));
const bankAccount = (bankRes.docs[0] as BankAccount | undefined) ?? null; const bankAccount = (bankRes.docs[0] as BankAccount | undefined) ?? null;
const bankCurrency = // Currency emblem/label is shared across all accounts (from Game Rules), so
bankAccount && typeof bankAccount.currency === "object" // the bank balance renders the same emblem the banking surfaces show.
? (bankAccount.currency as Resource) const currencyConfig = await getCurrencyConfig(payload);
: null;
// Missions involving this faction // Missions involving this faction
const missionsRes = await payload const missionsRes = await payload
@ -365,7 +365,7 @@ export default async function FactionDetailPage({ params }: FactionDetailPagePro
<ItemContent> <ItemContent>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-lg font-semibold font-mono"> <span className="text-lg font-semibold font-mono">
{formatAmount(bankAccount.balance ?? 0, bankCurrency)} <CurrencyAmount value={bankAccount.balance ?? 0} currency={currencyConfig} />
</span> </span>
<span className="text-xs text-muted-foreground">{bankAccount.name}</span> <span className="text-xs text-muted-foreground">{bankAccount.name}</span>
{bankAccount.status !== "open" && ( {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 type { CurrencyDisplay } from "@/lib/banking/format";
import { CurrencySymbol } from "./CurrencySymbol"; import { CurrencySymbol } from "./CurrencySymbol";
/** /**
* Renders the currency emblem inline with the formatted amount. Delegates * Renders the currency emblem inline with the formatted amount. Delegates
* symbol rendering to <CurrencySymbol> and formatting to formatAmount. * symbol rendering to <CurrencySymbol> and formatting to formatAmount. When an
* Renders nothing when currency is null/undefined/number (no emblem to show). * 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({ export function CurrencyAmount({
value, value,
@ -14,10 +16,12 @@ export function CurrencyAmount({
readonly value: number; readonly value: number;
readonly currency?: CurrencyDisplay | null; readonly currency?: CurrencyDisplay | null;
}) { }) {
const showSymbol =
currency != null && typeof currency === "object" && isCurrencyConfig(currency) && !!currency.symbol;
return ( return (
<> <div className="flex items-center gap-1.5">
<CurrencySymbol currency={currency} /> <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} aria-hidden={ariaHidden}
loading="lazy" loading="lazy"
className={cn( 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, 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 n = amount.toLocaleString();
const label = currencyLabel(currency, amount); const label = options?.hideLabel ? "" : currencyLabel(currency, amount);
return label ? `${n} ${label}` : n; return label ? `${n} ${label}` : n;
} }