adding volunteer page
This commit is contained in:
parent
d15058f0c9
commit
1eb24d812e
8 changed files with 458 additions and 70 deletions
|
|
@ -2,8 +2,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Registration.API.Services;
|
using Registration.API.Services;
|
||||||
using Registration.Domain.Models;
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
namespace Registration.API.Controllers
|
namespace Registration.API.Controllers;
|
||||||
{
|
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class VolunteerController(IVbytesVolunteerRelayService relayService) : ControllerBase
|
public class VolunteerController(IVbytesVolunteerRelayService relayService) : ControllerBase
|
||||||
|
|
@ -15,12 +15,11 @@ namespace Registration.API.Controllers
|
||||||
{
|
{
|
||||||
var result = await _relayService.RegisterVolunteerAsync(volunteer, cancellationToken);
|
var result = await _relayService.RegisterVolunteerAsync(volunteer, cancellationToken);
|
||||||
|
|
||||||
if (result.Success)
|
if (result.StatusCode == 200 && !result.Message.Contains("401"))
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatusCode(result.StatusCode, new { message = result.Message });
|
return Unauthorized();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,8 @@ public class EventContent
|
||||||
public string LocationAddress { get; set; } = string.Empty;
|
public string LocationAddress { get; set; } = string.Empty;
|
||||||
public string WhatToBring { get; set; } = string.Empty;
|
public string WhatToBring { get; set; } = string.Empty;
|
||||||
public string RulesAndGdpr { get; set; } = string.Empty;
|
public string RulesAndGdpr { get; set; } = string.Empty;
|
||||||
|
public string AdditionalInfo { get; set; } = string.Empty;
|
||||||
|
public bool RegistrationEnabled { get; set; } = true;
|
||||||
|
public bool VisitorOnly { get; set; } = false;
|
||||||
|
public string VolunteerAreas { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class MemberRepository(IConfiguration configuration) : IMemberRepository
|
||||||
CREATE INDEX IF NOT EXISTS idx_members_ssn_index ON members(ssn_index);
|
CREATE INDEX IF NOT EXISTS idx_members_ssn_index ON members(ssn_index);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS event_content (
|
CREATE TABLE IF NOT EXISTS event_content (
|
||||||
id INT PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
sub_title TEXT,
|
sub_title TEXT,
|
||||||
event_date TEXT,
|
event_date TEXT,
|
||||||
|
|
@ -54,11 +54,21 @@ public class MemberRepository(IConfiguration configuration) : IMemberRepository
|
||||||
location_name TEXT,
|
location_name TEXT,
|
||||||
location_address TEXT,
|
location_address TEXT,
|
||||||
what_to_bring TEXT,
|
what_to_bring TEXT,
|
||||||
rules_and_gdpr TEXT
|
rules_and_gdpr TEXT,
|
||||||
|
additional_info TEXT,
|
||||||
|
registration_enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
visitor_only BOOLEAN DEFAULT FALSE,
|
||||||
|
volunteer_areas TEXT DEFAULT ''
|
||||||
);
|
);
|
||||||
|
ALTER TABLE event_content ADD COLUMN IF NOT EXISTS additional_info TEXT;
|
||||||
|
ALTER TABLE event_content ADD COLUMN IF NOT EXISTS registration_enabled BOOLEAN DEFAULT TRUE;
|
||||||
|
ALTER TABLE event_content ADD COLUMN IF NOT EXISTS visitor_only BOOLEAN DEFAULT FALSE;
|
||||||
|
ALTER TABLE event_content ADD COLUMN IF NOT EXISTS volunteer_areas TEXT DEFAULT '';
|
||||||
|
|
||||||
INSERT INTO event_content (id, title, sub_title, event_date, event_time, location_name, location_address, what_to_bring, rules_and_gdpr)
|
UPDATE event_content SET volunteer_areas = 'Kiosk & Kök' || chr(10) || 'Städning' || chr(10) || 'Entré & Incheckning' || chr(10) || 'Teknisk Support' || chr(10) || 'All-round' WHERE id = 1 AND (volunteer_areas IS NULL OR volunteer_areas = '');
|
||||||
SELECT 1, '', '', '', '', '', '', '', ''
|
|
||||||
|
INSERT INTO event_content (id, title, sub_title, event_date, event_time, location_name, location_address, what_to_bring, rules_and_gdpr, additional_info, registration_enabled, visitor_only, volunteer_areas)
|
||||||
|
SELECT 1, '', '', '', '', '', '', '', '', '', TRUE, FALSE, 'Kiosk & Kök' || chr(10) || 'Städning' || chr(10) || 'Entré & Incheckning' || chr(10) || 'Teknisk Support' || chr(10) || 'All-round'
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM event_content WHERE id = 1);
|
WHERE NOT EXISTS (SELECT 1 FROM event_content WHERE id = 1);
|
||||||
";
|
";
|
||||||
|
|
||||||
|
|
@ -98,7 +108,7 @@ public class MemberRepository(IConfiguration configuration) : IMemberRepository
|
||||||
public async Task<EventContent> GetEventContent()
|
public async Task<EventContent> GetEventContent()
|
||||||
{
|
{
|
||||||
using var connection = CreateConnection();
|
using var connection = CreateConnection();
|
||||||
var content = await connection.QueryFirstOrDefaultAsync<EventContent>("SELECT title, sub_title as SubTitle, event_date as EventDate, event_time as EventTime, location_name as LocationName, location_address as LocationAddress, what_to_bring as WhatToBring, rules_and_gdpr as RulesAndGdpr FROM event_content WHERE id = 1");
|
var content = await connection.QueryFirstOrDefaultAsync<EventContent>("SELECT title, sub_title as SubTitle, event_date as EventDate, event_time as EventTime, location_name as LocationName, location_address as LocationAddress, what_to_bring as WhatToBring, rules_and_gdpr as RulesAndGdpr, additional_info as AdditionalInfo, registration_enabled as RegistrationEnabled, visitor_only as VisitorOnly, volunteer_areas as VolunteerAreas FROM event_content WHERE id = 1");
|
||||||
return content ?? new EventContent();
|
return content ?? new EventContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +124,11 @@ public class MemberRepository(IConfiguration configuration) : IMemberRepository
|
||||||
location_name = @LocationName,
|
location_name = @LocationName,
|
||||||
location_address = @LocationAddress,
|
location_address = @LocationAddress,
|
||||||
what_to_bring = @WhatToBring,
|
what_to_bring = @WhatToBring,
|
||||||
rules_and_gdpr = @RulesAndGdpr
|
rules_and_gdpr = @RulesAndGdpr,
|
||||||
|
additional_info = @AdditionalInfo,
|
||||||
|
registration_enabled = @RegistrationEnabled,
|
||||||
|
visitor_only = @VisitorOnly,
|
||||||
|
volunteer_areas = @VolunteerAreas
|
||||||
WHERE id = 1";
|
WHERE id = 1";
|
||||||
await connection.ExecuteAsync(sql, content);
|
await connection.ExecuteAsync(sql, content);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ interface EventContent {
|
||||||
locationAddress: string;
|
locationAddress: string;
|
||||||
whatToBring: string;
|
whatToBring: string;
|
||||||
rulesAndGdpr: string;
|
rulesAndGdpr: string;
|
||||||
|
additionalInfo: string;
|
||||||
|
registrationEnabled: boolean;
|
||||||
|
visitorOnly: boolean;
|
||||||
|
volunteerAreas: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AdminDashboard() {
|
export default function AdminDashboard() {
|
||||||
|
|
@ -97,8 +101,37 @@ export default function AdminDashboard() {
|
||||||
|
|
||||||
<form onSubmit={handleSave} className="bg-white p-8 rounded-lg shadow-md space-y-6">
|
<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="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="col-span-2 flex flex-col sm:flex-row gap-6 bg-blue-50 p-4 rounded-md">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="registrationEnabled"
|
||||||
|
name="registrationEnabled"
|
||||||
|
className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded cursor-pointer"
|
||||||
|
checked={content.registrationEnabled}
|
||||||
|
onChange={(e) => setContent(prev => prev ? { ...prev, registrationEnabled: e.target.checked } : null)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="registrationEnabled" className="ml-3 text-sm font-semibold text-gray-900 cursor-pointer">
|
||||||
|
Visa Registrerings Knappar
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="visitorOnly"
|
||||||
|
name="visitorOnly"
|
||||||
|
className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded cursor-pointer"
|
||||||
|
checked={content.visitorOnly}
|
||||||
|
onChange={(e) => setContent(prev => prev ? { ...prev, visitorOnly: e.target.checked } : null)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="visitorOnly" className="ml-3 text-sm font-semibold text-gray-900 cursor-pointer">
|
||||||
|
Endast Besöks-registrering (Ingen Dator)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">Page Title</label>
|
<label className="block text-sm font-medium text-gray-700">HuvudSidan Titel</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="title"
|
name="title"
|
||||||
|
|
@ -118,7 +151,7 @@ export default function AdminDashboard() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Event Date</label>
|
<label className="block text-sm font-medium text-gray-700">LAN Datum</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="eventDate"
|
name="eventDate"
|
||||||
|
|
@ -128,7 +161,7 @@ export default function AdminDashboard() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Event Time</label>
|
<label className="block text-sm font-medium text-gray-700">LAN Tid</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="eventTime"
|
name="eventTime"
|
||||||
|
|
@ -138,7 +171,7 @@ export default function AdminDashboard() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Location Name</label>
|
<label className="block text-sm font-medium text-gray-700">Plats namn "Viskafors"</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="locationName"
|
name="locationName"
|
||||||
|
|
@ -148,7 +181,7 @@ export default function AdminDashboard() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Location Address</label>
|
<label className="block text-sm font-medium text-gray-700">Adress</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="locationAddress"
|
name="locationAddress"
|
||||||
|
|
@ -158,7 +191,17 @@ export default function AdminDashboard() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">What to Bring (One item per line)</label>
|
<label className="block text-sm font-medium text-gray-700">Extra Information (Visas under Info sektionen)</label>
|
||||||
|
<textarea
|
||||||
|
name="additionalInfo"
|
||||||
|
rows={4}
|
||||||
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
||||||
|
value={content.additionalInfo}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Att ta med till Lanet (En sak per rad)</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="whatToBring"
|
name="whatToBring"
|
||||||
rows={6}
|
rows={6}
|
||||||
|
|
@ -168,7 +211,7 @@ export default function AdminDashboard() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">Rules & GDPR</label>
|
<label className="block text-sm font-medium text-gray-700">Regler för LAN & GDPR</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="rulesAndGdpr"
|
name="rulesAndGdpr"
|
||||||
rows={4}
|
rows={4}
|
||||||
|
|
@ -177,6 +220,16 @@ export default function AdminDashboard() {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Funktionär Områden (En per rad)</label>
|
||||||
|
<textarea
|
||||||
|
name="volunteerAreas"
|
||||||
|
rows={4}
|
||||||
|
className="mt-1 block w-full rounded-md border border-gray-300 p-2 text-gray-900"
|
||||||
|
value={content.volunteerAreas}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import Script from "next/script";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
|
|
@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Vbytes LAN Registration",
|
||||||
description: "Generated by create next app",
|
description: "Register for the upcoming Vbytes LAN event.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|
@ -27,7 +28,36 @@ export default function RootLayout({
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
|
<Script id="matomo-tracking" strategy="afterInteractive">
|
||||||
|
{`
|
||||||
|
var _paq = window._paq = window._paq || [];
|
||||||
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
|
_paq.push(["setCookieDomain", "*.vbytes.se"]);
|
||||||
|
_paq.push(["setDomains", ["*.vbytes.se","*.anmalan.vbytes.se","*.boka.vbytes.se","*.inventory.vbytes.se","*.kiosk.vbytes.se","*.lan.vbytes.se","*.www.vbytes.se","*.anmalan.vbytes.se","*.boka.vbytes.se","*.inventory.vbytes.se","*.kiosk.vbytes.se","*.lan.vbytes.se","*.vbytes.se","*.www.vbytes.se"]]);
|
||||||
|
_paq.push(["enableCrossDomainLinking"]);
|
||||||
|
_paq.push(["setDoNotTrack", true]);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="https://analytics.vbytes.se/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||||
|
_paq.push(['setSiteId', '27']);
|
||||||
|
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
|
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||||
|
})();
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
{children}
|
{children}
|
||||||
|
<noscript>
|
||||||
|
<p>
|
||||||
|
<img
|
||||||
|
referrerPolicy="no-referrer-when-downgrade"
|
||||||
|
src="https://analytics.vbytes.se/matomo.php?idsite=27&rec=1"
|
||||||
|
style={{ border: 0 }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ interface EventContent {
|
||||||
locationAddress: string;
|
locationAddress: string;
|
||||||
whatToBring: string;
|
whatToBring: string;
|
||||||
rulesAndGdpr: string;
|
rulesAndGdpr: string;
|
||||||
|
additionalInfo: string;
|
||||||
|
registrationEnabled: boolean;
|
||||||
|
visitorOnly: boolean;
|
||||||
|
volunteerAreas: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
|
|
@ -58,63 +62,81 @@ export default function LandingPage() {
|
||||||
|
|
||||||
{/* Info Section */}
|
{/* Info Section */}
|
||||||
<main className="max-w-4xl mx-auto py-16 px-6 space-y-20">
|
<main className="max-w-4xl mx-auto py-16 px-6 space-y-20">
|
||||||
{(content.eventDate || content.eventTime || content.locationName || content.locationAddress) && (
|
{(content.eventDate || content.eventTime || content.locationName || content.locationAddress || content.additionalInfo) && (
|
||||||
<section className="space-y-6">
|
<section className="space-y-6">
|
||||||
<h2 className="text-3xl font-bold border-b-2 border-blue-500 pb-2 inline-block">Event Information</h2>
|
<h2 className="text-3xl font-bold border-b-2 border-blue-500 pb-2 inline-block">Information</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-6">
|
||||||
{(content.eventDate || content.eventTime) && (
|
{(content.eventDate || content.eventTime) && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="font-semibold text-lg text-blue-600">Date & Time</h3>
|
<h3 className="font-semibold text-lg text-blue-600">Tid/Datum</h3>
|
||||||
{content.eventDate && <p>{content.eventDate}</p>}
|
{content.eventDate && <p>{content.eventDate}</p>}
|
||||||
{content.eventTime && <p>{content.eventTime}</p>}
|
{content.eventTime && <p>{content.eventTime}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(content.locationName || content.locationAddress) && (
|
{(content.locationName || content.locationAddress) && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="font-semibold text-lg text-blue-600">Location</h3>
|
<h3 className="font-semibold text-lg text-blue-600">Plats</h3>
|
||||||
{content.locationName && <p>{content.locationName}</p>}
|
{content.locationName && <p>{content.locationName}</p>}
|
||||||
{content.locationAddress && <p>{content.locationAddress}</p>}
|
{content.locationAddress && <p>{content.locationAddress}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{content.additionalInfo && (
|
||||||
|
<div className="mt-8 text-lg text-gray-700 whitespace-pre-wrap leading-relaxed">
|
||||||
|
{content.additionalInfo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{content.whatToBring && (
|
{content.whatToBring && (
|
||||||
<section className="space-y-6">
|
<section className="space-y-6">
|
||||||
<h2 className="text-3xl font-bold border-b-2 border-blue-500 pb-2 inline-block">What to Bring</h2>
|
<h2 className="text-3xl font-bold border-b-2 border-blue-500 pb-2 inline-block">Ta Med(bokad LAN-plats)</h2>
|
||||||
<ul className="list-disc list-inside space-y-3 text-lg text-gray-700 whitespace-pre-line">
|
<ul className="list-disc list-inside space-y-3 text-lg text-gray-700">
|
||||||
{content.whatToBring}
|
{content.whatToBring.split('\n').filter(line => line.trim()).map((line, i) => (
|
||||||
|
<li key={i}>{line}</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{content.rulesAndGdpr && (
|
{content.rulesAndGdpr && (
|
||||||
<section className="space-y-6">
|
<section className="space-y-6">
|
||||||
<h2 className="text-3xl font-bold border-b-2 border-blue-500 pb-2 inline-block">Rules & GDPR</h2>
|
<h2 className="text-3xl font-bold border-b-2 border-blue-500 pb-2 inline-block">Regler för LAN & GDPR</h2>
|
||||||
<p className="text-lg text-gray-700 leading-relaxed">
|
<p className="text-lg text-gray-700 leading-relaxed whitespace-pre-wrap">
|
||||||
{content.rulesAndGdpr}
|
{content.rulesAndGdpr}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Call to Action */}
|
{/* Call to Action */}
|
||||||
|
{content.registrationEnabled && (
|
||||||
<section className="flex flex-col items-center justify-center space-y-8 pt-12 border-t border-gray-100">
|
<section className="flex flex-col items-center justify-center space-y-8 pt-12 border-t border-gray-100">
|
||||||
<div className="text-center space-y-2">
|
<div className="text-center space-y-2">
|
||||||
<h2 className="text-4xl font-bold">Ready to join?</h2>
|
<h2 className="text-4xl font-bold">Vill du delta?</h2>
|
||||||
<p className="text-gray-600 text-lg">Seats are limited, so secure your spot today!</p>
|
<p className="text-gray-600 text-lg">Begränsade platser, Säkra din plats idag!</p>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href="/register"
|
href="/register"
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white text-xl font-bold py-4 px-12 rounded-full shadow-lg transform transition hover:scale-105 active:scale-95"
|
className="bg-blue-600 hover:bg-blue-700 text-white text-xl font-bold py-4 px-12 rounded-full shadow-lg transform transition hover:scale-105 active:scale-95"
|
||||||
>
|
>
|
||||||
Register Now
|
Anmäl dig nu
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="bg-gray-50 py-12 text-center text-gray-500 border-t border-gray-100 relative">
|
<footer className="bg-gray-50 py-12 text-center text-gray-500 border-t border-gray-100 relative">
|
||||||
|
<div className="mb-8">
|
||||||
|
<p className="text-gray-600 mb-4">Vill du hjälpa till?</p>
|
||||||
|
<Link
|
||||||
|
href="/volunteer"
|
||||||
|
className="inline-block bg-gray-800 hover:bg-black text-white font-bold py-2 px-6 rounded-md transition"
|
||||||
|
>
|
||||||
|
Bli Funktionär
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
<p>© 2026 Vbytes Gaming. All rights reserved.</p>
|
<p>© 2026 Vbytes Gaming. All rights reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,19 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
interface EventContent {
|
||||||
|
registrationEnabled: boolean;
|
||||||
|
visitorOnly: boolean;
|
||||||
|
volunteerAreas: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [content, setContent] = useState<EventContent | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
firstName: "",
|
firstName: "",
|
||||||
surName: "",
|
surName: "",
|
||||||
|
|
@ -24,6 +33,30 @@ export default function RegisterPage() {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [message, setMessage] = useState({ type: "", text: "" });
|
const [message, setMessage] = useState({ type: "", text: "" });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("/api/Content")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
setContent(data);
|
||||||
|
if (data.visitorOnly) {
|
||||||
|
setFormData((prev) => ({ ...prev, isVisitor: true }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Failed to fetch content", err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (content && !content.registrationEnabled) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||||
|
<div className="max-w-md w-full bg-white p-8 rounded-xl shadow-md text-center">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">Registreringen är stängd</h1>
|
||||||
|
<p className="text-gray-600 mb-6">Vi tar tyvärr inte emot fler anmälningar just nu.</p>
|
||||||
|
<Link href="/" className="text-blue-600 hover:underline">Gå tillbaka till startsidan</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target;
|
||||||
const val = type === "checkbox" ? (e.target as HTMLInputElement).checked : value;
|
const val = type === "checkbox" ? (e.target as HTMLInputElement).checked : value;
|
||||||
|
|
@ -108,7 +141,7 @@ export default function RegisterPage() {
|
||||||
<div className="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-2">
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label htmlFor="ssn" className="block text-sm font-medium text-gray-700">Person Nummer*</label>
|
<label htmlFor="ssn" className="block text-sm font-medium text-gray-700">Personnummer*</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="ssn"
|
name="ssn"
|
||||||
|
|
@ -119,11 +152,11 @@ export default function RegisterPage() {
|
||||||
value={formData.ssn}
|
value={formData.ssn}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-gray-500">Required for membership verification. Not stored in event records.</p>
|
<p className="mt-1 text-xs text-gray-500">Används endast för att kolla om du redan är medlem eller inte. Sparas ej.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700">First Name *</label>
|
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700">Förnamn *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="firstName"
|
name="firstName"
|
||||||
|
|
@ -136,7 +169,7 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="surName" className="block text-sm font-medium text-gray-700">Surname *</label>
|
<label htmlFor="surName" className="block text-sm font-medium text-gray-700">Efternamn *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="surName"
|
name="surName"
|
||||||
|
|
@ -149,7 +182,7 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="grade" className="block text-sm font-medium text-gray-700">Grade *</label>
|
<label htmlFor="grade" className="block text-sm font-medium text-gray-700">Årskurs *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="grade"
|
name="grade"
|
||||||
|
|
@ -162,7 +195,7 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="phoneNumber" className="block text-sm font-medium text-gray-700">Phone Number</label>
|
<label htmlFor="phoneNumber" className="block text-sm font-medium text-gray-700">Mobilnummer</label>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
|
|
@ -186,11 +219,11 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-2 pt-4 border-t border-gray-100 mt-4">
|
<div className="sm:col-span-2 pt-4 border-t border-gray-100 mt-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Guardian Information</h3>
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Vårdnadshavares Information</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="guardianName" className="block text-sm font-medium text-gray-700">Guardian Name *</label>
|
<label htmlFor="guardianName" className="block text-sm font-medium text-gray-700">Vårdnadshavares Namn *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="guardianName"
|
name="guardianName"
|
||||||
|
|
@ -203,7 +236,7 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="guardianPhoneNumber" className="block text-sm font-medium text-gray-700">Guardian Phone *</label>
|
<label htmlFor="guardianPhoneNumber" className="block text-sm font-medium text-gray-700">Vårdnadshavares Mobilnummer *</label>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
name="guardianPhoneNumber"
|
name="guardianPhoneNumber"
|
||||||
|
|
@ -216,7 +249,7 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label htmlFor="guardianEmail" className="block text-sm font-medium text-gray-700">Guardian Email *</label>
|
<label htmlFor="guardianEmail" className="block text-sm font-medium text-gray-700">Vårdnadshavares Email *</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="guardianEmail"
|
name="guardianEmail"
|
||||||
|
|
@ -229,11 +262,11 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-2 pt-4 border-t border-gray-100 mt-4">
|
<div className="sm:col-span-2 pt-4 border-t border-gray-100 mt-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Additional Details</h3>
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Yttrligare uppgifter</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label htmlFor="friends" className="block text-sm font-medium text-gray-700">Friends (to sit with)</label>
|
<label htmlFor="friends" className="block text-sm font-medium text-gray-700">"Önska" Vänner (att sitta jämte)</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="friends"
|
name="friends"
|
||||||
|
|
@ -245,7 +278,7 @@ export default function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label htmlFor="specialDiet" className="block text-sm font-medium text-gray-700">Special Diet / Allergies</label>
|
<label htmlFor="specialDiet" className="block text-sm font-medium text-gray-700">Specialkost / Allergier</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="specialDiet"
|
name="specialDiet"
|
||||||
id="specialDiet"
|
id="specialDiet"
|
||||||
|
|
@ -263,14 +296,19 @@ export default function RegisterPage() {
|
||||||
id="isVisitor"
|
id="isVisitor"
|
||||||
name="isVisitor"
|
name="isVisitor"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||||
checked={formData.isVisitor}
|
checked={formData.isVisitor}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
disabled={content?.visitorOnly}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-sm">
|
<div className="ml-3 text-sm">
|
||||||
<label htmlFor="isVisitor" className="font-medium text-gray-700">Register as Visitor</label>
|
<label htmlFor="isVisitor" className="font-medium text-gray-700">Registrera dig som besökare</label>
|
||||||
<p className="text-gray-500">Check this if you are visiting and not bringing a computer.</p>
|
<p className="text-gray-500">
|
||||||
|
{content?.visitorOnly
|
||||||
|
? "Just nu tillåts endast besöks-registreringar."
|
||||||
|
: "Klicka i denna om du bara ska besöka och inte planerar på att ta med dator eller annan form av konsol."}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -286,8 +324,8 @@ export default function RegisterPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-sm">
|
<div className="ml-3 text-sm">
|
||||||
<label htmlFor="hasApprovedGdpr" className="font-medium text-gray-700">GDPR Consent *</label>
|
<label htmlFor="hasApprovedGdpr" className="font-medium text-gray-700">GDPR gokännande *</label>
|
||||||
<p className="text-gray-500">I agree to the processing of my personal data for the purpose of this event.</p>
|
<p className="text-gray-500">Jag Godkänner att min personliga uppgifter sparas och hanteras i syfte för detta event.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
228
src/Web/lan-frontend/app/volunteer/page.tsx
Normal file
228
src/Web/lan-frontend/app/volunteer/page.tsx
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
interface EventContent {
|
||||||
|
volunteerAreas: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VolunteerPage() {
|
||||||
|
const [content, setContent] = useState<EventContent | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
firstName: "",
|
||||||
|
surName: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
email: "",
|
||||||
|
hasApprovedGdpr: false,
|
||||||
|
selectedAreas: [] as string[],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [message, setMessage] = useState({ type: "", text: "" });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("/api/Content")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => setContent(data))
|
||||||
|
.catch((err) => console.error("Failed to fetch content", err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value, type, checked } = e.target;
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: type === "checkbox" ? checked : value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAreaToggle = (area: string) => {
|
||||||
|
setFormData((prev) => {
|
||||||
|
const areas = prev.selectedAreas.includes(area)
|
||||||
|
? prev.selectedAreas.filter((a) => a !== area)
|
||||||
|
: [...prev.selectedAreas, area];
|
||||||
|
return { ...prev, selectedAreas: areas };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (formData.selectedAreas.length === 0) {
|
||||||
|
setMessage({ type: "error", text: "Vänligen välj minst ett område du vill hjälpa till med." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setMessage({ type: "info", text: "Skickar in din ansökan..." });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
firstName: formData.firstName,
|
||||||
|
surName: formData.surName,
|
||||||
|
phoneNumber: formData.phoneNumber,
|
||||||
|
email: formData.email,
|
||||||
|
hasApprovedGdpr: formData.hasApprovedGdpr,
|
||||||
|
areasOfInterest: formData.selectedAreas.map(name => ({ name }))
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch("/api/Volunteer/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setMessage({ type: "success", text: "Tack för din ansökan! Vi kommer kontakta dig snart." });
|
||||||
|
setFormData({
|
||||||
|
firstName: "",
|
||||||
|
surName: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
email: "",
|
||||||
|
hasApprovedGdpr: false,
|
||||||
|
selectedAreas: [],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
setMessage({ type: "error", text: errorData.message || "Ett fel uppstod vid registreringen." });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Volunteer registration error:", error);
|
||||||
|
setMessage({ type: "error", text: "Kunde inte ansluta till servern. Försök igen senare." });
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const areas = content?.volunteerAreas?.split("\n").filter(a => a.trim()) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-2xl mx-auto bg-white p-8 rounded-xl shadow-md">
|
||||||
|
<div className="mb-6">
|
||||||
|
<Link href="/" className="text-blue-600 hover:text-blue-800 flex items-center gap-1 text-sm font-medium">
|
||||||
|
← Tillbaka till startsidan
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="text-center mb-10">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Bli Funktionär</h1>
|
||||||
|
<p className="mt-2 text-gray-600">Fyll i formuläret för att anmäla ditt intresse som funktionär under LANet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700">Förnamn *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="firstName"
|
||||||
|
id="firstName"
|
||||||
|
required
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2 border text-gray-900"
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="surName" className="block text-sm font-medium text-gray-700">Efternamn *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="surName"
|
||||||
|
id="surName"
|
||||||
|
required
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2 border text-gray-900"
|
||||||
|
value={formData.surName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="phoneNumber" className="block text-sm font-medium text-gray-700">Mobilnummer *</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="phoneNumber"
|
||||||
|
id="phoneNumber"
|
||||||
|
required
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2 border text-gray-900"
|
||||||
|
value={formData.phoneNumber}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700">E-post *</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
id="email"
|
||||||
|
required
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2 border text-gray-900"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Områden jag kan hjälpa till med *</label>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
|
{areas.map((area) => (
|
||||||
|
<label key={area} className="flex items-center space-x-3 p-3 border rounded-md hover:bg-gray-50 cursor-pointer transition">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||||
|
checked={formData.selectedAreas.includes(area)}
|
||||||
|
onChange={() => handleAreaToggle(area)}
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-gray-700">{area}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sm:col-span-2 space-y-4 pt-4 border-t border-gray-100">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<input
|
||||||
|
id="hasApprovedGdpr"
|
||||||
|
name="hasApprovedGdpr"
|
||||||
|
type="checkbox"
|
||||||
|
required
|
||||||
|
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||||
|
checked={formData.hasApprovedGdpr}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm">
|
||||||
|
<label htmlFor="hasApprovedGdpr" className="font-medium text-gray-700">GDPR-godkännande *</label>
|
||||||
|
<p className="text-gray-500">Jag godkänner att mina personuppgifter behandlas i syfte för detta evenemang.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{message.text && (
|
||||||
|
<div className={`p-4 rounded-md ${message.type === "success" ? "bg-green-50 text-green-800 border border-green-200" :
|
||||||
|
message.type === "error" ? "bg-red-50 text-red-800 border border-red-200" :
|
||||||
|
"bg-blue-50 text-blue-800 border border-blue-200"
|
||||||
|
}`}>
|
||||||
|
{message.text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="pt-6">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-blue-400"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "Skickar..." : "Anmäl dig som funktionär"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue