Almost done

This commit is contained in:
2026-03-05 17:35:27 +01:00 Unverified
parent 278471e53b
commit e44fceaf32
30 changed files with 1077 additions and 572 deletions
+78
View File
@@ -0,0 +1,78 @@
// app/admin/AdminForm.tsx
'use client';
import { useActionState, useEffect, useState } from 'react';
import { saveConfigAction } from './actions';
export default function AdminForm({ initialConfig }: { initialConfig: any }) {
const [state, formAction, isPending] = useActionState(saveConfigAction, null);
const [showToast, setShowToast] = useState(false);
useEffect(() => {
if (state?.success) {
setShowToast(true);
const timer = setTimeout(() => setShowToast(false), 3000);
return () => clearTimeout(timer);
}
}, [state]);
return (
<form action={formAction} className="flex flex-col gap-8">
{showToast && (
<div className="fixed top-5 right-5 bg-green-500 text-white px-6 py-3 rounded-lg shadow-2xl font-bold z-50 animate-bounce">
Ändringar sparade!
</div>
)}
{/* Startsida */}
<section className="border-b pb-6">
<h2 className="text-xl font-bold mb-4 border-l-4 border-[#fdb84b] pl-2 text-[#406185]">Startsida</h2>
<label className="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="showBeachPromo" defaultChecked={initialConfig.showBeachPromo} className="w-5 h-5 accent-[#406185]" />
<span className="font-semibold">Visa Beach-Promo bar</span>
</label>
</section>
{/* Beach-Turnering */}
<section className="flex flex-col gap-6">
<h2 className="text-xl font-bold border-l-4 border-[#fdb84b] pl-2 text-[#406185]">Beach-Turnering</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-bold mb-1">Matchstart</label>
<input type="datetime-local" name="matchStart" defaultValue={initialConfig.beachPage.matchStart} className="w-full border p-2 rounded focus:ring-2 focus:ring-[#406185] outline-none" />
</div>
<div>
<label className="block text-sm font-bold mb-1">Länk till Mat- & Sommarfesten</label>
<input name="festivalLink" defaultValue={initialConfig.beachPage.festivalLink} className="w-full border p-2 rounded focus:ring-2 focus:ring-[#406185] outline-none" />
</div>
</div>
<div>
<label className="block text-sm font-bold mb-1">Meddelande vid stängd anmälan</label>
<textarea name="closeReason" defaultValue={initialConfig.beachPage.closeReason} className="w-full border p-2 rounded h-20" />
</div>
<div>
<label className="block text-sm font-bold mb-1">Övrig info (en rad per punkt sajten)</label>
<textarea name="otherInfo" defaultValue={initialConfig.beachPage.otherInfo} className="w-full border p-2 rounded h-24" />
</div>
<div>
<label className="block text-sm font-bold mb-1">Pris-text (längst ner infosidan)</label>
<input name="prizesText" defaultValue={initialConfig.beachPage.prizesText} className="w-full border p-2 rounded" />
</div>
<label className="flex items-center gap-2 cursor-pointer text-red-600 bg-red-50 p-3 rounded-lg border border-red-100">
<input type="checkbox" name="isClosedOverride" defaultChecked={initialConfig.beachPage.isClosedOverride} className="w-5 h-5 accent-red-600" />
<span className="font-bold uppercase">Stäng anmälan manuellt</span>
</label>
</section>
<button type="submit" disabled={isPending} className="bg-[#406185] text-white py-4 rounded-lg font-bold text-lg hover:bg-black transition-all active:scale-95 disabled:bg-gray-400">
{isPending ? 'Sparar...' : 'Spara inställningar'}
</button>
</form>
);
}
+30
View File
@@ -0,0 +1,30 @@
// app/admin/actions.ts
'use server';
import { getConfig, updateConfig } from '../lib/config';
import { revalidatePath } from 'next/cache';
export async function saveConfigAction(prevState: any, formData: FormData) {
const config = await getConfig();
const newConfig = {
...config,
showBeachPromo: formData.get('showBeachPromo') === 'on',
beachPage: {
...config.beachPage,
matchStart: formData.get('matchStart') as string,
festivalLink: formData.get('festivalLink') as string,
closeReason: formData.get('closeReason') as string,
otherInfo: formData.get('otherInfo') as string,
prizesText: formData.get('prizesText') as string,
isClosedOverride: formData.get('isClosedOverride') === 'on',
}
};
await updateConfig(newConfig);
revalidatePath('/');
revalidatePath('/beach');
return { success: true };
}
+47
View File
@@ -0,0 +1,47 @@
// app/admin/page.tsx
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { getConfig } from '../lib/config';
import AdminForm from './AdminForm';
import { logoutAction } from '../login/actions';
import Header from '../components/Header';
import Footer from '../components/Footer';
export const metadata = {
title: 'Kontrollpanel',
};
export default async function AdminPage() {
const cookieStore = await cookies();
if (cookieStore.get('admin_session')?.value !== 'true') redirect('/login');
const config = await getConfig();
return (
<div className="min-h-screen flex flex-col font-sans">
<Header />
<div className="grow bg-gray-100 p-4 md:p-10">
<div className="max-w-2xl mx-auto bg-white p-6 md:p-8 rounded-xl shadow-md">
<div className="flex justify-between items-center mb-8">
<div className="flex flex-col">
<h1 className="text-3xl font-bold text-[#406185]">Kontrollpanel</h1>
</div>
<form action={logoutAction}>
<button
type="submit"
className="text-sm font-bold text-gray-500 hover:text-red-600 border border-gray-300 hover:border-red-600 px-1 md:p-4 py-2 rounded-lg transition-all active:scale-90"
>
Logga ut
</button>
</form>
</div>
<AdminForm initialConfig={config} />
</div>
</div>
<Footer />
</div>
);
}