You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
8.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"use client";
import { useState } from "react";
import { FLOCK_UI } from "@/lib/flock/ui-labels";
import type { FlockDisplayLine } from "@/modules/flock/quote-storage";
import type { FlockCheckoutTier } from "@/lib/flock/flock-checkout-selection";
import { validateFlockCheckoutSelection } from "@/lib/flock/flock-checkout-selection";
import { ErrorBanner } from "@/components/ui/error-banner";
export type FlockCheckoutIntent = {
preferredTier: FlockCheckoutTier;
line: FlockDisplayLine;
};
export interface FlockQuoteResultProps {
lines: FlockDisplayLine[];
reference?: string | null;
onReset?: () => void;
/** 登录态:允许选档结账 */
checkoutEnabled?: boolean;
checkoutBusy?: boolean;
checkoutMessage?: string | null;
onCheckout?: (intent: FlockCheckoutIntent) => void;
}
function formatUsd(n: number): string {
return n.toLocaleString("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
}
/** Flock 两档对比结果卡(中文);登录态可选手选档结账 */
export function FlockQuoteResult({
lines,
reference,
onReset,
checkoutEnabled = false,
checkoutBusy = false,
checkoutMessage = null,
onCheckout,
}: FlockQuoteResultProps) {
const direct = lines.find((l) => l.tier === "flock_direct");
const standard = lines.find((l) => l.tier === "standard");
const [selectedTier, setSelectedTier] = useState<FlockCheckoutTier | null>(
null,
);
const [localError, setLocalError] = useState<string | null>(null);
const selectedLine =
selectedTier === "flock_direct"
? direct
: selectedTier === "standard"
? standard
: null;
if (!direct && !standard) {
return (
<div className="rounded-lg border border-border bg-surface p-5 text-sm text-text-secondary">
{FLOCK_UI.emptyResult}
</div>
);
}
const selectTier = (tier: FlockCheckoutTier) => {
if (!checkoutEnabled) return;
setSelectedTier(tier);
setLocalError(null);
};
return (
<div className="space-y-4">
<div className="flex flex-wrap items-end justify-between gap-2">
<div>
<h2 className="text-lg font-semibold text-text-primary">
{FLOCK_UI.resultTitle}
</h2>
{reference && (
<p className="mt-1 text-xs text-text-secondary">
{FLOCK_UI.reference} #{reference}
</p>
)}
</div>
{onReset && (
<button
type="button"
onClick={onReset}
className="rounded-md border border-border bg-surface px-3 py-1.5 text-sm text-text-primary"
>
{FLOCK_UI.buildAnother}
</button>
)}
</div>
<div className="grid gap-4 md:grid-cols-2">
{direct && (
<article
className={
"rounded-lg border p-5 text-white shadow-card " +
(checkoutEnabled
? "cursor-pointer " +
(selectedTier === "flock_direct"
? "border-emerald-400 bg-[#1f2420] ring-2 ring-emerald-400/60"
: "border-primary/30 bg-[#1f2420] opacity-90 hover:opacity-100")
: "border-primary/30 bg-[#1f2420]")
}
onClick={() => selectTier("flock_direct")}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
selectTier("flock_direct");
}
}}
role={checkoutEnabled ? "button" : undefined}
tabIndex={checkoutEnabled ? 0 : undefined}
aria-pressed={
checkoutEnabled ? selectedTier === "flock_direct" : undefined
}
>
<div className="mb-1 text-xs uppercase tracking-wide text-emerald-300">
{FLOCK_UI.directTag}
</div>
<h3 className="text-xl font-semibold">{FLOCK_UI.directName}</h3>
<p className="mt-3 text-2xl font-bold">
{FLOCK_UI.pricesFrom} {formatUsd(direct.final_total_usd)}
</p>
<p className="mt-1 text-sm text-white/80">
{FLOCK_UI.transit}:{direct.transitDescription || direct.transitDays}
</p>
<ul className="mt-4 space-y-2 text-sm text-white/90">
{FLOCK_UI.directFeatures.map((f) => (
<li key={f}>✓ {f}</li>
))}
</ul>
</article>
)}
{standard && (
<article
className={
"rounded-lg border bg-surface p-5 shadow-card " +
(checkoutEnabled
? "cursor-pointer " +
(selectedTier === "standard"
? "border-primary ring-2 ring-primary/40"
: "border-border hover:border-primary/40")
: "border-border")
}
onClick={() => selectTier("standard")}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
selectTier("standard");
}
}}
role={checkoutEnabled ? "button" : undefined}
tabIndex={checkoutEnabled ? 0 : undefined}
aria-pressed={
checkoutEnabled ? selectedTier === "standard" : undefined
}
>
<div className="mb-1 text-xs uppercase tracking-wide text-text-secondary">
{FLOCK_UI.standardTag}
</div>
<h3 className="text-xl font-semibold text-text-primary">
{FLOCK_UI.standardName}
</h3>
<p className="mt-3 text-2xl font-bold text-text-primary">
{FLOCK_UI.pricesFrom} {formatUsd(standard.final_total_usd)}
</p>
<p className="mt-1 text-sm text-text-secondary">
{FLOCK_UI.transit}:
{standard.transitDescription || standard.transitDays}
</p>
<ul className="mt-4 space-y-2 text-sm text-text-secondary">
{FLOCK_UI.standardFeatures.map((f, i) => (
<li key={f}>
{i === 0 ? "✓" : "×"} {f}
</li>
))}
</ul>
<p className="mt-3 text-xs text-text-secondary">
{FLOCK_UI.standardNote}
</p>
</article>
)}
</div>
{checkoutEnabled && (
<div className="border-t border-border pt-4">
{(localError || checkoutMessage) && (
<div className="mb-3">
<ErrorBanner>{localError || checkoutMessage}</ErrorBanner>
</div>
)}
{selectedLine && selectedTier && (
<p className="mb-2 text-center text-xs text-success">
{FLOCK_UI.checkoutSelected}:
{selectedTier === "flock_direct"
? FLOCK_UI.directName
: FLOCK_UI.standardName}{" "}
· {formatUsd(selectedLine.final_total_usd)}
</p>
)}
<button
type="button"
disabled={
!selectedTier || !selectedLine || checkoutBusy || !onCheckout
}
onClick={() => {
if (!selectedTier || !selectedLine || !onCheckout) return;
const err = validateFlockCheckoutSelection({
preferredTier: selectedTier,
});
if (err) {
setLocalError(err);
return;
}
setLocalError(null);
onCheckout({ preferredTier: selectedTier, line: selectedLine });
}}
className="flex h-12 w-full items-center justify-center rounded-md bg-success px-4 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50"
>
{checkoutBusy
? FLOCK_UI.checkoutSyncing
: selectedLine
? `${FLOCK_UI.checkoutCta} — ${formatUsd(selectedLine.final_total_usd)}`
: FLOCK_UI.checkoutCta}
</button>
{!selectedTier && (
<p className="mt-2 text-center text-[11px] text-text-disabled">
{FLOCK_UI.checkoutSelectHint}
</p>
)}
<p className="mt-2 text-center text-[11px] text-text-disabled">
{FLOCK_UI.checkoutFootnote}
</p>
</div>
)}
</div>
);
}