61 lines
2.6 KiB
TypeScript
61 lines
2.6 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import { formatCredits, formatMillionKm } from "../format";
|
|
import { errorMessage, useGameAction, useLoadedPlayer } from "../state/actions";
|
|
|
|
const LANDING_LABELS = { vacuum: "Vacuum", atmospheric: "Atmospheric", hardened: "Hardened" };
|
|
|
|
/** Ship models for sale; new ships appear at Earth (Step 9.8). */
|
|
export function Market() {
|
|
const state = useLoadedPlayer();
|
|
const buy = useGameAction("buyShip");
|
|
|
|
return (
|
|
<section className="space-y-3 p-4">
|
|
<h1 className="text-xl font-semibold">Market</h1>
|
|
<p className="text-sm text-slate-400">New ships are delivered to Earth with no pilot.</p>
|
|
{buy.isError && (
|
|
<p role="alert" className="text-sm text-red-400">
|
|
{errorMessage(buy.error)}
|
|
</p>
|
|
)}
|
|
{buy.isSuccess && (
|
|
<p role="status" className="text-sm text-emerald-400">
|
|
{buy.data.ship.name} is waiting at Earth.
|
|
</p>
|
|
)}
|
|
<ul className="space-y-2">
|
|
{content.purchasableShipModels.map((model) => {
|
|
const affordable = state.company.credits >= model.price;
|
|
return (
|
|
<li key={model.id} className="rounded-lg border border-slate-800 p-3 text-sm">
|
|
<div className="flex items-baseline justify-between gap-2">
|
|
<span className="font-medium">{model.name}</span>
|
|
<span className="font-semibold tabular-nums">{formatCredits(model.price)}</span>
|
|
</div>
|
|
<dl className="mt-2 grid grid-cols-2 gap-x-3 gap-y-0.5 text-slate-300">
|
|
<dt className="text-slate-400">Speed</dt>
|
|
<dd>{formatMillionKm(model.cruiseSpeedKmPerGameHour / 1e6)}/h</dd>
|
|
<dt className="text-slate-400">Capacity</dt>
|
|
<dd>{model.cargoCapacityTonnes} t</dd>
|
|
<dt className="text-slate-400">Range</dt>
|
|
<dd>{formatMillionKm(model.fuelRangeMillionKm)}</dd>
|
|
<dt className="text-slate-400">Landing</dt>
|
|
<dd>{LANDING_LABELS[model.landingClass]}</dd>
|
|
</dl>
|
|
<button
|
|
type="button"
|
|
aria-label={`Buy ${model.name}`}
|
|
disabled={!affordable || buy.isPending}
|
|
onClick={() => buy.mutate({ modelId: model.id })}
|
|
className="mt-2 w-full rounded-md bg-amber-500 py-1.5 font-semibold text-slate-950 disabled:opacity-40"
|
|
>
|
|
{affordable ? "Buy" : "Not enough credits"}
|
|
</button>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</section>
|
|
);
|
|
}
|