1
0
Fork 0

refactor(banking): pass currency config through banking UI

Banking overview, cards, detail, and activity feed now render amounts from the full CurrencyConfig (symbol + singular/plural) fetched from Game Rules instead of a plain currency label.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-06 01:23:57 -04:00
parent 8205c33768
commit 52ecc4f3b6
7 changed files with 92 additions and 46 deletions

View file

@ -6,6 +6,7 @@ import type { BankAccount, LedgerEntry } from "@/payload-types";
import { AccountDetail } from "@/components/frontend/banking/AccountDetail";
import { hasPermission } from "@/utils/access-control/hasPermission";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
import { getCurrencyConfig } from "@/lib/banking";
export const metadata = {
title: "Account — Polaris Task Force",
@ -35,12 +36,12 @@ export default async function AccountPage({ params }: AccountPageProps) {
notFound();
}
const typedAccount = account as unknown as BankAccount;
const typedAccount = account as unknown as BankAccount;
const isManager = user
? (await hasPermission(payload, user, "banking:manage")) ||
(await hasLogisticsQualification(payload, user).catch(() => false))
: false;
const isManager = user
? (await hasPermission(payload, user, "banking:manage")) ||
(await hasLogisticsQualification(payload, user).catch(() => false))
: false;
const ownerId =
typeof typedAccount.ownerUser === "object"
@ -70,6 +71,7 @@ export default async function AccountPage({ params }: AccountPageProps) {
const otherAccounts = (allAccountsRes.docs as unknown as BankAccount[])
.filter((a) => a.id !== typedAccount.id)
.map((a) => ({ id: a.id, name: a.name }));
const currencyConfig = await getCurrencyConfig(payload);
return (
<div className="p-5 max-w-7xl">
@ -78,6 +80,7 @@ export default async function AccountPage({ params }: AccountPageProps) {
ledger={ledger}
otherAccounts={otherAccounts}
canTransact={isManager || isOwner}
currencyConfig={currencyConfig}
/>
</div>
);

View file

@ -7,6 +7,7 @@ import { LandmarkIcon } from "lucide-react";
import { BankingOverview } from "@/components/frontend/banking/BankingOverview";
import { hasPermission } from "@/utils/access-control/hasPermission";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
import { getCurrencyConfig } from "@/lib/banking";
export const metadata = {
title: "Banking — Polaris Task Force",
@ -36,20 +37,20 @@ export default async function BankingPage() {
});
const recentTransactions = transactionsRes.docs as unknown as BankTransaction[];
const factionsRes = await payload.find({
collection: "factions",
limit: 100,
depth: 0,
});
const factions = (factionsRes.docs as unknown as Faction[]).map((f) => ({
id: f.id,
name: f.name,
}));
const factionsRes = await payload.find({
collection: "factions",
limit: 100,
depth: 0,
});
const factions = (factionsRes.docs as unknown as Faction[]).map((f) => ({
id: f.id,
name: f.name,
}));
const isManager = user
? (await hasPermission(payload, user, "banking:manage")) ||
(await hasLogisticsQualification(payload, user).catch(() => false))
: false;
const isManager = user
? (await hasPermission(payload, user, "banking:manage")) ||
(await hasLogisticsQualification(payload, user).catch(() => false))
: false;
const currentUser = user
? {
@ -65,6 +66,7 @@ export default async function BankingPage() {
accounts.length > 0 && typeof accounts[0].currency === "object"
? (accounts[0].currency as Resource)
: null;
const currencyConfig = await getCurrencyConfig(payload);
return (
<div className="p-5 flex flex-col gap-6">
@ -83,9 +85,10 @@ export default async function BankingPage() {
recentTransactions={recentTransactions}
currentUser={currentUser}
currencyResource={currencyResource}
currencyConfig={currencyConfig}
isManager={isManager}
factions={factions}
/>
</div>
);
}
}

View file

@ -11,7 +11,7 @@ import {
ItemTitle,
} from "@/components/ui/item";
import { LandmarkIcon, UsersIcon, WalletIcon } from "lucide-react";
import { formatAmount } from "@/lib/banking/format";
import { formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import { type AccountStatus, accountStatusBase, accountStatusClass } from "@/lib/banking/ui";
const accountTypeMeta = {
@ -39,7 +39,13 @@ export function accountTypeInfo(accountType: string) {
return accountTypeMeta[accountType as keyof typeof accountTypeMeta] ?? accountTypeMeta.personal;
}
export function AccountCard({ account }: { account: BankAccount }) {
export function AccountCard({
account,
currencyConfig,
}: {
account: BankAccount;
currencyConfig: CurrencyConfig;
}) {
const meta = accountTypeInfo(account.accountType);
const Icon = meta.icon;
const currency = typeof account.currency === "object" ? account.currency : null;
@ -79,7 +85,10 @@ export function AccountCard({ account }: { account: BankAccount }) {
</ItemDescription>
<div className="mt-1">
<span className="text-lg font-semibold font-mono">
{formatAmount(account.balance ?? 0, currency)}
{formatAmount(
account.balance ?? 0,
currencyConfig.resourceName ? currencyConfig : currency,
)}
</span>
</div>
</ItemContent>

View file

@ -4,7 +4,7 @@ import Link from "next/link";
import { ArrowLeftIcon } from "lucide-react";
import type { BankAccount, LedgerEntry } from "@/payload-types";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { formatAmount } from "@/lib/banking/format";
import { formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import { type AccountStatus, accountStatusBase, accountStatusClass } from "@/lib/banking/ui";
import { accountTypeInfo } from "@/components/frontend/banking/AccountCard";
import { BankTransactionDialog } from "@/components/frontend/banking/BankTransactionDialog";
@ -15,9 +15,16 @@ interface AccountDetailProps {
ledger: LedgerEntry[];
otherAccounts: { id: number; name: string }[];
canTransact: boolean;
currencyConfig: CurrencyConfig;
}
export function AccountDetail({ account, ledger, otherAccounts, canTransact }: AccountDetailProps) {
export function AccountDetail({
account,
ledger,
otherAccounts,
canTransact,
currencyConfig,
}: AccountDetailProps) {
const meta = accountTypeInfo(account.accountType);
const Icon = meta.icon;
const currency = typeof account.currency === "object" ? account.currency : null;
@ -65,7 +72,10 @@ export function AccountDetail({ account, ledger, otherAccounts, canTransact }: A
Balance
</p>
<p className="text-2xl font-semibold font-mono">
{formatAmount(account.balance ?? 0, currency)}
{formatAmount(
account.balance ?? 0,
currencyConfig.resourceName ? currencyConfig : currency,
)}
</p>
</div>
</div>

View file

@ -1,9 +1,9 @@
"use client";
import Link from "next/link";
import type { BankAccount, BankTransaction, Resource, User } from "@/payload-types";
import type { BankAccount, BankTransaction, User } from "@/payload-types";
import { ArrowDownLeftIcon, ArrowRightLeftIcon, ArrowUpRightIcon } from "lucide-react";
import { currencyLabel } from "@/lib/banking/format";
import { formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import {
formatTxTimestamp,
txAmountClass,
@ -15,7 +15,7 @@ import {
interface ActivityFeedProps {
transactions: BankTransaction[];
currencyResource: Resource | null;
currencyConfig: CurrencyConfig;
}
// Icon per type — kept local because the icons are React components used only
@ -60,9 +60,7 @@ function resolveActorLabel(actor: (number | null) | User | undefined): string {
return `#${actor}`;
}
export function ActivityFeed({ transactions, currencyResource }: ActivityFeedProps) {
const cur = currencyLabel(currencyResource);
export function ActivityFeed({ transactions, currencyConfig }: ActivityFeedProps) {
if (transactions.length === 0) {
return (
<div className="rounded-lg border border-border/60 bg-muted/20 py-10 flex items-center justify-center">
@ -87,7 +85,7 @@ export function ActivityFeed({ transactions, currencyResource }: ActivityFeedPro
const targetAccountId = to.id ?? from.id;
const href = targetAccountId != null ? `/logistics/banking/${targetAccountId}` : null;
const amount = tx.amount ?? 0;
const signed = `${txAmountPrefix(flow)}${amount.toLocaleString()}`;
const signed = `${txAmountPrefix(flow)}${formatAmount(amount, currencyConfig)}`;
const RowInner = (
<div className="flex items-start gap-3">
@ -115,7 +113,6 @@ export function ActivityFeed({ transactions, currencyResource }: ActivityFeedPro
className={`font-mono text-sm tabular-nums shrink-0 ml-auto pr-1 ${txAmountClass(flow)}`}
>
{signed}
{cur && <span className="ml-1 text-[10px] text-muted-foreground/60">{cur}</span>}
</div>
</div>
);
@ -139,4 +136,4 @@ export function ActivityFeed({ transactions, currencyResource }: ActivityFeedPro
</ul>
</div>
);
}
}

View file

@ -8,7 +8,7 @@ import { AccountCard } from "@/components/frontend/banking/AccountCard";
import { CreateAccountDialog } from "@/components/frontend/banking/CreateAccountDialog";
import { WalletHero } from "@/components/frontend/banking/WalletHero";
import { ActivityFeed } from "@/components/frontend/banking/ActivityFeed";
import { currencyLabel, formatAmount } from "@/lib/banking/format";
import { currencyLabel, formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import { usePageCommands } from "@/components/command-palette/CommandPaletteContext";
interface BankingOverviewProps {
@ -16,6 +16,7 @@ interface BankingOverviewProps {
recentTransactions: BankTransaction[];
currentUser: { id: number; username: string; displayName?: string } | null;
currencyResource: Resource | null;
currencyConfig: CurrencyConfig;
isManager: boolean;
factions: { id: number; name: string }[];
}
@ -53,6 +54,7 @@ export function BankingOverview({
recentTransactions,
currentUser,
currencyResource,
currencyConfig,
isManager,
factions,
}: BankingOverviewProps) {
@ -101,6 +103,7 @@ export function BankingOverview({
account={myAccount ?? null}
currentUser={currentUser}
currencyResource={currencyResource}
currencyConfig={currencyConfig}
/>
<Tabs defaultValue="activity" className="gap-4">
@ -123,13 +126,13 @@ export function BankingOverview({
{isManager && (
<p className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70">
UNIT FUNDS&nbsp;
{formatAmount(unitFundsTotal, currencyResource)}
{formatAmount(unitFundsTotal, currencyConfig)}
</p>
)}
</div>
<TabsContent value="activity" className="m-0">
<ActivityFeed transactions={recentTransactions} currencyResource={currencyResource} />
<ActivityFeed transactions={recentTransactions} currencyConfig={currencyConfig} />
</TabsContent>
<TabsContent value="accounts" className="m-0 pt-4 flex flex-col gap-6">
@ -161,7 +164,11 @@ export function BankingOverview({
{isManager && treasury.length > 0 && (
<Section title="Treasury" count={treasury.length}>
{treasury.map((account) => (
<AccountCard key={account.id} account={account} />
<AccountCard
key={account.id}
account={account}
currencyConfig={currencyConfig}
/>
))}
</Section>
)}
@ -169,7 +176,11 @@ export function BankingOverview({
{isManager && factionAccounts.length > 0 && (
<Section title="Factions" count={factionAccounts.length}>
{factionAccounts.map((account) => (
<AccountCard key={account.id} account={account} />
<AccountCard
key={account.id}
account={account}
currencyConfig={currencyConfig}
/>
))}
</Section>
)}
@ -180,7 +191,11 @@ export function BankingOverview({
count={visiblePersonal.length}
>
{visiblePersonal.map((account) => (
<AccountCard key={account.id} account={account} />
<AccountCard
key={account.id}
account={account}
currencyConfig={currencyConfig}
/>
))}
</Section>
)}
@ -196,8 +211,8 @@ export function BankingOverview({
)}
<p className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground/40">
SYS.BANKING / currency={tickerCurrency(currencyLabel(currencyResource))}
SYS.BANKING / currency={tickerCurrency(currencyLabel(currencyConfig))}
</p>
</div>
);
}
}

View file

@ -6,7 +6,7 @@ import Link from "next/link";
import { ChevronRightIcon, WalletIcon } from "lucide-react";
import type { BankAccount, Resource } from "@/payload-types";
import { Button } from "@/components/ui/button";
import { formatAmount } from "@/lib/banking/format";
import { formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import {
type AccountStatus,
accountStatusBase,
@ -20,12 +20,18 @@ interface WalletHeroProps {
account: BankAccount | null;
currentUser: { id: number; username: string; displayName?: string } | null;
currencyResource: Resource | null;
currencyConfig: CurrencyConfig;
}
// Shared terminal-aesthetic class for small uppercase mono labels in the hero.
const labelCls = "font-mono text-[10px] uppercase tracking-wider text-muted-foreground";
export function WalletHero({ account, currentUser, currencyResource }: WalletHeroProps) {
export function WalletHero({
account,
currentUser,
currencyResource,
currencyConfig,
}: WalletHeroProps) {
const router = useRouter();
const [initializing, setInitializing] = useState(false);
const [initError, setInitError] = useState<string | null>(null);
@ -114,7 +120,10 @@ export function WalletHero({ account, currentUser, currencyResource }: WalletHer
<div className="flex flex-col gap-1">
<p className="text-4xl md:text-5xl font-bold font-mono tracking-tight">
{formatAmount(account.balance ?? 0, currency)}
{formatAmount(
account.balance ?? 0,
currencyConfig.resourceName ? currencyConfig : currency,
)}
</p>
<p className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground/60">
LAST ACTIVITY {lastActivity}
@ -137,4 +146,4 @@ export function WalletHero({ account, currentUser, currencyResource }: WalletHer
</div>
</section>
);
}
}