226 lines
8.5 KiB
TypeScript
226 lines
8.5 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import ImageUploader from "@/components/ImageUploader";
|
||
import SubdomainPicker from "@/components/SubdomainPicker";
|
||
import TemplatePicker from "@/components/TemplatePicker";
|
||
|
||
const STEPS = ["Податоци", "Датуми", "Фотографии", "Поддомен", "Шаблон"] as const;
|
||
|
||
export default function OnboardingWizard() {
|
||
const router = useRouter();
|
||
const [step, setStep] = useState(0);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState("");
|
||
|
||
const [title, setTitle] = useState("");
|
||
const [description, setDescription] = useState("");
|
||
const [bornDate, setBornDate] = useState("");
|
||
const [passedDate, setPassedDate] = useState("");
|
||
const [images, setImages] = useState<{ key: string; order: number; url?: string }[]>([]);
|
||
const [subdomain, setSubdomain] = useState("");
|
||
const [templateId, setTemplateId] = useState(1);
|
||
|
||
const canProceed = () => {
|
||
switch (step) {
|
||
case 0: return title.trim().length > 0;
|
||
case 1: return true;
|
||
case 2: return images.length > 0;
|
||
case 3: return subdomain.length >= 3;
|
||
case 4: return templateId >= 1 && templateId <= 3;
|
||
default: return false;
|
||
}
|
||
};
|
||
|
||
const handlePublish = async () => {
|
||
setLoading(true);
|
||
setError("");
|
||
try {
|
||
const res = await fetch("/api/publish", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
title,
|
||
description,
|
||
bornDate: bornDate || undefined,
|
||
passedDate: passedDate || undefined,
|
||
subdomain,
|
||
templateId,
|
||
images: images.map(({ key, order }) => ({ key, order })),
|
||
}),
|
||
});
|
||
if (!res.ok) {
|
||
const data = await res.json();
|
||
throw new Error(data.error || "Не успеа објавувањето");
|
||
}
|
||
router.push("/dashboard");
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : "Нешто тргна наопаку");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const imagePreviews = images
|
||
.filter((img) => img.url)
|
||
.map((img) => ({ url: img.url!, order: img.order }));
|
||
|
||
return (
|
||
<div className="min-h-screen bg-stone-50">
|
||
<header className="border-b border-stone-200 bg-white">
|
||
<div className="mx-auto max-w-2xl px-6 py-4">
|
||
<h1 className="text-xl font-semibold text-stone-900">Креирај спомен страница</h1>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="mx-auto max-w-2xl px-6 py-8">
|
||
<div className="mb-8 flex gap-1">
|
||
{STEPS.map((label, i) => (
|
||
<div key={label} className="flex flex-1 items-center">
|
||
<div
|
||
className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium ${
|
||
i <= step ? "bg-primary text-white" : "bg-stone-200 text-stone-500"
|
||
}`}
|
||
>
|
||
{i + 1}
|
||
</div>
|
||
<span className={`ml-1.5 hidden text-xs sm:inline ${i <= step ? "text-stone-900" : "text-stone-400"}`}>
|
||
{label}
|
||
</span>
|
||
{i < STEPS.length - 1 && (
|
||
<div className={`mx-1 h-px flex-1 ${i < step ? "bg-primary" : "bg-stone-200"}`} />
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="mb-4 rounded-lg bg-red-50 p-3 text-sm text-red-700">{error}</div>
|
||
)}
|
||
|
||
{step === 0 && (
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label htmlFor="title" className="block text-sm font-medium text-stone-700">
|
||
Име
|
||
</label>
|
||
<input
|
||
id="title"
|
||
type="text"
|
||
value={title}
|
||
onChange={(e) => setTitle(e.target.value)}
|
||
placeholder="нпр. Марија Новаковска"
|
||
className="mt-1 block w-full rounded-lg border border-stone-200 px-3 py-2.5 text-stone-900 placeholder:text-stone-400 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
||
maxLength={100}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="description" className="block text-sm font-medium text-stone-700">
|
||
Епитафија / Животна приказна
|
||
</label>
|
||
<textarea
|
||
id="description"
|
||
value={description}
|
||
onChange={(e) => setDescription(e.target.value)}
|
||
placeholder="Неколку зборови, песма или биографија во нивна чест..."
|
||
rows={6}
|
||
className="mt-1 block w-full rounded-lg border border-stone-200 px-3 py-2.5 text-stone-900 placeholder:text-stone-400 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
||
maxLength={2000}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{step === 1 && (
|
||
<div className="space-y-4">
|
||
<p className="text-sm text-stone-500">
|
||
Овие се опционални. Можете да внесете точни датуми, приближни години, или да ги оставите празни.
|
||
</p>
|
||
<div>
|
||
<label htmlFor="bornDate" className="block text-sm font-medium text-stone-700">
|
||
Роден/а
|
||
</label>
|
||
<input
|
||
id="bornDate"
|
||
type="text"
|
||
value={bornDate}
|
||
onChange={(e) => setBornDate(e.target.value)}
|
||
placeholder="нпр. 1960 или 15 март 1960"
|
||
className="mt-1 block w-full rounded-lg border border-stone-200 px-3 py-2.5 text-stone-900 placeholder:text-stone-400 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
||
maxLength={50}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="passedDate" className="block text-sm font-medium text-stone-700">
|
||
Починат/а
|
||
</label>
|
||
<input
|
||
id="passedDate"
|
||
type="text"
|
||
value={passedDate}
|
||
onChange={(e) => setPassedDate(e.target.value)}
|
||
placeholder="нпр. 2024 или 20 ноември 2024"
|
||
className="mt-1 block w-full rounded-lg border border-stone-200 px-3 py-2.5 text-stone-900 placeholder:text-stone-400 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
||
maxLength={50}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{step === 2 && (
|
||
<ImageUploader
|
||
images={images}
|
||
onImagesChange={(newImages) => setImages(newImages as typeof images)}
|
||
/>
|
||
)}
|
||
|
||
{step === 3 && (
|
||
<SubdomainPicker value={subdomain} onChange={setSubdomain} />
|
||
)}
|
||
|
||
{step === 4 && (
|
||
<TemplatePicker
|
||
value={templateId}
|
||
onChange={setTemplateId}
|
||
title={title}
|
||
description={description}
|
||
bornDate={bornDate}
|
||
passedDate={passedDate}
|
||
images={imagePreviews}
|
||
/>
|
||
)}
|
||
|
||
<div className="mt-8 flex justify-between">
|
||
{step > 0 ? (
|
||
<button
|
||
onClick={() => setStep(step - 1)}
|
||
className="rounded-lg border border-stone-200 px-6 py-2 text-sm font-medium text-stone-700 transition-colors hover:bg-stone-50"
|
||
>
|
||
Назад
|
||
</button>
|
||
) : (
|
||
<div />
|
||
)}
|
||
{step < STEPS.length - 1 ? (
|
||
<button
|
||
onClick={() => setStep(step + 1)}
|
||
disabled={!canProceed()}
|
||
className="rounded-lg bg-primary px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-light disabled:cursor-not-allowed disabled:opacity-50"
|
||
>
|
||
Продолжи
|
||
</button>
|
||
) : (
|
||
<button
|
||
onClick={handlePublish}
|
||
disabled={!canProceed() || loading}
|
||
className="rounded-lg bg-primary px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-light disabled:cursor-not-allowed disabled:opacity-50"
|
||
>
|
||
{loading ? "Објавување..." : "Објави спомен"}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
} |