Diese Änderungen liefen bereits live auf dem Server, lagen dort aber ausschließlich als nicht eingecheckte Arbeitskopie — bei jedem Deploy (stash/pull/pop) und bei jedem Serverproblem wären sie verloren gewesen. Deshalb hier unverändert in git übernommen, bevor darauf aufgebaut wird. Enthalten (nicht von mir gebaut, nur gesichert): - Supporter: eigener fester Zugangscode statt Einmalcode-Login (Migration 0009, lib/crypto.js scrypt-Hash, routes/supporter.js, abonnieren.html, supporter.html, i18n-abonnieren/-supporter) - Stimmen: Profilbilder (Migration 0010, routes/testimonials.js) - Event-Bild-Upload: voller Pfad statt relativem (routes/events.js) - Neue/überarbeitete Hintergrundbilder für viele Seiten - Teilen-Funktion (streamplan.js, i18n-index/-streamplan, main.css) Co-Authored-By: Claude Opus 5 <[email protected]>
96 lines
4.1 KiB
JavaScript
96 lines
4.1 KiB
JavaScript
/* =====================================================================
|
|
lib/supporter-mail.js — Transaktions-E-Mails für den Supporter-Bereich
|
|
(Resend-Adapter). 1:1 portiert, env.X → process.env.X.
|
|
===================================================================== */
|
|
|
|
export class EmailNotConfiguredError extends Error {
|
|
constructor() {
|
|
super("E-Mail-Versand ist noch nicht eingerichtet (RESEND_API_KEY fehlt).");
|
|
this.name = "EmailNotConfiguredError";
|
|
this.code = "EMAIL_NOT_CONFIGURED";
|
|
}
|
|
}
|
|
|
|
async function send({ to, subject, html }) {
|
|
if (!process.env.RESEND_API_KEY || !process.env.RESEND_FROM) throw new EmailNotConfiguredError();
|
|
const res = await fetch("https://api.resend.com/emails", {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${process.env.RESEND_API_KEY}`, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ from: process.env.RESEND_FROM, to: [to], subject, html }),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`E-Mail-Versand fehlgeschlagen (${res.status}): ${text.slice(0, 300)}`);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const WRAP = (title, bodyHtml) => `
|
|
<div style="font-family:Arial,sans-serif;background:#0b0d10;color:#f3f5f7;padding:32px;">
|
|
<div style="max-width:480px;margin:0 auto;background:#171b21;border-radius:14px;padding:28px;border:1px solid #262c35;">
|
|
<h1 style="color:#8fd9ea;font-size:20px;margin:0 0 16px;">${title}</h1>
|
|
${bodyHtml}
|
|
<p style="color:#9aa4b2;font-size:12px;margin-top:24px;">DogiCrew 🩵</p>
|
|
</div>
|
|
</div>`;
|
|
|
|
export async function sendVerifyEmailCode({ to, name, code }) {
|
|
return send({
|
|
to,
|
|
subject: "Bestätige deine E-Mail-Adresse — DogiCrew 🩵",
|
|
html: WRAP(
|
|
`Willkommen, ${name}! 🩵`,
|
|
`<p style="line-height:1.6;">Bestätige deine E-Mail-Adresse mit diesem Code:</p>
|
|
<p style="font-size:32px;font-weight:800;letter-spacing:4px;color:#fff;">${code}</p>
|
|
<p style="color:#9aa4b2;font-size:13px;">Der Code ist 15 Minuten gültig.</p>`
|
|
),
|
|
});
|
|
}
|
|
|
|
export async function sendLoginCode({ to, name, code }) {
|
|
return send({
|
|
to,
|
|
subject: "Dein Login-Code — Dogfather Supporter-Bereich",
|
|
html: WRAP(
|
|
`Hallo ${name} 👋`,
|
|
`<p style="line-height:1.6;">Dein Login-Code für den Supporter-Bereich:</p>
|
|
<p style="font-size:32px;font-weight:800;letter-spacing:4px;color:#fff;">${code}</p>
|
|
<p style="color:#9aa4b2;font-size:13px;">Der Code ist 15 Minuten gültig. Hast du das nicht angefordert, ignoriere diese E-Mail.</p>`
|
|
),
|
|
});
|
|
}
|
|
|
|
/* Willkommens-E-Mail nach der Registrierung (21.08.2026). Enthält bewusst
|
|
KEINEN Code — man hat sich ja gerade selbst einen gesetzt und ist schon
|
|
eingeloggt. Sie dient als Beleg, dass die Adresse stimmt, und als
|
|
Erinnerung, wo man wieder reinkommt. */
|
|
export async function sendWelcomeMail({ to, name }) {
|
|
return send({
|
|
to,
|
|
subject: "Willkommen in der DogiCrew 🩵",
|
|
html: WRAP(
|
|
`Willkommen, ${name}! 🩵`,
|
|
`<p style="line-height:1.6;">Dein Konto ist fertig. Du kommst ab jetzt jederzeit mit dieser
|
|
E-Mail-Adresse und deinem selbst gewählten Zugangscode rein.</p>
|
|
<p style="line-height:1.6;">Solltest du deinen Code mal vergessen, gibt es auf der Anmeldeseite
|
|
„Code vergessen?“ — darüber bekommst du hier per E-Mail einen Einmalcode und kannst dir
|
|
einen neuen Zugangscode setzen.</p>
|
|
<p style="color:#9aa4b2;font-size:13px;">Hast du dich nicht angemeldet? Dann ignoriere diese
|
|
E-Mail einfach — ohne deinen Zugangscode kommt niemand in das Konto.</p>`
|
|
),
|
|
});
|
|
}
|
|
|
|
export async function sendMilestoneUnlocked({ to, name, premiumLabel }) {
|
|
return send({
|
|
to,
|
|
subject: `Neuer Meilenstein freigeschaltet — ${premiumLabel} 🎉`,
|
|
html: WRAP(
|
|
`Glückwunsch, ${name}! 🩵`,
|
|
`<p style="line-height:1.6;">Du hast einen neuen Meilenstein erreicht und kannst jetzt deine Prämie einlösen:</p>
|
|
<p style="font-size:20px;font-weight:800;color:#fff;">${premiumLabel}</p>
|
|
<p style="color:#9aa4b2;font-size:13px;">Logge dich in deinen Supporter-Bereich ein, um die Prämie einzulösen.</p>`
|
|
),
|
|
});
|
|
}
|