256 lines
9.3 KiB
TypeScript
256 lines
9.3 KiB
TypeScript
"use client";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
import { browserFetch } from "../lib/api";
|
|
|
|
type Props = {
|
|
makerId: string;
|
|
sourceType: "profile" | "service" | "work";
|
|
sourceId?: string;
|
|
makerName: string;
|
|
contextTitle: string;
|
|
returnTo: string;
|
|
};
|
|
|
|
type Draft = {
|
|
needText: string;
|
|
sizeText: string;
|
|
usageText: string;
|
|
materialPreference: string;
|
|
quantity: number;
|
|
urgency: string;
|
|
deliveryType: string;
|
|
hasModel: boolean;
|
|
referenceNotes: string;
|
|
};
|
|
|
|
const initialDraft: Draft = {
|
|
needText: "",
|
|
sizeText: "",
|
|
usageText: "",
|
|
materialPreference: "",
|
|
quantity: 1,
|
|
urgency: "medium",
|
|
deliveryType: "shipping",
|
|
hasModel: false,
|
|
referenceNotes: ""
|
|
};
|
|
|
|
export default function InquiryWizardClient({ makerId, sourceType, sourceId, makerName, contextTitle, returnTo }: Props) {
|
|
const router = useRouter();
|
|
const [step, setStep] = useState(1);
|
|
const [status, setStatus] = useState("");
|
|
const [me, setMe] = useState<{ id: string } | null>(null);
|
|
const [draft, setDraft] = useState<Draft>(initialDraft);
|
|
|
|
const storageKey = useMemo(
|
|
() => `makers3d_inquiry_draft:${makerId}:${sourceType}:${sourceId || "root"}`,
|
|
[makerId, sourceId, sourceType]
|
|
);
|
|
|
|
useEffect(() => {
|
|
const saved = window.sessionStorage.getItem(storageKey);
|
|
if (saved) {
|
|
try {
|
|
setDraft({ ...initialDraft, ...JSON.parse(saved) as Draft });
|
|
} catch {
|
|
window.sessionStorage.removeItem(storageKey);
|
|
}
|
|
}
|
|
|
|
void browserFetch<{ user: { id: string } | null }>("/auth/me")
|
|
.then((result) => setMe(result.user))
|
|
.catch(() => setMe(null));
|
|
}, [storageKey]);
|
|
|
|
useEffect(() => {
|
|
window.sessionStorage.setItem(storageKey, JSON.stringify(draft));
|
|
}, [draft, storageKey]);
|
|
|
|
function nextStep() {
|
|
setStep((current) => Math.min(5, current + 1));
|
|
}
|
|
|
|
function previousStep() {
|
|
setStep((current) => Math.max(1, current - 1));
|
|
}
|
|
|
|
async function submitInquiry() {
|
|
if (!me) {
|
|
const nextUrl = `/consultas/nueva?makerId=${encodeURIComponent(makerId)}&sourceType=${encodeURIComponent(sourceType)}&sourceId=${encodeURIComponent(sourceId || "")}&makerName=${encodeURIComponent(makerName)}&contextTitle=${encodeURIComponent(contextTitle)}`;
|
|
window.location.href = `/register?returnTo=${encodeURIComponent(nextUrl)}`;
|
|
return;
|
|
}
|
|
|
|
setStatus("Enviando consulta...");
|
|
try {
|
|
await browserFetch("/inquiries", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
makerId,
|
|
sourceType,
|
|
sourceId,
|
|
needText: `${draft.needText}\n\nUso final: ${draft.usageText}\nReferencia: ${draft.referenceNotes}`.trim(),
|
|
sizeText: draft.sizeText,
|
|
hasModel: draft.hasModel,
|
|
materialPreference: draft.materialPreference,
|
|
quantity: draft.quantity,
|
|
urgency: draft.urgency,
|
|
deliveryType: draft.deliveryType
|
|
})
|
|
});
|
|
window.sessionStorage.removeItem(storageKey);
|
|
setStatus("Consulta enviada.");
|
|
router.push("/account/inbox");
|
|
router.refresh();
|
|
} catch (error) {
|
|
setStatus(error instanceof Error ? error.message : "No se pudo enviar la consulta");
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="wizard-shell">
|
|
<div className="panel wizard-context stack">
|
|
<span className="eyebrow">Consulta guiada</span>
|
|
<h1 className="section-title">Paso {step} de 5</h1>
|
|
<div className="list-item stack">
|
|
<strong>{contextTitle}</strong>
|
|
<span className="mini-note">Maker: {makerName}</span>
|
|
<span className="mini-note">Si vuelves tras registrarte, el borrador se conserva.</span>
|
|
</div>
|
|
<div className="wizard-progress">
|
|
{[1, 2, 3, 4, 5].map((value) => (
|
|
<span key={value} className={`wizard-dot ${value <= step ? "active" : ""}`} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="panel stack">
|
|
{step === 1 && (
|
|
<>
|
|
<span className="eyebrow">Necesidad</span>
|
|
<h2 className="section-title">Que necesitas resolver?</h2>
|
|
<textarea
|
|
className="textarea"
|
|
placeholder="Quiero algo igual, modificar una pieza o fabricar algo parecido..."
|
|
value={draft.needText}
|
|
onChange={(event) => setDraft((current) => ({ ...current, needText: event.target.value }))}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<>
|
|
<span className="eyebrow">Detalles del trabajo</span>
|
|
<h2 className="section-title">Dimension, material y referencia</h2>
|
|
<div className="input-grid-compact">
|
|
<input
|
|
className="field"
|
|
placeholder="Tamano o medidas"
|
|
value={draft.sizeText}
|
|
onChange={(event) => setDraft((current) => ({ ...current, sizeText: event.target.value }))}
|
|
/>
|
|
<input
|
|
className="field"
|
|
placeholder="Material preferido"
|
|
value={draft.materialPreference}
|
|
onChange={(event) => setDraft((current) => ({ ...current, materialPreference: event.target.value }))}
|
|
/>
|
|
</div>
|
|
<label className="choice-chip">
|
|
<input
|
|
type="checkbox"
|
|
checked={draft.hasModel}
|
|
onChange={(event) => setDraft((current) => ({ ...current, hasModel: event.target.checked }))}
|
|
/>
|
|
<span>Ya tengo archivo o una pieza de referencia</span>
|
|
</label>
|
|
<textarea
|
|
className="textarea"
|
|
placeholder="Describe el archivo, la pieza o lo que puedes adjuntar despues en la conversacion."
|
|
value={draft.referenceNotes}
|
|
onChange={(event) => setDraft((current) => ({ ...current, referenceNotes: event.target.value }))}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<>
|
|
<span className="eyebrow">Uso final</span>
|
|
<h2 className="section-title">Para que se utilizara?</h2>
|
|
<textarea
|
|
className="textarea"
|
|
placeholder="Pieza funcional, exterior, calor, agua, decoracion, prototipo..."
|
|
value={draft.usageText}
|
|
onChange={(event) => setDraft((current) => ({ ...current, usageText: event.target.value }))}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{step === 4 && (
|
|
<>
|
|
<span className="eyebrow">Urgencia y entrega</span>
|
|
<h2 className="section-title">Cantidad, plazo y modalidad</h2>
|
|
<div className="input-grid-compact">
|
|
<input
|
|
className="field"
|
|
type="number"
|
|
min={1}
|
|
value={draft.quantity}
|
|
onChange={(event) => setDraft((current) => ({ ...current, quantity: Number(event.target.value) }))}
|
|
/>
|
|
<select
|
|
className="select"
|
|
value={draft.urgency}
|
|
onChange={(event) => setDraft((current) => ({ ...current, urgency: event.target.value }))}
|
|
>
|
|
<option value="low">Sin urgencia</option>
|
|
<option value="medium">Esta semana</option>
|
|
<option value="high">Urgente</option>
|
|
</select>
|
|
<select
|
|
className="select"
|
|
value={draft.deliveryType}
|
|
onChange={(event) => setDraft((current) => ({ ...current, deliveryType: event.target.value }))}
|
|
>
|
|
<option value="shipping">Necesito envio</option>
|
|
<option value="local">Puedo retirar</option>
|
|
</select>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{step === 5 && (
|
|
<>
|
|
<span className="eyebrow">Resumen</span>
|
|
<h2 className="section-title">Revisa antes de enviar</h2>
|
|
<div className="checklist-list">
|
|
<div className="checklist-item"><strong>Necesidad</strong><span>{draft.needText || "Pendiente"}</span></div>
|
|
<div className="checklist-item"><strong>Detalles</strong><span>{draft.sizeText || "Sin medidas"} / {draft.materialPreference || "Sin material"}</span></div>
|
|
<div className="checklist-item"><strong>Uso final</strong><span>{draft.usageText || "No indicado"}</span></div>
|
|
<div className="checklist-item"><strong>Entrega</strong><span>{draft.quantity} unidad / {draft.deliveryType}</span></div>
|
|
</div>
|
|
{!me ? (
|
|
<p className="muted">Para enviar la consulta necesitas crear cuenta o entrar. El borrador no se pierde.</p>
|
|
) : null}
|
|
</>
|
|
)}
|
|
|
|
<div className="wizard-actions">
|
|
<a href={returnTo} className="button button-ghost">Volver</a>
|
|
<div className="wizard-actions-right">
|
|
{step > 1 ? <button className="button button-secondary" type="button" onClick={previousStep}>Anterior</button> : null}
|
|
{step < 5 ? (
|
|
<button className="button button-primary" type="button" onClick={nextStep}>Continuar</button>
|
|
) : (
|
|
<button className="button button-primary" type="button" onClick={submitInquiry}>Enviar consulta</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{status ? <p className="muted">{status}</p> : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|