Modulo makers3d desarrollado con codex V 0.0.1
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user