201 lines
6.9 KiB
TypeScript
201 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { useEffect, useState } from "react";
|
|
|
|
interface EventContent {
|
|
title: string;
|
|
subTitle: string;
|
|
eventDate: string;
|
|
eventTime: string;
|
|
locationName: string;
|
|
locationAddress: string;
|
|
whatToBring: string;
|
|
rulesAndGdpr: string;
|
|
}
|
|
|
|
export default function AdminDashboard() {
|
|
const [content, setContent] = useState<EventContent | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [message, setMessage] = useState({ type: "", text: "" });
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
const token = localStorage.getItem("adminToken");
|
|
if (!token) {
|
|
router.push("/admin/login");
|
|
return;
|
|
}
|
|
|
|
fetch("/api/Content")
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
setContent(data);
|
|
setLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
console.error("Failed to fetch content", err);
|
|
setLoading(false);
|
|
});
|
|
}, [router]);
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem("adminToken");
|
|
router.push("/admin/login");
|
|
};
|
|
|
|
const handleSave = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!content) return;
|
|
setSaving(true);
|
|
setMessage({ type: "", text: "" });
|
|
|
|
try {
|
|
const response = await fetch("/api/Content", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(content),
|
|
});
|
|
|
|
if (response.ok) {
|
|
setMessage({ type: "success", text: "Content saved successfully!" });
|
|
} else {
|
|
setMessage({ type: "error", text: "Failed to save content." });
|
|
}
|
|
} catch (err) {
|
|
setMessage({ type: "error", text: "An error occurred while saving." });
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const { name, value } = e.target;
|
|
setContent((prev) => prev ? { ...prev, [name]: value } : null);
|
|
};
|
|
|
|
if (loading) return <div className="p-8">Loading...</div>;
|
|
if (!content) return <div className="p-8 text-red-500">Error loading content.</div>;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 p-8 font-sans">
|
|
<div className="max-w-4xl mx-auto">
|
|
<div className="flex justify-between items-center mb-8">
|
|
<h1 className="text-3xl font-bold text-gray-900">Admin Dashboard</h1>
|
|
<div className="space-x-4">
|
|
<Link href="/" className="text-blue-600 hover:underline">View Site</Link>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="bg-gray-200 px-4 py-2 rounded-md hover:bg-gray-300"
|
|
>
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSave} className="bg-white p-8 rounded-lg shadow-md space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700">Page Title</label>
|
|
<input
|
|
type="text"
|
|
name="title"
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.title}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700">Sub-title</label>
|
|
<input
|
|
type="text"
|
|
name="subTitle"
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.subTitle}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700">Event Date</label>
|
|
<input
|
|
type="text"
|
|
name="eventDate"
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.eventDate}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700">Event Time</label>
|
|
<input
|
|
type="text"
|
|
name="eventTime"
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.eventTime}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700">Location Name</label>
|
|
<input
|
|
type="text"
|
|
name="locationName"
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.locationName}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700">Location Address</label>
|
|
<input
|
|
type="text"
|
|
name="locationAddress"
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.locationAddress}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700">What to Bring (One item per line)</label>
|
|
<textarea
|
|
name="whatToBring"
|
|
rows={6}
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 font-mono text-sm text-gray-900"
|
|
value={content.whatToBring}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700">Rules & GDPR</label>
|
|
<textarea
|
|
name="rulesAndGdpr"
|
|
rows={4}
|
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
|
value={content.rulesAndGdpr}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{message.text && (
|
|
<div className={`p-4 rounded-md ${message.type === "success" ? "bg-green-50 text-green-800" : "bg-red-50 text-red-800"}`}>
|
|
{message.text}
|
|
</div>
|
|
)}
|
|
|
|
<div className="pt-4">
|
|
<button
|
|
type="submit"
|
|
disabled={saving}
|
|
className="w-full bg-blue-600 text-white py-3 rounded-md hover:bg-blue-700 disabled:bg-gray-400 font-bold"
|
|
>
|
|
{saving ? "Saving Changes..." : "Save Content Changes"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|