spomeni/src/app/onboarding/page.tsx
2026-06-20 19:44:36 +02:00

226 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}