Files
abi2007/src/main/resources/templates/index.html
T
2025-11-15 20:43:18 +01:00

769 lines
45 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 &amp; 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
&amp; 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>