Almost done
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
// app/beach/BeachClientPage.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { IoIosMail } from "react-icons/io";
|
||||
import Footer from '../components/Footer';
|
||||
import Header from '../components/Header';
|
||||
import BeachRegistrationForm from './BeachRegistrationForm';
|
||||
|
||||
export default function BeachClientPage({ config }: { config: any }) {
|
||||
const [timeInfo, setTimeInfo] = useState({
|
||||
dateStr: 'Laddar...',
|
||||
matchStr: 'Laddar...',
|
||||
deadlineText: 'Laddar...',
|
||||
isClosed: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const updateTournamentInfo = () => {
|
||||
const matchStart = new Date(config.beachPage.matchStart);
|
||||
const now = new Date();
|
||||
const infoGathering = new Date(matchStart.getTime() - 30 * 60000);
|
||||
const closingDate = new Date(matchStart.getTime() - 24 * 60 * 60000);
|
||||
const dateOptions: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' };
|
||||
const timeOptions: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' };
|
||||
|
||||
setTimeInfo({
|
||||
dateStr: matchStart.toLocaleDateString('sv-SE', dateOptions),
|
||||
matchStr: `Matchstart kl.${matchStart.toLocaleTimeString('sv-SE', timeOptions)} (Infosamling kl.${infoGathering.toLocaleTimeString('sv-SE', timeOptions)})`,
|
||||
deadlineText: `Anmälan stänger den ${closingDate.toLocaleDateString('sv-SE', dateOptions)} kl.${closingDate.toLocaleTimeString('sv-SE', timeOptions)} (24 timmar innan start) eller om alla platser har blivit fyllda.`,
|
||||
isClosed: config.beachPage.isClosedOverride || now >= closingDate,
|
||||
});
|
||||
};
|
||||
updateTournamentInfo();
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col font-sans bg-white text-black">
|
||||
<Header />
|
||||
|
||||
<main className="grow flex flex-col items-center bg-[#c8e9f2] bg-[url('/images/volleyball-net.webp')] bg-cover bg-center bg-no-repeat w-full pb-20">
|
||||
<section className="flex flex-col items-center text-center w-full px-1.25">
|
||||
<h2 className="mt-12.5"><img className="max-w-62.5" src="/images/hk-mosf.webp" alt="Mat- & Sommarfesten" /></h2>
|
||||
<h1 className="font-beachday text-[clamp(45px,9vw,80px)] text-[#fdb84b] uppercase leading-none m-[20px_0_10px_0]">Beachturnering</h1>
|
||||
<p className="font-bold italic text-[25px] mb-5 text-black">Samla ihop ett kompisgäng och anmäl er!</p>
|
||||
|
||||
{timeInfo.isClosed && (
|
||||
<div className="bg-black p-5 rounded-[20px] my-5 mx-1.25 w-full max-w-150 shadow-xl border-l-8 border-red-600">
|
||||
<h3 className="text-red-500 text-[clamp(1.5rem,6.5vw,2rem)] font-bold pb-2.5 text-center">Anmälan är stängd!</h3>
|
||||
<p className="text-white text-center whitespace-pre-wrap">{config.beachPage.closeReason}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-[#b1e0e8] p-[0_20px_20px_20px] rounded-[20px] my-7.5 mx-1.25 flex flex-col items-center text-center w-full max-w-200 shadow-lg">
|
||||
{/* INFORMATION TEXT (Var, När, Regler, etc.) */}
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Var:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]"><a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://maps.app.goo.gl/vJcdSDCbvdGYXPNY6">Kvickbadet</a> i Höganäs</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">När:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">{timeInfo.dateStr}</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">{timeInfo.matchStr}</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">
|
||||
Samtidigt som <a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href={config.beachPage.festivalLink}>Höganäs Mat- & Sommarfest</a>
|
||||
</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Regler:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150">Matcherna spelas 4v4 med <a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://www.volleyboll.se/forbundet/valkommen-till-volleyboll/grenar-och-spelformer/volleyboll">inomhusregler</a>, förutom poängräkningen som följer reglerna för <a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://www.volleyboll.se/forbundet/valkommen-till-volleyboll/grenar-och-spelformer/beachvolley">beachvolleyboll</a>.</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Klasser:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150">
|
||||
Turneringen kan komma att delas upp i tre olika klasser beroende på antal anmälda lag och baserat på nivå:<br />
|
||||
<strong className="text-green-700">Grön</strong> (Nybörjare),{' '}
|
||||
<strong className="text-[#0f65bf]">Blå</strong> (Amatör),{' '}
|
||||
<strong className="text-black">Svart</strong> (Proffs)
|
||||
</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150 italic">Gör en gissning i vilken nivå ni tror att ert lag skulle passa och skriv gärna något i anmälan om ni är osäkra.</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Kostnad:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">Gratis!!!</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Anmälan:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150">Anmälan görs genom anmälningsformuläret nedan. Du får ett bekräftelsemejl när vi kollat igenom er anmälan.</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px] italic">{timeInfo.deadlineText}</p>
|
||||
|
||||
{/* Övrig info sektionen */}
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Övrig info:</h4>
|
||||
{(config.beachPage?.otherInfo || "").split('\n').map((line: string, i: number) => (
|
||||
line.trim() && <p key={i} className="text-black text-[18px] m-[4px_10px]">{line}</p>
|
||||
))}
|
||||
|
||||
{/* Pris sektionen */}
|
||||
<h4 className="font-beachday uppercase text-[30px] font-medium p-2.5 mt-5 text-black">
|
||||
{config.beachPage?.prizesText || "Fina priser till alla pallplatser! 🏆"}
|
||||
</h4>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Rendera formuläret endast om anmälan är öppen */}
|
||||
{!timeInfo.isClosed && <BeachRegistrationForm />}
|
||||
|
||||
<section className="w-full flex justify-center px-1.25 mt-5">
|
||||
<div className="bg-[#FAAC4F] flex flex-col justify-center items-center py-2.5 rounded-[20px] m-[10px_5px] w-full max-w-100 shadow-md">
|
||||
<h3 className="text-white text-[clamp(1.5rem,6.5vw,2rem)] text-center font-bold">Kontakta oss</h3>
|
||||
<a href="mailto:beach@lerbergetsvolleyboll.se?subject=Beach%20turnering" className="group flex justify-center items-center text-black font-bold text-[1.1rem] mt-2 relative pb-1">
|
||||
<IoIosMail className="h-7 w-7 mr-2" size={24} />
|
||||
<span>beach@lerbergetsvolleyboll.se</span>
|
||||
<span className="absolute bottom-0 left-0 w-full h-0.5 bg-black transform scale-x-0 transition-transform duration-300 origin-left group-hover:scale-x-100" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// app/beach/BeachRegistrationForm.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { PiCheckCircle, PiWarningCircle } from "react-icons/pi";
|
||||
|
||||
interface AlertState {
|
||||
show: boolean;
|
||||
type: 'success' | 'danger' | '';
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface SubmitResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export default function BeachRegistrationForm() {
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
const [wasValidated, setWasValidated] = useState<boolean>(false);
|
||||
const [alert, setAlert] = useState<AlertState>({ show: false, type: '', message: '' });
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const turnstileRef = useRef<TurnstileInstance>(null);
|
||||
|
||||
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
||||
e.preventDefault();
|
||||
const form = formRef.current;
|
||||
if (!form) return;
|
||||
|
||||
setWasValidated(true);
|
||||
if (!form.checkValidity()) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
setAlert({ show: false, type: '', message: '' });
|
||||
const formData = new FormData(form);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/submit', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Network error: ${response.status}`);
|
||||
|
||||
const result = (await response.json()) as SubmitResponse;
|
||||
const message = result.detail ? `${result.message} ${result.detail}` : result.message;
|
||||
|
||||
setAlert({
|
||||
show: true,
|
||||
type: result.success ? 'success' : 'danger',
|
||||
message: message,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
form.reset();
|
||||
setWasValidated(false);
|
||||
if (turnstileRef.current) turnstileRef.current.reset();
|
||||
}
|
||||
} catch (error) {
|
||||
setAlert({
|
||||
show: true,
|
||||
type: 'danger',
|
||||
message: 'Ett tekniskt fel uppstod. Försök igen senare.',
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="w-full flex justify-center px-1.25">
|
||||
<div className="bg-black p-5 rounded-[20px] my-7.5 mx-1.25 w-full max-w-180 shadow-2xl">
|
||||
<h2 className="font-beachday text-[clamp(1.875rem,5.2vw,50px)] text-white text-center m-[5px_0] uppercase tracking-wide">
|
||||
Anmälningsformulär
|
||||
</h2>
|
||||
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleSubmit}
|
||||
className={`group/form flex flex-col items-center w-full mt-5 ${wasValidated ? 'was-validated' : ''}`}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
{/* INPUT FÄLT (Namn, Email, Tel, Lagnamn, Antal) */}
|
||||
{[
|
||||
{ name: 'name', type: 'text', placeholder: 'Ditt namn', auto: 'name', error: 'Glöm inte ditt namn!' },
|
||||
{ name: 'email', type: 'email', placeholder: 'Email', auto: 'email', error: 'Emailadressen är inte giltig.' },
|
||||
{ name: 'tel', type: 'tel', placeholder: 'Telefonnummer', auto: 'tel', error: 'Glöm inte ett telefonnummer!' },
|
||||
{ name: 'team', type: 'text', placeholder: 'Lagnamn', auto: 'organization', error: 'Glöm inte ert lagnamn!' },
|
||||
].map((f) => (
|
||||
<div key={f.name} className="relative w-full mb-3.75">
|
||||
<input
|
||||
type={f.type} name={f.name} placeholder={f.placeholder} required autoComplete={f.auto}
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] transition-colors"
|
||||
/>
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block absolute right-2.5 top-2.25"><PiWarningCircle color='#dc3545' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-valid:block absolute right-2.5 top-2.25"><PiCheckCircle color='#198754' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">{f.error}</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="relative w-full mb-3.75">
|
||||
<input type="number" name="count" placeholder="Antal spelare" min="2" required
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] transition-colors" />
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">Glöm inte ange antal lagmedlemmar!</div>
|
||||
</div>
|
||||
|
||||
{/* RADIOS */}
|
||||
<div className="w-full mt-2.5 text-left group/radios">
|
||||
{[
|
||||
{ id: 'level-gron', val: 'Grön', label: 'Grön', color: 'text-green-700', accent: 'accent-green-600', desc: '(Nybörjare)' },
|
||||
{ id: 'level-bla', val: 'Blå', label: 'Blå', color: 'text-[#0f65bf]', accent: 'accent-[#0f65bf]', desc: '(Spelat lite)' },
|
||||
{ id: 'level-svart', val: 'Svart', label: 'Svart', color: 'text-black', accent: 'accent-black', desc: '(Spelat mycket)' },
|
||||
].map((l) => (
|
||||
<React.Fragment key={l.id}>
|
||||
<input type="radio" id={l.id} name="level" value={l.val} required className={`inline-block align-middle m-[6px_5px] w-4 h-4 ${l.accent} cursor-pointer`} />
|
||||
<label htmlFor={l.id} className="text-white inline-block align-middle cursor-pointer select-none">
|
||||
<strong className={`${l.color} bg-white px-0.75 rounded-sm font-bold`}>{l.label}</strong> {l.desc}
|
||||
</label>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
))}
|
||||
<div className="hidden group-[.was-validated]/form:group-has-invalid/radios:block text-[#e60b20] text-[14px] pl-2.5 mt-2">Glöm inte ange spelarnivå!</div>
|
||||
</div>
|
||||
|
||||
<textarea name="message" autoComplete="off" placeholder="Övrig info..." className="w-full min-h-37.5 p-[12px_16px] border-[5px] border-white rounded-[10px] bg-transparent text-white text-[15px] resize-y outline-none mt-5" />
|
||||
|
||||
<div className="hidden">
|
||||
<input type="text" name="website" tabIndex={-1} autoComplete="off" />
|
||||
</div>
|
||||
|
||||
{alert.show && (
|
||||
<div className={`w-full p-4 rounded-md text-center font-bold my-4 ${alert.type === 'success' ? 'bg-[#d1e7dd] text-[#0a3622] border-[#a3cfbb]' : 'bg-[#f8d7da] text-[#58151c] border-[#f1aeb5]'}`}>
|
||||
{alert.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full flex justify-center mt-2.5">
|
||||
<Turnstile ref={turnstileRef} siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || ''} options={{ theme: 'dark' }} />
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={isSubmitting} className="bg-white text-black uppercase w-full max-w-75 h-12.5 text-[20px] font-bold rounded-[10px] my-5 flex justify-center items-center cursor-pointer hover:bg-gray-200 transition-all active:scale-95 disabled:opacity-70">
|
||||
{isSubmitting && (
|
||||
<svg className="animate-spin h-5 w-5 text-black mr-3" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
)}
|
||||
<span>Skicka anmälan</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+8
-340
@@ -1,344 +1,12 @@
|
||||
// app/beach/page.tsx
|
||||
import { getConfig } from '../lib/config';
|
||||
import BeachClientPage from './BeachClientPage';
|
||||
|
||||
'use client';
|
||||
export const metadata = {
|
||||
title: 'Beachturnering',
|
||||
};
|
||||
|
||||
import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile';
|
||||
import Link from 'next/link';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { IoIosMail } from "react-icons/io";
|
||||
import { PiWarningCircle } from "react-icons/pi";
|
||||
import { PiCheckCircle } from "react-icons/pi";
|
||||
|
||||
interface TimeInfo {
|
||||
dateStr: string;
|
||||
matchStr: string;
|
||||
deadlineText: string;
|
||||
isClosed: boolean;
|
||||
}
|
||||
|
||||
interface AlertState {
|
||||
show: boolean;
|
||||
type: 'success' | 'danger' | '';
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface SubmitResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export default function BeachPage() {
|
||||
const [timeInfo, setTimeInfo] = useState<TimeInfo>({
|
||||
dateStr: 'Laddar datum...',
|
||||
matchStr: 'Laddar tider...',
|
||||
deadlineText: 'Anmälningar kan göras senast...',
|
||||
isClosed: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const updateTournamentInfo = () => {
|
||||
const matchStart = new Date('2026-06-13T10:00:00+02:00');
|
||||
const now = new Date();
|
||||
|
||||
const infoGathering = new Date(matchStart.getTime() - 30 * 60000);
|
||||
const closingDate = new Date(matchStart.getTime() - 24 * 60 * 60000);
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' };
|
||||
const timeOptions: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' };
|
||||
|
||||
const dateStr = matchStart.toLocaleDateString('sv-SE', dateOptions);
|
||||
const matchTime = matchStart.toLocaleTimeString('sv-SE', timeOptions);
|
||||
const infoTime = infoGathering.toLocaleTimeString('sv-SE', timeOptions);
|
||||
const deadlineDate = closingDate.toLocaleDateString('sv-SE', dateOptions);
|
||||
const deadlineTime = closingDate.toLocaleTimeString('sv-SE', timeOptions);
|
||||
|
||||
setTimeInfo({
|
||||
dateStr,
|
||||
matchStr: `Matchstart kl.${matchTime} (Infosamling kl.${infoTime})`,
|
||||
deadlineText: `Anmälan stänger den ${deadlineDate} kl.${deadlineTime} (24 timmar innan start) eller om alla platser har blivit fyllda.`,
|
||||
isClosed: now >= closingDate,
|
||||
});
|
||||
};
|
||||
|
||||
updateTournamentInfo();
|
||||
const interval = setInterval(updateTournamentInfo, 60000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
const [wasValidated, setWasValidated] = useState<boolean>(false);
|
||||
const [alert, setAlert] = useState<AlertState>({ show: false, type: '', message: '' });
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const turnstileRef = useRef<TurnstileInstance>(null);
|
||||
|
||||
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
||||
e.preventDefault();
|
||||
const form = formRef.current;
|
||||
if (!form) return;
|
||||
|
||||
setWasValidated(true);
|
||||
|
||||
if (!form.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
setAlert({ show: false, type: '', message: '' });
|
||||
const formData = new FormData(form);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/submit', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Network error: ${response.status}`);
|
||||
|
||||
const result = (await response.json()) as SubmitResponse;
|
||||
const message = result.detail ? `${result.message} ${result.detail}` : result.message;
|
||||
|
||||
setAlert({
|
||||
show: true,
|
||||
type: result.success ? 'success' : 'danger',
|
||||
message: message,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
form.reset();
|
||||
setWasValidated(false);
|
||||
if (turnstileRef.current) turnstileRef.current.reset();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('An error occurred:', error);
|
||||
setAlert({
|
||||
show: true,
|
||||
type: 'danger',
|
||||
message: 'Ett tekniskt fel uppstod. Försök igen senare.',
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// SVG Icons for validation (Extracted from your original CSS)
|
||||
// const CheckmarkIcon = () => #198754
|
||||
|
||||
// const ErrorIcon = () => #dc3545
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col font-sans bg-white text-black">
|
||||
|
||||
<header className="bg-black flex justify-center py-2.5">
|
||||
<div className="w-85 mx-auto text-center">
|
||||
<Link href="/">
|
||||
<img src="/images/logo.svg" alt="Lerbergets Volleybollsällskap" className="max-h-45 min-h-20 my-5.75 mx-auto block" />
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="grow flex flex-col items-center bg-[#c8e9f2] bg-[url('/images/volleyball-net.webp')] bg-cover bg-center bg-no-repeat w-full pb-20">
|
||||
|
||||
<section className="flex flex-col items-center text-center w-full px-1.25">
|
||||
<h2 className="mt-12.5"><img className="max-w-62.5" src="/images/hk-mosf.webp" alt="Mat- & Sommarfesten" /></h2>
|
||||
<h1 className="font-beachday text-[clamp(45px,9vw,80px)] text-[#fdb84b] uppercase leading-none m-[20px_0_10px_0]" style={{ fontFamily: "'Beachday', sans-serif" }}>Beachturnering</h1>
|
||||
<p className="font-bold italic text-[25px] mb-5 text-black">Samla ihop ett kompisgäng och anmäl er!</p>
|
||||
|
||||
{timeInfo.isClosed && (
|
||||
<div className="bg-black p-5 rounded-[20px] my-5 mx-1.25 w-full max-w-150">
|
||||
<h3 className="text-red-500 text-[clamp(1.5rem,6.5vw,2rem)] font-bold pb-2.5">Anmälan är nu stängd!</h3>
|
||||
<p className="text-white text-center pb-2.5">Det är hela 26 lag anmälda och vi kan tyvärr inte ta emot fler anmälningar.</p>
|
||||
<p className="text-white text-center">Vi är glada över förväntan och bjuder in alla andra till att komma och kolla men vi har tyvärr inte kapaciteten till att ha med fler lag.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-[#b1e0e8] p-[0_20px_20px_20px] rounded-[20px] my-7.5 mx-1.25 flex flex-col items-center text-center w-full max-w-200">
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Var:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]"><a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://maps.app.goo.gl/vJcdSDCbvdGYXPNY6">Kvickbadet</a> i Höganäs</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">När:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">{timeInfo.dateStr}</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">{timeInfo.matchStr}</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">Samtidigt som <a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://www.kullahalvon.com/upptacka--uppleva/kultur--noje/evenemang-pa-kullahalvon/mat---sommarfesten.html">Höganäs Mat- & Sommarfest</a></p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Regler:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150">Matcherna spelas 4v4 med <a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://www.volleyboll.se/forbundet/valkommen-till-volleyboll/grenar-och-spelformer/volleyboll">inomhusregler</a>, förutom poängräkningen som följer reglerna för <a target="_blank" rel="noreferrer" className="font-medium text-[#cc8124] hover:underline" href="https://www.volleyboll.se/forbundet/valkommen-till-volleyboll/grenar-och-spelformer/beachvolley">beachvolleyboll</a>.</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Klasser:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150">
|
||||
Turneringen kan komma att delas upp i tre olika klasser beroende på antal anmälda lag och baserat på nivå:<br />
|
||||
<strong className="text-green-700">Grön</strong> (Nybörjare),{' '}
|
||||
<strong className="text-[#0f65bf]">Blå</strong> (Amatör),{' '}
|
||||
<strong className="text-black">Svart</strong> (Proffs)
|
||||
</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150 italic">Gör en gissning i vilken nivå ni tror att ert lag skulle passa och skriv gärna något i anmälan om ni är osäkra.</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Kostnad:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">Gratis!!!</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Anmälan:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px] max-w-150">Anmälan görs genom anmälningsformuläret nedan. Du får ett bekräftelsemejl när vi kollat igenom er anmälan.</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px] italic">{timeInfo.deadlineText}</p>
|
||||
|
||||
<h4 className="text-black font-bold text-[1.25rem] mt-5 mb-0 italic">Övrig info:</h4>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">Turneringen upskattas hålla på till ca 17-18 tiden</p>
|
||||
<p className="text-black text-[18px] m-[4px_10px]">Det kommer finnas duschmöjligheter i anslutning till stranden.</p>
|
||||
|
||||
<h4 className="font-beachday uppercase text-[30px] font-medium p-2.5 mt-5 text-black" style={{ fontFamily: "'Beachday', sans-serif" }}>
|
||||
Det kommer att finnas fina priser till alla pallplatser! 🏆
|
||||
</h4>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{!timeInfo.isClosed && (
|
||||
<section className="w-full flex justify-center px-1.25">
|
||||
<div className="bg-black p-5 rounded-[20px] my-7.5 mx-1.25 w-full max-w-180">
|
||||
<h2 className="font-beachday text-[clamp(1.875rem,5.2vw,50px)] text-white text-center m-[5px_0] uppercase" style={{ fontFamily: "'Beachday', sans-serif" }}>Anmälningsformulär</h2>
|
||||
|
||||
{/* Form starts here - uses group/form to track the .was-validated state natively */}
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleSubmit}
|
||||
className={`group/form flex flex-col items-center w-full mt-5 ${wasValidated ? 'was-validated' : ''}`}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
|
||||
{/* NAME */}
|
||||
<div className="relative w-full mb-3.75">
|
||||
<input type="text" name="name" placeholder="Ditt namn" required autoComplete="name"
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none appearance-none shadow-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] group-[.was-validated]/form:pr-10 transition-colors" />
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block absolute right-2.5 top-2.25"><PiWarningCircle color='#dc3545' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-valid:block absolute right-2.5 top-2.25"><PiCheckCircle color='#198754' size={28} /></div>
|
||||
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">Glöm inte ditt namn!</div>
|
||||
</div>
|
||||
|
||||
{/* EMAIL */}
|
||||
<div className="relative w-full mb-3.75">
|
||||
<input type="email" name="email" placeholder="Email" required autoComplete="email"
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none appearance-none shadow-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] group-[.was-validated]/form:pr-10 transition-colors" />
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block absolute right-2.5 top-2.25"><PiWarningCircle color='#dc3545' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-valid:block absolute right-2.5 top-2.25"><PiCheckCircle color='#198754' size={28} /></div>
|
||||
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">Email addressen är inte giltig.</div>
|
||||
</div>
|
||||
|
||||
{/* PHONE */}
|
||||
<div className="relative w-full mb-3.75">
|
||||
<input type="tel" name="tel" placeholder="Telefonnummer" required autoComplete="tel"
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none appearance-none shadow-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] group-[.was-validated]/form:pr-10 transition-colors" />
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block absolute right-2.5 top-2.25"><PiWarningCircle color='#dc3545' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-valid:block absolute right-2.5 top-2.25"><PiCheckCircle color='#198754' size={28} /></div>
|
||||
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">Glöm inte ett telefonnummer!</div>
|
||||
</div>
|
||||
|
||||
{/* TEAM */}
|
||||
<div className="relative w-full mb-3.75">
|
||||
<input type="text" name="team" placeholder="Lagnamn" required autoComplete="organization"
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none appearance-none shadow-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] group-[.was-validated]/form:pr-10 transition-colors" />
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block absolute right-2.5 top-2.25"><PiWarningCircle color='#dc3545' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-valid:block absolute right-2.5 top-2.25"><PiCheckCircle color='#198754' size={28} /></div>
|
||||
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">Glöm inte ert Lagnamn!</div>
|
||||
</div>
|
||||
|
||||
{/* COUNT */}
|
||||
<div className="relative w-full mb-3.75">
|
||||
<input type="number" name="count" placeholder="Antal spelare" min="2" required autoComplete="off"
|
||||
className="peer bg-transparent text-white border-0 border-b-[5px] border-white w-full h-12.5 outline-none text-[20px] pl-2.5 rounded-none appearance-none shadow-none focus:ring-0 group-[.was-validated]/form:invalid:border-[#e60b20] group-[.was-validated]/form:valid:border-[#07ba4c] group-[.was-validated]/form:pr-10 transition-colors" />
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block absolute right-2.5 top-2.25"><PiWarningCircle color='#dc3545' size={28} /></div>
|
||||
<div className="hidden group-[.was-validated]/form:peer-valid:block absolute right-2.5 top-2.25"><PiCheckCircle color='#198754' size={28} /></div>
|
||||
|
||||
<div className="hidden group-[.was-validated]/form:peer-invalid:block text-[#e60b20] text-[14px] pl-2.5 mt-1 text-left w-full">Glöm inte ange antal lagmedlemmar!</div>
|
||||
</div>
|
||||
|
||||
{/* RADIOS */}
|
||||
<div className="w-full mt-2.5 text-left group/radios">
|
||||
<input type="radio" id="level-gron" name="level" value="Grön" required className="inline-block align-middle m-[6px_5px] w-4 h-4 accent-green-600 cursor-pointer" />
|
||||
<label htmlFor="level-gron" className="text-white inline-block align-middle cursor-pointer select-none">
|
||||
<strong className="text-green-700 bg-white px-0.75 rounded-sm font-bold">Grön</strong> (Nybörjare)
|
||||
</label>
|
||||
<br />
|
||||
|
||||
<input type="radio" id="level-bla" name="level" value="Blå" required className="inline-block align-middle m-[6px_5px] w-4 h-4 accent-[#0f65bf] cursor-pointer" />
|
||||
<label htmlFor="level-bla" className="text-white inline-block align-middle cursor-pointer select-none">
|
||||
<strong className="text-[#0f65bf] bg-white px-0.75 rounded-sm font-bold">Blå</strong> (Spelat lite)
|
||||
</label>
|
||||
<br />
|
||||
|
||||
<input type="radio" id="level-svart" name="level" value="Svart" required className="inline-block align-middle m-[6px_5px] w-4 h-4 accent-black cursor-pointer" />
|
||||
<label htmlFor="level-svart" className="text-white inline-block align-middle cursor-pointer select-none">
|
||||
<strong className="text-black bg-white px-0.75 rounded-sm font-bold">Svart</strong> (Spelat mycket)
|
||||
</label>
|
||||
<div className="hidden group-[.was-validated]/form:group-has-invalid/radios:block text-[#e60b20] text-[14px] pl-2.5 mt-2">Glöm inte ange spelarnivå!</div>
|
||||
</div>
|
||||
|
||||
{/* TEXTAREA */}
|
||||
<div className="w-full mt-2.5">
|
||||
<textarea name="message" placeholder="Övrig info..." autoComplete="off"
|
||||
className="w-full min-h-37.5 p-[12px_16px] border-[5px] border-white rounded-[10px] bg-transparent text-white text-[15px] resize-y outline-none appearance-none mt-5"></textarea>
|
||||
</div>
|
||||
|
||||
{/* HONEYPOT */}
|
||||
<div className="hidden">
|
||||
<label htmlFor="website">Website</label>
|
||||
<input type="text" name="website" tabIndex={-1} autoComplete="off" />
|
||||
</div>
|
||||
|
||||
{/* ALERT */}
|
||||
{alert.show && (
|
||||
<div className={`w-full p-4 rounded-md text-center font-bold my-4 ${alert.type === 'success' ? 'bg-[#d1e7dd] text-[#0a3622] border border-[#a3cfbb]' : 'bg-[#f8d7da] text-[#58151c] border border-[#f1aeb5]'}`}>
|
||||
{alert.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TURNSTILE */}
|
||||
<div className="w-full flex justify-center mt-2.5">
|
||||
<Turnstile
|
||||
ref={turnstileRef}
|
||||
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || ''}
|
||||
options={{ theme: 'dark' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* SUBMIT BUTTON */}
|
||||
<button type="submit" className="bg-white text-black uppercase border-none w-full max-w-75 h-12.5 outline-none text-[20px] font-bold text-center rounded-[10px] my-5 flex justify-center items-center cursor-pointer hover:bg-gray-200 transition disabled:opacity-70" disabled={isSubmitting}>
|
||||
{isSubmitting ? (
|
||||
<svg className="animate-spin h-5 w-5 text-black mr-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
) : null}
|
||||
<span>Skicka anmälan</span>
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Contact Section */}
|
||||
<section className="w-full flex justify-center px-1.25">
|
||||
<div className="bg-[#fdb84b] flex flex-col justify-center items-center py-2.5 rounded-[20px] m-[30px_5px_60px_5px] w-full max-w-100">
|
||||
<h3 className="text-black text-[clamp(1.5rem,6.5vw,2rem)] text-center font-bold m-[5px_0]">Kontakta oss</h3>
|
||||
<a href="mailto:beach@lerbergetsvolleyboll.se?subject=Beach%20turnering" className="flex justify-center items-center my-1.25 text-[clamp(1rem,5vw,1.1rem)] font-bold leading-[1.6] text-black hover:underline">
|
||||
<IoIosMail className='h-[clamp(1.5rem,5vw,2rem)] mr-2' />
|
||||
beach@lerbergetsvolleyboll.se
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-black text-center py-3.75">
|
||||
<p className="text-white font-bold">LVS - Lerbergets Volleybollsällskap {new Date().getFullYear()} ®</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
);
|
||||
export default async function BeachPage() {
|
||||
const config = await getConfig();
|
||||
return <BeachClientPage config={config} />;
|
||||
}
|
||||
Reference in New Issue
Block a user