77 lines
3.0 KiB
TypeScript
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'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>
|
|
);
|
|
}
|