source: Klonkt/src/views/pages/newsletter.ejs@ f33b24a

main
Last change on this file since f33b24a was 2e247e4, checked in by roboburr <roboburr@…>, 3 months ago

Newsletter / mailing list (premium feature #1) + SMTP wiring

Email infrastructure reuses src/config/mailer.js (env SMTP_*, graceful: without
SMTP no sending, sign-ups still work via single opt-in).

  • DB: subscribers table (per-site, status pending/confirmed/unsub, token) + newsletters table (send history). Idempotent in initializeDatabase.
  • SubscriberService: addSubscriber (double opt-in if SMTP, otherwise single), confirm, unsubscribe, confirmedFor, counts. Email validation + dedupe per (site, email).
  • routes/newsletter.js (public, premium-gated; unsubscribe always works): GET/POST /nieuwsbrief, /nieuwsbrief/bevestigen/:token, /nieuwsbrief/uitschrijven/:token. Confirm/unsub links absolute via PUBLIC_BASE_URL + siteUrlBase.
  • routes/admin-newsletter.js (site admin + premium): GET /admin/newsletter (compose + counts + history + shareable signup link + SMTP warning), POST /admin/newsletter/send (to confirmed subscribers, unsubscribe link per email, history written).
  • views: pages/newsletter.ejs (multi-state) + pages/admin-newsletter.ejs.
  • admin dashboard: premium links Newsletter + Press kit added.

Feature #2 (download-for-email) and #8 (notify) reuse subscribers. node --check
passed; local test suite missing better-sqlite3 (runs on the VPS).

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 3.4 KB
Line 
1<%
2 var _site = site || {};
3 var _title = _site.title || 'deze artiest';
4 var st = (typeof nlState !== 'undefined') ? nlState : 'form';
5%>
6<section class="nl">
7 <div class="nl-card">
8 <% if (st === 'form') { %>
9 <h1 class="nl-h1">Blijf op de hoogte</h1>
10 <p class="nl-sub">Schrijf je in voor de nieuwsbrief van <strong><%= _title %></strong> — nieuwe muziek, shows en updates, rechtstreeks in je inbox. Uitschrijven kan altijd met één klik.</p>
11 <form method="POST" action="<%= siteUrlBase %>/nieuwsbrief" class="nl-form">
12 <input type="email" name="email" required placeholder="jouw@email.nl" value="<%= (typeof nlPrefill !== 'undefined') ? nlPrefill : '' %>" autocomplete="email">
13 <button type="submit" class="nl-btn">Inschrijven</button>
14 </form>
15 <% } else if (st === 'check') { %>
16 <h1 class="nl-h1">Bijna klaar ✉</h1>
17 <p class="nl-sub">We hebben een bevestigingsmail gestuurd naar <strong><%= (typeof nlEmail!=='undefined')?nlEmail:'je adres' %></strong>. Klik op de link in die mail om je inschrijving te bevestigen.</p>
18 <% } else if (st === 'done') { %>
19 <h1 class="nl-h1">Je bent ingeschreven ✓</h1>
20 <p class="nl-sub">Bedankt — je staat op de lijst van <strong><%= _title %></strong>.</p>
21 <% } else if (st === 'confirmed') { %>
22 <h1 class="nl-h1">Inschrijving bevestigd ✓</h1>
23 <p class="nl-sub">Top! Je ontvangt voortaan de nieuwsbrief van <strong><%= _title %></strong>.</p>
24 <% } else if (st === 'unsubbed') { %>
25 <h1 class="nl-h1">Uitgeschreven</h1>
26 <p class="nl-sub">Je bent afgemeld. Je ontvangt geen nieuwsbrieven meer. Van gedachten veranderd? Je kunt je altijd opnieuw inschrijven.</p>
27 <% } else if (st === 'invalid') { %>
28 <h1 class="nl-h1">Ongeldig e-mailadres</h1>
29 <p class="nl-sub">Controleer je adres en probeer 't opnieuw.</p>
30 <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief">← Terug</a></p>
31 <% } else if (st === 'smtperror') { %>
32 <h1 class="nl-h1">Even niet gelukt</h1>
33 <p class="nl-sub">De bevestigingsmail kon niet verstuurd worden. Probeer 't later nog eens.</p>
34 <% } else if (st === 'badtoken') { %>
35 <h1 class="nl-h1">Link ongeldig of verlopen</h1>
36 <p class="nl-sub">Deze link werkt niet meer. Schrijf je eventueel opnieuw in.</p>
37 <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief">Naar inschrijven</a></p>
38 <% } else { %>
39 <h1 class="nl-h1">Er ging iets mis</h1>
40 <p class="nl-sub">Probeer 't later nog eens.</p>
41 <% } %>
42 </div>
43</section>
44
45<style>
46 .nl { max-width: 560px; margin: 0 auto; padding: 48px 18px; }
47 .nl-card { border: 1px solid rgba(128,128,128,.2); border-radius: 18px; padding: 32px 28px; }
48 .nl-h1 { font-size: clamp(24px, 5vw, 34px); margin: 0 0 10px; }
49 .nl-sub { font-size: 15.5px; line-height: 1.6; opacity: .85; margin: 0 0 20px; }
50 .nl-form { display: flex; gap: 10px; flex-wrap: wrap; }
51 .nl-form input { flex: 1 1 220px; padding: 12px 14px; border-radius: 10px; border: 1px solid rgba(128,128,128,.4); background: transparent; color: inherit; font-size: 15px; }
52 .nl-btn { padding: 12px 20px; border-radius: 10px; border: none; background: var(--accent, #6b8f71); color: #fff; font-weight: 600; font-size: 15px; cursor: pointer; text-decoration: none; display: inline-block; }
53 .nl-btn-ghost { background: transparent; color: inherit; border: 1px solid rgba(128,128,128,.4); }
54</style>
Note: See TracBrowser for help on using the repository browser.