änderung hier wird nun die verttragsnummer übergeben
This commit is contained in:
parent
795b8a66ce
commit
03913c6680
@ -1,135 +1,325 @@
|
||||
const express = require('express');
|
||||
const Database = require('better-sqlite3');
|
||||
const auth = require('../middleware/authMiddleware');
|
||||
<%- include('partials/header') %>
|
||||
|
||||
const router = express.Router();
|
||||
const db = new Database('plusfit.db');
|
||||
<style>
|
||||
.step { display: none; }
|
||||
.step.active { display: block; }
|
||||
|
||||
/* Übersicht */
|
||||
router.get('/', auth, (req, res) => {
|
||||
const vertragsarten = db
|
||||
.prepare('SELECT * FROM vertragsarten ORDER BY id ASC')
|
||||
.all();
|
||||
.container-form {
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
res.render('contracts', { vertragsarten });
|
||||
});
|
||||
.progress {
|
||||
height: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* Neu anlegen */
|
||||
router.post('/create', auth, (req, res) => {
|
||||
const { name, laufzeit, betrag, aktiv, beschreibung } = req.body;
|
||||
.step-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO vertragsarten
|
||||
(name, beschreibung, laufzeit, betrag, aktiv)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
name,
|
||||
beschreibung,
|
||||
laufzeit,
|
||||
betrag,
|
||||
aktiv ? 1 : 0
|
||||
);
|
||||
.step-nav span.active {
|
||||
font-weight: bold;
|
||||
color: #3b3be3;
|
||||
}
|
||||
|
||||
@media(max-width:768px){
|
||||
h4{font-size:18px;}
|
||||
button{width:100%;}
|
||||
}
|
||||
</style>
|
||||
|
||||
res.redirect('/contracts');
|
||||
});
|
||||
<div class="container-form">
|
||||
|
||||
// Vertragsart aktiv / inaktiv setzen
|
||||
router.post('/toggle/:id', auth, (req, res) => {
|
||||
const { id } = req.params;
|
||||
<% if (typeof error !== 'undefined') { %>
|
||||
<div class="alert alert-danger">
|
||||
⚠️ <%= error %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
db.prepare(`
|
||||
UPDATE vertragsarten
|
||||
SET aktiv = CASE
|
||||
WHEN aktiv = 1 THEN 0
|
||||
ELSE 1
|
||||
END
|
||||
WHERE id = ?
|
||||
`).run(id);
|
||||
<h3 class="text-center mb-4">Mitglied werden</h3>
|
||||
|
||||
res.redirect('/contracts');
|
||||
});
|
||||
<div class="progress">
|
||||
<div id="progressBar"
|
||||
class="progress-bar bg-primary"
|
||||
style="width:33%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Vertragsart deaktivieren + User migrieren
|
||||
router.post('/deactivate/:id', auth, (req, res) => {
|
||||
const oldId = req.params.id;
|
||||
const { newContractId } = req.body;
|
||||
<div class="step-nav text-center mb-3">
|
||||
<span id="nav1">Daten</span>
|
||||
<span id="nav2">Bank</span>
|
||||
<span id="nav3">Prüfen</span>
|
||||
</div>
|
||||
|
||||
if (!newContractId) {
|
||||
return res.status(400).send('Neue Vertragsart fehlt');
|
||||
<form method="POST" action="/register/create" id="registerForm">
|
||||
|
||||
<!-- ================= STEP 1 ================= -->
|
||||
<div class="step" id="step1">
|
||||
|
||||
<h4>Mitgliedsdaten</h4>
|
||||
|
||||
<input name="vorname" class="form-control mb-2" placeholder="Vorname" required>
|
||||
<input name="nachname" class="form-control mb-2" placeholder="Nachname" required>
|
||||
|
||||
<input type="date"
|
||||
name="geburtsdatum"
|
||||
id="geburtsdatum"
|
||||
class="form-control mb-2"
|
||||
required>
|
||||
|
||||
<input name="mobil" class="form-control mb-2" placeholder="Mobilnummer" required>
|
||||
<input type="email" name="email" class="form-control mb-2" placeholder="E-Mail" required>
|
||||
|
||||
<h5 class="mt-3">Adresse</h5>
|
||||
|
||||
<input name="strasse" class="form-control mb-2" placeholder="Straße" required>
|
||||
<input name="hausnummer" class="form-control mb-2" placeholder="Nr." required>
|
||||
<input name="plz" class="form-control mb-2" placeholder="PLZ" required>
|
||||
<input name="ort" class="form-control mb-2" placeholder="Ort" required>
|
||||
<input name="land" class="form-control mb-2" value="Deutschland">
|
||||
|
||||
<button type="button" class="btn btn-primary mt-3" onclick="nextStep(2)">
|
||||
Weiter →
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================= STEP 2 ================= -->
|
||||
<div class="step" id="step2">
|
||||
|
||||
<h4>Bankdaten</h4>
|
||||
|
||||
<input name="kontoinhaber" class="form-control mb-2" placeholder="Kontoinhaber" required>
|
||||
<input name="mandatsreferenz" class="form-control mb-2" placeholder="Mandatsreferenz" required>
|
||||
<input name="iban" class="form-control mb-2" placeholder="IBAN" required>
|
||||
<input name="bic" class="form-control mb-2" placeholder="BIC" required>
|
||||
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
name="agreeSepa"
|
||||
value="on"
|
||||
required>
|
||||
<label class="form-check-label">
|
||||
SEPA-Mandat erteilen
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="button" class="btn btn-secondary" onclick="nextStep(1)">
|
||||
← Zurück
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary" onclick="nextStep(3)">
|
||||
Weiter →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================= STEP 3 ================= -->
|
||||
<div class="step" id="step3">
|
||||
|
||||
<h4>Zusammenfassung</h4>
|
||||
|
||||
<div class="card p-3 mb-3 bg-light">
|
||||
<div id="summaryBox"></div>
|
||||
</div>
|
||||
|
||||
<h5>Vertrag</h5>
|
||||
|
||||
<select name="vertragsvariante" class="form-select mb-3" required>
|
||||
<option value="">Bitte wählen</option>
|
||||
<% vertragsarten.forEach(v => { %>
|
||||
<option value="<%= v.id %>">
|
||||
<%= v.name %> – <%= v.betrag.toFixed(2) %> €
|
||||
</option>
|
||||
<% }) %>
|
||||
</select>
|
||||
|
||||
<h5>Rechtliches</h5>
|
||||
|
||||
<div class="form-check mb-2 d-none" id="consentBox">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
name="agreeConsent"
|
||||
id="agreeConsent"
|
||||
value="on">
|
||||
<label class="form-check-label">
|
||||
Einverständnis der Erziehungsberechtigten
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
name="agreeAgb"
|
||||
value="on"
|
||||
required>
|
||||
<label class="form-check-label">
|
||||
AGB akzeptiert
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="button" class="btn btn-secondary" onclick="nextStep(2)">
|
||||
← Zurück
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-success btn-lg">
|
||||
Abschließen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
window.fromServerError = <%= typeof errorStep !== 'undefined' ? 'true' : 'false' %>;
|
||||
let currentStep = <%= typeof errorStep !== 'undefined' ? errorStep : 1 %>;
|
||||
|
||||
const form = document.getElementById('registerForm');
|
||||
|
||||
function nextStep(step){
|
||||
|
||||
if(step > currentStep && !window.fromServerError){
|
||||
if(!validateStep(currentStep)) return;
|
||||
}
|
||||
|
||||
const tx = db.transaction(() => {
|
||||
saveForm();
|
||||
|
||||
// 1️⃣ Alle User auf neue Vertragsart umstellen
|
||||
db.prepare(`
|
||||
UPDATE users
|
||||
SET vertragsvariante = ?
|
||||
WHERE vertragsvariante = ?
|
||||
`).run(newContractId, oldId);
|
||||
currentStep = step;
|
||||
|
||||
// 2️⃣ Alte Vertragsart deaktivieren
|
||||
db.prepare(`
|
||||
UPDATE vertragsarten
|
||||
SET aktiv = 0
|
||||
WHERE id = ?
|
||||
`).run(oldId);
|
||||
document.querySelectorAll('.step')
|
||||
.forEach(s => s.classList.remove('active'));
|
||||
|
||||
document.getElementById('step'+step)
|
||||
.classList.add('active');
|
||||
|
||||
updateNav();
|
||||
updateProgress();
|
||||
|
||||
if(step === 3){
|
||||
buildSummary();
|
||||
checkAgeAndConsent();
|
||||
}
|
||||
}
|
||||
|
||||
function validateStep(step){
|
||||
const container = document.getElementById('step'+step);
|
||||
const fields = container.querySelectorAll('input,select');
|
||||
|
||||
for(let field of fields){
|
||||
if(!field.checkValidity()){
|
||||
field.reportValidity();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateProgress(){
|
||||
document.getElementById('progressBar')
|
||||
.style.width = (currentStep * 33) + '%';
|
||||
}
|
||||
|
||||
function updateNav(){
|
||||
document.querySelectorAll('.step-nav span')
|
||||
.forEach(n => n.classList.remove('active'));
|
||||
|
||||
document.getElementById('nav'+currentStep)
|
||||
.classList.add('active');
|
||||
}
|
||||
|
||||
function isUnder18(){
|
||||
const bday = document.getElementById('geburtsdatum').value;
|
||||
if(!bday) return false;
|
||||
|
||||
const birth = new Date(bday);
|
||||
const today = new Date();
|
||||
|
||||
let age = today.getFullYear() - birth.getFullYear();
|
||||
if(today.getMonth() < birth.getMonth() ||
|
||||
(today.getMonth() === birth.getMonth() &&
|
||||
today.getDate() < birth.getDate())){
|
||||
age--;
|
||||
}
|
||||
return age < 18;
|
||||
}
|
||||
|
||||
function checkAgeAndConsent(){
|
||||
const box = document.getElementById('consentBox');
|
||||
const checkbox = document.getElementById('agreeConsent');
|
||||
|
||||
if(isUnder18()){
|
||||
box.classList.remove('d-none');
|
||||
checkbox.required = true;
|
||||
}else{
|
||||
box.classList.add('d-none');
|
||||
checkbox.required = false;
|
||||
checkbox.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener('input', saveForm);
|
||||
|
||||
function saveForm(){
|
||||
const data = {};
|
||||
Array.from(form.elements).forEach(el=>{
|
||||
if(!el.name) return;
|
||||
if(el.type === 'checkbox'){
|
||||
data[el.name] = el.checked ? 'on' : '';
|
||||
}else{
|
||||
data[el.name] = el.value;
|
||||
}
|
||||
});
|
||||
localStorage.setItem('registerData', JSON.stringify(data));
|
||||
}
|
||||
|
||||
tx();
|
||||
function loadForm(){
|
||||
const data = JSON.parse(localStorage.getItem('registerData'));
|
||||
if(!data) return;
|
||||
|
||||
res.redirect('/contracts');
|
||||
});
|
||||
Object.keys(data).forEach(key=>{
|
||||
const field = form.elements[key];
|
||||
if(!field) return;
|
||||
|
||||
// Öffentliche Vertragsauswahl – für ALLE Besucher
|
||||
router.get('/select', (req, res) => {
|
||||
const vertragsarten = db.prepare(`
|
||||
SELECT *
|
||||
FROM vertragsarten
|
||||
WHERE aktiv = 1
|
||||
ORDER BY betrag ASC
|
||||
`).all();
|
||||
|
||||
res.render('contractsSelect', { vertragsarten });
|
||||
});
|
||||
|
||||
|
||||
const PDFDocument = require('pdfkit');
|
||||
|
||||
router.get('/pdf/:id', (req, res) => {
|
||||
const v = db.prepare(`
|
||||
SELECT *
|
||||
FROM vertragsarten
|
||||
WHERE id = ? AND aktiv = 1
|
||||
`).get(req.params.id);
|
||||
|
||||
if (!v) {
|
||||
return res.status(404).send('Vertrag nicht gefunden');
|
||||
if(field.type === 'checkbox'){
|
||||
field.checked = data[key] === 'on';
|
||||
}else{
|
||||
field.value = data[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const doc = new PDFDocument();
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
`inline; filename=vertrag_${v.name}.pdf`
|
||||
);
|
||||
function buildSummary(){
|
||||
const data = JSON.parse(localStorage.getItem('registerData'));
|
||||
if(!data) return;
|
||||
|
||||
doc.pipe(res);
|
||||
document.getElementById('summaryBox').innerHTML = `
|
||||
<b>Name:</b> ${data.vorname} ${data.nachname}<br>
|
||||
<b>Geburtsdatum:</b> ${data.geburtsdatum}<br>
|
||||
<b>Email:</b> ${data.email}<br>
|
||||
<b>Mobil:</b> ${data.mobil}<br>
|
||||
<b>Adresse:</b> ${data.strasse} ${data.hausnummer}, ${data.plz} ${data.ort}<br>
|
||||
<b>IBAN:</b> ${data.iban}
|
||||
`;
|
||||
}
|
||||
|
||||
doc.fontSize(20).text(`Vertrag: ${v.name}`, { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
doc.fontSize(12)
|
||||
.text(`Laufzeit: ${v.laufzeit} Monate`)
|
||||
.text(`Betrag: ${v.betrag.toFixed(2)} € / Monat`)
|
||||
.moveDown();
|
||||
|
||||
doc.text(v.beschreibung || 'Keine weitere Beschreibung.');
|
||||
|
||||
doc.end();
|
||||
form.addEventListener('submit',()=>{
|
||||
localStorage.removeItem('registerData');
|
||||
});
|
||||
|
||||
loadForm();
|
||||
setTimeout(()=>{ nextStep(currentStep); },100);
|
||||
|
||||
</script>
|
||||
|
||||
module.exports = router;
|
||||
<%- include('partials/footer') %></br>
|
||||
@ -1,265 +1,325 @@
|
||||
const express = require("express");
|
||||
const Database = require("better-sqlite3");
|
||||
const validateSepa = require("../utils/sepaValidator");
|
||||
const { encrypt } = require("../utils/crypto");
|
||||
const generateVertragsnummer = require("../utils/vertragsnummer");
|
||||
const createContractPdf = require("../utils/contractPdf");
|
||||
const sendContractMail = require("../utils/sendContractMail");
|
||||
const sendAdminMail = require("../utils/sendAdminMail");
|
||||
<%- include('partials/header') %>
|
||||
|
||||
const router = express.Router();
|
||||
const db = new Database("plusfit.db");
|
||||
<style>
|
||||
.step { display: none; }
|
||||
.step.active { display: block; }
|
||||
|
||||
/* =========================
|
||||
Helper
|
||||
========================= */
|
||||
function loadActiveContracts() {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT *
|
||||
FROM vertragsarten
|
||||
WHERE aktiv = 1
|
||||
ORDER BY betrag ASC
|
||||
`,
|
||||
)
|
||||
.all();
|
||||
.container-form {
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
GET /register
|
||||
========================= */
|
||||
router.get("/", (req, res) => {
|
||||
const vertragId = req.query.vertrag || null;
|
||||
.progress {
|
||||
height: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
const vertraege = loadActiveContracts();
|
||||
.step-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// Prüfen ob Vertrag existiert
|
||||
const selected = vertraege.find((v) => v.id == vertragId);
|
||||
.step-nav span.active {
|
||||
font-weight: bold;
|
||||
color: #3b3be3;
|
||||
}
|
||||
|
||||
return res.render("register", {
|
||||
vertragsarten: vertraege,
|
||||
selectedVertrag: selected ? selected.id : null,
|
||||
selectedVertragData: selected || null,
|
||||
formData: {},
|
||||
@media(max-width:768px){
|
||||
h4{font-size:18px;}
|
||||
button{width:100%;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container-form">
|
||||
|
||||
<% if (typeof error !== 'undefined') { %>
|
||||
<div class="alert alert-danger">
|
||||
⚠️ <%= error %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<h3 class="text-center mb-4">Mitglied werden</h3>
|
||||
|
||||
<div class="progress">
|
||||
<div id="progressBar"
|
||||
class="progress-bar bg-primary"
|
||||
style="width:33%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-nav text-center mb-3">
|
||||
<span id="nav1">Daten</span>
|
||||
<span id="nav2">Bank</span>
|
||||
<span id="nav3">Prüfen</span>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/register/create" id="registerForm">
|
||||
|
||||
<!-- ================= STEP 1 ================= -->
|
||||
<div class="step" id="step1">
|
||||
|
||||
<h4>Mitgliedsdaten</h4>
|
||||
|
||||
<input name="vorname" class="form-control mb-2" placeholder="Vorname" required>
|
||||
<input name="nachname" class="form-control mb-2" placeholder="Nachname" required>
|
||||
|
||||
<input type="date"
|
||||
name="geburtsdatum"
|
||||
id="geburtsdatum"
|
||||
class="form-control mb-2"
|
||||
required>
|
||||
|
||||
<input name="mobil" class="form-control mb-2" placeholder="Mobilnummer" required>
|
||||
<input type="email" name="email" class="form-control mb-2" placeholder="E-Mail" required>
|
||||
|
||||
<h5 class="mt-3">Adresse</h5>
|
||||
|
||||
<input name="strasse" class="form-control mb-2" placeholder="Straße" required>
|
||||
<input name="hausnummer" class="form-control mb-2" placeholder="Nr." required>
|
||||
<input name="plz" class="form-control mb-2" placeholder="PLZ" required>
|
||||
<input name="ort" class="form-control mb-2" placeholder="Ort" required>
|
||||
<input name="land" class="form-control mb-2" value="Deutschland">
|
||||
|
||||
<button type="button" class="btn btn-primary mt-3" onclick="nextStep(2)">
|
||||
Weiter →
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================= STEP 2 ================= -->
|
||||
<div class="step" id="step2">
|
||||
|
||||
<h4>Bankdaten</h4>
|
||||
|
||||
<input name="kontoinhaber" class="form-control mb-2" placeholder="Kontoinhaber" required>
|
||||
<input name="mandatsreferenz" class="form-control mb-2" placeholder="Mandatsreferenz" required>
|
||||
<input name="iban" class="form-control mb-2" placeholder="IBAN" required>
|
||||
<input name="bic" class="form-control mb-2" placeholder="BIC" required>
|
||||
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
name="agreeSepa"
|
||||
value="on"
|
||||
required>
|
||||
<label class="form-check-label">
|
||||
SEPA-Mandat erteilen
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="button" class="btn btn-secondary" onclick="nextStep(1)">
|
||||
← Zurück
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary" onclick="nextStep(3)">
|
||||
Weiter →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================= STEP 3 ================= -->
|
||||
<div class="step" id="step3">
|
||||
|
||||
<h4>Zusammenfassung</h4>
|
||||
|
||||
<div class="card p-3 mb-3 bg-light">
|
||||
<div id="summaryBox"></div>
|
||||
</div>
|
||||
|
||||
<h5>Vertrag</h5>
|
||||
|
||||
<select name="vertragsvariante" class="form-select mb-3" required>
|
||||
<option value="">Bitte wählen</option>
|
||||
<% vertragsarten.forEach(v => { %>
|
||||
<option value="<%= v.id %>">
|
||||
<%= v.name %> – <%= v.betrag.toFixed(2) %> €
|
||||
</option>
|
||||
<% }) %>
|
||||
</select>
|
||||
|
||||
<h5>Rechtliches</h5>
|
||||
|
||||
<div class="form-check mb-2 d-none" id="consentBox">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
name="agreeConsent"
|
||||
id="agreeConsent"
|
||||
value="on">
|
||||
<label class="form-check-label">
|
||||
Einverständnis der Erziehungsberechtigten
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
name="agreeAgb"
|
||||
value="on"
|
||||
required>
|
||||
<label class="form-check-label">
|
||||
AGB akzeptiert
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="button" class="btn btn-secondary" onclick="nextStep(2)">
|
||||
← Zurück
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-success btn-lg">
|
||||
Abschließen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
window.fromServerError = <%= typeof errorStep !== 'undefined' ? 'true' : 'false' %>;
|
||||
let currentStep = <%= typeof errorStep !== 'undefined' ? errorStep : 1 %>;
|
||||
|
||||
const form = document.getElementById('registerForm');
|
||||
|
||||
function nextStep(step){
|
||||
|
||||
if(step > currentStep && !window.fromServerError){
|
||||
if(!validateStep(currentStep)) return;
|
||||
}
|
||||
|
||||
saveForm();
|
||||
|
||||
currentStep = step;
|
||||
|
||||
document.querySelectorAll('.step')
|
||||
.forEach(s => s.classList.remove('active'));
|
||||
|
||||
document.getElementById('step'+step)
|
||||
.classList.add('active');
|
||||
|
||||
updateNav();
|
||||
updateProgress();
|
||||
|
||||
if(step === 3){
|
||||
buildSummary();
|
||||
checkAgeAndConsent();
|
||||
}
|
||||
}
|
||||
|
||||
function validateStep(step){
|
||||
const container = document.getElementById('step'+step);
|
||||
const fields = container.querySelectorAll('input,select');
|
||||
|
||||
for(let field of fields){
|
||||
if(!field.checkValidity()){
|
||||
field.reportValidity();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateProgress(){
|
||||
document.getElementById('progressBar')
|
||||
.style.width = (currentStep * 33) + '%';
|
||||
}
|
||||
|
||||
function updateNav(){
|
||||
document.querySelectorAll('.step-nav span')
|
||||
.forEach(n => n.classList.remove('active'));
|
||||
|
||||
document.getElementById('nav'+currentStep)
|
||||
.classList.add('active');
|
||||
}
|
||||
|
||||
function isUnder18(){
|
||||
const bday = document.getElementById('geburtsdatum').value;
|
||||
if(!bday) return false;
|
||||
|
||||
const birth = new Date(bday);
|
||||
const today = new Date();
|
||||
|
||||
let age = today.getFullYear() - birth.getFullYear();
|
||||
if(today.getMonth() < birth.getMonth() ||
|
||||
(today.getMonth() === birth.getMonth() &&
|
||||
today.getDate() < birth.getDate())){
|
||||
age--;
|
||||
}
|
||||
return age < 18;
|
||||
}
|
||||
|
||||
function checkAgeAndConsent(){
|
||||
const box = document.getElementById('consentBox');
|
||||
const checkbox = document.getElementById('agreeConsent');
|
||||
|
||||
if(isUnder18()){
|
||||
box.classList.remove('d-none');
|
||||
checkbox.required = true;
|
||||
}else{
|
||||
box.classList.add('d-none');
|
||||
checkbox.required = false;
|
||||
checkbox.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener('input', saveForm);
|
||||
|
||||
function saveForm(){
|
||||
const data = {};
|
||||
Array.from(form.elements).forEach(el=>{
|
||||
if(!el.name) return;
|
||||
if(el.type === 'checkbox'){
|
||||
data[el.name] = el.checked ? 'on' : '';
|
||||
}else{
|
||||
data[el.name] = el.value;
|
||||
}
|
||||
});
|
||||
localStorage.setItem('registerData', JSON.stringify(data));
|
||||
}
|
||||
|
||||
function loadForm(){
|
||||
const data = JSON.parse(localStorage.getItem('registerData'));
|
||||
if(!data) return;
|
||||
|
||||
Object.keys(data).forEach(key=>{
|
||||
const field = form.elements[key];
|
||||
if(!field) return;
|
||||
|
||||
if(field.type === 'checkbox'){
|
||||
field.checked = data[key] === 'on';
|
||||
}else{
|
||||
field.value = data[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildSummary(){
|
||||
const data = JSON.parse(localStorage.getItem('registerData'));
|
||||
if(!data) return;
|
||||
|
||||
document.getElementById('summaryBox').innerHTML = `
|
||||
<b>Name:</b> ${data.vorname} ${data.nachname}<br>
|
||||
<b>Geburtsdatum:</b> ${data.geburtsdatum}<br>
|
||||
<b>Email:</b> ${data.email}<br>
|
||||
<b>Mobil:</b> ${data.mobil}<br>
|
||||
<b>Adresse:</b> ${data.strasse} ${data.hausnummer}, ${data.plz} ${data.ort}<br>
|
||||
<b>IBAN:</b> ${data.iban}
|
||||
`;
|
||||
}
|
||||
|
||||
form.addEventListener('submit',()=>{
|
||||
localStorage.removeItem('registerData');
|
||||
});
|
||||
|
||||
/* =========================
|
||||
POST /register/create
|
||||
========================= */
|
||||
router.post("/create", async (req, res) => {
|
||||
const u = req.body;
|
||||
const vertragsarten = loadActiveContracts();
|
||||
loadForm();
|
||||
setTimeout(()=>{ nextStep(currentStep); },100);
|
||||
|
||||
/* =========================
|
||||
Vertragsdaten laden
|
||||
========================= */
|
||||
const contractData = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT name, laufzeit, betrag
|
||||
FROM vertragsarten
|
||||
WHERE id = ? AND aktiv = 1
|
||||
`,
|
||||
)
|
||||
.get(u.vertragsvariante);
|
||||
</script>
|
||||
|
||||
if (!contractData) {
|
||||
return res.render("register", {
|
||||
vertragsarten,
|
||||
selectedVertrag: u.vertragsvariante,
|
||||
error: "Vertragsdaten konnten nicht geladen werden.",
|
||||
formData: u,
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Pflicht-Zustimmungen
|
||||
========================= */
|
||||
if (!u.agreeConsent || !u.agreeAgb || !u.agreeSepa) {
|
||||
return res.render("register", {
|
||||
vertragsarten,
|
||||
selectedVertrag: u.vertragsvariante,
|
||||
error: "Bitte bestätige alle rechtlichen Hinweise, um fortzufahren.",
|
||||
formData: u,
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Formale SEPA-Prüfung
|
||||
========================= */
|
||||
const sepaError = validateSepa({
|
||||
ibanValue: u.iban,
|
||||
bic: u.bic,
|
||||
mandatsreferenz: u.mandatsreferenz,
|
||||
});
|
||||
|
||||
if (sepaError) {
|
||||
return res.render("register", {
|
||||
vertragsarten,
|
||||
selectedVertrag: u.vertragsvariante,
|
||||
error: sepaError,
|
||||
formData: u,
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Logische Prüfungen
|
||||
========================= */
|
||||
const mandatsExists = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id FROM users
|
||||
WHERE mandatsreferenz = ?
|
||||
`,
|
||||
)
|
||||
.get(u.mandatsreferenz);
|
||||
|
||||
if (mandatsExists) {
|
||||
return res.render("register", {
|
||||
vertragsarten,
|
||||
selectedVertrag: u.vertragsvariante,
|
||||
error: "Diese Mandatsreferenz ist bereits vergeben.",
|
||||
formData: u,
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Vertrags- & Zustimmungsdaten
|
||||
========================= */
|
||||
const vertragsnummer = generateVertragsnummer();
|
||||
|
||||
const zustimmungsDatum = new Date().toISOString();
|
||||
const zustimmungsIp = (
|
||||
req.headers["x-forwarded-for"] ||
|
||||
req.socket?.remoteAddress ||
|
||||
""
|
||||
)
|
||||
.split(",")[0]
|
||||
.trim();
|
||||
|
||||
const vertragsversion = "v1.0";
|
||||
|
||||
const widerrufBis = new Date();
|
||||
widerrufBis.setDate(widerrufBis.getDate() + 14);
|
||||
|
||||
/* =========================
|
||||
Verschlüsselung
|
||||
========================= */
|
||||
const ibanEncrypted = encrypt(u.iban);
|
||||
const bicEncrypted = encrypt(u.bic);
|
||||
|
||||
/* =========================
|
||||
SPEICHERN
|
||||
========================= */
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO users (
|
||||
vertragsnummer,
|
||||
vertragsvariante,
|
||||
|
||||
vorname, nachname,
|
||||
strasse, hausnummer, plz, ort, land,
|
||||
mobil, telefon, email,
|
||||
|
||||
kontoinhaber, iban, bic, mandatsreferenz,
|
||||
|
||||
zustimmung_agb,
|
||||
zustimmung_sepa,
|
||||
zustimmung_einverstaendnis,
|
||||
zustimmung_datum,
|
||||
zustimmung_ip,
|
||||
vertragsversion,
|
||||
|
||||
widerruf_moeglich_bis,
|
||||
status,
|
||||
gesperrt
|
||||
) VALUES (
|
||||
?,?,
|
||||
?,?,
|
||||
?,?,?,?,?,
|
||||
?,?,?,
|
||||
?,?,?,?,
|
||||
?,?,?,?,
|
||||
?,?,
|
||||
?,?,
|
||||
0
|
||||
)
|
||||
`,
|
||||
).run(
|
||||
vertragsnummer,
|
||||
u.vertragsvariante,
|
||||
|
||||
u.vorname,
|
||||
u.nachname,
|
||||
u.strasse,
|
||||
u.hausnummer,
|
||||
u.plz,
|
||||
u.ort,
|
||||
u.land,
|
||||
u.mobil,
|
||||
u.telefon,
|
||||
u.email,
|
||||
|
||||
u.kontoinhaber,
|
||||
ibanEncrypted,
|
||||
bicEncrypted,
|
||||
u.mandatsreferenz,
|
||||
|
||||
u.agreeAgb ? 1 : 0,
|
||||
u.agreeSepa ? 1 : 0,
|
||||
u.agreeConsent ? 1 : 0,
|
||||
zustimmungsDatum,
|
||||
zustimmungsIp,
|
||||
vertragsversion,
|
||||
|
||||
widerrufBis.toISOString(),
|
||||
"aktiv",
|
||||
);
|
||||
|
||||
/* =========================
|
||||
Vertrags-PDF
|
||||
========================= */
|
||||
const pdfPath = await createContractPdf({
|
||||
vertragsnummer,
|
||||
vorname: u.vorname,
|
||||
nachname: u.nachname,
|
||||
vertragName: contractData.name,
|
||||
laufzeit: contractData.laufzeit,
|
||||
betrag: contractData.betrag,
|
||||
datum: zustimmungsDatum,
|
||||
ip: zustimmungsIp,
|
||||
});
|
||||
|
||||
/* =========================
|
||||
Vertragsmail
|
||||
========================= */
|
||||
await sendContractMail({
|
||||
email: u.email,
|
||||
vorname: u.vorname,
|
||||
vertragsnummer,
|
||||
vertragName: contractData.name,
|
||||
betrag: contractData.betrag,
|
||||
datum: zustimmungsDatum,
|
||||
pdfPath,
|
||||
});
|
||||
|
||||
await sendAdminMail({
|
||||
vertragsnummer,
|
||||
vorname: u.vorname,
|
||||
nachname: u.nachname,
|
||||
email: u.email,
|
||||
vertragName: contractData.name,
|
||||
betrag: contractData.betrag,
|
||||
datum: zustimmungsDatum,
|
||||
ip: zustimmungsIp,
|
||||
});
|
||||
|
||||
/* =========================
|
||||
ERFOLG
|
||||
========================= */
|
||||
return res.render("registerSuccess", { vertragsnummer });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
<%- include('partials/footer') %></br>
|
||||
@ -1,28 +1,34 @@
|
||||
<%- include('partials/header') %>
|
||||
|
||||
<style>
|
||||
.step{display:none}
|
||||
.step.active{display:block}
|
||||
.step { display:none; }
|
||||
.step.active { display:block; }
|
||||
|
||||
.container-form{max-width:700px;margin:auto}
|
||||
.container-form{
|
||||
max-width:700px;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
.progress{height:10px;margin-bottom:30px}
|
||||
.progress{
|
||||
height:10px;
|
||||
margin-bottom:30px;
|
||||
}
|
||||
|
||||
.step-nav{
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
font-size:14px;
|
||||
margin-bottom:20px
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
font-size:14px;
|
||||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
.step-nav span.active{
|
||||
font-weight:bold;
|
||||
color:#3b3be3
|
||||
font-weight:bold;
|
||||
color:#3b3be3;
|
||||
}
|
||||
|
||||
@media(max-width:768px){
|
||||
h4{font-size:18px}
|
||||
button{width:100%}
|
||||
h4{font-size:18px;}
|
||||
button{width:100%;}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -35,6 +41,7 @@ button{width:100%}
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
|
||||
<h3 class="text-center mb-4">Mitglied werden</h3>
|
||||
|
||||
|
||||
@ -47,7 +54,7 @@ style="width:33%">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Nav -->
|
||||
<!-- Navigation -->
|
||||
<div class="step-nav text-center mb-3">
|
||||
<span id="nav1">Daten</span>
|
||||
<span id="nav2">Bank</span>
|
||||
@ -155,7 +162,6 @@ Weiter →
|
||||
<!-- Vertrag hidden -->
|
||||
<input type="hidden"
|
||||
name="vertragsvariante"
|
||||
id="vertragHidden"
|
||||
value="<%= selectedVertrag || '' %>">
|
||||
|
||||
|
||||
@ -167,7 +173,7 @@ value="<%= selectedVertrag || '' %>">
|
||||
<% if(selectedVertragData){ %>
|
||||
<%= selectedVertragData.name %> –
|
||||
<%= selectedVertragData.betrag.toFixed(2) %> € / Monat
|
||||
<% }else{ %>
|
||||
<% } else { %>
|
||||
Kein Vertrag gewählt
|
||||
<% } %>
|
||||
</b>
|
||||
@ -179,7 +185,7 @@ Kein Vertrag gewählt
|
||||
<h5>Rechtliches</h5>
|
||||
|
||||
|
||||
<!-- Eltern -->
|
||||
<!-- Eltern-Zustimmung -->
|
||||
<div class="form-check mb-2 d-none" id="consentBox">
|
||||
|
||||
<input class="form-check-input"
|
||||
@ -205,10 +211,7 @@ value="on"
|
||||
required>
|
||||
|
||||
<label class="form-check-label">
|
||||
<a href="https://plusfit24.de/wp-content/uploads/2022/11/AG_PlusFit24.pdf"
|
||||
target="_blank">
|
||||
AGB gelesen
|
||||
</a>
|
||||
AGB akzeptiert
|
||||
</label>
|
||||
|
||||
</div>
|
||||
@ -267,7 +270,7 @@ document.getElementById('step'+step)
|
||||
updateNav();
|
||||
updateProgress();
|
||||
|
||||
if(step===3){
|
||||
if(step === 3){
|
||||
buildSummary();
|
||||
checkAgeAndConsent();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user