fix: add missing i18n keys for wizard success states

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-03-01 21:45:34 +00:00
parent 3eb38b75f9
commit c642218bf8
3 changed files with 202 additions and 29 deletions

View File

@@ -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">

View File

@@ -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.",

View File

@@ -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.",