diff --git a/src/components/public/quote/QuoteMultistep.tsx b/src/components/public/quote/QuoteMultistep.tsx
new file mode 100644
index 0000000..ec61b9a
--- /dev/null
+++ b/src/components/public/quote/QuoteMultistep.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { useState } from "react";
+import type { PublicQuoteView } from "@/lib/quote-service";
+import { QuoteStep1Overview } from "./QuoteStep1Overview";
+import { QuoteStep2Selection } from "./QuoteStep2Selection";
+import { QuoteStep3Summary } from "./QuoteStep3Summary";
+
+interface QuoteMultistepProps {
+ quote: PublicQuoteView;
+}
+
+export function QuoteMultistep({ quote }: QuoteMultistepProps) {
+ const [currentStep, setCurrentStep] = useState(1);
+
+ const handleNextStep = () => {
+ if (currentStep < 3) {
+ setCurrentStep(currentStep + 1);
+ }
+ };
+
+ const handlePrevStep = () => {
+ if (currentStep > 1) {
+ setCurrentStep(currentStep - 1);
+ }
+ };
+
+ // Step progress indicator
+ const stepIndicator = (
+
+ {[1, 2, 3].map((step) => (
+
+
+ {step}
+
+ {step < 3 && (
+
+ )}
+
+ ))}
+
+ );
+
+ return (
+
+ {stepIndicator}
+
+ {currentStep === 1 && (
+
+ )}
+
+ {currentStep === 2 && (
+
+ )}
+
+ {currentStep === 3 && (
+
+ )}
+
+ );
+}
diff --git a/src/components/public/quote/QuoteStep1Overview.tsx b/src/components/public/quote/QuoteStep1Overview.tsx
new file mode 100644
index 0000000..6860ff1
--- /dev/null
+++ b/src/components/public/quote/QuoteStep1Overview.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import type { PublicQuoteView } from "@/lib/quote-service";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { ArrowRight } from "lucide-react";
+
+interface QuoteStep1OverviewProps {
+ quote: PublicQuoteView;
+ onNext: () => void;
+}
+
+export function QuoteStep1Overview({ quote, onNext }: QuoteStep1OverviewProps) {
+ // Format total as currency
+ const totalInCents = Math.round(parseFloat(quote.accepted_total) * 100);
+ const totalFormatted = (totalInCents / 100).toLocaleString("it-IT", {
+ style: "currency",
+ currency: "EUR",
+ });
+
+ return (
+
+ {/* Offer Name */}
+
+
{quote.offerName}
+
+
+ {/* Total Price (Large & Prominent) */}
+
+
Investimento totale
+
{totalFormatted}
+
+ Tutto ciò che serve per il tuo progetto di personal branding
+
+
+
+ {/* Phase Summary */}
+
+
+ Cosa è incluso
+
+
+ {quote.phaseSummary.length > 0 ? (
+ quote.phaseSummary.map((phase) => (
+
+
+
+
+ {phase.serviceCount} servizio{phase.serviceCount !== 1 ? "i" : ""}
+
+
+
+ ))
+ ) : (
+
+ Nessuna fase configurata per questo preventivo
+
+ )}
+
+
+
+ {/* CTA */}
+
+
+ {/* Info text */}
+
+ Passo 1 di 3 • Panoramica preventivo
+
+
+ );
+}
diff --git a/src/components/public/quote/QuoteStep2Selection.tsx b/src/components/public/quote/QuoteStep2Selection.tsx
new file mode 100644
index 0000000..998ce7e
--- /dev/null
+++ b/src/components/public/quote/QuoteStep2Selection.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import type { PublicQuoteView } from "@/lib/quote-service";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { ArrowLeft, ArrowRight, CheckCircle } from "lucide-react";
+
+interface QuoteStep2SelectionProps {
+ quote: PublicQuoteView;
+ onNext: () => void;
+ onPrev: () => void;
+}
+
+export function QuoteStep2Selection({ quote, onNext, onPrev }: QuoteStep2SelectionProps) {
+ // Format total as currency
+ const totalInCents = Math.round(parseFloat(quote.accepted_total) * 100);
+ const totalFormatted = (totalInCents / 100).toLocaleString("it-IT", {
+ style: "currency",
+ currency: "EUR",
+ });
+
+ return (
+
+ {/* Phase Listing */}
+
+
Fasi incluse nel preventivo
+
+ {quote.phaseSummary.length > 0 ? (
+
+ {quote.phaseSummary.map((phase) => (
+
+
+
+
+
{phase.title}
+
+ {phase.serviceCount} servizio{phase.serviceCount !== 1 ? "i" : ""}
+
+
+
+
+ ))}
+
+ ) : (
+
+ Nessuna fase configurata per questo preventivo
+
+ )}
+
+
+ {/* Divider */}
+
+
+ {/* Total Summary */}
+
+
+ Totale preventivo:
+ {totalFormatted}
+
+
+ Questo totale è immutabile e comprende tutti i servizi sopra elencati
+
+
+
+ {/* Info */}
+
+
+ Nota: Nel passo successivo potrai confermare l'accettazione fornendo
+ il tuo indirizzo email e eventuali note.
+
+
+
+ {/* Navigation */}
+
+
+
+
+
+ {/* Info text */}
+
+ Passo 2 di 3 • Dettagli fasi
+
+
+ );
+}
diff --git a/src/components/public/quote/QuoteStep3Summary.tsx b/src/components/public/quote/QuoteStep3Summary.tsx
new file mode 100644
index 0000000..b8453b3
--- /dev/null
+++ b/src/components/public/quote/QuoteStep3Summary.tsx
@@ -0,0 +1,201 @@
+"use client";
+
+import { useState } from "react";
+import type { PublicQuoteView } from "@/lib/quote-service";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { ArrowLeft, CheckCircle, X } from "lucide-react";
+import { acceptQuote, rejectQuote } from "@/app/quote/[token]/actions";
+
+interface QuoteStep3SummaryProps {
+ quote: PublicQuoteView;
+ onPrev: () => void;
+}
+
+export function QuoteStep3Summary({ quote, onPrev }: QuoteStep3SummaryProps) {
+ const [email, setEmail] = useState("");
+ const [notes, setNotes] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [successMessage, setSuccessMessage] = useState(null);
+ const [errorMessage, setErrorMessage] = useState(null);
+
+ // Format total as currency
+ const totalInCents = Math.round(parseFloat(quote.accepted_total) * 100);
+ const totalFormatted = (totalInCents / 100).toLocaleString("it-IT", {
+ style: "currency",
+ currency: "EUR",
+ });
+
+ const handleAccept = async () => {
+ setIsSubmitting(true);
+ setErrorMessage(null);
+ try {
+ const result = await acceptQuote(quote.token, email || undefined, notes || undefined);
+ if (result.success) {
+ setSuccessMessage(result.message || "Preventivo accettato con successo!");
+ } else {
+ setErrorMessage(result.error || "Errore sconosciuto");
+ }
+ } catch (error) {
+ setErrorMessage("Errore durante l'invio");
+ console.error(error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const handleReject = async () => {
+ if (!window.confirm("Sei sicuro di rifiutare questo preventivo?")) return;
+
+ setIsSubmitting(true);
+ setErrorMessage(null);
+ try {
+ const result = await rejectQuote(quote.token, notes || undefined);
+ if (result.success) {
+ setSuccessMessage("Preventivo rifiutato");
+ } else {
+ setErrorMessage(result.error || "Errore sconosciuto");
+ }
+ } catch (error) {
+ setErrorMessage("Errore durante l'invio");
+ console.error(error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ // Success state
+ if (successMessage) {
+ return (
+
+
+
+
Perfetto!
+
{successMessage}
+
+
+
Cosa succede ora?
+
+ - ✓ Abbiamo registrato l'accettazione del preventivo
+ - ✓ Ti contatteremo entro 24 ore per concordare i prossimi passi
+ - ✓ Inizieremo la pianificazione del tuo progetto di personal branding
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Summary Card */}
+
+ Riepilogo
+
+
+ Offerta:
+ {quote.offerName}
+
+
+ Investimento totale:
+ {totalFormatted}
+
+
+ Fasi incluse:
+ {quote.phaseSummary.length}
+
+
+
+
+ {/* Form Fields */}
+
+
+
+
setEmail(e.target.value)}
+ disabled={isSubmitting}
+ className="h-10"
+ />
+
+ Useremo questa email per contattarti riguardo al tuo preventivo
+
+
+
+
+
+
+
+
+ {/* Error message */}
+ {errorMessage && (
+
+ )}
+
+ {/* CTA Buttons */}
+
+
+
+
+
+
+
+
+
+ {/* Info text */}
+
+ Passo 3 di 3 • Conferma e accettazione
+
+
+ );
+}