feat(banking): inline currency emblem display with optional label hiding
This commit is contained in:
parent
ae4ed8402d
commit
213a8c106f
4 changed files with 24 additions and 16 deletions
|
|
@ -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" && (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue