140 lines
No EOL
5.4 KiB
TypeScript
140 lines
No EOL
5.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
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 {
|
|
type AccountStatus,
|
|
accountStatusBase,
|
|
accountStatusClass,
|
|
formatLastActivity,
|
|
} from "@/lib/banking/ui";
|
|
import { BankTransactionDialog } from "@/components/frontend/banking/BankTransactionDialog";
|
|
import { ensureMyAccount } from "@/app/(frontend)/logistics/banking/actions";
|
|
|
|
interface WalletHeroProps {
|
|
account: BankAccount | null;
|
|
currentUser: { id: number; username: string; displayName?: string } | null;
|
|
currencyResource: Resource | null;
|
|
}
|
|
|
|
// 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) {
|
|
const router = useRouter();
|
|
const [initializing, setInitializing] = useState(false);
|
|
const [initError, setInitError] = useState<string | null>(null);
|
|
|
|
async function handleInitialize() {
|
|
setInitializing(true);
|
|
setInitError(null);
|
|
const res = await ensureMyAccount();
|
|
if (res.success) {
|
|
router.push(`/logistics/banking/${res.data}`);
|
|
router.refresh();
|
|
} else {
|
|
setInitError(res.error ?? "Failed to initialize wallet.");
|
|
setInitializing(false);
|
|
}
|
|
}
|
|
|
|
// Empty state — terminal-flavored "WALLET NOT INITIALIZED"
|
|
if (!account || !currentUser) {
|
|
return (
|
|
<section className="rounded-lg border border-blue-500/25 bg-blue-500/[0.03] p-5">
|
|
<div className="flex flex-col gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<WalletIcon className="size-4 text-blue-400/80" />
|
|
<span className={labelCls}><OPERATOR WALLET></span>
|
|
</div>
|
|
<p className="font-mono text-sm text-muted-foreground/80">{"> WALLET NOT INITIALIZED"}</p>
|
|
<p className="font-mono text-xs text-muted-foreground/60">
|
|
Run initialization to provision your personal account.
|
|
</p>
|
|
{initError && (
|
|
<p className="font-mono text-xs text-red-400">
|
|
{"> ERROR: "}
|
|
{initError}
|
|
</p>
|
|
)}
|
|
<div>
|
|
<Button
|
|
size="sm"
|
|
onClick={handleInitialize}
|
|
disabled={initializing}
|
|
className="font-mono text-xs uppercase tracking-wider"
|
|
>
|
|
{initializing ? "> INITIALIZING…" : "> INITIALIZE"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const accountId = account.id;
|
|
const lastActivity = formatLastActivity(account.updatedAt);
|
|
const currency = typeof account.currency === "object" ? account.currency : currencyResource;
|
|
const operatorLabel = currentUser.displayName ?? currentUser.username;
|
|
const statusKey = (account.status ?? "open") as AccountStatus;
|
|
|
|
// otherAccounts requires names for the transfer destination select.
|
|
// We only know this wallet here; the BankTransactionDialog receives an empty
|
|
// list — transfer mode will prompt the user to use the account detail page.
|
|
// Deposits/withdrawals work standalone on the hero.
|
|
const otherAccounts: { id: number; name: string }[] = [];
|
|
|
|
return (
|
|
<section className="rounded-lg border border-blue-500/25 bg-blue-500/[0.03] p-5">
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center gap-2">
|
|
<WalletIcon className="size-4 text-blue-400/80" />
|
|
<span className={labelCls}><OPERATOR WALLET></span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-mono text-sm text-muted-foreground/90">@{operatorLabel}</span>
|
|
<span
|
|
className={`${accountStatusBase} ${accountStatusClass[statusKey] ?? accountStatusClass.open}`}
|
|
>
|
|
{statusKey}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<span className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground/50">
|
|
ID #{accountId.toString().padStart(6, "0")}
|
|
</span>
|
|
</div>
|
|
|
|
<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)}
|
|
</p>
|
|
<p className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground/60">
|
|
LAST ACTIVITY {lastActivity}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2 pt-3 border-t border-blue-500/15">
|
|
<BankTransactionDialog accountId={accountId} otherAccounts={otherAccounts} />
|
|
<Link href={`/logistics/banking/${accountId}`} prefetch={false}>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="font-mono text-xs uppercase tracking-wider"
|
|
>
|
|
{"> LEDGER"}
|
|
<ChevronRightIcon className="size-3.5" />
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
} |