769 lines
45 KiB
HTML
769 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
<html xmlns:th="http://www.thymeleaf.org">
|
||
<script>
|
||
(() => {
|
||
const isDark = localStorage.getItem('darkMode') === 'true' ||
|
||
(localStorage.getItem('darkMode') === null &&
|
||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||
|
||
if (isDark) {
|
||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<head>
|
||
<title>20 Jahre Abitur 2007</title>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<script src="/lib/axios.min.js"></script>
|
||
<script defer src="/lib/alpinejs.min.js"></script>
|
||
<link rel="stylesheet" href="/lib/bootstrap.min.css">
|
||
<link rel="stylesheet" href="/style.css" />
|
||
</head>
|
||
|
||
<body x-data="app()" :data-bs-theme="darkMode ? 'dark' : 'light'">
|
||
<div class="main-content">
|
||
<div class="container pt-5">
|
||
<div class="mb-4">
|
||
<div class="text-center p-4" style>
|
||
<div class="d-flex justify-content-center align-items-center mb-2 flex-wrap">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true" class="text-primary me-2">
|
||
<path
|
||
d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z">
|
||
</path>
|
||
<path d="M22 10v6"></path>
|
||
<path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5"></path>
|
||
</svg>
|
||
<h1 class="h3 mb-0 fw-bold text-primary" style="vertical-align:middle; display:inline-block;">
|
||
20 Jahre Abitur 2007
|
||
</h1>
|
||
</div>
|
||
<p class="text-secondary mb-0">
|
||
Wir wollen 2027 ein Abi-Treffen organisieren und brauchen dafür deine Kontaktdaten – und sag uns
|
||
bitte kurz, ob du dabei bist oder nicht.<br><br>Lukas, Michi & Max
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex justify-content-center align-items-center mb-4 flex-wrap">
|
||
<div class="d-flex align-items-center flex-wrap">
|
||
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||
:class="step === 1 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||
style="width:32px;height:32px;font-weight:600;">1</div>
|
||
<div class="mx-1">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor"
|
||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="m9 18 6-6-6-6"></path>
|
||
</svg>
|
||
</div>
|
||
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||
:class="step === 2 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||
style="width:32px;height:32px;font-weight:600;">2</div>
|
||
<div class="mx-1">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor"
|
||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="m9 18 6-6-6-6"></path>
|
||
</svg>
|
||
</div>
|
||
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||
:class="step === 3 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||
style="width:32px;height:32px;font-weight:600;">3</div>
|
||
<div class="mx-1">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor"
|
||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="m9 18 6-6-6-6"></path>
|
||
</svg>
|
||
</div>
|
||
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||
:class="step === 4 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||
style="width:32px;height:32px;font-weight:600;">4</div>
|
||
</div>
|
||
</div>
|
||
<div class="card shadow">
|
||
<div class="card-body">
|
||
<div id="step1" x-show="step === 1" x-transition>
|
||
<h2 class="h5 mb-3">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
||
<path d="M16 3.128a4 4 0 0 1 0 7.744"></path>
|
||
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
||
<circle cx="9" cy="7" r="4"></circle>
|
||
</svg> Schritt 1: Dein Name
|
||
</h2>
|
||
<div x-show="!userName">
|
||
<form @submit.prevent="findSelf" class="d-flex flex-column gap-3 mb-3">
|
||
<div class="mb-3">
|
||
<label class="form-label">
|
||
Dein Name (wie im Abi-Buch)
|
||
</label>
|
||
<input type="text" x-model="yourName" placeholder="Vorname Nachname" required
|
||
autocomplete="off" class="form-control" autofocus />
|
||
</div>
|
||
<button type="submit" class="btn btn-primary flex-fill"
|
||
:class="{'disabled': working}">Suchen</button>
|
||
</form>
|
||
</div>
|
||
<div class="alert py-2" :class="'alert-' + stepMsgType" x-text="stepMsg" x-show="stepMsg"></div>
|
||
<div x-show="userName">
|
||
<form @submit.prevent="continueStep1">
|
||
<p>
|
||
Hallo <span x-text="altName || userName"></span>! Falls sich dein Name geändert hat,
|
||
kannst du ihn hier anpassen - oder einfach direkt weitermachen.
|
||
</p>
|
||
<div id="altNameCheckboxDiv" class="form-check mt-3" x-show="userName" x-transition>
|
||
<input type="checkbox" x-model="altNameChecked" class="form-check-input"
|
||
id="altNameCheckbox" style="width:18px;height:18px;"
|
||
@change="$nextTick(() => { if(altNameChecked) $refs.altNameInput && $refs.altNameInput.focus(); else $refs.continueBtn && $refs.continueBtn.focus(); })" />
|
||
<label class="form-check-label" for="altNameCheckbox" style="font-size:1rem;">
|
||
Ich möchte meinen Namen aktualisieren.
|
||
</label>
|
||
</div>
|
||
<div id="altNameDiv" class="mt-2" x-show="altNameChecked && userName" x-transition>
|
||
<div class="mb-3">
|
||
<label class="form-label">Mein aktueller Name:</label>
|
||
<input type="text" x-model="altName" placeholder="Vorname Nachname"
|
||
autocomplete="off" class="form-control" x-ref="altNameInput"
|
||
@keydown.enter.prevent="$refs.continueBtn.click()" />
|
||
</div>
|
||
</div>
|
||
<div id="step1ContinueDiv" class="d-flex gap-2 mt-3 justify-content-center"
|
||
:class="userName ? '' : 'd-none'" x-transition>
|
||
<button id="step1ContinueBtn" type="submit" class="btn btn-primary flex-fill"
|
||
:class="{'disabled': working}" x-ref="continueBtn" autofocus>
|
||
Weiter <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||
<path d="m9 18 6-6-6-6"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div id="step2" x-show="step === 2" x-transition>
|
||
<h2 class="h5 mb-3">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||
<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path>
|
||
<rect x="2" y="4" width="20" height="16" rx="2"></rect>
|
||
</svg> Schritt 2: Deine Kontaktdaten
|
||
</h2>
|
||
<div class="alert py-2" :class="'alert-' + stepMsgType" x-text="stepMsg" x-show="stepMsg"></div>
|
||
<form @submit.prevent="submitContactData" class="mb-3" autocomplete="off">
|
||
<div class="mb-3">
|
||
<label class="form-label">E-Mail:</label>
|
||
<input id="step2InputEmail" type="email" x-model="email" placeholder="name@example.com"
|
||
required autocomplete="off" class="form-control" autofocus />
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Handynummer (optional):</label>
|
||
<input type="text" x-model="phone" placeholder="+49123456789" autocomplete="off"
|
||
class="form-control" />
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button type="button" class="btn btn-outline-secondary flex-fill"
|
||
:class="{'disabled': working}" @click="setStep(1)">
|
||
Zurück
|
||
</button>
|
||
<button type="submit" class="btn btn-primary flex-fill" :class="{'disabled': working}"
|
||
autofocus>Weiter <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||
<path d="m9 18 6-6-6-6"></path>
|
||
</svg></button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div id="step3" x-show="step === 3" x-transition>
|
||
<h2 class="h5 mb-3">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||
<path
|
||
d="M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384">
|
||
</path>
|
||
</svg> Schritt 3: Kennst du noch jemanden?
|
||
</h2>
|
||
<form @submit.prevent="findKnownContact" class="mb-3">
|
||
<div class="mb-3">
|
||
<label class="form-label">Name (wie im Abi-Buch):</label>
|
||
<input type="text" x-model="knownContactName" placeholder="Vorname Nachname" required
|
||
autocomplete="off" class="form-control" autofocus />
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button type="submit" class="btn btn-outline-secondary flex-fill"
|
||
:class="{'disabled': working}" autofocus>Suchen</button>
|
||
</div>
|
||
</form>
|
||
<div class="alert py-2" :class="'alert-' + stepMsgType" x-text="stepMsg" x-show="stepMsg"></div>
|
||
<ul class="list-group mb-3" id="enteredContacts"
|
||
x-show="enteredContacts && enteredContacts.length > 0">
|
||
<template x-for="(c, index) in enteredContacts || []" :key="c.name + '_' + index">
|
||
<li class="list-group-item">
|
||
<!-- Normal view -->
|
||
<div x-show="editingContactIndex !== index"
|
||
:class="editingContactIndex === index ? 'd-none' : 'd-flex justify-content-between align-items-center'">
|
||
<div>
|
||
<strong x-text="c.name"></strong><br>
|
||
<small class="text-muted">
|
||
<span x-text="c.email"></span>
|
||
<span x-show="c.phone" x-text="', ' + c.phone"></span>
|
||
</small>
|
||
</div>
|
||
<button class="btn btn-sm btn-outline-primary" @click="startEdit(index)"
|
||
:disabled="working">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||
fill="currentColor" viewBox="0 0 16 16">
|
||
<path
|
||
d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708L4.707 14.707a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168L12.146.146zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293L12.793 5.5z" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Edit view - fully inline -->
|
||
<div x-show="editingContactIndex === index"
|
||
:class="editingContactIndex === index ? 'row g-2 align-items-start' : 'd-none'">
|
||
<div class="col-12">
|
||
<strong x-text="c.name"></strong>
|
||
</div>
|
||
<div class="col-12 col-md">
|
||
<input type="email" class="form-control form-control-sm"
|
||
x-model="editingContactData.email" placeholder="E-Mail" required>
|
||
</div>
|
||
<div class="col-12 col-md">
|
||
<input type="text" class="form-control form-control-sm"
|
||
x-model="editingContactData.phone" placeholder="Telefon (optional)">
|
||
</div>
|
||
<div class="col-12 col-md-auto">
|
||
<div class="d-flex gap-1">
|
||
<button class="btn btn-sm btn-outline-danger" @click="cancelEdit()"
|
||
:disabled="working">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"
|
||
fill="currentColor" viewBox="0 0 16 16">
|
||
<path
|
||
d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z" />
|
||
</svg>
|
||
</button>
|
||
<button class="btn btn-sm btn-success" @click="saveContactData(index)"
|
||
:disabled="working">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"
|
||
fill="currentColor" viewBox="0 0 16 16">
|
||
<path
|
||
d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</template>
|
||
</ul>
|
||
<div class="d-flex gap-2 mt-3 justify-content-center">
|
||
<button type="button" class="btn btn-outline-secondary flex-fill"
|
||
:class="{'disabled': working}" @click="setStep(2)">
|
||
Zurück
|
||
</button>
|
||
<button id="step3ContinueBtn" class="btn btn-primary flex-fill"
|
||
:class="{'disabled': working}" @click="setStep(4)">Weiter <svg
|
||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true">
|
||
<path d="m9 18 6-6-6-6"></path>
|
||
</svg></button>
|
||
</div>
|
||
</div>
|
||
<div id="step4" x-show="step === 4" x-transition>
|
||
<h2 class="h5 mb-3">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||
</svg> Schritt 4: Bist du beim 20-Jahrestreffen am 13.11.2027 in Gevelsberg dabei?
|
||
</h2>
|
||
<div class="alert py-2" :class="'alert-' + stepMsgType" x-text="stepMsg" x-show="stepMsg"></div>
|
||
<p x-show="!stepMsg && !committed">
|
||
Hast du Lust, am Samstag, den 13.11.2027, beim Nachtreffen in Gevelsberg dabei zu sein?
|
||
Genauere Infos und Details gibt's dann später hier und per Mail.
|
||
</p>
|
||
<p x-show="committed === 'YES'">
|
||
Du hast für Samstag, den 13.11.2027, <strong class="text-success">zugesagt</strong>.
|
||
Genauere Infos und
|
||
Details gibt's dann später hier und per Mail. Falls du deine Meinung ändern möchtest, kannst
|
||
du hier nochmal wählen.
|
||
</p>
|
||
<p x-show="committed === 'NO'">
|
||
Du hast für Samstag, den 13.11.2027, <strong class="text-danger">abgesagt</strong>. Falls du
|
||
deine
|
||
Meinung ändern möchtest, kannst du hier nochmal wählen.
|
||
</p>
|
||
<div class="d-flex gap-2 mb-3">
|
||
<button id="commitYes" class="btn flex-fill"
|
||
:class="{'btn-success disabled': committed != 'NO', 'btn-outline-success opacity-75' : committed == 'NO', 'disabled': working}"
|
||
@click="submitCommit('YES')" autofocus>Ja, ich bin dabei!</button>
|
||
<button id="commitNo" class="btn flex-fill"
|
||
:class="{'btn-danger disabled' : committed != 'YES','btn-outline-danger opacity-75' : committed == 'YES', 'disabled': working}"
|
||
@click="submitCommit('NO')">Nein, ich bin nicht dabei</button>
|
||
</div>
|
||
<div class="d-flex mt-2">
|
||
<button type="button" class="btn btn-outline-secondary flex-fill"
|
||
:class="{'disabled': working}" @click="setStep(3)">
|
||
Zurück
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<footer class="text-center py-4 bg-light border-top">
|
||
|
||
<div class="spacer"></div>
|
||
<a href="#" @click.prevent="showImprint = true" class="text-secondary" style="text-decoration: none;">Impressum
|
||
& Datenschutz</a>
|
||
|
||
<div class="spacer"></div>
|
||
|
||
<button class="dark-mode-toggle" @click="toggleDarkMode()"
|
||
:title="darkMode ? 'Switch to light mode' : 'Switch to dark mode'">
|
||
<svg x-show="!darkMode" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||
</svg>
|
||
<svg x-show="darkMode" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<path
|
||
d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4">
|
||
</path>
|
||
</svg>
|
||
</button>
|
||
</footer>
|
||
|
||
<div class="modal fade" :class="{'show': showImprint, 'd-block': showImprint}" x-transition.opacity>
|
||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Impressum & Datenschutz</h5>
|
||
<button type="button" class="btn-close" @click="showImprint = false" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<h4>Impressum</h4>
|
||
<p>
|
||
Lukas Haubaum<br>
|
||
Adresse aus Datenschutzgründen nur auf Nachfrage.
|
||
</p>
|
||
<p>
|
||
<strong>Kontakt:</strong><br>
|
||
E-Mail: abi2007@haubaum.de
|
||
</p>
|
||
|
||
<h5>Haftung für Inhalte</h5>
|
||
<p>
|
||
Ich habe die Inhalte dieser Seite mit größter Sorgfalt erstellt. Für die Richtigkeit,
|
||
Vollständigkeit und
|
||
Aktualität kann ich aber keine Gewähr übernehmen.
|
||
</p>
|
||
|
||
<h5>Haftung für Links</h5>
|
||
<p>
|
||
Auf dieser Seite kannst Du Links zu externen Webseiten Dritter finden, auf deren Inhalte ich
|
||
keinen
|
||
Einfluss habe.
|
||
Deshalb kann ich für diese fremden Inhalte auch keine Gewähr übernehmen.
|
||
</p>
|
||
|
||
<h5>Urheberrecht</h5>
|
||
<p>
|
||
Die von mir erstellten Inhalte und Werke auf dieser Seite unterliegen dem deutschen
|
||
Urheberrecht.
|
||
Beiträge
|
||
von anderen sind als solche gekennzeichnet.
|
||
</p>
|
||
|
||
<h4>Datenschutzerklärung</h4>
|
||
<p>
|
||
Der Schutz Deiner persönlichen Daten ist mir ein besonderes Anliegen. Ich verarbeite Deine Daten
|
||
daher
|
||
ausschließlich auf Grundlage der gesetzlichen Bestimmungen (DSGVO, TMG).
|
||
</p>
|
||
<h5>Erhebung und Speicherung personenbezogener Daten</h5>
|
||
<p>
|
||
Auf diesem Server werden keinerlei personenbezogene Daten gespeichert, außer den Kontaktdaten,
|
||
die
|
||
Du
|
||
freiwillig angibst (z.B. Name, E-Mail-Adresse).
|
||
</p>
|
||
<h5>Verwendung der Kontaktdaten</h5>
|
||
<p>
|
||
Deine angegebenen Kontaktdaten werden ausschließlich genutzt, um Dich im Rahmen des
|
||
Abiturjahrgangs
|
||
2007 zu
|
||
kontaktieren und werden nicht an Dritte wefindestitergegeben.
|
||
</p>
|
||
<h5>E-Mail-Bestätigungen und Einladungen</h5>
|
||
<p>
|
||
Wenn du deine E-Mail-Adresse angibst, bekommst du Bestätigungen zu deiner Anmeldung und
|
||
Einladungen oder Infos rund ums Abi-Treffen per E-Mail. Deine Adresse wird nicht für Werbung
|
||
genutzt und nicht weitergegeben.
|
||
</p>
|
||
<h5>Serverdaten</h5>
|
||
<p>Aus technischen Gründen werden bei deinem Besuch auf dieser Seite normalerweise Daten wie
|
||
Browsertyp, Betriebssystem, die Seite, von der du kommst (Referrer URL), besuchte Seiten, Datum
|
||
und Uhrzeit sowie deine IP-Adresse an den Server übermittelt. Ich erhebe diese Daten aber
|
||
grundsätzlich nicht(!), weil mir dein Datenschutz wichtig ist. Nur falls es technisch nötig ist,
|
||
kann es sein, dass solche Daten kurzfristig gespeichert werden, zum Beispiel zur Verbesserung,
|
||
Stabilität, Funktionalität oder Sicherheit der Seite. Rechtsgrundlage dafür ist Art. 6 Abs. 1
|
||
lit. f) DSGVO.</p>
|
||
<p>Falls solche Daten doch mal in sogenannten Server-Logfiles landen, werden sie nicht mit anderen
|
||
Daten von dir zusammengeführt und spätestens nach 14 Tagen gelöscht.</p>
|
||
|
||
<h5>Kontaktanfragen / Kontaktmöglichkeit</h5>
|
||
<p>Wenn du mir per Kontaktformular oder E-Mail schreibst, nutze ich die von dir angegebenen Daten
|
||
nur, um deine Anfrage zu bearbeiten. Ohne diese Angaben kann ich dir leider nicht oder nur
|
||
eingeschränkt antworten.</p>
|
||
<p>Rechtsgrundlage dafür ist Art. 6 Abs. 1 lit. b) DSGVO.</p>
|
||
|
||
<h5>Deine Rechte</h5>
|
||
<p>
|
||
Du hast grundsätzlich das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung,
|
||
Datenübertragbarkeit,
|
||
Widerruf und Widerspruch.
|
||
</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" @click="showImprint = false">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function app() {
|
||
return {
|
||
darkMode: false,
|
||
showImprint: false,
|
||
step: 1,
|
||
stepMsg: '',
|
||
stepMsgType: '',
|
||
working: false,
|
||
yourName: '',
|
||
altNameChecked: false,
|
||
altName: '[[${altName}]]',
|
||
showStep1Continue: false,
|
||
userName: '[[${userName}]]',
|
||
userToken: '[[${token}]]',
|
||
pendingUpdatedName: null,
|
||
email: '[[${email}]]',
|
||
phone: '[[${phone}]]',
|
||
knownContactName: '',
|
||
enteredContacts: [],
|
||
editingContactIndex: -1,
|
||
editingContactData: { email: '', phone: '' },
|
||
committed: '[[${committed}]]',
|
||
|
||
init() {
|
||
const isDarkApplied = document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
||
this.darkMode = isDarkApplied;
|
||
|
||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||
if (localStorage.getItem('darkMode') === null) {
|
||
this.darkMode = e.matches;
|
||
}
|
||
});
|
||
},
|
||
|
||
toggleDarkMode() {
|
||
this.darkMode = !this.darkMode;
|
||
localStorage.setItem('darkMode', this.darkMode.toString());
|
||
},
|
||
|
||
setStep(step) {
|
||
this.stepMsg = '';
|
||
this.stepMsgType = '';
|
||
this.step = step;
|
||
|
||
if (!this.enteredContacts) {
|
||
this.enteredContacts = [];
|
||
}
|
||
|
||
if (step === 3 && this.userToken) {
|
||
this.loadPendingContacts();
|
||
}
|
||
},
|
||
|
||
async loadPendingContacts() {
|
||
try {
|
||
const resp = await axios.get('/api/v1/contact/data/pending', {
|
||
headers: { 'Authorization': this.userToken },
|
||
validateStatus: () => true
|
||
});
|
||
|
||
if (resp.status === 200) {
|
||
this.enteredContacts = (this.enteredContacts || []).filter((c) => c.isNew);
|
||
resp.data.forEach(contact => {
|
||
this.enteredContacts.push({
|
||
name: contact.contactName,
|
||
email: contact.email,
|
||
phone: contact.phone || ''
|
||
});
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('Fehler beim Laden der Kontakte:', error);
|
||
}
|
||
},
|
||
|
||
async findSelf() {
|
||
this.stepMsg = '';
|
||
this.stepMsgType = '';
|
||
this.altNameChecked = false;
|
||
this.altName = '';
|
||
this.pendingUpdatedName = null;
|
||
try {
|
||
this.working = true;
|
||
const resp = await axios.get(`/api/v1/contact`, { params: { name: this.yourName.trim() }, validateStatus: () => true });
|
||
if (resp.status === 200) {
|
||
this.userName = resp.data.name;
|
||
this.userToken = resp.data.token || null;
|
||
} else if (resp.status === 404) {
|
||
this.stepMsg = 'Sorry, wir konnten dich nicht finden. Schreib deinen Namen bitte so, wie er im Abi-Buch stand!';
|
||
this.stepMsgType = 'warning';
|
||
} else if (resp.status === 208) {
|
||
this.stepMsg = 'Du hast deine Daten bereits eingetragen - danke dir! Über deinen persönlichen Link kannst du sie jederzeit aktualisieren. Den Link haben wir dir per E-Mail zugeschickt.';
|
||
this.stepMsgType = 'info';
|
||
this.userName = null;
|
||
} else {
|
||
this.stepMsg = 'Uups, da ist was schiefgelaufen. Versuch es gleich nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
} catch {
|
||
this.stepMsg = 'Uups, da ist was schiefgelaufen. Versuch es gleich nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
this.working = false;
|
||
if (this.userName) {
|
||
setTimeout(() => {
|
||
document.getElementById('step1ContinueBtn').focus();
|
||
}, 100);
|
||
}
|
||
},
|
||
async continueStep1() {
|
||
this.pendingUpdatedName = this.altNameChecked ? this.altName.trim() : null;
|
||
if (this.pendingUpdatedName && !this.userToken) {
|
||
this.working = true;
|
||
try {
|
||
await axios.post('/api/v1/contact', {
|
||
name: this.userName,
|
||
updatedName: this.pendingUpdatedName
|
||
});
|
||
} catch {
|
||
this.stepMsg = 'Uups, da ist was schiefgelaufen. Versuch es gleich nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
this.working = false;
|
||
}
|
||
setTimeout(() => {
|
||
document.getElementById('step2InputEmail').focus();
|
||
}, 100);
|
||
this.setStep(2);
|
||
},
|
||
|
||
async submitContactData() {
|
||
this.stepMsg = '';
|
||
this.stepMsgType = '';
|
||
this.working = true;
|
||
try {
|
||
const resp = await axios.post('/api/v1/contact/data', {
|
||
name: this.userName,
|
||
reportedBy: this.userName,
|
||
email: this.email.trim(),
|
||
phone: this.phone.trim()
|
||
}, { validateStatus: () => true });
|
||
if (resp.status === 200 || resp.status === 201) {
|
||
this.setStep(3);
|
||
} else if (resp.status === 400) {
|
||
this.stepMsg = 'Bitte schau nochmal über deine Angaben – da stimmt was nicht.';
|
||
this.stepMsgType = 'warning';
|
||
} else {
|
||
this.stepMsg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
} catch {
|
||
this.stepMsg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
this.working = false;
|
||
|
||
|
||
setTimeout(() => {
|
||
document.getElementById('step3ContinueBtn').focus();
|
||
}, 100);
|
||
},
|
||
|
||
async findKnownContact() {
|
||
this.stepMsg = '';
|
||
this.stepMsgType = '';
|
||
this.working = true;
|
||
try {
|
||
const resp = await axios.get('/api/v1/contact', {
|
||
params: { name: this.knownContactName.trim() },
|
||
headers: this.userToken ? { 'Authorization': this.userToken } : {},
|
||
validateStatus: () => true
|
||
});
|
||
if (resp.status === 200) {
|
||
const contactName = resp.data.name.trim();
|
||
if (contactName == this.yourName || contactName == this.userName) {
|
||
this.stepMsg = 'Witzig, das bist du selbst - deine Daten hast du ja schon eingetragen!';
|
||
this.stepMsgType = 'warning';
|
||
this.knownContactName = '';
|
||
} else {
|
||
const existingIndex = this.enteredContacts.findIndex(c => c.name === contactName);
|
||
if (existingIndex !== -1) {
|
||
this.startEdit(existingIndex);
|
||
this.stepMsg = 'Kontakt bereits vorhanden - du kannst die Daten hier bearbeiten.';
|
||
this.stepMsgType = 'info';
|
||
this.knownContactName = '';
|
||
} else {
|
||
this.enteredContacts.unshift({
|
||
name: contactName,
|
||
email: '',
|
||
phone: '',
|
||
isNew: true // Mark as new for cancel behavior
|
||
});
|
||
this.startEdit(0);
|
||
this.stepMsg = 'Super, trag bitte die Kontaktdaten ein!';
|
||
this.stepMsgType = 'success';
|
||
this.knownContactName = '';
|
||
}
|
||
}
|
||
} else if (resp.status === 404) {
|
||
this.stepMsg = 'Diesen Namen gibt es leider nicht in unserer Liste.';
|
||
this.stepMsgType = 'warning';
|
||
} else if (resp.status === 208) {
|
||
|
||
|
||
|
||
this.stepMsg = 'Für diese Person haben wir schon Kontaktdaten. Danke trotzdem fürs Mitmachen!';
|
||
this.stepMsgType = 'info';
|
||
} else {
|
||
this.stepMsg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
} catch {
|
||
this.stepMsg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
this.working = false;
|
||
},
|
||
|
||
startEdit(index) {
|
||
this.editingContactIndex = index;
|
||
this.editingContactData = {
|
||
email: this.enteredContacts[index].email,
|
||
phone: this.enteredContacts[index].phone || ''
|
||
};
|
||
|
||
// Focus the email input of the specific contact being edited
|
||
setTimeout(() => {
|
||
// Find all email inputs and select the one that's currently visible (not d-none)
|
||
const emailInputs = document.querySelectorAll('.list-group-item input[type="email"]');
|
||
for (let input of emailInputs) {
|
||
const parentDiv = input.closest('div[x-show]');
|
||
if (parentDiv && !parentDiv.classList.contains('d-none')) {
|
||
input.focus();
|
||
input.select();
|
||
break;
|
||
}
|
||
}
|
||
}, 100);
|
||
},
|
||
|
||
cancelEdit() {
|
||
// If this is a new contact that hasn't been saved, remove it from the list
|
||
if (this.editingContactIndex >= 0 && this.enteredContacts[this.editingContactIndex].isNew) {
|
||
this.enteredContacts.splice(this.editingContactIndex, 1);
|
||
}
|
||
|
||
this.editingContactIndex = -1;
|
||
this.editingContactData = { email: '', phone: '' };
|
||
},
|
||
|
||
async saveContactData(index) {
|
||
this.working = true;
|
||
try {
|
||
const contact = this.enteredContacts[index];
|
||
const resp = await axios.post('/api/v1/contact/data', {
|
||
name: contact.name,
|
||
reportedBy: this.userName,
|
||
email: this.editingContactData.email.trim(),
|
||
phone: this.editingContactData.phone.trim()
|
||
}, {
|
||
headers: this.userToken ? { 'Authorization': this.userToken } : {},
|
||
validateStatus: () => true
|
||
});
|
||
|
||
if (resp.status === 200 || resp.status === 201) {
|
||
// Update the contact in our local list
|
||
this.enteredContacts[index].email = this.editingContactData.email.trim();
|
||
this.enteredContacts[index].phone = this.editingContactData.phone.trim();
|
||
// Remove the isNew flag since it's now saved
|
||
delete this.enteredContacts[index].isNew;
|
||
|
||
this.stepMsg = 'Kontakt erfolgreich gespeichert!';
|
||
this.stepMsgType = 'success';
|
||
this.editingContactIndex = -1;
|
||
this.editingContactData = { email: '', phone: '' };
|
||
} else if (resp.status === 400) {
|
||
this.stepMsg = 'Bitte prüfe die Angaben nochmal – da stimmt was nicht.';
|
||
this.stepMsgType = 'warning';
|
||
} else {
|
||
this.stepMsg = 'Fehler beim Speichern des Kontakts.';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
} catch {
|
||
this.stepMsg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
this.working = false;
|
||
},
|
||
|
||
async submitCommit(committed) {
|
||
this.working = true;
|
||
this.committed = committed;
|
||
try {
|
||
await axios.post('/api/v1/contact', {
|
||
name: this.userName,
|
||
updatedName: this.pendingUpdatedName,
|
||
token: this.userToken,
|
||
committed: this.committed
|
||
}, {
|
||
headers: this.userToken ? { 'Authorization': this.userToken } : {}
|
||
});
|
||
|
||
if (this.committed == 'YES') {
|
||
this.stepMsg = 'Danke für deine Rückmeldung! Schön, dass du dabei bist! Wir melden uns rechtzeitig mit allen weiteren Infos.' + (this.userToken ? '' : ' Du solltest schon automatisch eine E-Mail mit einem Link bekommen haben, über den du deine Daten jederzeit anpassen kannst.');
|
||
this.stepMsgType = 'success';
|
||
} else if (this.committed == 'NO') {
|
||
this.stepMsg = 'Danke für deine Rückmeldung! Schade, dass du nicht dabei bist.' + (this.userToken ? '' : ' Du solltest trotzdem automatisch eine E-Mail mit einem Link bekommen haben, über den du deine Daten jederzeit anpassen kannst und auch noch zusagen kannst, solltest du doch noch kommen wollen.');
|
||
this.stepMsgType = 'info';
|
||
}
|
||
} catch {
|
||
this.stepMsg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||
this.stepMsgType = 'danger';
|
||
}
|
||
this.working = false;
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
|
||
</html> |