First commit
@@ -0,0 +1,127 @@
|
||||
// app/api/submit/route.js
|
||||
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
|
||||
// 1. Extract form data (Matching your PHP POST variables)
|
||||
const turnstileToken = formData.get('cf-turnstile-response');
|
||||
const email = formData.get('email');
|
||||
const name = formData.get('name');
|
||||
const team = formData.get('team');
|
||||
const tel = formData.get('tel');
|
||||
const count = formData.get('count');
|
||||
const level = formData.get('level');
|
||||
const message = formData.get('message');
|
||||
const honeypot = formData.get('website');
|
||||
|
||||
// 2. Security Checks (Honeypot & Turnstile)
|
||||
if (honeypot) {
|
||||
return Response.json({ success: false, message: 'Spam detekterat.' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Verify Turnstile
|
||||
const turnstileRes = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
secret: process.env.TURNSTILE_SECRET || '', // Pulls from .env.local
|
||||
response: turnstileToken?.toString() || '',
|
||||
}),
|
||||
});
|
||||
|
||||
const turnstileData = await turnstileRes.json();
|
||||
|
||||
if (!turnstileData.success) {
|
||||
return Response.json({ success: false, message: 'Säkerhetsverifiering misslyckades (Turnstile).' }, { status: 400 });
|
||||
}
|
||||
|
||||
let formattedTel = tel.replace(/\D/g, '');
|
||||
if (formattedTel.startsWith('46')) formattedTel = formattedTel.substring(2);
|
||||
else if (formattedTel.startsWith('0')) formattedTel = formattedTel.substring(1);
|
||||
|
||||
if (formattedTel.length === 9) {
|
||||
formattedTel = `+46 (0)${formattedTel.substring(0, 2)}-${formattedTel.substring(2, 5)} ${formattedTel.substring(5, 7)} ${formattedTel.substring(7, 9)}`;
|
||||
} else {
|
||||
formattedTel = tel;
|
||||
}
|
||||
|
||||
// 4. Send Email via Nodemailer (Replicating PHPMailer)
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: '10.10.0.113', // Your internal SMTP Host
|
||||
port: 25,
|
||||
secure: false,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
});
|
||||
|
||||
const cleanMessage = message ? message.replace(/[\r\n;]/g, " ") : "";
|
||||
const dateStr = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||
|
||||
const emailHtml = `
|
||||
<html>
|
||||
<body style='font-family: Arial, sans-serif; line-height: 1.6; color: #333;'>
|
||||
<h2 style='color: #cc8124;'>Beach-anmälan</h2>
|
||||
<table style='width: 100%; max-width: 600px; border-collapse: collapse;'>
|
||||
<tr><td style='padding: 5px; font-weight: bold; width: 150px;'>Lagnamn:</td><td>${team}</td></tr>
|
||||
<tr><td style='padding: 5px; font-weight: bold;'>Kontakt:</td><td>${name}</td></tr>
|
||||
<tr><td style='padding: 5px; font-weight: bold;'>E-post:</td><td>${email}</td></tr>
|
||||
<tr><td style='padding: 5px; font-weight: bold;'>Telefon:</td><td>${formattedTel}</td></tr>
|
||||
<tr><td style='padding: 5px; font-weight: bold;'>Spelare:</td><td>${count}</td></tr>
|
||||
<tr><td style='padding: 5px; font-weight: bold;'>Nivå:</td><td>${level}</td></tr>
|
||||
</table>
|
||||
<p><strong>Meddelande:</strong><br>${message ? message.replace(/\n/g, '<br>') : ''}</p>
|
||||
<hr style='border: none; border-top: 1px solid #ddd; margin: 30px 0;'>
|
||||
<table border='1' style='border-collapse: collapse; font-family: Calibri, sans-serif; font-size: 11pt; width: 100%;'>
|
||||
<tr>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${team}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${count}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${level}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${name}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${formattedTel}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${email}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${dateStr}</td>
|
||||
<td style='padding: 8px; border: 1px solid #ccc;'>${cleanMessage}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// 1. Send the data to the Club
|
||||
await transporter.sendMail({
|
||||
from: `"Anmälan: ${name}" <no-reply@lerbergetsvolleyboll.se>`,
|
||||
to: 'beach@lerbergetsvolleyboll.se',
|
||||
replyTo: email?.toString(),
|
||||
subject: `Beach turnering: ${team}`,
|
||||
html: emailHtml,
|
||||
});
|
||||
|
||||
// 2. Send the friendly auto-reply to the Applicant
|
||||
await transporter.sendMail({
|
||||
from: `"Lerbergets Volleybollsällskap" <no-reply@lerbergetsvolleyboll.se>`,
|
||||
to: email?.toString(),
|
||||
subject: `Bekräftelse på anmälan: ${team}`,
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; color: #333; padding: 20px;">
|
||||
<h2 style="color: #406185;">Hej ${name}!</h2>
|
||||
<p style="font-size: 16px;">Tack för din anmälan vi återkommer så fort vi kan.</p>
|
||||
<br>
|
||||
<p style="font-size: 14px; color: #666;">
|
||||
Med vänliga hälsningar,<br>
|
||||
Lerbergets Volleybollsällskap
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
return Response.json({ success: true, message: 'Tack så mycket för anmälan! Vi svarar med en bekräftelse så fort vi kan.' });
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return Response.json({ success: false, message: 'Ett tekniskt fel uppstod. Försök igen senare.' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
// app/beach/page.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,26 +1,44 @@
|
||||
/* app/globals.css */
|
||||
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
|
||||
html,
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Beachday';
|
||||
src: url('/fonts/Beachday.woff2') format('woff2'),
|
||||
url('/fonts/Beachday.woff') format('woff'),
|
||||
url('/fonts/Beachday.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 1. Add your exact keyframes from the old site */
|
||||
@keyframes animateright {
|
||||
from {
|
||||
right: -300px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
right: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. Create a custom class to apply it */
|
||||
.player-animation {
|
||||
position: relative;
|
||||
animation: animateright 1.5s;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-beachday: 'Beachday', sans-serif;
|
||||
--font-sans: var(--font-montserrat), sans-serif;
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
// app/layout.tsx
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Montserrat } from 'next/font/google';
|
||||
import './globals.css';
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
const montserrat = Montserrat({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-montserrat',
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: 'Lerbergets Volleybollsällskap',
|
||||
description: 'Klubben startade den 1 mars 2018 och har i snabb takt utvecklats...',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -23,10 +20,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<html lang="sv-SE" className={montserrat.variable}>
|
||||
<body className="font-sans antialiased">
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,65 +1,234 @@
|
||||
import Image from "next/image";
|
||||
// app/page.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { FaFacebookSquare, FaInstagram } from "react-icons/fa";
|
||||
import { IoIosMail } from "react-icons/io";
|
||||
|
||||
export default function Home() {
|
||||
const [isMedlemOpen, setIsMedlemOpen] = useState(false);
|
||||
const [isPrenumereraOpen, setIsPrenumereraOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
<div className="min-h-screen flex flex-col font-sans bg-white text-black">
|
||||
|
||||
{/* Header */}
|
||||
<header className="bg-black flex justify-center">
|
||||
<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"
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="grow flex flex-col">
|
||||
|
||||
{/* Promo Section (Hidden) */}
|
||||
<section className="bg-[#fdb84b] justify-center">
|
||||
<div className="p-2.5">
|
||||
<h2 className="font-beachday text-black text-center text-[clamp(1.5rem,8vw,2.25rem)] uppercase leading-[1.1]">
|
||||
Kolla in vår <Link href="/beach" className="text-[#406185] underline">Beachturnering</Link>
|
||||
</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Hero / Info Section */}
|
||||
<section className="bg-[#406185] flex justify-center text-white">
|
||||
<div className="flex flex-col md:flex-row max-w-300 my-12.5 mx-5 w-full">
|
||||
|
||||
<div className="flex flex-col justify-center flex-1">
|
||||
<div className="w-fit">
|
||||
<h1 className="uppercase text-[2.5rem] font-bold leading-[1.1] mb-2.5">LVS</h1>
|
||||
<div className="bg-white w-full h-1.5"></div>
|
||||
</div>
|
||||
|
||||
<h4 className="italic text-xl my-2.5 leading-[1.2] font-bold">Lerbergets Volleybollsällskap</h4>
|
||||
<p className="max-w-200 leading-[1.1]">
|
||||
Klubben startade den 1 mars 2018 och har i snabb takt utvecklats med både kidsvolley,
|
||||
4-mannavolley och ett gott gäng killar och tjejer som tränar tillsammans minst två gånger i
|
||||
veckan och på sommaren spelas det beachvolley
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
<div className="flex flex-col gap-2.5 mt-10">
|
||||
<button
|
||||
onClick={() => setIsMedlemOpen(true)}
|
||||
className="text-[#406185] bg-white border-none py-3.5 px-7.5 text-center font-bold uppercase w-fit text-[1.1rem]"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
Bli Medlem
|
||||
</button>
|
||||
<Link
|
||||
href="#traningstider"
|
||||
className="text-[#406185] bg-white border-none py-3.5 px-7.5 text-center font-bold uppercase w-fit text-[1.1rem]"
|
||||
>
|
||||
Träningstider
|
||||
</Link>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://www.basesport.se/category/lerbergets-vbk"
|
||||
className="text-[#406185] bg-white border-none py-3.5 px-7.5 text-center font-bold uppercase w-fit text-[1.1rem]"
|
||||
>
|
||||
Klubbkollektion
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Player Image with restored animation class */}
|
||||
<div className="hidden md:flex items-center p-2.5 justify-end max-h-full">
|
||||
<img
|
||||
src="/images/player.webp"
|
||||
alt="Volleyboll spelare"
|
||||
className="max-h-112.5 player-animation"
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Träningstider Section - Restored to vertical stacking */}
|
||||
<section id="traningstider" className="bg-white flex justify-center">
|
||||
<div className="flex flex-col items-center w-full my-7.5 gap-2">
|
||||
|
||||
<div className="bg-[#406185] text-white w-full max-w-180 py-5 px-2.5 text-center">
|
||||
<h2 className="uppercase text-[clamp(1.5rem,8vw,2.25rem)] font-bold">Träningstider</h2>
|
||||
<p className="my-3.75 text-[1rem] leading-[1.1]">Här hittar du alltid uppdaterade träningstider från Laget.se</p>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="noreferrer"
|
||||
href="https://www.laget.se/LVS/Event/Month"
|
||||
className="text-[#f1c50e] border-b-2 border-[#f1c50e] font-bold uppercase"
|
||||
>
|
||||
Documentation
|
||||
Se nästa träningstid
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#406185] text-white w-full max-w-180 py-5 px-2.5 text-center">
|
||||
<h2 className="uppercase text-[clamp(1.5rem,8vw,2.25rem)] font-bold">Prenumerera på träningstider</h2>
|
||||
<p className="my-3.75 text-[1rem] leading-[1.1]">Våra träningstider finns som en kalender man kan prenumerera på genom knappen nedan</p>
|
||||
<button
|
||||
onClick={() => setIsPrenumereraOpen(true)}
|
||||
className="text-[#f1c50e] border-b-2 border-[#f1c50e] font-bold uppercase pb-0.5 cursor-pointer"
|
||||
>
|
||||
Prenumerera
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Kontakt Section */}
|
||||
<section className="bg-[#406185] flex justify-center text-white">
|
||||
<div className="flex flex-wrap-reverse justify-center gap-7.5 w-full max-w-300 my-10 px-5">
|
||||
|
||||
<div className="flex flex-wrap justify-center items-center gap-7.5 mx-5">
|
||||
<a target="_blank" rel="noreferrer" title="Instagram" href="https://www.instagram.com/lerbergets_volleyboll/">
|
||||
<FaInstagram className='h-20 w-20' />
|
||||
</a>
|
||||
<a target="_blank" rel="noreferrer" title="Facebook" href="https://www.facebook.com/lerbergetsvolleyboll">
|
||||
<FaFacebookSquare className='h-20 w-20' />
|
||||
</a>
|
||||
<a target="_blank" rel="noreferrer" title="Laget.se" href="https://www.laget.se/LVS">
|
||||
<img src="/images/laget.se.svg" alt="Laget.se" className="h-18 w-18" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-2.5 flex flex-col justify-center items-center">
|
||||
<h3 className="uppercase font-bold text-[1.875rem] leading-[1.2]">Kontakta oss</h3>
|
||||
<p className="text-[1rem] leading-[1.6] my-1.25">Vi finns på Instagram, Facebook och laget.se</p>
|
||||
<p className="text-[1rem] leading-[1.6] my-1.25">Höganäs Sportcenter, Friluftsvägen 10, 263 54 Lerberget</p>
|
||||
<a
|
||||
href="mailto:info@lerbergetsvolleyboll.se?subject=Kontakta%20oss"
|
||||
className="flex justify-center items-center text-[#f1c50e] font-bold text-[1rem] leading-[1.6] my-1.25"
|
||||
>
|
||||
<IoIosMail color='#f1c50e' className='h-6 w-6' />
|
||||
info@lerbergetsvolleyboll.se
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-black text-[#lightgrey] text-center py-3.75">
|
||||
<p className="font-bold text-[#d3d3d3]">LVS - Lerbergets Volleybollsällskap {new Date().getFullYear()} ®</p>
|
||||
</footer>
|
||||
|
||||
{/* --- MODALS --- */}
|
||||
{/* Medlem Modal */}
|
||||
{isMedlemOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/60 flex justify-center items-center overflow-auto"
|
||||
onClick={() => setIsMedlemOpen(false)}
|
||||
>
|
||||
<div
|
||||
className="bg-white p-5 w-[80vw] max-w-max min-w-75 rounded-[15px] relative flex flex-col items-center text-center font-semibold text-[#406185]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className="absolute right-3.75 top-3.75 w-5 h-5 block mx-auto"
|
||||
onClick={() => setIsMedlemOpen(false)}
|
||||
>
|
||||
<svg viewBox="0 0 413.348 413.348" className="fill-black">
|
||||
<path d="m413.348 24.354-24.354-24.354-182.32 182.32-182.32-182.32-24.354 24.354 182.32 182.32-182.32 182.32 24.354 24.354 182.32-182.32 182.32 182.32 24.354-24.354-182.32-182.32z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<img src="/images/logo-black.svg" alt="Logo" className="h-25" />
|
||||
<h2 className="text-[#406185] my-2.5 text-2xl font-bold uppercase">Registrera dig</h2>
|
||||
<p className="text-[#406185] my-2.5">Trycka på knappen nedan för att starta registreringen. Du kommer att skickas till laget.se</p>
|
||||
<a
|
||||
href="https://www.laget.se/LVS/Member"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-white bg-[#406185] uppercase font-bold py-2.25 px-11.5 mt-5 rounded-[50px] text-[1rem]"
|
||||
>
|
||||
Bli medlem
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Prenumerera Modal */}
|
||||
{isPrenumereraOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/60 flex justify-center items-center overflow-auto"
|
||||
onClick={() => setIsPrenumereraOpen(false)}
|
||||
>
|
||||
<div
|
||||
className="bg-white p-5 w-[80vw] max-w-max min-w-75 rounded-[15px] relative flex flex-col items-center text-center font-semibold text-[#406185]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className="absolute right-3.75 top-3.75 w-5 h-5 block mx-auto"
|
||||
onClick={() => setIsPrenumereraOpen(false)}
|
||||
>
|
||||
<svg viewBox="0 0 413.348 413.348" className="fill-black">
|
||||
<path d="m413.348 24.354-24.354-24.354-182.32 182.32-182.32-182.32-24.354 24.354 182.32 182.32-182.32 182.32 24.354 24.354 182.32-182.32 182.32 182.32 24.354-24.354-182.32-182.32z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<h2 className="text-[#406185] my-2.5 text-2xl font-bold uppercase">Prenumerera</h2>
|
||||
<p className="text-[#406185] my-2.5">Välj vilken LEVEL du vill prenumerera på.</p>
|
||||
|
||||
<div className="flex flex-row flex-wrap justify-center gap-6.25 mt-5 max-w-162.5">
|
||||
<a href="webcal://cal.laget.se/lvs-level-5-6.ics" className="text-white bg-[#406185] uppercase font-bold py-2.25 px-11.5 rounded-[50px] text-[1rem]">Level 5-6</a>
|
||||
<a href="webcal://cal.laget.se/lvs-level-7-8.ics" className="text-white bg-[#406185] uppercase font-bold py-2.25 px-11.5 rounded-[50px] text-[1rem]">level 7-8</a>
|
||||
<a href="webcal://cal.laget.se/lvs-level-8-9.ics" className="text-white bg-[#406185] uppercase font-bold py-2.25 px-11.5 rounded-[50px] text-[1rem]">Level 8-9</a>
|
||||
<a href="webcal://cal.laget.se/lvs-level-10.ics" className="text-white bg-[#406185] uppercase font-bold py-2.25 px-11.5 rounded-[50px] text-[1rem]">Level 10</a>
|
||||
<a href="webcal://cal.laget.se/LVS.ics" className="text-white bg-[#406185] uppercase font-bold py-2.25 px-11.5 rounded-[50px] text-[1rem]">Hela klubben</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,18 +8,22 @@
|
||||
"name": "lvs-site",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@marsidev/react-turnstile": "^1.4.2",
|
||||
"next": "16.1.6",
|
||||
"nodemailer": "^8.0.1",
|
||||
"postcss": "^8.5.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
"react-dom": "19.2.3",
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/postcss": "^4.2.1",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "^4",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
@@ -1021,6 +1025,16 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@marsidev/react-turnstile": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-1.4.2.tgz",
|
||||
"integrity": "sha512-xs1qOuyeMOz6t9BXXCXWiukC0/0+48vR08B7uwNdG05wCMnbcNgxiFmdFKDOFbM76qFYFRYlGeRfhfq1U/iZmA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2 || ^18.0.0 || ^19.0",
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||
@@ -5090,6 +5104,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz",
|
||||
"integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -5354,7 +5377,6 @@
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -5453,6 +5475,15 @@
|
||||
"react": "^19.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
||||
@@ -9,18 +9,22 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@marsidev/react-turnstile": "^1.4.2",
|
||||
"next": "16.1.6",
|
||||
"nodemailer": "^8.0.1",
|
||||
"postcss": "^8.5.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
"react-dom": "19.2.3",
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/postcss": "^4.2.1",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "^4",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 500 500">
|
||||
<!-- Generator: Adobe Illustrator 30.1.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 136) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="st0" d="M450,0H188.1l-.17.68-83.43,309.86c-.15,1.43.21,2.65,1.48,3.4l15.06,4.92,6.18,26.9c-4.32,17.46-9.51,35.2-14.14,52.67-42.14-10.03-89.48-21.47-95.57-71.63-1.32-10.86-.45-21.87,1.89-32.57L83.08,59.66c.25-1.92-.04-3.72-1.93-4.68l-28.42-7.39L64.88,1.39l.39-1.39h-15.27C22.39,0,0,22.39,0,50v400c0,27.61,22.39,50,50,50h400c27.61,0,50-22.39,50-50V50c0-27.61-22.39-50-50-50ZM499.9,270.78l-21.63-5.91c-3.29-.52-3.73,2.53-4.5,5.69-11.7,42.16-23.31,84.39-34.46,126.7-.51,1.93-.46,5.97,1.76,6.62l21.21,5.61c-13,42.07-41.04,69.33-88.82,59.22l-39.42-11.36,5.23-20.39c.94-3.75-1.95-4.75-5.42-2.88-53.61,32.44-127.04,5.2-163.3-42.44-68.27-89.71-26.76-218.3,78.96-254.63,54.6-18.76,126.5-5.67,151.23,51.52,1.47,2.67,4.01,3.52,6.05.9,1.53-8.26,3.65-16.32,6.36-24.17h-.01s86.85,23.48,86.85,23.48l-.09,82.04Z"/>
|
||||
<path class="st0" d="M293.96,218.28c-54.85,5.6-83.76,70.08-53.21,115.65,30.77,45.89,99.44,40.22,125.24-7.62,28.44-52.74-12.67-114.09-72.03-108.03Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 95 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
Before Width: | Height: | Size: 385 B |