Sell 8 premium templates, with the content actually withheld - #341
Merged
Conversation
Eight of the twenty-one templates become individually purchasable; thirteen stay free, chosen so every category a first site reaches for still has a free option. The gate is real rather than cosmetic. lib/templates.ts is imported by six client components, so everything in it ships to every visitor: a lock drawn in the UI would be defeated by opening devtools. A premium template therefore publishes only its opening section, and the rest lives in premiumTemplateSections.ts, which is server-only and served by an entitlement-checked route. Verified against the production bundle: none of the 33 withheld strings appears in any client chunk, while free template copy still does. The teaser is not duplicated across the two modules — it lives once in the public catalogue and fullTemplateSections joins the halves, so they cannot drift. The numbers the gallery quotes about a premium template are baked in and held to the real sections by a parity test, so a locked card cannot understate what is behind it. Purchases mirror the export flow: a Lemon Squeezy variant of its own, a TemplatePurchase row keyed by slug rather than a foreign key so a purchase survives a rename, idempotent grants, the refund tombstone, and reuse of an in-flight checkout instead of a second charge. What was bought is decided by the variant the store recorded, never by the browser's custom_data, so a template order cannot unlock an export or the reverse. Pricing now shows Free, premium templates, and enterprise by contact. Fixes nothing on the tracker — this is the new business model.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of three PRs for the new model. 8 of 21 templates become individually purchasable; 13 stay free.
The gate is real, not cosmetic
lib/templates.tsis imported by six client components, so everything in it ships to every visitor. A lock drawn in the UI would be defeated by opening devtools — so this splits the data:premiumTemplateSections.ts, markedimport "server-only", served by an entitlement-checked route.Verified against the production bundle: none of the 33 withheld strings appears in any client chunk, while free-template copy still does (control check, so the test can't pass vacuously). A test also fails if any
"use client"file ever imports the server-only module.No duplication, no drift
The teaser is not copied into both modules — it lives once in the public catalogue and
fullTemplateSections()joins the halves. The numbers the gallery quotes about a locked template (fullSectionCount,fullScrollHeight) are baked in and held to the real sections by a parity test, so a locked card can't understate what's behind it. Without that, premium cards would have advertised "1 section".Purchases mirror the export flow
Its own Lemon Squeezy variant; a
TemplatePurchaserow keyed by slug rather than a foreign key, so a purchase survives a template being renamed or withdrawn; idempotent grants; the refund tombstone from #263; and reuse of an in-flight checkout instead of a second charge.What was bought is decided by the variant the store recorded, never by the browser's
custom_data— otherwise a cheaper template order could carry asite_idand unlock an export, or the reverse. Both directions are tested.Which 8, and why
Chosen for category spread rather than content depth (depth was uniform): Meridian, AuraBeauty, NightRealm, Ledger, Harbour, Ember, GreenShift, TripVault. A test asserts SaaS, Portfolio, Agency, Editorial and Food & Drink each keep a free option, so a first site never requires a purchase.
Verified
tsc,eslint,next buildclean; suite 462 → 497.custom_dataover the variant fails 2 webhook tests.setStatein an effect) and restructured both rather than suppressing.TemplatePurchasemigration auto-applies on merge. RequiresLEMONSQUEEZY_TEMPLATE_VARIANT_IDandLEMONSQUEEZY_TEMPLATE_PRICE_CENTSin Vercel — until those are set the checkout returns 503 and templates simply stay locked, which is the safe failure.