1
0
Fork 0

refactor(market): pass currency config through market UI

Market listings, offer dialogs, negotiation panels, and history now render prices from the full CurrencyConfig (symbol + singular/plural) instead of a nullable currency label string.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-06 01:23:57 -04:00
parent 52ecc4f3b6
commit 80ec3a0386
7 changed files with 42 additions and 47 deletions

View file

@ -6,7 +6,7 @@ import { StoreIcon } from "lucide-react";
import { MarketView } from "@/components/frontend/market/MarketView";
import { hasPermission } from "@/utils/access-control/hasPermission";
import { ensureLockerStorage } from "@/lib/locker";
import { getMainCurrencyName } from "@/lib/banking";
import { getCurrencyConfig } from "@/lib/banking";
export const metadata = {
title: "Market — Polaris Task Force",
@ -76,8 +76,8 @@ export default async function MarketPage() {
return false;
});
const isManager = user ? (await hasPermission(payload, user, "logistics:manage")) : false;
const currencyLabel = await getMainCurrencyName(payload);
const isManager = user ? await hasPermission(payload, user, "logistics:manage") : false;
const currencyConfig = await getCurrencyConfig(payload);
return (
<div className="p-5 flex flex-col gap-6">
@ -97,7 +97,7 @@ export default async function MarketPage() {
negotiations={negotiations}
locker={lockerFull}
currentUserId={userId}
currencyLabel={currencyLabel}
currencyConfig={currencyConfig}
isManager={isManager}
/>
</div>

View file

@ -30,7 +30,7 @@ import { createMarketListing } from "@/app/(frontend)/logistics/market/actions";
interface CreateListingDialogProps {
locker: LockerStorage | null;
currencyLabel: string | null;
currencyLabel: string;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
@ -225,7 +225,7 @@ export function CreateListingDialog({
/>
</div>
<div className="flex flex-1 flex-col gap-1.5">
<Label>Price per unit ({currencyLabel ?? "units"})</Label>
<Label>Price per unit ({currencyLabel})</Label>
<Input
type="number"
min={0.01}
@ -254,7 +254,7 @@ export function CreateListingDialog({
</label>
{negotiable && (
<div className="flex flex-col gap-1.5">
<Label>Minimum price per unit ({currencyLabel ?? "units"})</Label>
<Label>Minimum price per unit ({currencyLabel})</Label>
<Input
type="number"
min={0.01}

View file

@ -18,7 +18,7 @@ import {
} from "@/components/ui/item";
import { getAssetTypeLabel } from "@/lib/locker";
import { getRarityLabel, isNegotiable } from "@/lib/market";
import { formatAmount } from "@/lib/banking/format";
import { formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import { PackageIcon, ShoppingCartIcon, XIcon } from "lucide-react";
import { buyMarketListing, cancelMarketListing } from "@/app/(frontend)/logistics/market/actions";
import { Countdown } from "@/components/frontend/market/Countdown";
@ -28,7 +28,7 @@ import { useNow } from "@/hooks/useNow";
interface ListingCardProps {
listing: MarketListing;
currentUserId: number;
currencyLabel: string | null;
currencyConfig: CurrencyConfig;
canManage: boolean;
}
@ -54,7 +54,7 @@ function sellerName(listing: MarketListing): string {
export function ListingCard({
listing,
currentUserId,
currencyLabel,
currencyConfig,
canManage,
}: ListingCardProps) {
const router = useRouter();
@ -141,7 +141,7 @@ export function ListingCard({
</ItemDescription>
<div className="flex items-center justify-between gap-2 w-full">
<span className="text-sm font-semibold font-mono">
{formatAmount(listing.price ?? 0, { name: currencyLabel ?? undefined })} / unit
{formatAmount(listing.price ?? 0, currencyConfig)} / unit
</span>
<span className="text-xs text-muted-foreground">
{stock > 1 && (
@ -149,7 +149,7 @@ export function ListingCard({
{selectedQty}/{stock} ·{" "}
</>
)}
total {formatAmount(selectedTotal, { name: currencyLabel ?? undefined })}
total {formatAmount(selectedTotal, currencyConfig)}
</span>
</div>
{error && <p className="text-sm text-red-400">{error}</p>}
@ -188,7 +188,7 @@ export function ListingCard({
<MakeOfferDialog
listing={listing}
currentUserId={currentUserId}
currencyLabel={currencyLabel}
currencyConfig={currencyConfig}
/>
)}
</div>

View file

@ -16,7 +16,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { formatAmount } from "@/lib/banking/format";
import { currencyLabel, formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import {
acceptMarketOffer,
cancelMarketOffer,
@ -55,10 +55,10 @@ function outcomeOf(negotiation: MarketNegotiation | null): OfferOutcome | null {
interface MakeOfferDialogProps {
listing: MarketListing;
currentUserId: number;
currencyLabel: string | null;
currencyConfig: CurrencyConfig;
}
export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeOfferDialogProps) {
export function MakeOfferDialog({ listing, currentUserId, currencyConfig }: MakeOfferDialogProps) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [amount, setAmount] = useState("");
@ -76,7 +76,8 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
const isVendor = listing.isAutoGenerated;
const stock = listing.quantity ?? 1;
const outcome = outcomeOf(negotiation);
const fmt = (n: number) => formatAmount(n, { name: currencyLabel ?? undefined });
const displayCurrency = currencyLabel(currencyConfig);
const fmt = (n: number) => formatAmount(n, currencyConfig);
const clampedQty = Math.max(1, Math.min(stock, qty));
const sinceAccept = acceptedAt != null ? nowTick - acceptedAt : 0;
@ -244,7 +245,7 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
<div className="flex flex-col gap-4">
{isVendor && (
<NpcChat listing={listing} negotiation={negotiation} currencyLabel={currencyLabel} />
<NpcChat listing={listing} negotiation={negotiation} currencyLabel={displayCurrency} />
)}
{loadingThread && !outcome && (
@ -254,7 +255,7 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
{!outcome && !loadingThread && (
<>
<div className="flex flex-col gap-1.5">
<Label>Your offer ({currencyLabel ?? "units"} per unit)</Label>
<Label>Your offer ({displayCurrency} per unit)</Label>
<Input
type="number"
min={0.01}
@ -310,7 +311,7 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
.
</p>
<div className="flex flex-col gap-1.5">
<Label>Your counter ({currencyLabel ?? "units"} per unit)</Label>
<Label>Your counter ({displayCurrency} per unit)</Label>
<Input
type="number"
min={0.01}

View file

@ -17,6 +17,7 @@ import { CreateListingDialog } from "@/components/frontend/market/CreateListingD
import { NegotiationPanel } from "@/components/frontend/market/NegotiationPanel";
import { PlusIcon, SearchIcon } from "lucide-react";
import { usePageCommands } from "@/components/command-palette/CommandPaletteContext";
import { currencyLabel, type CurrencyConfig } from "@/lib/banking/format";
const ASSET_TYPE_OPTIONS: { value: string; label: string }[] = [
{ value: "weapon", label: "Weapon" },
@ -61,7 +62,7 @@ interface MarketViewProps {
negotiations: MarketNegotiation[];
locker: LockerStorage | null;
currentUserId: number;
currencyLabel: string | null;
currencyConfig: CurrencyConfig;
isManager: boolean;
}
@ -71,13 +72,14 @@ export function MarketView({
negotiations,
locker,
currentUserId,
currencyLabel,
currencyConfig,
isManager,
}: MarketViewProps) {
const [search, setSearch] = useState("");
const [rarity, setRarity] = useState("all");
const [assetType, setAssetType] = useState("all");
const [createListingOpen, setCreateListingOpen] = useState(false);
const currencyName = currencyLabel(currencyConfig);
usePageCommands([
{
@ -153,7 +155,7 @@ export function MarketView({
</div>
<CreateListingDialog
locker={locker}
currencyLabel={currencyLabel}
currencyLabel={currencyName}
open={createListingOpen}
onOpenChange={setCreateListingOpen}
/>
@ -179,7 +181,7 @@ export function MarketView({
key={listing.id}
listing={listing}
currentUserId={currentUserId}
currencyLabel={currencyLabel}
currencyConfig={currencyConfig}
canManage={isManager}
/>
))}
@ -190,7 +192,7 @@ export function MarketView({
<NegotiationPanel
negotiations={negotiations}
currentUserId={currentUserId}
currencyLabel={currencyLabel}
currencyConfig={currencyConfig}
/>
{myListings.length > 0 && (
@ -207,7 +209,7 @@ export function MarketView({
key={listing.id}
listing={listing}
currentUserId={currentUserId}
currencyLabel={currencyLabel}
currencyConfig={currencyConfig}
canManage={isManager}
/>
))}

View file

@ -14,7 +14,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { formatAmount } from "@/lib/banking/format";
import { currencyLabel, formatAmount, type CurrencyConfig } from "@/lib/banking/format";
import { buildChatBubbles } from "@/lib/market/chatBubbles";
import { ChatBubbleList } from "@/components/frontend/market/ChatBubbleList";
import { ChevronDownIcon, HistoryIcon } from "lucide-react";
@ -71,15 +71,15 @@ function counterpartyLabel(negotiation: MarketNegotiation, role: "buyer" | "sell
function HistoryRow({
negotiation,
role,
currencyLabel,
currencyConfig,
}: {
negotiation: MarketNegotiation;
role: "buyer" | "seller";
currencyLabel: string | null;
currencyConfig: CurrencyConfig;
}) {
const [expanded, setExpanded] = useState(false);
const listing = listingOf(negotiation);
const fmt = (n: number) => formatAmount(n, { name: currencyLabel ?? undefined });
const fmt = (n: number) => formatAmount(n, currencyConfig);
const buyerLabel = role === "buyer" ? "You" : counterpartyLabel(negotiation, "buyer");
const sellerLabel = role === "seller" ? "You" : counterpartyLabel(negotiation, "seller");
@ -112,7 +112,7 @@ function HistoryRow({
{listing && (
<CollapsibleContent className="border-t">
<ChatBubbleList
bubbles={buildChatBubbles(negotiation, listing, currencyLabel)}
bubbles={buildChatBubbles(negotiation, listing, currencyLabel(currencyConfig))}
buyerLabel={buyerLabel}
sellerLabel={sellerLabel}
maxHeight="max-h-64"
@ -126,13 +126,13 @@ function HistoryRow({
interface NegotiationHistoryDialogProps {
negotiations: MarketNegotiation[];
currentUserId: number;
currencyLabel: string | null;
currencyConfig: CurrencyConfig;
}
export function NegotiationHistoryDialog({
negotiations,
currentUserId,
currencyLabel,
currencyConfig,
}: NegotiationHistoryDialogProps) {
const [open, setOpen] = useState(false);
@ -170,7 +170,7 @@ export function NegotiationHistoryDialog({
key={negotiation.id}
negotiation={negotiation}
role={role}
currencyLabel={currencyLabel}
currencyConfig={currencyConfig}
/>
);
})

View file

@ -1,13 +1,5 @@
export type NpcDialogueKind =
| "firm"
| "lowball"
| "backward"
| "stall"
| "concede"
| "good"
| "overpay"
| "closed"
| "accept";
"firm" | "lowball" | "backward" | "stall" | "concede" | "good" | "overpay" | "closed" | "accept";
type DialogueLine = (amount: string, asking: string) => string;
@ -18,8 +10,8 @@ const LINES: Record<NpcDialogueKind, DialogueLine[]> = {
(amount) => `I'm holding at ${amount}. My prices are already fair.`,
],
lowball: [
(amount) => `That's an insult! I'll come down to ${amount} — not a unit less.`,
(amount) => `You're joking, right? ${amount} and not a unit less.`,
(amount) => `That's an insult! I'll come down to ${amount} — not a fraction less.`,
(amount) => `You're joking, right? ${amount} and not a fraction less.`,
(amount) => `I paid more than that for lunch! ${amount} — final offer.`,
],
backward: [
@ -28,8 +20,8 @@ const LINES: Record<NpcDialogueKind, DialogueLine[]> = {
],
stall: [
() => "Really? That's hardly different. Negotiate like you mean it.",
() => "A whole dollar more? That's not negotiating, that's teasing. Come back with a real number.",
() => "You're just wearing me down with pennies now. Bring something meaningful.",
() => "That tiny increase isn't negotiating. Come back with a real number.",
() => "You're just inching up now. Bring something meaningful.",
],
concede: [
(amount) => `Hmm, not sure about that... how about ${amount}?`,