|
|
"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>
|
|
|
);
|
|
|
}
|