deadhead-space-freight-tycoon/apps/web/src/components/LoanCard.tsx

77 lines
3.0 KiB
TypeScript

import { content } from "@deadhead/content";
import { useState } from "react";
import { formatCredits } from "../format";
import { errorMessage, useGameAction, useLoadedPlayer } from "../state/actions";
import { Button, Meter, Panel } from "../ui";
/** Why a pay-down amount cannot be sent, or null if it can. */
export function payDownProblem(amount: number, credits: number, balance: number): string | null {
if (!Number.isInteger(amount) || amount <= 0) return "Enter a whole number of credits.";
if (amount > balance) return `You only owe ${formatCredits(balance)}.`;
if (amount > credits) return `You only have ${formatCredits(credits)}.`;
return null;
}
/** The deal's loan on HQ: balance, paid-off progress and paying it down (Step 10A.14). */
export function LoanCard() {
const state = useLoadedPlayer();
const repay = useGameAction("repayLoan");
const [input, setInput] = useState("");
const { credits, loanBalance } = state.company;
const total = content.config.dealLoanAmount;
const paidOff = Math.max(0, Math.min(100, Math.floor(((total - loanBalance) / total) * 100)));
const amount = Number(input);
const problem = input === "" ? null : payDownProblem(amount, credits, loanBalance);
return (
<Panel>
<div className="flex items-baseline justify-between gap-2">
<h2 className="font-display text-lg font-bold">Loan from Halvorsen</h2>
<span
aria-label="Loan balance"
className="font-display font-semibold text-amber-200 tabular-nums"
>
{formatCredits(loanBalance)} left
</span>
</div>
<p className="mt-1 text-xs text-ink-400">
{Math.round(content.config.loanRepaymentShare * 100)}% of every payout goes to the lender
until it&apos;s paid off.
</p>
<Meter label="Loan paid off" value={paidOff} tone="emerald" className="mt-3" />
<p className="mt-1 text-right font-display text-xs text-emerald-300/80">
{paidOff}% paid off
</p>
<form
className="mt-2 flex items-center gap-2"
onSubmit={(e) => {
e.preventDefault();
if (input === "" || problem) return;
repay.mutate({ amount }, { onSuccess: () => setInput("") });
}}
>
<input
type="number"
inputMode="numeric"
min={1}
aria-label="Pay down amount"
placeholder="Amount"
value={input}
onChange={(e) => setInput(e.target.value)}
className="min-h-11 min-w-0 flex-1 rounded-lg border border-space-600 bg-space-950/70 px-3 font-display tabular-nums placeholder:text-ink-500 focus:border-amber-400 focus:outline-none"
/>
<Button type="submit" disabled={input === "" || problem !== null || repay.isPending}>
Pay down
</Button>
</form>
{problem && <p className="mt-1 text-xs text-rose-300">{problem}</p>}
{repay.isError && (
<p role="alert" className="mt-1 text-xs text-rose-300">
{errorMessage(repay.error)}
</p>
)}
</Panel>
);
}