Email Bestätigung
This commit is contained in:
parent
193f46ba62
commit
b2784e7b63
@ -319,4 +319,18 @@ router.post('/members/:id/update-nfc', requireAdmin, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Mitglied manuell bestätigen
|
||||||
|
router.post('/members/:id/confirm', requireAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
await db.query(
|
||||||
|
"UPDATE memberships SET status='active', confirmed_at=NOW() WHERE id=?",
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
res.redirect(`/admin/members/${req.params.id}?success=Mitglied+manuell+bestätigt`);
|
||||||
|
} catch (err) {
|
||||||
|
res.redirect(`/admin/members/${req.params.id}?error=Fehler+bei+Bestätigung`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
169
routes/api.js
169
routes/api.js
@ -1,8 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const dns = require('dns').promises;
|
const dns = require('dns').promises;
|
||||||
const db = require('../config/database');
|
const db = require('../config/database');
|
||||||
|
const mailer = require('../config/mailer');
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// E-Mail Validierung via DNS MX-Record
|
// E-Mail Validierung via DNS MX-Record
|
||||||
@ -53,7 +54,49 @@ function validateIBANServer(iban) {
|
|||||||
return { valid: true };
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Bestätigungs-E-Mail Template
|
||||||
|
// ============================================
|
||||||
|
function confirmationEmailHtml(member, confirmLink) {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head><meta charset="UTF-8"></head>
|
||||||
|
<body style="font-family:Outfit,Arial,sans-serif;background:#f8f9ff;margin:0;padding:20px">
|
||||||
|
<div style="max-width:600px;margin:0 auto;background:white;border-radius:16px;overflow:hidden;box-shadow:0 2px 16px rgba(0,0,0,0.08)">
|
||||||
|
<div style="background:#2d2dcc;padding:32px;text-align:center">
|
||||||
|
<h1 style="color:white;margin:0;font-size:1.8rem">Plusfit<span style="color:#a5b4fc">24</span></h1>
|
||||||
|
<p style="color:#c7d2fe;margin:8px 0 0">Mitgliedschaft bestätigen</p>
|
||||||
|
</div>
|
||||||
|
<div style="padding:32px">
|
||||||
|
<p>Hallo ${member.first_name} ${member.last_name},</p>
|
||||||
|
<p>vielen Dank für deine Anmeldung bei PlusFit24! Um deine Mitgliedschaft zu aktivieren, bestätige bitte deine E-Mail-Adresse:</p>
|
||||||
|
|
||||||
|
<div style="text-align:center;margin:32px 0">
|
||||||
|
<a href="${confirmLink}"
|
||||||
|
style="display:inline-block;background:#2d2dcc;color:white;padding:16px 40px;border-radius:12px;text-decoration:none;font-weight:700;font-size:1.05rem">
|
||||||
|
✅ Mitgliedschaft jetzt bestätigen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background:#f8f9ff;border-radius:10px;padding:16px;margin-bottom:20px">
|
||||||
|
<p style="margin:0 0 8px;font-weight:700">Deine Vertragsdaten:</p>
|
||||||
|
<p style="margin:4px 0;color:#374151">📋 Tarif: ${member.tariff_name}</p>
|
||||||
|
<p style="margin:4px 0;color:#374151">💰 Monatsbeitrag: ${Number(member.agreed_price).toFixed(2).replace('.', ',')} €</p>
|
||||||
|
<p style="margin:4px 0;color:#374151">📦 Startpaket: ${Number(member.start_package_price).toFixed(2).replace('.', ',')} €</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="color:#6b7280;font-size:0.85rem">
|
||||||
|
Dieser Link ist <strong>24 Stunden</strong> gültig. Falls du diese Anmeldung nicht durchgeführt hast, ignoriere diese E-Mail.<br><br>
|
||||||
|
<strong>PlusFit24 UG</strong> · Moosleiten 12 · 84089 Aiglsbach
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body></html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
// POST /api/verify-email
|
// POST /api/verify-email
|
||||||
|
// ============================================
|
||||||
router.post('/verify-email', async (req, res) => {
|
router.post('/verify-email', async (req, res) => {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
if (!email) return res.json({ valid: false, reason: 'Keine E-Mail angegeben' });
|
if (!email) return res.json({ valid: false, reason: 'Keine E-Mail angegeben' });
|
||||||
@ -61,7 +104,9 @@ router.post('/verify-email', async (req, res) => {
|
|||||||
res.json(result);
|
res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
// POST /api/submit-membership
|
// POST /api/submit-membership
|
||||||
|
// ============================================
|
||||||
router.post('/submit-membership', async (req, res) => {
|
router.post('/submit-membership', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
@ -72,7 +117,7 @@ router.post('/submit-membership', async (req, res) => {
|
|||||||
guardian_consent
|
guardian_consent
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Pflichtfelder prüfen
|
// Pflichtfelder
|
||||||
if (!tariff_id || !first_name || !last_name || !birth_date || !email || !street || !zip || !city) {
|
if (!tariff_id || !first_name || !last_name || !birth_date || !email || !street || !zip || !city) {
|
||||||
return res.json({ success: false, error: 'Bitte alle Pflichtfelder ausfüllen.' });
|
return res.json({ success: false, error: 'Bitte alle Pflichtfelder ausfüllen.' });
|
||||||
}
|
}
|
||||||
@ -100,7 +145,6 @@ router.post('/submit-membership', async (req, res) => {
|
|||||||
let age = today.getFullYear() - birthDateObj.getFullYear();
|
let age = today.getFullYear() - birthDateObj.getFullYear();
|
||||||
const mo = today.getMonth() - birthDateObj.getMonth();
|
const mo = today.getMonth() - birthDateObj.getMonth();
|
||||||
if (mo < 0 || (mo === 0 && today.getDate() < birthDateObj.getDate())) age--;
|
if (mo < 0 || (mo === 0 && today.getDate() < birthDateObj.getDate())) age--;
|
||||||
|
|
||||||
const is_minor = age < 18 ? 1 : 0;
|
const is_minor = age < 18 ? 1 : 0;
|
||||||
|
|
||||||
if (is_minor && !guardian_consent) {
|
if (is_minor && !guardian_consent) {
|
||||||
@ -110,58 +154,52 @@ router.post('/submit-membership', async (req, res) => {
|
|||||||
return res.json({ success: false, error: 'Das Mindestalter für eine Mitgliedschaft beträgt 14 Jahre.' });
|
return res.json({ success: false, error: 'Das Mindestalter für eine Mitgliedschaft beträgt 14 Jahre.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tarif laden — MUSS vor Berechnungen stehen
|
// Tarif laden
|
||||||
const [tariffs] = await db.query('SELECT * FROM tariffs WHERE id = ? AND active = 1', [tariff_id]);
|
const [tariffs] = await db.query('SELECT * FROM tariffs WHERE id = ? AND active = 1', [tariff_id]);
|
||||||
if (tariffs.length === 0) {
|
if (tariffs.length === 0) {
|
||||||
return res.json({ success: false, error: 'Ungültiger oder inaktiver Tarif.' });
|
return res.json({ success: false, error: 'Ungültiger oder inaktiver Tarif.' });
|
||||||
}
|
}
|
||||||
const tariff = tariffs[0];
|
const tariff = tariffs[0];
|
||||||
|
|
||||||
// Startpaket-Preis
|
// Berechnungen
|
||||||
const startPackagePrice = parseFloat(tariff.start_package_price) || 35.00;
|
const startPackagePrice = parseFloat(tariff.start_package_price) || 35.00;
|
||||||
|
const contractStart = new Date(today);
|
||||||
// Vertragsbeginn = heute + 15 Tage
|
|
||||||
const contractStart = new Date();
|
|
||||||
contractStart.setDate(contractStart.getDate() + 15);
|
contractStart.setDate(contractStart.getDate() + 15);
|
||||||
|
const daysInMonth = new Date(contractStart.getFullYear(), contractStart.getMonth() + 1, 0).getDate();
|
||||||
// Anteiligen ersten Monatsbeitrag berechnen
|
const remainingDays = daysInMonth - contractStart.getDate() + 1;
|
||||||
const daysInMonth = new Date(contractStart.getFullYear(), contractStart.getMonth() + 1, 0).getDate();
|
const partialMonth = Math.round((parseFloat(tariff.price_monthly) / daysInMonth) * remainingDays * 100) / 100;
|
||||||
const remainingDays = daysInMonth - contractStart.getDate() + 1;
|
|
||||||
const dailyRate = parseFloat(tariff.price_monthly) / daysInMonth;
|
|
||||||
const partialMonth = Math.round(dailyRate * remainingDays * 100) / 100;
|
|
||||||
|
|
||||||
// Erste Zahlung = anteiliger Monat + Startpaket
|
|
||||||
const firstPaymentAmt = Math.round((partialMonth + startPackagePrice) * 100) / 100;
|
const firstPaymentAmt = Math.round((partialMonth + startPackagePrice) * 100) / 100;
|
||||||
|
const contractEnd = new Date(contractStart);
|
||||||
// Vertragsende
|
|
||||||
const contractEnd = new Date(contractStart);
|
|
||||||
contractEnd.setMonth(contractEnd.getMonth() + tariff.duration_months);
|
contractEnd.setMonth(contractEnd.getMonth() + tariff.duration_months);
|
||||||
contractEnd.setDate(contractEnd.getDate() - 1);
|
contractEnd.setDate(contractEnd.getDate() - 1);
|
||||||
|
|
||||||
// Zugangstoken generieren
|
// Tokens generieren
|
||||||
const access_token = crypto.randomBytes(16).toString('hex');
|
const access_token = crypto.randomBytes(16).toString('hex');
|
||||||
|
const confirmation_token = crypto.randomBytes(32).toString('hex');
|
||||||
|
|
||||||
// In DB speichern
|
// In DB speichern — Status: pending
|
||||||
// Spalten: 26, Werte: 26
|
|
||||||
await db.query(`
|
await db.query(`
|
||||||
INSERT INTO memberships
|
INSERT INTO memberships
|
||||||
(tariff_id, salutation, title, first_name, last_name, birth_date,
|
(tariff_id, salutation, title, first_name, last_name, birth_date,
|
||||||
email, phone, street, address_addition, zip, city,
|
email, phone, street, address_addition, zip, city,
|
||||||
bank_name, account_holder, iban,
|
bank_name, account_holder, iban,
|
||||||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct,
|
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct,
|
||||||
guardian_consent, is_minor, access_token,
|
guardian_consent, is_minor,
|
||||||
|
access_token, confirmation_token,
|
||||||
agreed_price, agreed_duration,
|
agreed_price, agreed_duration,
|
||||||
start_package_price,
|
start_package_price,
|
||||||
signup_date, contract_start, contract_end, effective_end,
|
signup_date, contract_start, contract_end, effective_end,
|
||||||
first_payment_date, first_payment_amt)
|
first_payment_date, first_payment_amt,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
status)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
`, [
|
`, [
|
||||||
tariff_id, salutation, title || '', first_name, last_name, birth_date,
|
tariff_id, salutation, title || '', first_name, last_name, birth_date,
|
||||||
email, phone || '', street, address_addition || '', zip, city,
|
email, phone || '', street, address_addition || '', zip, city,
|
||||||
bank_name || '', account_holder || '', iban || '',
|
bank_name || '', account_holder || '', iban || '',
|
||||||
sepa_accepted ? 1 : 0, agb_accepted ? 1 : 0,
|
sepa_accepted ? 1 : 0, agb_accepted ? 1 : 0,
|
||||||
datenschutz_accepted ? 1 : 0, data_correct ? 1 : 0,
|
datenschutz_accepted ? 1 : 0, data_correct ? 1 : 0,
|
||||||
guardian_consent ? 1 : 0, is_minor, access_token,
|
guardian_consent ? 1 : 0, is_minor,
|
||||||
|
access_token, confirmation_token,
|
||||||
tariff.price_monthly, tariff.duration_months,
|
tariff.price_monthly, tariff.duration_months,
|
||||||
startPackagePrice,
|
startPackagePrice,
|
||||||
today.toISOString().split('T')[0],
|
today.toISOString().split('T')[0],
|
||||||
@ -169,10 +207,28 @@ router.post('/submit-membership', async (req, res) => {
|
|||||||
contractEnd.toISOString().split('T')[0],
|
contractEnd.toISOString().split('T')[0],
|
||||||
contractEnd.toISOString().split('T')[0],
|
contractEnd.toISOString().split('T')[0],
|
||||||
contractStart.toISOString().split('T')[0],
|
contractStart.toISOString().split('T')[0],
|
||||||
firstPaymentAmt
|
firstPaymentAmt,
|
||||||
|
'pending'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.json({ success: true });
|
// Bestätigungs-E-Mail senden
|
||||||
|
const baseUrl = process.env.APP_URL || 'https://plusfit24.software-joksch.com';
|
||||||
|
const confirmLink = `${baseUrl}/confirm/${confirmation_token}`;
|
||||||
|
const memberData = { first_name, last_name, agreed_price: tariff.price_monthly, start_package_price: startPackagePrice, tariff_name: tariff.name };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mailer.sendMail({
|
||||||
|
from: process.env.MAIL_FROM,
|
||||||
|
to: email,
|
||||||
|
subject: 'PlusFit24 – Bitte bestätige deine Mitgliedschaft',
|
||||||
|
html: confirmationEmailHtml(memberData, confirmLink)
|
||||||
|
});
|
||||||
|
} catch (mailErr) {
|
||||||
|
console.error('E-Mail Fehler:', mailErr.message);
|
||||||
|
// Trotzdem Erfolg zurückgeben — Admin kann manuell bestätigen
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, pending: true });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Submit error:', err);
|
console.error('Submit error:', err);
|
||||||
@ -180,4 +236,49 @@ router.post('/submit-membership', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// GET /confirm/:token — E-Mail Bestätigung
|
||||||
|
// ============================================
|
||||||
|
router.get('/confirm/:token', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await db.query(
|
||||||
|
"SELECT * FROM memberships WHERE confirmation_token = ? AND status = 'pending'",
|
||||||
|
[req.params.token]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
// Prüfen ob bereits bestätigt
|
||||||
|
const [confirmed] = await db.query(
|
||||||
|
"SELECT * FROM memberships WHERE confirmation_token = ? AND status = 'active'",
|
||||||
|
[req.params.token]
|
||||||
|
);
|
||||||
|
if (confirmed.length > 0) {
|
||||||
|
return res.render('confirmation-success'); // Bereits bestätigt
|
||||||
|
}
|
||||||
|
return res.render('confirmation-invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = rows[0];
|
||||||
|
|
||||||
|
// Prüfen ob Link abgelaufen (24h)
|
||||||
|
const createdAt = new Date(member.created_at);
|
||||||
|
const now = new Date();
|
||||||
|
const hoursDiff = (now - createdAt) / (1000 * 60 * 60);
|
||||||
|
if (hoursDiff > 24) {
|
||||||
|
return res.render('confirmation-invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mitgliedschaft aktivieren
|
||||||
|
await db.query(
|
||||||
|
"UPDATE memberships SET status='active', confirmed_at=NOW() WHERE id=?",
|
||||||
|
[member.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
res.render('confirmation-success');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Confirm error:', err);
|
||||||
|
res.render('error', { message: 'Fehler bei der Bestätigung.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@ -32,6 +32,11 @@ router.get('/anmelden/:tariffId', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bestätigung ausstehend
|
||||||
|
router.get('/bestaetigung-ausstehend', (req, res) => {
|
||||||
|
res.render('confirmation-pending', { email: req.query.email || '' });
|
||||||
|
});
|
||||||
|
|
||||||
// Erfolgsseite
|
// Erfolgsseite
|
||||||
router.get('/erfolg', (req, res) => {
|
router.get('/erfolg', (req, res) => {
|
||||||
res.render('success');
|
res.render('success');
|
||||||
|
|||||||
@ -34,9 +34,14 @@
|
|||||||
<div class="detail-header-title">
|
<div class="detail-header-title">
|
||||||
<h1><%= member.first_name %> <%= member.last_name %></h1>
|
<h1><%= member.first_name %> <%= member.last_name %></h1>
|
||||||
<span class="member-id-badge">Mitglied #<%= member.id %></span>
|
<span class="member-id-badge">Mitglied #<%= member.id %></span>
|
||||||
<span class="status-badge <%= member.status === 'active' ? 'active' : (member.status === 'paused' ? 'warning' : 'inactive') %>">
|
<span class="status-badge <%= member.status === 'active' ? 'active' : member.status === 'paused' ? 'warning' : member.status === 'pending' ? 'warning' : 'inactive' %>">
|
||||||
<%= member.status === 'active' ? '✅ Aktiv' : member.status === 'paused' ? '⏸ Pausiert' : '❌ Inaktiv' %>
|
<%= member.status === 'active' ? '✅ Aktiv' : member.status === 'paused' ? '⏸ Pausiert' : member.status === 'pending' ? '⏳ Ausstehend' : '❌ Inaktiv' %>
|
||||||
</span>
|
</span>
|
||||||
|
<% if (member.status === 'pending') { %>
|
||||||
|
<form method="POST" action="/admin/members/<%= member.id %>/confirm" style="display:inline">
|
||||||
|
<button type="submit" class="btn btn-sm btn-success">✅ Manuell bestätigen</button>
|
||||||
|
</form>
|
||||||
|
<% } %>
|
||||||
<% if (member.is_minor) { %>
|
<% if (member.is_minor) { %>
|
||||||
<span class="status-badge warning">⚠️ Minderjährig</span>
|
<span class="status-badge warning">⚠️ Minderjährig</span>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|||||||
27
views/confirmation-invalid.ejs
Normal file
27
views/confirmation-invalid.ejs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PlusFit24 – Link ungültig</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner"><div class="logo">Plusfit<span>24</span></div></div>
|
||||||
|
</header>
|
||||||
|
<main class="success-main">
|
||||||
|
<div class="success-card">
|
||||||
|
<div class="success-icon">⚠️</div>
|
||||||
|
<h1>Link ungültig oder abgelaufen</h1>
|
||||||
|
<p>Dieser Bestätigungslink ist nicht mehr gültig.</p>
|
||||||
|
<p class="success-sub">Bitte kontaktiere uns direkt im Studio oder ruf uns an.</p>
|
||||||
|
<a href="/" class="btn btn-outline" style="margin-top:16px">Zur Startseite</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="site-footer">
|
||||||
|
<p>© 2024 PlusFit24 UG</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
32
views/confirmation-pending.ejs
Normal file
32
views/confirmation-pending.ejs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PlusFit24 – E-Mail bestätigen</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner"><div class="logo">Plusfit<span>24</span></div></div>
|
||||||
|
</header>
|
||||||
|
<main class="success-main">
|
||||||
|
<div class="success-card">
|
||||||
|
<div class="success-icon">📧</div>
|
||||||
|
<h1>Fast geschafft!</h1>
|
||||||
|
<p>Wir haben eine Bestätigungs-E-Mail an</p>
|
||||||
|
<p><strong><%= email %></strong></p>
|
||||||
|
<p>gesendet. Bitte klicke auf den Link in der E-Mail um deine Mitgliedschaft zu aktivieren.</p>
|
||||||
|
<p class="success-sub">Der Link ist 24 Stunden gültig.</p>
|
||||||
|
<div class="info-box" style="margin-top:16px;text-align:left">
|
||||||
|
<strong>ℹ️ Keine E-Mail erhalten?</strong><br>
|
||||||
|
Bitte prüfe deinen Spam-Ordner oder kontaktiere uns direkt im Studio.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="site-footer">
|
||||||
|
<p>© 2024 PlusFit24 UG</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
views/confirmation-success.ejs
Normal file
27
views/confirmation-success.ejs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PlusFit24 – Bestätigt!</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner"><div class="logo">Plusfit<span>24</span></div></div>
|
||||||
|
</header>
|
||||||
|
<main class="success-main">
|
||||||
|
<div class="success-card">
|
||||||
|
<div class="success-icon">🎉</div>
|
||||||
|
<h1>Herzlich Willkommen!</h1>
|
||||||
|
<p>Deine Mitgliedschaft bei PlusFit24 wurde erfolgreich bestätigt und ist jetzt aktiv.</p>
|
||||||
|
<p class="success-sub">Wir freuen uns auf dich im Studio!</p>
|
||||||
|
<a href="/" class="btn btn-primary" style="margin-top:16px">Zurück zur Startseite</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="site-footer">
|
||||||
|
<p>© 2024 PlusFit24 UG</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -457,7 +457,7 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
window.location.href = '/erfolg';
|
window.location.href = data.pending ? '/bestaetigung-ausstehend?email=' + encodeURIComponent(document.getElementById('email').value) : '/erfolg';
|
||||||
} else {
|
} else {
|
||||||
errorDiv.textContent = data.error || 'Fehler beim Absenden.';
|
errorDiv.textContent = data.error || 'Fehler beim Absenden.';
|
||||||
errorDiv.classList.remove('hidden');
|
errorDiv.classList.remove('hidden');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user