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:
parent
8205c33768
commit
52ecc4f3b6
7 changed files with 92 additions and 46 deletions
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue