source: Klonkt/src/views/pages/paid-gate.ejs@ 9e9e6f9

main
Last change on this file since 9e9e6f9 was 928d1c7, checked in by Robin <roboburr@…>, 7 weeks ago

Feature: paid posts slice 2, post model + teaser gate

A post can be marked paid (klonkt-demo-aki), premium-gated in the
editor with an optional per-post price; additive columns posts.paid +
paid_min_cents. On the web, a paid post shows only a teaser to anyone
who is not the owner/editor (new pages/paid-gate, mirroring the
fan-gate); the owner previews the full post. The passkey unlock arrives
in slices 3-4, so the gate says so for now.

Federation is leak-safe: buildNote federates only a PUBLIC teaser (the
excerpt, else the first paragraph, never later content) plus a
"read the full post (supporters)" link back, and no media attachments.
A short paid post can no longer spill its body: the teaser is the first
paragraph only, pinned by a test.

Changed files:
src/config/database.js

  • additive columns posts.paid, posts.paid_min_cents

src/routes/posts.js

  • create/update read paid + price (premium-gated), store them, pass to deliverCreate/Update; paidTeaser helper; the paid web gate

src/services/ActivityPubService.js

  • buildNote: paid post -> public teaser + link, first paragraph only

src/views/pages/post-edit.ejs

  • paid toggle + price field (in the premium block)

New file:
src/views/pages/paid-gate.ejs

  • teaser + supporters notice

test/paid-federation.test.js

  • teaser + link, no full content, excerpt-as-teaser, non-paid intact

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

  • Property mode set to 100644
File size: 2.0 KB
RevLine 
[928d1c7]1<div class="container pg-navwrap">
2 <%- include('../partials/post-nav', { newerPost: (typeof newerPost !== 'undefined' ? newerPost : null), olderPost: (typeof olderPost !== 'undefined' ? olderPost : null) }) %>
3</div>
4
5<article class="pg-page">
6 <% if (typeof pgTitle !== 'undefined' && pgTitle) { %><h1 class="pg-title"><%= pgTitle %></h1><% } %>
7
8 <% if (typeof pgTeaser !== 'undefined' && pgTeaser) { %>
9 <div class="pg-teaser"><p><%= pgTeaser %></p></div>
10 <% } %>
11
12 <section class="pg-card">
13 <div class="pg-lock">💶</div>
14 <h2 class="pg-h2">Voor supporters</h2>
15 <p class="pg-sub">
16 Deze post is voor supporters van deze site. Steun je de maker op Patreon
17 <% if (typeof pgCents !== 'undefined' && pgCents) { %>(vanaf &euro;<%= (pgCents/100).toFixed(2) %>)<% } %>,
18 dan ontgrendel je 'm met je passkey. Geen account, geen cookie.
19 </p>
20 <p class="pg-soon">Ontgrendelen met je Patreon-passkey komt eraan.</p>
21 </section>
22</article>
23
24<style>
25 .pg-navwrap { max-width: 720px; margin: 0.5rem auto 1.5rem; padding: 0 1rem; }
26 .pg-page { max-width: 720px; margin: 0 auto 3rem; padding: 0 1rem; }
27 .pg-title { font-family: var(--font-display, serif); font-size: clamp(1.6rem, 4vw, 2.2rem); margin: 0 0 1rem; }
28 .pg-teaser { font-family: var(--font-body, serif); font-size: 1.1rem; line-height: 1.7; color: var(--ink); opacity: .95;
29 -webkit-mask-image: linear-gradient(180deg, #000 55%, transparent); mask-image: linear-gradient(180deg, #000 55%, transparent); }
30 .pg-card { margin: 1.5rem 0 0; border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent); border-radius: 18px; padding: 30px 26px; text-align: center; }
31 .pg-lock { font-size: 38px; margin-bottom: 6px; }
32 .pg-h2 { font-size: 22px; margin: 0 0 8px; }
33 .pg-sub { opacity: .85; line-height: 1.6; margin: 0 auto 12px; max-width: 34em; }
34 .pg-soon { display: inline-block; padding: 10px 18px; border-radius: 10px; font-weight: 600;
35 background: color-mix(in srgb, var(--accent, #6b8f71) 14%, transparent); color: var(--ink, inherit); }
36</style>
Note: See TracBrowser for help on using the repository browser.