diff --git a/apps/web/src/app/[locale]/(marketing)/get-started/page.tsx b/apps/web/src/app/[locale]/(marketing)/get-started/page.tsx new file mode 100644 index 0000000..9bcf98a --- /dev/null +++ b/apps/web/src/app/[locale]/(marketing)/get-started/page.tsx @@ -0,0 +1,16 @@ +import { getTranslation } from "@turbostarter/i18n/server"; +import { withI18n } from "@turbostarter/i18n/with-i18n"; + +import { getMetadata } from "~/lib/metadata"; +import { Wizard } from "~/modules/marketing/get-started/wizard"; + +export const generateMetadata = getMetadata({ + title: "marketing:wizard.pageTitle", + description: "marketing:wizard.pageDescription", +}); + +const GetStartedPage = async () => { + return ; +}; + +export default withI18n(GetStartedPage); diff --git a/apps/web/src/app/api/blueprint/checkout/route.ts b/apps/web/src/app/api/blueprint/checkout/route.ts new file mode 100644 index 0000000..44b4a76 --- /dev/null +++ b/apps/web/src/app/api/blueprint/checkout/route.ts @@ -0,0 +1,72 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import Stripe from "stripe"; + +const BLUEPRINT_PRICE_CENTS = 4700; +const CURRENCY = "eur"; + +function getStripe(): Stripe { + const key = process.env.STRIPE_SECRET_KEY; + if (!key) { + throw new Error("STRIPE_SECRET_KEY is required"); + } + return new Stripe(key); +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { email, businessName, placeId, locale } = body as { + email: string; + businessName: string; + placeId: string; + locale?: string; + }; + + if (!email || !businessName) { + return NextResponse.json( + { error: "Email and business name are required" }, + { status: 400 }, + ); + } + + const stripe = getStripe(); + const origin = request.headers.get("origin") || ""; + const lang = locale || "en"; + + const session = await stripe.checkout.sessions.create({ + mode: "payment", + payment_method_types: ["card"], + customer_email: email, + line_items: [ + { + price_data: { + currency: CURRENCY, + product_data: { + name: "Reputation Blueprint", + description: `Reputation Blueprint for ${businessName}`, + }, + unit_amount: BLUEPRINT_PRICE_CENTS, + }, + quantity: 1, + }, + ], + metadata: { + product: "blueprint", + business_name: businessName, + place_id: placeId || "", + email, + }, + success_url: `${origin}/${lang}/get-started?step=success&session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${origin}/${lang}/get-started?step=payment`, + }); + + return NextResponse.json({ url: session.url }); + } catch (error) { + console.error("Checkout error:", error); + return NextResponse.json( + { error: "Failed to create checkout session" }, + { status: 500 }, + ); + } +} diff --git a/apps/web/src/app/api/business/search/route.ts b/apps/web/src/app/api/business/search/route.ts new file mode 100644 index 0000000..8b53d2b --- /dev/null +++ b/apps/web/src/app/api/business/search/route.ts @@ -0,0 +1,149 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +interface Business { + place_id: string; + name: string; + address: string; + rating: number; + review_count: number; + category: string; + maps_url: string; +} + +const GOOGLE_MAPS_URL_PATTERN = + /(?:google\.com\/maps|maps\.google\.com|goo\.gl\/maps|maps\.app\.goo\.gl)/i; + +function isGoogleMapsUrl(input: string): boolean { + return GOOGLE_MAPS_URL_PATTERN.test(input); +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { query } = body as { query: string }; + + if (!query || typeof query !== "string" || query.trim().length < 2) { + return NextResponse.json( + { error: "Query must be at least 2 characters" }, + { status: 400 }, + ); + } + + const trimmed = query.trim(); + + if (isGoogleMapsUrl(trimmed)) { + return handleUrlSearch(trimmed); + } + + return handleTextSearch(trimmed); + } catch { + return NextResponse.json( + { error: "Invalid request body" }, + { status: 400 }, + ); + } +} + +async function handleUrlSearch(url: string): Promise { + const engineUrl = process.env.NEXT_PUBLIC_ENGINE_URL; + + if (!engineUrl) { + return NextResponse.json( + { error: "Engine service not configured" }, + { status: 503 }, + ); + } + + try { + const response = await fetch(`${engineUrl}/api/check-reviews`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url }), + }); + + if (!response.ok) { + return NextResponse.json( + { error: "Could not find business from URL" }, + { status: 404 }, + ); + } + + const data = (await response.json()) as Record; + + const business: Business = { + place_id: (data.place_id as string) || "", + name: (data.name as string) || "Unknown Business", + address: (data.address as string) || "", + rating: (data.rating as number) || 0, + review_count: (data.review_count as number) || (data.reviews_count as number) || 0, + category: (data.category as string) || (data.type as string) || "", + maps_url: url, + }; + + return NextResponse.json({ businesses: [business] }); + } catch { + return NextResponse.json( + { error: "Failed to fetch business details" }, + { status: 502 }, + ); + } +} + +async function handleTextSearch(query: string): Promise { + const apiKey = process.env.GOOGLE_PLACES_API_KEY; + + if (!apiKey) { + return NextResponse.json( + { error: "Places search not configured" }, + { status: 503 }, + ); + } + + try { + const response = await fetch( + "https://places.googleapis.com/v1/places:searchText", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Goog-Api-Key": apiKey, + "X-Goog-FieldMask": + "places.id,places.displayName,places.formattedAddress,places.rating,places.userRatingCount,places.primaryTypeDisplayName,places.googleMapsUri", + }, + body: JSON.stringify({ + textQuery: query, + languageCode: "en", + }), + }, + ); + + if (!response.ok) { + return NextResponse.json( + { error: "Places search failed" }, + { status: 502 }, + ); + } + + const data = (await response.json()) as Record; + + const businesses: Business[] = ((data.places as any[]) || []) + .slice(0, 5) + .map((place: any) => ({ + place_id: place.id || "", + name: place.displayName?.text || "Unknown", + address: place.formattedAddress || "", + rating: place.rating || 0, + review_count: place.userRatingCount || 0, + category: place.primaryTypeDisplayName?.text || "", + maps_url: place.googleMapsUri || "", + })); + + return NextResponse.json({ businesses }); + } catch { + return NextResponse.json( + { error: "Failed to search businesses" }, + { status: 502 }, + ); + } +} diff --git a/apps/web/src/config/paths.ts b/apps/web/src/config/paths.ts index 597289c..df87aac 100644 --- a/apps/web/src/config/paths.ts +++ b/apps/web/src/config/paths.ts @@ -17,6 +17,7 @@ const DEMO_PREFIX = "/demo"; const pathsConfig = { index: "/", + getStarted: "/get-started", demo: { index: DEMO_PREFIX, report: "/report-demo", diff --git a/apps/web/src/modules/marketing/get-started/business-card.tsx b/apps/web/src/modules/marketing/get-started/business-card.tsx new file mode 100644 index 0000000..ae8f076 --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/business-card.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { cn } from "@turbostarter/ui"; + +import type { BusinessData } from "./wizard"; + +interface BusinessCardProps { + business: BusinessData; + onClick?: () => void; + selected?: boolean; + className?: string; +} + +export const BusinessCard = ({ + business, + onClick, + selected, + className, +}: BusinessCardProps) => { + return ( + + ); +}; diff --git a/apps/web/src/modules/marketing/get-started/step-confirm.tsx b/apps/web/src/modules/marketing/get-started/step-confirm.tsx new file mode 100644 index 0000000..0269ccf --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/step-confirm.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; +import { buttonVariants } from "@turbostarter/ui-web/button"; + +import { BusinessCard } from "./business-card"; + +import type { BusinessData } from "./wizard"; + +interface StepConfirmProps { + business: BusinessData; + onConfirm: () => void; + onBack: () => void; +} + +export const StepConfirm = ({ + business, + onConfirm, + onBack, +}: StepConfirmProps) => { + const { t } = useTranslation("marketing"); + + return ( +
+
+

+ {t("wizard.confirm.title")} +

+

+ {t("wizard.confirm.description")} +

+
+ + + +
+ + {t("wizard.confirm.reviews", { count: business.review_count })} + + · + + {t("wizard.confirm.rating", { rating: business.rating })} + + {business.maps_url && ( + <> + · + + {t("wizard.confirm.viewOnGoogle")} + + + )} +
+ +
+ + +
+
+ ); +}; diff --git a/apps/web/src/modules/marketing/get-started/step-email.tsx b/apps/web/src/modules/marketing/get-started/step-email.tsx new file mode 100644 index 0000000..92a15b7 --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/step-email.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { useState } from "react"; +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; +import { buttonVariants } from "@turbostarter/ui-web/button"; + +interface StepEmailProps { + initialEmail: string; + initialName: string; + onSubmit: (email: string, name: string) => void; + onBack: () => void; +} + +export const StepEmail = ({ + initialEmail, + initialName, + onSubmit, + onBack, +}: StepEmailProps) => { + const { t } = useTranslation("marketing"); + const [email, setEmail] = useState(initialEmail); + const [name, setName] = useState(initialName); + const [emailError, setEmailError] = useState(""); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setEmailError(""); + + const trimmed = email.trim(); + if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) { + setEmailError("Please enter a valid email address"); + return; + } + + onSubmit(trimmed, name.trim()); + }; + + return ( +
+
+

+ {t("wizard.email.title")} +

+

+ {t("wizard.email.description")} +

+
+ +
+
+ { + setEmail(e.target.value); + setEmailError(""); + }} + placeholder={t("wizard.email.placeholder")} + className={cn( + "border-input bg-background text-foreground placeholder:text-muted-foreground w-full rounded-xl border py-3 px-4 text-sm shadow-sm outline-none transition-colors", + "focus:border-primary focus:ring-primary/20 focus:ring-2", + emailError && "border-red-500", + )} + autoFocus + /> + {emailError && ( +

{emailError}

+ )} +
+ + setName(e.target.value)} + placeholder={t("wizard.email.namePlaceholder")} + className={cn( + "border-input bg-background text-foreground placeholder:text-muted-foreground w-full rounded-xl border py-3 px-4 text-sm shadow-sm outline-none transition-colors", + "focus:border-primary focus:ring-primary/20 focus:ring-2", + )} + /> + + + + +
+
+ ); +}; diff --git a/apps/web/src/modules/marketing/get-started/step-find-business.tsx b/apps/web/src/modules/marketing/get-started/step-find-business.tsx new file mode 100644 index 0000000..9871197 --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/step-find-business.tsx @@ -0,0 +1,166 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; + +import { BusinessCard } from "./business-card"; + +import type { BusinessData } from "./wizard"; + +interface StepFindBusinessProps { + onSelect: (business: BusinessData) => void; +} + +export const StepFindBusiness = ({ onSelect }: StepFindBusinessProps) => { + const { t } = useTranslation("marketing"); + const [query, setQuery] = useState(""); + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [searched, setSearched] = useState(false); + const debounceRef = useRef>(null); + + const search = useCallback(async (q: string) => { + if (q.trim().length < 2) { + setResults([]); + setSearched(false); + return; + } + + setLoading(true); + setError(""); + + try { + const res = await fetch("/api/business/search", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query: q.trim() }), + }); + + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as Record; + setError(data.error || t("wizard.findBusiness.error")); + setResults([]); + } else { + const data = (await res.json()) as { businesses: BusinessData[] }; + setResults(data.businesses || []); + } + } catch { + setError(t("wizard.findBusiness.error")); + setResults([]); + } finally { + setLoading(false); + setSearched(true); + } + }, [t]); + + const handleInputChange = (value: string) => { + setQuery(value); + + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + + const isUrl = /(?:google\.com\/maps|maps\.google\.com|goo\.gl\/maps|maps\.app\.goo\.gl)/i.test(value); + + if (isUrl && value.trim().length > 10) { + search(value); + return; + } + + debounceRef.current = setTimeout(() => { + if (value.trim().length >= 3) { + search(value); + } + }, 500); + }; + + useEffect(() => { + return () => { + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + }; + }, []); + + const isUrl = /(?:google\.com\/maps|maps\.google\.com|goo\.gl\/maps|maps\.app\.goo\.gl)/i.test(query); + + return ( +
+
+

+ {t("wizard.findBusiness.title")} +

+

+ {t("wizard.findBusiness.description")} +

+
+ +
+
+ + + + handleInputChange(e.target.value)} + placeholder={t("wizard.findBusiness.placeholder")} + className={cn( + "border-input bg-background text-foreground placeholder:text-muted-foreground w-full rounded-xl border py-3 pl-10 pr-4 text-sm shadow-sm outline-none transition-colors", + "focus:border-primary focus:ring-primary/20 focus:ring-2", + )} + autoFocus + /> + {isUrl && ( + + {t("wizard.findBusiness.urlDetected")} + + )} +
+
+ + {loading && ( +
+
+ + {t("wizard.findBusiness.searching")} + +
+ )} + + {error && ( +

{error}

+ )} + + {!loading && searched && results.length === 0 && !error && ( +

+ {t("wizard.findBusiness.noResults")} +

+ )} + + {results.length > 0 && ( +
+ {results.map((business) => ( + onSelect(business)} + /> + ))} +
+ )} +
+ ); +}; diff --git a/apps/web/src/modules/marketing/get-started/step-indicator.tsx b/apps/web/src/modules/marketing/get-started/step-indicator.tsx new file mode 100644 index 0000000..bc7c44b --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/step-indicator.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; + +interface StepIndicatorProps { + currentStep: number; + totalSteps: number; +} + +export const StepIndicator = ({ + currentStep, + totalSteps, +}: StepIndicatorProps) => { + const { t } = useTranslation("marketing"); + + return ( +
+

+ {t("wizard.step", { current: currentStep, total: totalSteps })} +

+
+ {Array.from({ length: totalSteps }, (_, i) => ( +
+ ))} +
+
+ ); +}; diff --git a/apps/web/src/modules/marketing/get-started/step-payment.tsx b/apps/web/src/modules/marketing/get-started/step-payment.tsx new file mode 100644 index 0000000..fb0bc3f --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/step-payment.tsx @@ -0,0 +1,163 @@ +"use client"; + +import { useState } from "react"; +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; +import { buttonVariants } from "@turbostarter/ui-web/button"; + +import type { BusinessData } from "./wizard"; + +interface StepPaymentProps { + business: BusinessData; + email: string; + onBack: () => void; +} + +export const StepPayment = ({ business, email, onBack }: StepPaymentProps) => { + const { t } = useTranslation("marketing"); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const handlePay = async () => { + setLoading(true); + setError(""); + + try { + const locale = window.location.pathname.split("/")[1] || "en"; + + const res = await fetch("/api/blueprint/checkout", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email, + businessName: business.name, + placeId: business.place_id, + locale, + }), + }); + + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as Record; + setError(data.error || "Payment failed"); + return; + } + + const data = (await res.json()) as { url: string }; + if (data.url) { + window.location.href = data.url; + } + } catch { + setError("Something went wrong. Please try again."); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

+ {t("wizard.payment.title")} +

+

+ {t("wizard.payment.description")} +

+
+ + {/* Order summary card */} +
+

+ {t("wizard.payment.orderSummary")} +

+ +
+
+

+ {t("wizard.payment.product")} +

+

+ {t("wizard.payment.for", { business: business.name })} +

+
+
+ + {t("wizard.payment.originalPrice")} + + + {t("wizard.payment.launchPrice")} + +
+
+ +
+ + {t("wizard.payment.launchBadge")} + +
+ +
+
+ + {t("wizard.payment.total")} + + + {t("wizard.payment.launchPrice")} + +
+
+
+ + {error &&

{error}

} + +
+ + +
+ + + + + {t("wizard.payment.securePayment")} + + {t("wizard.payment.guarantee")} +
+ + +
+
+ ); +}; diff --git a/apps/web/src/modules/marketing/get-started/step-success.tsx b/apps/web/src/modules/marketing/get-started/step-success.tsx new file mode 100644 index 0000000..d819014 --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/step-success.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; +import { buttonVariants } from "@turbostarter/ui-web/button"; + +interface StepSuccessProps { + email: string; +} + +export const StepSuccess = ({ email }: StepSuccessProps) => { + const { t } = useTranslation("marketing"); + + return ( +
+ {/* Success icon */} +
+ + + +
+ +
+

+ {t("wizard.success.title")} +

+

+ {t("wizard.success.description", { + email: email || "your email", + })} +

+
+ + {/* Processing indicator */} +
+
+
+ + {t("wizard.success.processing")} + +
+

+ {t("wizard.success.delivery")} +

+
+ + {/* Create account section */} +
+

+ {t("wizard.success.createAccount")} +

+

+ {t("wizard.success.createAccountDescription")} +

+ +
+ + + +
+ + +
+
+ ); +}; diff --git a/apps/web/src/modules/marketing/get-started/wizard.tsx b/apps/web/src/modules/marketing/get-started/wizard.tsx new file mode 100644 index 0000000..698511b --- /dev/null +++ b/apps/web/src/modules/marketing/get-started/wizard.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { useSearchParams, useRouter, usePathname } from "next/navigation"; +import { useTranslation } from "@turbostarter/i18n"; + +import { Section } from "~/modules/marketing/layout/section"; +import { StepIndicator } from "./step-indicator"; +import { StepFindBusiness } from "./step-find-business"; +import { StepConfirm } from "./step-confirm"; +import { StepEmail } from "./step-email"; +import { StepPayment } from "./step-payment"; +import { StepSuccess } from "./step-success"; + +export interface BusinessData { + place_id: string; + name: string; + address: string; + rating: number; + review_count: number; + category: string; + maps_url: string; +} + +interface WizardState { + business: BusinessData | null; + email: string; + name: string; +} + +const TOTAL_STEPS = 5; + +export const Wizard = () => { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + + const stepParam = searchParams.get("step"); + const sessionId = searchParams.get("session_id"); + + const getInitialStep = (): number => { + if (stepParam === "success" && sessionId) return 5; + if (stepParam === "payment") return 4; + const n = Number(stepParam); + if (n >= 1 && n <= 5) return n; + return 1; + }; + + const [step, setStep] = useState(getInitialStep); + const [wizardState, setWizardState] = useState({ + business: null, + email: "", + name: "", + }); + + const updateStep = useCallback( + (newStep: number) => { + setStep(newStep); + const params = new URLSearchParams(searchParams.toString()); + params.set("step", String(newStep)); + router.replace(`${pathname}?${params.toString()}`, { scroll: false }); + }, + [searchParams, router, pathname], + ); + + const handleBusinessSelect = (business: BusinessData) => { + setWizardState((prev) => ({ ...prev, business })); + updateStep(2); + }; + + const handleConfirm = () => { + updateStep(3); + }; + + const handleEmail = (email: string, name: string) => { + setWizardState((prev) => ({ ...prev, email, name })); + updateStep(4); + }; + + const handleBack = (toStep: number) => { + updateStep(toStep); + }; + + return ( +
+ {step < 5 && ( + + )} + + {step === 1 && } + + {step === 2 && wizardState.business && ( + handleBack(1)} + /> + )} + + {step === 3 && ( + handleBack(2)} + /> + )} + + {step === 4 && wizardState.business && ( + handleBack(3)} + /> + )} + + {step === 5 && } +
+ ); +}; diff --git a/apps/web/src/modules/marketing/home/pricing.tsx b/apps/web/src/modules/marketing/home/pricing.tsx index 1098925..13657fc 100644 --- a/apps/web/src/modules/marketing/home/pricing.tsx +++ b/apps/web/src/modules/marketing/home/pricing.tsx @@ -99,7 +99,7 @@ export const Pricing = () => { {/* CTA */} diff --git a/packages/i18n/src/translations/en/marketing.json b/packages/i18n/src/translations/en/marketing.json index 22bc8ee..0047908 100644 --- a/packages/i18n/src/translations/en/marketing.json +++ b/packages/i18n/src/translations/en/marketing.json @@ -393,5 +393,60 @@ "title": "Installing update...", "description": "The update is being installed. Please wait a moment." } + }, + "wizard": { + "pageTitle": "Get Started", + "pageDescription": "Find your business and get your Reputation Blueprint", + "step": "Step {{current}} of {{total}}", + "findBusiness": { + "title": "Find your business", + "description": "Enter your business name or paste a Google Maps URL", + "placeholder": "Business name or Google Maps URL...", + "searching": "Searching...", + "noResults": "No businesses found. Try a different name or paste a Google Maps URL.", + "error": "Something went wrong. Please try again.", + "urlDetected": "Google Maps URL detected" + }, + "confirm": { + "title": "Is this your business?", + "description": "Confirm the details below to continue", + "reviews": "{{count}} reviews", + "rating": "{{rating}} rating", + "viewOnGoogle": "View on Google Maps", + "confirmButton": "This is my business", + "wrongBusiness": "Not your business? Search again" + }, + "email": { + "title": "Where should we send your Blueprint?", + "description": "We'll deliver your Reputation Blueprint to this email within 24 hours.", + "placeholder": "your@email.com", + "namePlaceholder": "Your name (optional)", + "continueButton": "Continue to payment" + }, + "payment": { + "title": "Complete your order", + "description": "One-time payment — no subscription, no hidden fees.", + "orderSummary": "Order summary", + "product": "Reputation Blueprint", + "for": "for {{business}}", + "originalPrice": "€97", + "launchPrice": "€47", + "launchBadge": "Launch price", + "total": "Total", + "payButton": "Pay €47", + "securePayment": "Secure payment powered by Stripe", + "guarantee": "Report delivered to your email within 24h" + }, + "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.", + "processing": "Analyzing reviews...", + "createAccount": "Create an account", + "createAccountDescription": "Track your report status and download it when ready.", + "passwordPlaceholder": "Choose a password", + "registerButton": "Create account", + "skipAccount": "Continue without account", + "delivery": "Expected delivery: within 24 hours" + } } } diff --git a/packages/i18n/src/translations/es/marketing.json b/packages/i18n/src/translations/es/marketing.json index 221ff70..39d8671 100644 --- a/packages/i18n/src/translations/es/marketing.json +++ b/packages/i18n/src/translations/es/marketing.json @@ -393,5 +393,60 @@ "title": "Instalando actualización...", "description": "La actualización se está instalando. Por favor, espera un momento." } + }, + "wizard": { + "pageTitle": "Empezar", + "pageDescription": "Encuentra tu negocio y obtén tu Radiografía de Reputación", + "step": "Paso {{current}} de {{total}}", + "findBusiness": { + "title": "Encuentra tu negocio", + "description": "Escribe el nombre de tu negocio o pega un enlace de Google Maps", + "placeholder": "Nombre del negocio o URL de Google Maps...", + "searching": "Buscando...", + "noResults": "No se encontraron negocios. Prueba con otro nombre o pega un enlace de Google Maps.", + "error": "Algo salió mal. Inténtalo de nuevo.", + "urlDetected": "URL de Google Maps detectada" + }, + "confirm": { + "title": "¿Es este tu negocio?", + "description": "Confirma los detalles para continuar", + "reviews": "{{count}} reseñas", + "rating": "{{rating}} calificación", + "viewOnGoogle": "Ver en Google Maps", + "confirmButton": "Este es mi negocio", + "wrongBusiness": "¿No es tu negocio? Busca de nuevo" + }, + "email": { + "title": "¿Dónde enviamos tu Radiografía?", + "description": "Entregaremos tu Radiografía de Reputación a este email en 24 horas.", + "placeholder": "tu@correo.com", + "namePlaceholder": "Tu nombre (opcional)", + "continueButton": "Continuar al pago" + }, + "payment": { + "title": "Completa tu pedido", + "description": "Pago único — sin suscripción, sin cargos ocultos.", + "orderSummary": "Resumen del pedido", + "product": "Radiografía de Reputación", + "for": "para {{business}}", + "originalPrice": "97 €", + "launchPrice": "47 €", + "launchBadge": "Precio de lanzamiento", + "total": "Total", + "payButton": "Pagar 47 €", + "securePayment": "Pago seguro con Stripe", + "guarantee": "Informe entregado a tu email en 24h" + }, + "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.", + "processing": "Analizando reseñas...", + "createAccount": "Crear una cuenta", + "createAccountDescription": "Sigue el estado de tu informe y descárgalo cuando esté listo.", + "passwordPlaceholder": "Elige una contraseña", + "registerButton": "Crear cuenta", + "skipAccount": "Continuar sin cuenta", + "delivery": "Entrega estimada: en 24 horas" + } } }