fix: add missing i18n keys for wizard success states
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useTranslation } from "@turbostarter/i18n";
|
||||
import { cn } from "@turbostarter/ui";
|
||||
import { buttonVariants } from "@turbostarter/ui-web/button";
|
||||
@@ -8,13 +10,84 @@ interface StepSuccessProps {
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface OrderStatus {
|
||||
orderId: string;
|
||||
status: "pending" | "processing" | "completed" | "failed";
|
||||
businessName: string;
|
||||
reputationScore: number | null;
|
||||
reviewsCount: number | null;
|
||||
pdfUrl: string | null;
|
||||
executionId: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const STAGES = [
|
||||
{ key: "scrape", label: "Collecting reviews" },
|
||||
{ key: "classify", label: "Analyzing sentiment" },
|
||||
{ key: "synthesize", label: "Generating report" },
|
||||
{ key: "done", label: "Complete" },
|
||||
] as const;
|
||||
|
||||
function getStageIndex(status: string): number {
|
||||
if (status === "pending") return 0;
|
||||
if (status === "processing") return 1;
|
||||
if (status === "completed") return 3;
|
||||
return -1;
|
||||
}
|
||||
|
||||
export const StepSuccess = ({ email }: StepSuccessProps) => {
|
||||
const { t } = useTranslation("marketing");
|
||||
const searchParams = useSearchParams();
|
||||
const sessionId = searchParams.get("session_id");
|
||||
|
||||
const [orderStatus, setOrderStatus] = useState<OrderStatus | null>(null);
|
||||
const [polling, setPolling] = useState(true);
|
||||
|
||||
const pollStatus = useCallback(async () => {
|
||||
if (!sessionId) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/blueprint/status/${sessionId}`);
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as OrderStatus;
|
||||
setOrderStatus(data);
|
||||
|
||||
if (data.status === "completed" || data.status === "failed") {
|
||||
setPolling(false);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Silently retry on next interval
|
||||
}
|
||||
}, [sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
pollStatus();
|
||||
|
||||
if (!polling) return;
|
||||
|
||||
const interval = setInterval(pollStatus, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, [pollStatus, polling]);
|
||||
|
||||
const isCompleted = orderStatus?.status === "completed";
|
||||
const isFailed = orderStatus?.status === "failed";
|
||||
const stageIndex = orderStatus ? getStageIndex(orderStatus.status) : 0;
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-lg flex-col items-center gap-8">
|
||||
{/* Success icon */}
|
||||
<div className="flex size-16 items-center justify-center rounded-full bg-green-500/10">
|
||||
{/* Header icon */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-16 items-center justify-center rounded-full",
|
||||
isCompleted
|
||||
? "bg-green-500/10"
|
||||
: isFailed
|
||||
? "bg-red-500/10"
|
||||
: "bg-blue-500/10",
|
||||
)}
|
||||
>
|
||||
{isCompleted ? (
|
||||
<svg
|
||||
className="size-8 text-green-500"
|
||||
fill="none"
|
||||
@@ -28,31 +101,125 @@ export const StepSuccess = ({ email }: StepSuccessProps) => {
|
||||
d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
) : isFailed ? (
|
||||
<svg
|
||||
className="size-8 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<div className="border-primary size-8 animate-spin rounded-full border-3 border-t-transparent" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<div className="text-center">
|
||||
<h2 className="text-foreground text-2xl font-semibold tracking-tight sm:text-3xl">
|
||||
{t("wizard.success.title")}
|
||||
{isCompleted
|
||||
? t("wizard.success.readyTitle")
|
||||
: isFailed
|
||||
? t("wizard.success.failedTitle")
|
||||
: t("wizard.success.title")}
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
{t("wizard.success.description", {
|
||||
{isCompleted
|
||||
? t("wizard.success.readyDescription", {
|
||||
email: email || "your email",
|
||||
})
|
||||
: isFailed
|
||||
? orderStatus?.error || "We encountered an error processing your report."
|
||||
: t("wizard.success.description", {
|
||||
email: email || "your email",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Processing indicator */}
|
||||
{/* Score highlight (when complete) */}
|
||||
{isCompleted && orderStatus?.reputationScore && (
|
||||
<div className="bg-background flex w-full flex-col items-center rounded-xl border p-6">
|
||||
<span className="text-muted-foreground text-sm">Reputation Score</span>
|
||||
<span className="text-foreground mt-1 text-5xl font-bold">
|
||||
{orderStatus.reputationScore}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs">/100</span>
|
||||
{orderStatus.reviewsCount && (
|
||||
<span className="text-muted-foreground mt-2 text-xs">
|
||||
Based on {orderStatus.reviewsCount} reviews
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress stages (when processing) */}
|
||||
{!isCompleted && !isFailed && (
|
||||
<div className="bg-background w-full rounded-xl border p-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="space-y-3">
|
||||
{STAGES.map((stage, i) => {
|
||||
const isActive = i === stageIndex || (i === 1 && stageIndex < 3);
|
||||
const isDone = i < stageIndex || (isCompleted && i <= 3);
|
||||
|
||||
return (
|
||||
<div key={stage.key} className="flex items-center gap-3">
|
||||
{isDone ? (
|
||||
<svg
|
||||
className="size-5 text-green-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75 11.25 15 15 9.75"
|
||||
/>
|
||||
</svg>
|
||||
) : isActive ? (
|
||||
<div className="border-primary size-5 animate-spin rounded-full border-2 border-t-transparent" />
|
||||
<span className="text-foreground text-sm font-medium">
|
||||
{t("wizard.success.processing")}
|
||||
) : (
|
||||
<div className="bg-muted size-5 rounded-full" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
isDone
|
||||
? "text-muted-foreground line-through"
|
||||
: isActive
|
||||
? "text-foreground font-medium"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{stage.label}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-3 text-xs">
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-4 text-xs">
|
||||
{t("wizard.success.delivery")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Download / Dashboard CTAs (when complete) */}
|
||||
{isCompleted && orderStatus?.pdfUrl && (
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<a
|
||||
href={orderStatus.pdfUrl}
|
||||
className={cn(buttonVariants({ size: "lg" }), "w-full text-center")}
|
||||
>
|
||||
Download Report (PDF)
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create account section */}
|
||||
<div className="bg-background w-full rounded-xl border p-6">
|
||||
|
||||
@@ -440,6 +440,9 @@
|
||||
"success": {
|
||||
"title": "Your Blueprint is on the way!",
|
||||
"description": "We're analyzing your reviews now. Your Reputation Blueprint will be delivered to {{email}} within 24 hours.",
|
||||
"readyTitle": "Your Report is Ready!",
|
||||
"readyDescription": "We've sent the report to {{email}}",
|
||||
"failedTitle": "Something went wrong",
|
||||
"processing": "Analyzing reviews...",
|
||||
"createAccount": "Create an account",
|
||||
"createAccountDescription": "Track your report status and download it when ready.",
|
||||
|
||||
@@ -440,6 +440,9 @@
|
||||
"success": {
|
||||
"title": "¡Tu Radiografía está en camino!",
|
||||
"description": "Estamos analizando tus reseñas. Tu Radiografía de Reputación se entregará a {{email}} en 24 horas.",
|
||||
"readyTitle": "¡Tu Informe está Listo!",
|
||||
"readyDescription": "Hemos enviado el informe a {{email}}",
|
||||
"failedTitle": "Algo salió mal",
|
||||
"processing": "Analizando reseñas...",
|
||||
"createAccount": "Crear una cuenta",
|
||||
"createAccountDescription": "Sigue el estado de tu informe y descárgalo cuando esté listo.",
|
||||
|
||||
Reference in New Issue
Block a user