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:
parent
52ecc4f3b6
commit
80ec3a0386
7 changed files with 42 additions and 47 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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}?`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue