This commit is contained in:
1 parent
b32fc64de4
commit
2b983e5c6d
10 files changed
+450
-277
No files matched your search
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
serverIp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { serverIp }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="alert">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Die Anmeldung für Bedrock-Spieler ist über die Webseite nicht mehr möglich. Die Anmeldung erfolgt zukünftig direkt
|
||||||
|
über den Minecraft-Server unter <b>{serverIp}</b>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
onSelect: (edition: 'java' | 'bedrock') => void;
|
||||||
|
selected?: 'java' | 'bedrock' | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { onSelect, selected = null }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="divider">Minecraft-Version</div>
|
||||||
|
<p class="text-center mb-4">Bitte wähle zuerst deine Minecraft-Version aus.</p>
|
||||||
|
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline h-auto py-4 flex-col gap-1"
|
||||||
|
class:btn-active={selected === 'java'}
|
||||||
|
onclick={() => onSelect('java')}
|
||||||
|
>
|
||||||
|
<span class="text-lg">Java (PC)</span>
|
||||||
|
<span class="text-xs font-normal">Anmeldung über die Webseite</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline h-auto py-4 flex-col gap-1"
|
||||||
|
class:btn-active={selected === 'bedrock'}
|
||||||
|
onclick={() => onSelect('bedrock')}
|
||||||
|
>
|
||||||
|
<span class="text-lg">Bedrock (Konsole und Handy)</span>
|
||||||
|
<span class="text-xs font-normal">Anmeldung über den Minecraft-Server</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { actions } from 'astro:actions';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
import Checkbox from '@components/input/Checkbox.svelte';
|
||||||
|
import Input from '@components/input/Input.svelte';
|
||||||
|
import Popup from '@components/popup/Popup.svelte';
|
||||||
|
import { popupState } from '@components/popup/Popup.ts';
|
||||||
|
import RegisteredPopup, { type RegistrationData } from './RegisteredPopup.svelte';
|
||||||
|
import RulesPopup from './RulesPopup.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
signupHash?: string;
|
||||||
|
serverIp: string;
|
||||||
|
discordLink: string;
|
||||||
|
paypalLink: string;
|
||||||
|
teamspeakLink: string;
|
||||||
|
startDate: string;
|
||||||
|
onFinished: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { signupHash, serverIp, discordLink, paypalLink, teamspeakLink, startDate, onFinished }: Props = $props();
|
||||||
|
|
||||||
|
let rulesAccepted = $state(false);
|
||||||
|
let rulesOpen = $state(false);
|
||||||
|
let rulesRead = $state(false);
|
||||||
|
let submitting = $state(false);
|
||||||
|
let registration = $state<RegistrationData | null>(null);
|
||||||
|
|
||||||
|
let form: HTMLFormElement;
|
||||||
|
|
||||||
|
function formPopupOnReload(event: BeforeUnloadEvent) {
|
||||||
|
if (
|
||||||
|
form.firstname.value ||
|
||||||
|
form.lastname.value ||
|
||||||
|
form.birthday.value ||
|
||||||
|
form.phone.value ||
|
||||||
|
form.username.value ||
|
||||||
|
form.privacy.checked ||
|
||||||
|
form.logs.checked ||
|
||||||
|
form.rules.checked
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let beforeUnloadAbortFn: (() => void) | null = null;
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener('beforeunload', formPopupOnReload);
|
||||||
|
beforeUnloadAbortFn = () => window.removeEventListener('beforeunload', formPopupOnReload);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
beforeUnloadAbortFn?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
form.reset();
|
||||||
|
rulesAccepted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit(event: SubmitEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (submitting) return;
|
||||||
|
|
||||||
|
submitting = true;
|
||||||
|
|
||||||
|
const { error } = await actions.signup.signup({
|
||||||
|
firstname: form.firstname.value,
|
||||||
|
lastname: form.lastname.value,
|
||||||
|
birthday: form.birthday.value,
|
||||||
|
phone: form.phone.value,
|
||||||
|
username: form.username.value,
|
||||||
|
edition: 'java',
|
||||||
|
hash: signupHash
|
||||||
|
});
|
||||||
|
|
||||||
|
submitting = false;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
popupState.set({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Fehler',
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
registration = {
|
||||||
|
firstname: form.firstname.value,
|
||||||
|
lastname: form.lastname.value,
|
||||||
|
birthday: form.birthday.value,
|
||||||
|
phone: form.phone.value,
|
||||||
|
username: form.username.value,
|
||||||
|
edition: 'Java'
|
||||||
|
};
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="alert">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Du kannst dich auch direkt in Minecraft unter <b>{serverIp}</b> anmelden.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
bind:this={form}
|
||||||
|
onsubmit={(e) => {
|
||||||
|
submit(e);
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
autocomplete="off"
|
||||||
|
method="get"
|
||||||
|
>
|
||||||
|
<div class="divider">Persönliche Angaben</div>
|
||||||
|
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-x-4">
|
||||||
|
<Input
|
||||||
|
id="firstname"
|
||||||
|
type="text"
|
||||||
|
label="Vorname"
|
||||||
|
required
|
||||||
|
validation={{
|
||||||
|
pattern: '^\\p{L}{2,}',
|
||||||
|
hint: 'Bitte gib Deinen vollständigen Vornamen an, dieser muss mindestens aus 2 Zeichen bestehen.'
|
||||||
|
}}
|
||||||
|
dynamicWidth
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="lastname"
|
||||||
|
type="text"
|
||||||
|
label="Nachname"
|
||||||
|
required
|
||||||
|
validation={{
|
||||||
|
pattern: '^\\p{L}{2,}',
|
||||||
|
hint: 'Bitte gib Deinen vollständigen Nachnamen an, dieser muss mindestens aus 2 Zeichen bestehen.'
|
||||||
|
}}
|
||||||
|
dynamicWidth
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="birthday"
|
||||||
|
type="date"
|
||||||
|
label="Geburtstag"
|
||||||
|
required
|
||||||
|
validation={{
|
||||||
|
max: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 6).toLocaleDateString('sv-SE'),
|
||||||
|
hint: 'Bitte gib Deinen vollständigen Geburtstag und die korrekte Jahreszahl an. Du musst mindestens 6 Jahre alt sein.'
|
||||||
|
}}
|
||||||
|
dynamicWidth
|
||||||
|
>
|
||||||
|
{#snippet notice()}
|
||||||
|
<span>Die Angabe hat keine Auswirkungen auf das Spielgeschehen.</span>
|
||||||
|
{/snippet}
|
||||||
|
</Input>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
type="tel"
|
||||||
|
label="Telefonnummer"
|
||||||
|
validation={{
|
||||||
|
pattern: '^[+\\(\\)\\s\\d]+$',
|
||||||
|
hint: 'Bitte gib Deine vollständige Telefonnummer an, diese darf keine ungültigen Zeichen enthalten'
|
||||||
|
}}
|
||||||
|
dynamicWidth
|
||||||
|
>
|
||||||
|
{#snippet notice()}
|
||||||
|
Diese nutzen wir, um Dich in der Whatsapp-Gruppe zuzuordnen und kontaktieren zu können.
|
||||||
|
<br />
|
||||||
|
<b>Die Angabe ist freiwillig, hilft den Administratoren jedoch sehr!</b>
|
||||||
|
{/snippet}
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
|
<div class="divider">Spiel</div>
|
||||||
|
<div class="mx-2 grid grid-cols-1 gap-x-4">
|
||||||
|
<Input id="username" type="text" label="Minecraft-Spielername" required dynamicWidth>
|
||||||
|
{#snippet notice()}
|
||||||
|
<span class="text-[.65rem]">
|
||||||
|
Bitte gib deinen Minecraft-Java-Namen an. Dieser muss exakt mit deinem Ingame-Namen übereinstimmen.
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="mx-2 grid gap-y-3 mb-6">
|
||||||
|
<Checkbox id="privacy" required>
|
||||||
|
{#snippet label()}
|
||||||
|
Ich bin mit der Speicherung meiner in der Anmeldung angegebenen, persönlichen Daten einverstanden. Siehe
|
||||||
|
<a class="link" href="https://mhsl.eu/id.html" target="_blank">Datenschutz</a>
|
||||||
|
{/snippet}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox id="logs" required>
|
||||||
|
{#snippet label()}
|
||||||
|
Ich bin mit der Speicherung in Form von Logs aller meiner, beim Spielen anfallenden, persönlichen Daten durch
|
||||||
|
den Server einverstanden
|
||||||
|
{/snippet}
|
||||||
|
{#snippet notice()}
|
||||||
|
<span class="text-[.65rem]">
|
||||||
|
Dies betrifft jede Interaktion im Spiel und zugehörige Daten wie z.B. Chatnachrichten welche vom Minecraft
|
||||||
|
Client an den Server übermittelt werden
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
id="rules"
|
||||||
|
required
|
||||||
|
bind:checked={rulesAccepted}
|
||||||
|
onclick={(event) => {
|
||||||
|
if (!rulesRead) {
|
||||||
|
event.preventDefault();
|
||||||
|
rulesOpen = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#snippet label()}
|
||||||
|
Ich bin mit den
|
||||||
|
<button
|
||||||
|
class="link"
|
||||||
|
onclick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
rulesOpen = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Regeln
|
||||||
|
</button>
|
||||||
|
einverstanden und achte sie
|
||||||
|
{/snippet}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-neutral" class:loading-overlay={submitting} disabled={submitting}>
|
||||||
|
Anmeldung absenden
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<RulesPopup
|
||||||
|
open={rulesOpen}
|
||||||
|
onAccept={() => {
|
||||||
|
rulesRead = true;
|
||||||
|
rulesAccepted = true;
|
||||||
|
}}
|
||||||
|
onClose={() => (rulesOpen = false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Popup />
|
||||||
|
|
||||||
|
<RegisteredPopup
|
||||||
|
{registration}
|
||||||
|
{discordLink}
|
||||||
|
{paypalLink}
|
||||||
|
{teamspeakLink}
|
||||||
|
{startDate}
|
||||||
|
newRegistrationAllowed={!signupHash}
|
||||||
|
onClose={() => {
|
||||||
|
registration = null;
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
@@ -1,26 +1,43 @@
|
|||||||
|
<script module lang="ts">
|
||||||
|
export type RegistrationData = {
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
birthday: string;
|
||||||
|
phone: string;
|
||||||
|
username: string;
|
||||||
|
edition: string;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { registeredPopupState } from '@app/website/signup/RegisteredPopup.ts';
|
|
||||||
import Input from '@components/input/Input.svelte';
|
import Input from '@components/input/Input.svelte';
|
||||||
import { onDestroy } from 'svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
registration: RegistrationData | null;
|
||||||
discordLink: string;
|
discordLink: string;
|
||||||
paypalLink: string;
|
paypalLink: string;
|
||||||
teamspeakLink: string;
|
teamspeakLink: string;
|
||||||
startDate: string;
|
startDate: string;
|
||||||
newRegistrationAllowed: boolean;
|
newRegistrationAllowed: boolean;
|
||||||
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { discordLink, paypalLink, teamspeakLink, startDate, newRegistrationAllowed }: Props = $props();
|
let { registration, discordLink, paypalLink, teamspeakLink, startDate, newRegistrationAllowed, onClose }: Props =
|
||||||
|
$props();
|
||||||
|
|
||||||
let skin: string | null = $state(null);
|
let skin: string | null = $state(null);
|
||||||
|
|
||||||
let modal: HTMLDialogElement;
|
let modal: HTMLDialogElement;
|
||||||
|
|
||||||
const cancel = registeredPopupState.subscribe(async (value) => {
|
$effect(() => {
|
||||||
if (!value) return;
|
if (!registration) return;
|
||||||
|
|
||||||
modal.show();
|
if (!modal.open) modal.show();
|
||||||
|
void loadSkin(registration.username);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadSkin(username: string) {
|
||||||
|
skin = null;
|
||||||
|
|
||||||
const skinview3d = await import('skinview3d');
|
const skinview3d = await import('skinview3d');
|
||||||
const skinViewer = new skinview3d.SkinViewer({
|
const skinViewer = new skinview3d.SkinViewer({
|
||||||
@@ -36,17 +53,19 @@
|
|||||||
skinViewer.camera.position.y = 22.0;
|
skinViewer.camera.position.y = 22.0;
|
||||||
skinViewer.camera.position.z = 42.0;
|
skinViewer.camera.position.z = 42.0;
|
||||||
|
|
||||||
await skinViewer.loadSkin(`https://mc-heads.net/skin/${value.username}`);
|
try {
|
||||||
skinViewer.render();
|
await skinViewer.loadSkin(`https://mc-heads.net/skin/${username}`);
|
||||||
skin = skinViewer.canvas.toDataURL();
|
skinViewer.render();
|
||||||
|
skin = skinViewer.canvas.toDataURL();
|
||||||
skinViewer.dispose();
|
} catch {
|
||||||
});
|
// keep the loading spinner if the skin cannot be loaded
|
||||||
|
} finally {
|
||||||
onDestroy(cancel);
|
skinViewer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<dialog class="modal" bind:this={modal} onclose={() => ($registeredPopupState = null)}>
|
<dialog class="modal" bind:this={modal} onclose={() => onClose()}>
|
||||||
<form method="dialog" class="modal-box xl:w-5/12 max-w-10/12 z-10">
|
<form method="dialog" class="modal-box xl:w-5/12 max-w-10/12 z-10">
|
||||||
<h1 class="text-center text-xl sm:text-3xl mb-8">Registrierung erfolgreich</h1>
|
<h1 class="text-center text-xl sm:text-3xl mb-8">Registrierung erfolgreich</h1>
|
||||||
<p>
|
<p>
|
||||||
@@ -73,12 +92,12 @@
|
|||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="flex justify-around mt-2 mb-4">
|
<div class="flex justify-around mt-2 mb-4">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 w-full sm:w-fit gap-x-4 gap-y-2">
|
<div class="grid grid-cols-1 sm:grid-cols-2 w-full sm:w-fit gap-x-4 gap-y-2">
|
||||||
<Input type="text" value={$registeredPopupState?.firstname} label="Vorname" disabled />
|
<Input type="text" value={registration?.firstname} label="Vorname" disabled />
|
||||||
<Input type="text" value={$registeredPopupState?.lastname} label="Nachname" disabled />
|
<Input type="text" value={registration?.lastname} label="Nachname" disabled />
|
||||||
<Input type="date" value={$registeredPopupState?.birthday} label="Geburtstag" disabled />
|
<Input type="date" value={registration?.birthday} label="Geburtstag" disabled />
|
||||||
<Input type="tel" value={$registeredPopupState?.phone} label="Telefonnummer" disabled />
|
<Input type="tel" value={registration?.phone} label="Telefonnummer" disabled />
|
||||||
<Input type="text" value={$registeredPopupState?.username} label="Spielername" disabled />
|
<Input type="text" value={registration?.username} label="Spielername" disabled />
|
||||||
<Input type="text" value={$registeredPopupState?.edition} label="Edition" disabled />
|
<Input type="text" value={registration?.edition} label="Edition" disabled />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative hidden md:flex justify-center w-[200px] my-4">
|
<div class="relative hidden md:flex justify-center w-[200px] my-4">
|
||||||
{#if skin}
|
{#if skin}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { atom } from 'nanostores';
|
|
||||||
|
|
||||||
export const registeredPopupState = atom<{
|
|
||||||
firstname: string;
|
|
||||||
lastname: string;
|
|
||||||
birthday: string;
|
|
||||||
phone: string;
|
|
||||||
username: string;
|
|
||||||
edition: string;
|
|
||||||
} | null>(null);
|
|
||||||
@@ -1,35 +1,41 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { rulesPopupState, rulesPopupRead } from './RulesPopup.ts';
|
|
||||||
import { rules } from '../../../rules.ts';
|
|
||||||
import { popupState } from '@components/popup/Popup.ts';
|
import { popupState } from '@components/popup/Popup.ts';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import { rules } from '../../../rules.ts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onAccept: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open, onAccept, onClose }: Props = $props();
|
||||||
|
|
||||||
const modalTimeoutSeconds = 30;
|
const modalTimeoutSeconds = 30;
|
||||||
|
|
||||||
let modalElem: HTMLDialogElement;
|
let modalElem: HTMLDialogElement;
|
||||||
|
let modalTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
let modalTimer = $state<ReturnType<typeof setInterval> | null>(null);
|
|
||||||
let modalSecondsOpen = $state(import.meta.env.PROD ? 0 : modalTimeoutSeconds);
|
let modalSecondsOpen = $state(import.meta.env.PROD ? 0 : modalTimeoutSeconds);
|
||||||
|
|
||||||
const cancel = rulesPopupState.subscribe((value) => {
|
$effect(() => {
|
||||||
if (value == 'open') {
|
if (open) {
|
||||||
modalElem.show();
|
if (!modalElem.open) modalElem.show();
|
||||||
modalTimer = setInterval(() => modalSecondsOpen++, 1000);
|
|
||||||
} else if (value == 'closed') {
|
if (modalTimer === null) {
|
||||||
clearInterval(modalTimer!);
|
modalTimer = setInterval(() => modalSecondsOpen++, 1000);
|
||||||
|
}
|
||||||
|
} else if (modalTimer !== null) {
|
||||||
|
clearInterval(modalTimer);
|
||||||
|
modalTimer = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(cancel);
|
onDestroy(() => {
|
||||||
|
if (modalTimer !== null) clearInterval(modalTimer);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<dialog
|
<dialog class="modal" onclose={() => onClose()} bind:this={modalElem}>
|
||||||
class="modal"
|
|
||||||
onclose={() => {
|
|
||||||
if ($rulesPopupState !== 'accepted') $rulesPopupState = 'closed';
|
|
||||||
}}
|
|
||||||
bind:this={modalElem}
|
|
||||||
>
|
|
||||||
<form method="dialog" class="modal-box flex flex-col max-h-[90%] max-w-[95%] md:max-w-[90%] lg:max-w-[75%]">
|
<form method="dialog" class="modal-box flex flex-col max-h-[90%] max-w-[95%] md:max-w-[90%] lg:max-w-[75%]">
|
||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||||
<div class="overflow-auto mt-5">
|
<div class="overflow-auto mt-5">
|
||||||
@@ -86,8 +92,7 @@
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$rulesPopupRead = true;
|
onAccept();
|
||||||
$rulesPopupState = 'accepted';
|
|
||||||
}}>Akzeptieren</button
|
}}>Akzeptieren</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import { atom } from 'nanostores';
|
|
||||||
|
|
||||||
export const rulesPopupState = atom<'open' | 'closed' | 'accepted'>('closed');
|
|
||||||
export const rulesPopupRead = atom(false);
|
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
---
|
---
|
||||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||||
import Checkbox from '@components/input/Checkbox.svelte';
|
import SignupFlow from '@app/website/signup/SignupFlow.svelte';
|
||||||
import Input from '@components/input/Input.svelte';
|
import { DISCORD_LINK, PAYPAL_LINK, SERVER_IP, START_DATE, TEAMSPEAK_LINK } from 'astro:env/server';
|
||||||
import Select from '@components/input/Select.svelte';
|
|
||||||
import RulesPopup from '@app/website/signup/RulesPopup.svelte';
|
|
||||||
import Popup from '@components/popup/Popup.svelte';
|
|
||||||
import RegisteredPopup from '@app/website/signup/RegisteredPopup.svelte';
|
|
||||||
import { DISCORD_LINK, PAYPAL_LINK, START_DATE, TEAMSPEAK_LINK } from 'astro:env/server';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
signupDisabled?: {
|
signupDisabled?: {
|
||||||
@@ -25,227 +20,22 @@ const { signupDisabled, signupHash } = Astro.props;
|
|||||||
class:list={[signupDisabled ? 'max-h-screen overflow-hidden' : undefined]}
|
class:list={[signupDisabled ? 'max-h-screen overflow-hidden' : undefined]}
|
||||||
>
|
>
|
||||||
<div class="relative grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 my-12 bg-base-100 shadow-lg h-min">
|
<div class="relative grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 my-12 bg-base-100 shadow-lg h-min">
|
||||||
<h1 class="text-center text-3xl lg:text-5xl">Anmeldung</h1>
|
<SignupFlow
|
||||||
<form id="signup" data-signup-hash={signupHash}>
|
client:load
|
||||||
<div class="divider">Persönliche Angaben</div>
|
{signupHash}
|
||||||
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-x-4">
|
serverIp={SERVER_IP}
|
||||||
<Input
|
discordLink={DISCORD_LINK}
|
||||||
id="firstname"
|
paypalLink={PAYPAL_LINK}
|
||||||
type="text"
|
teamspeakLink={TEAMSPEAK_LINK}
|
||||||
label="Vorname"
|
startDate={START_DATE}
|
||||||
required
|
/>
|
||||||
validation={{
|
|
||||||
pattern: '^\\p{L}{2,}',
|
|
||||||
hint: 'Bitte gib Deinen vollständigen Vornamen an, dieser muss mindestens aus 2 Zeichen bestehen.'
|
|
||||||
}}
|
|
||||||
dynamicWidth
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
id="lastname"
|
|
||||||
type="text"
|
|
||||||
label="Nachname"
|
|
||||||
required
|
|
||||||
validation={{
|
|
||||||
pattern: '^\\p{L}{2,}',
|
|
||||||
hint: 'Bitte gib Deinen vollständigen Nachnamen an, dieser muss mindestens aus 2 Zeichen bestehen.'
|
|
||||||
}}
|
|
||||||
dynamicWidth
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
id="birthday"
|
|
||||||
type="date"
|
|
||||||
label="Geburtstag"
|
|
||||||
required
|
|
||||||
validation={{
|
|
||||||
max: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 6).toLocaleDateString('sv-SE'),
|
|
||||||
hint: 'Bitte gib Deinen vollständigen Geburtstag und die korrekte Jahreszahl an. Du musst mindestens 6 Jahre alt sein.'
|
|
||||||
}}
|
|
||||||
dynamicWidth
|
|
||||||
>
|
|
||||||
<span slot="notice">Die Angabe hat keine Auswirkungen auf das Spielgeschehen.</span>
|
|
||||||
</Input>
|
|
||||||
<Input
|
|
||||||
id="phone"
|
|
||||||
type="tel"
|
|
||||||
label="Telefonnummer"
|
|
||||||
validation={{
|
|
||||||
pattern: '^[+\\(\\)\\s\\d]+$',
|
|
||||||
hint: 'Bitte gib Deine vollständige Telefonnummer an, diese darf keine ungültigen Zeichen enthalten'
|
|
||||||
}}
|
|
||||||
dynamicWidth
|
|
||||||
>
|
|
||||||
<span slot="notice">
|
|
||||||
Diese nutzen wir, um Dich in der Whatsapp-Gruppe zuzuordnen und kontaktieren zu können.
|
|
||||||
<br />
|
|
||||||
<b>Die Angabe ist freiwillig, hilft den Administratoren jedoch sehr!</b>
|
|
||||||
</span>
|
|
||||||
</Input>
|
|
||||||
</div>
|
|
||||||
<div class="divider">Spiel</div>
|
|
||||||
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-x-4">
|
|
||||||
<Input id="username" type="text" label="Minecraft-Spielername" required dynamicWidth>
|
|
||||||
<span slot="notice" class="text-[.65rem]">
|
|
||||||
Spielern der Bedrock-Edition wird empfohlen, ihren Microsoft-Account Namen einzugeben. Der
|
|
||||||
Bedrock-Spielername kann nicht immer einem Minecraft Account zugeordnet werden.
|
|
||||||
</span>
|
|
||||||
</Input>
|
|
||||||
<Select
|
|
||||||
id="edition"
|
|
||||||
values={{ java: 'Java (PC)', bedrock: 'Bedrock (Konsole und Handys)' }}
|
|
||||||
label="Edition"
|
|
||||||
required
|
|
||||||
dynamicWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<div class="mx-2 grid gap-y-3 mb-6">
|
|
||||||
<Checkbox id="checkbox" required>
|
|
||||||
<span slot="label">
|
|
||||||
Ich bin mit der Speicherung meiner in der Anmeldung angegebenen, persönlichen Daten einverstanden. Siehe{' '}
|
|
||||||
<a class="link" href="https://mhsl.eu/id.html" target="_blank">
|
|
||||||
Datenschutz
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</Checkbox>
|
|
||||||
<Checkbox id="logs" required>
|
|
||||||
<span slot="label">
|
|
||||||
Ich bin mit der Speicherung in Form von Logs aller meiner, beim Spielen anfallenden, persönlichen Daten
|
|
||||||
durch den Server einverstanden
|
|
||||||
</span>
|
|
||||||
<span slot="notice" class="text-[.65rem]">
|
|
||||||
Dies betrifft jede Interaktion im Spiel und zugehörige Daten wie z.B. Chatnachrichten welche vom Minecraft
|
|
||||||
Client an den Server übermittelt werden
|
|
||||||
</span>
|
|
||||||
</Checkbox>
|
|
||||||
<Checkbox id="rules" required>
|
|
||||||
<span slot="label">
|
|
||||||
Ich bin mit den{' '}
|
|
||||||
<a class="link" onclick="">
|
|
||||||
Regeln
|
|
||||||
</a>{' '}
|
|
||||||
einverstanden und achte sie
|
|
||||||
</span>
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
<button id="send-btn" class="btn btn-neutral">
|
|
||||||
Anmeldung absenden
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</WebsiteLayout>
|
</WebsiteLayout>
|
||||||
|
|
||||||
<RulesPopup client:idle />
|
|
||||||
|
|
||||||
<Popup client:idle />
|
|
||||||
|
|
||||||
<RegisteredPopup
|
|
||||||
client:idle
|
|
||||||
discordLink={DISCORD_LINK}
|
|
||||||
paypalLink={PAYPAL_LINK}
|
|
||||||
teamspeakLink={TEAMSPEAK_LINK}
|
|
||||||
startDate={START_DATE}
|
|
||||||
newRegistrationAllowed={!signupHash}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{signupDisabled && (
|
{signupDisabled && (
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-black/50 backdrop-blur-sm z-10 rounded-xl flex justify-center items-center flex-col gap-2 pt-20 lg:pt-0 px-5 lg:px-10 2xl:px-28">
|
<div class="absolute top-0 left-0 w-full h-full bg-black/50 backdrop-blur-sm z-10 rounded-xl flex justify-center items-center flex-col gap-2 pt-20 lg:pt-0 px-5 lg:px-10 2xl:px-28">
|
||||||
<h1 class="text-center text-2xl sm:text-4xl">{signupDisabled.message}</h1>
|
<h1 class="text-center text-2xl sm:text-4xl">{signupDisabled.message}</h1>
|
||||||
<h3 class="text-xl sm:text-2xl">{signupDisabled.subMessage}</h3>
|
<h3 class="text-xl sm:text-2xl">{signupDisabled.subMessage}</h3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<script>
|
|
||||||
import { actions } from 'astro:actions';
|
|
||||||
import { popupState } from '@components/popup/Popup.ts';
|
|
||||||
import { rulesPopupState, rulesPopupRead } from '@app/website/signup/RulesPopup.ts';
|
|
||||||
import { registeredPopupState } from '@app/website/signup/RegisteredPopup.ts';
|
|
||||||
|
|
||||||
function setupClientValidation() {
|
|
||||||
const rulesCheckbox = document.getElementById('rules') as HTMLInputElement;
|
|
||||||
const rulesCheckboxRulesLink = rulesCheckbox.nextElementSibling!.querySelector('.link') as HTMLAnchorElement;
|
|
||||||
|
|
||||||
// add popup state subscriber to check when the accepted button is clicked
|
|
||||||
rulesPopupState.subscribe((value) => {
|
|
||||||
if (value == 'accepted') rulesCheckbox.checked = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// add click handler to open rules popup to rules checkbox
|
|
||||||
rulesCheckbox.addEventListener('click', (e) => {
|
|
||||||
if (!rulesPopupRead.get()) {
|
|
||||||
e.preventDefault();
|
|
||||||
rulesPopupState.set('open');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// add click handler to open rules popup when clicking the rules link in the rules checkbox label
|
|
||||||
rulesCheckboxRulesLink!.addEventListener('click', () => rulesPopupState.set('open'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupForm() {
|
|
||||||
const form = document.getElementById('signup')! as HTMLFormElement;
|
|
||||||
const sendBtn = document.getElementById('send-btn')! as HTMLButtonElement;
|
|
||||||
|
|
||||||
// reset form on site (re-)load
|
|
||||||
form.reset();
|
|
||||||
|
|
||||||
const sendSignup = async () => {
|
|
||||||
const { error } = await actions.signup.signup({
|
|
||||||
firstname: form.firstname.value,
|
|
||||||
lastname: form.lastname.value,
|
|
||||||
birthday: form.birthday.value,
|
|
||||||
phone: form.phone.value,
|
|
||||||
username: form.username.value,
|
|
||||||
edition: form.edition.value,
|
|
||||||
hash: form.dataset.signupHash
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
if (error.code == 'CONFLICT' || error.code == 'FORBIDDEN') {
|
|
||||||
popupState.set({
|
|
||||||
type: 'error',
|
|
||||||
title: 'Fehler',
|
|
||||||
message: error.message
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
popupState.set({
|
|
||||||
type: 'error',
|
|
||||||
title: 'Fehler',
|
|
||||||
message: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
registeredPopupState.set({
|
|
||||||
firstname: form.firstname.value,
|
|
||||||
lastname: form.lastname.value,
|
|
||||||
birthday: form.birthday.value,
|
|
||||||
phone: form.phone.value,
|
|
||||||
username: form.username.value,
|
|
||||||
edition: form.edition.value[0].toUpperCase() + form.edition.value.slice(1)
|
|
||||||
});
|
|
||||||
|
|
||||||
const cancel = registeredPopupState.subscribe((value) => {
|
|
||||||
if (value) return;
|
|
||||||
cancel();
|
|
||||||
form.reset();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
form.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
sendBtn.classList.add('loading-overlay');
|
|
||||||
await sendSignup();
|
|
||||||
sendBtn.classList.remove('loading-overlay');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = document.location.pathname;
|
|
||||||
document.addEventListener('astro:page-load', () => {
|
|
||||||
if (document.location.pathname !== pathname) return;
|
|
||||||
|
|
||||||
setupClientValidation();
|
|
||||||
setupForm();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { tick } from 'svelte';
|
||||||
|
import BedrockSignup from './BedrockSignup.svelte';
|
||||||
|
import EditionSelection from './EditionSelection.svelte';
|
||||||
|
import JavaSignup from './JavaSignup.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
signupHash?: string;
|
||||||
|
serverIp: string;
|
||||||
|
discordLink: string;
|
||||||
|
paypalLink: string;
|
||||||
|
teamspeakLink: string;
|
||||||
|
startDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { signupHash, serverIp, discordLink, paypalLink, teamspeakLink, startDate }: Props = $props();
|
||||||
|
|
||||||
|
type Step = 'java' | 'bedrock' | null;
|
||||||
|
|
||||||
|
let step = $state<Step>(null);
|
||||||
|
|
||||||
|
async function selectEdition(edition: 'java' | 'bedrock') {
|
||||||
|
step = edition;
|
||||||
|
|
||||||
|
if (edition === 'java') {
|
||||||
|
await tick();
|
||||||
|
document.getElementById('firstname')?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1 class="text-center text-3xl lg:text-5xl">Anmeldung</h1>
|
||||||
|
|
||||||
|
<EditionSelection onSelect={selectEdition} selected={step} />
|
||||||
|
|
||||||
|
<div class:hidden={step !== 'java'}>
|
||||||
|
<JavaSignup
|
||||||
|
{signupHash}
|
||||||
|
{serverIp}
|
||||||
|
{discordLink}
|
||||||
|
{paypalLink}
|
||||||
|
{teamspeakLink}
|
||||||
|
{startDate}
|
||||||
|
onFinished={() => (step = null)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class:hidden={step !== 'bedrock'}>
|
||||||
|
<BedrockSignup {serverIp} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -9,12 +9,13 @@
|
|||||||
hint: string;
|
hint: string;
|
||||||
};
|
};
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
onclick?: (event: MouseEvent) => void;
|
||||||
|
|
||||||
label?: Snippet | string;
|
label?: Snippet | string;
|
||||||
notice?: Snippet;
|
notice?: Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { id, checked = $bindable(), required, validation, disabled, label, notice }: Props = $props();
|
let { id, checked = $bindable(), required, validation, disabled, onclick, label, notice }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
class:validator={required || validation}
|
class:validator={required || validation}
|
||||||
required={required ? true : null}
|
required={required ? true : null}
|
||||||
disabled={disabled ? true : null}
|
disabled={disabled ? true : null}
|
||||||
|
{onclick}
|
||||||
/>
|
/>
|
||||||
<span class="ml-1">
|
<span class="ml-1">
|
||||||
{#if typeof label === 'string'}
|
{#if typeof label === 'string'}
|
||||||
|
|||||||
Reference in new issue
Block a user