Changeset c3d12a6 in Klonkt for src/routes


Ignore:
Timestamp:
07/21/2026 02:07:18 AM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
9019a3e
Parents:
603d246
git-author:
Robin <roboburr@…> (07/21/2026 02:07:07 AM)
git-committer:
Robin <roboburr@…> (07/21/2026 02:07:18 AM)
Message:

Feature: a way to actually become a supporter, and a styled expired page

Bart's test surfaced two gaps in the visitor flow:

  • After "Allow" on Patreon, a non-supporter landed on "Nog geen supporter" with no way to actually pledge. The page now shows a primary "Word supporter op Patreon" button. The site owner sets their public Patreon page in Beheer -> Betaalde posts (new field); it's also linked from the gate itself ("Nog geen supporter? Word het op Patreon").
  • The invalid/expired callback replied with a raw res.send() plain-text line. It now renders the normal paid-result page (reason 'expired').

Changed files:
src/config/database.js

  • paid_patreon.patreon_url column (additive)

src/services/PaidPatreonService.js

  • patreonUrl in config/status/save (undefined keeps, empty clears) + patreonUrl(siteId) helper

src/routes/admin-paid.js

  • save patreon_url from the form

src/views/pages/admin-paid.ejs

  • "Openbare Patreon-pagina" field

src/routes/paid.js

  • expired callback renders paid-result; pass patronUrl to the result pages

src/routes/posts.js

  • pass pgPatronUrl to the gate

src/views/pages/paid-gate.ejs

  • "Word supporter" join line under the unlock button

src/views/pages/paid-result.ejs

  • "Word supporter op Patreon" primary button, 'expired' reason, ghost back button always has a label

test/paid-patron.test.js

  • patreonUrl set/keep/clear semantics

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

Location:
src/routes
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-paid.js

    r603d246 rc3d12a6  
    6060      accessToken: (b.access_token || '').trim() || undefined,
    6161      refreshToken: (b.refresh_token || '').trim() || undefined,
     62      // Empty clears it (null), a value sets it. Unlike secrets, this is not
     63      // sensitive and there's a clear "remove the link" intent.
     64      patreonUrl: (b.patreon_url || '').trim() || null,
    6265      defaultMinCents: Number.isFinite(cents) ? cents : undefined,
    6366    });
  • src/routes/paid.js

    r603d246 rc3d12a6  
    5454  const payload = verifyBlob(String(req.query.state || ''));
    5555  if (!payload || payload.purpose !== 'patron' || payload.siteId !== r.site.id) {
    56     return res.status(400).send('Ongeldige of verlopen aanvraag. Probeer opnieuw vanaf de post.');
     56    return renderPage(req, res, 'pages/paid-result', {
     57      pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false, reason: 'expired',
     58    });
    5759  }
     60  const patronUrl = PaidPatreon.patreonUrl(r.site.id);
    5861  const code = String(req.query.code || '');
    5962  if (req.query.error || !code) {
    60     return renderPage(req, res, 'pages/paid-result', { pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false, reason: 'declined', postSlug: payload.post });
     63    return renderPage(req, res, 'pages/paid-result', { pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false, reason: 'declined', postSlug: payload.post, patronUrl });
    6164  }
    6265  const membership = await PaidPatreon.verifyPatron(r.site.id, code, baseUrl(req) + '/paid/callback').catch(() => null);
     
    6669    return renderPage(req, res, 'pages/paid-result', {
    6770      pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false,
    68       reason: active ? 'tier' : 'notpatron', neededCents: payload.cents, haveCents: cents, postSlug: payload.post,
     71      reason: active ? 'tier' : 'notpatron', neededCents: payload.cents, haveCents: cents, postSlug: payload.post, patronUrl,
    6972    });
    7073  }
  • src/routes/posts.js

    r603d246 rc3d12a6  
    2121import ActivityPubService from '../services/ActivityPubService.js';
    2222import { premiumUnlocked } from '../services/PatreonService.js';
    23 import { defaultMinCents as paidDefaultMinCents } from '../services/PaidPatreonService.js';
     23import { defaultMinCents as paidDefaultMinCents, patreonUrl as paidPatronUrl } from '../services/PaidPatreonService.js';
    2424import MusicMeta from '../services/MusicMeta.js';
    2525
     
    12471247      pgCents: post.paid_min_cents || paidDefaultMinCents(site.id),
    12481248      pgSlug: post.slug,
     1249      pgPatronUrl: paidPatronUrl(site.id),
    12491250      newerPost,
    12501251      olderPost,
Note: See TracChangeset for help on using the changeset viewer.