messages for platform winding down, updates to payments buttons to support free content for all content but still track payments#85
Conversation
…pport free content for all content but still track payments
WalkthroughIntroduces a promo-pricing system: adds constants and a PromoFreeBadge component, updates multiple UI surfaces to display promo “Free” messaging, adds auth guard and a promo-free short-circuit in payment buttons, validates amountPaid in the purchase API, adds an announcement banner on the home page, updates authorPubkeys, and adds AGENTS.md. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as UI Button
participant Auth as Auth Session
participant Promo as Promo Constants
participant Pay as Invoice/Payment Dialog
participant API as POST /api/purchase/resource
User->>UI: Click Purchase
UI->>Auth: Check session
alt Not signed in
UI->>User: Toast: Sign in required
UI->>User: Redirect /auth/signin
else Signed in
UI->>Promo: Read PROMO_FREE_PRICE_SATS
alt Promo free (0 sats)
UI->>UI: handlePaymentSuccess(preimage="promo-free", amount=0)
UI->>User: Toast: Access granted (promo)
else Normal pricing
UI->>Pay: fetchInvoice + open dialog
User-->>Pay: Complete/Cancel
alt Success
Pay->>API: Record purchase (amountPaid)
API-->>Pay: 200 OK
Pay->>UI: Payment success/preimage
UI->>User: Toast: Access granted
else Failure/Cancel
Pay->>UI: Close dialog
end
UI->>UI: Clear invoice state
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on October 1
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
|
|
||
| if (PROMO_FREE_PRICE_SATS === 0) { | ||
| await handlePaymentSuccess({ paid: true, preimage: 'promo-free-course' }); | ||
| showToast('success', 'Free Course Access', PROMO_PRICING_MESSAGE); |
There was a problem hiding this comment.
Bug: Promo Pricing Overwrites Discount Codes
The new promo pricing logic (lines 83-87) bypasses existing discount code functionality, leading to a confusing UX where discounts appear applied but are ignored. This also removes the check for 0-satoshi discounted amounts, which could cause issues when fetching invoices if a discount would make the course free.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/config/appConfig.js (1)
30-38: Server secrets in shared config risk browser exposureThis module is imported widely; in Next.js anything imported by client bundles risks leaking values or causing undefined-at-runtime differences. Keep LND credentials strictly server-only.
Proposed approach:
- Move
customLightningAddresses(or just the sensitive fields:invoiceMacaroon,lndHost,lndPort,lndCert) into a server-only module (e.g.,src/server/config/lndConfig.ts) that is imported only from API routes/server code.- Keep a client-safe shape in
appConfig(names, flags) without env references.Example diff to remove env usage from client config:
customLightningAddresses: [ { // todo remove need for lowercase // name will appear as name@plebdevs.com (lowercase) name: 'austin', // If enabled, zaps are enabled allowsNostr: true, // make you're own lud06 metadata description description: "Austin's Lightning Address", // millisats maxSendable: 10000000000, // millisats minSendable: 1000, - // Your LND invoice macaroon - invoiceMacaroon: process.env.LND_MACAROON, + // server-only: invoiceMacaroon configured in server code // your LND TLS certificate (may be optional depending on your LND configuration) lndCert: '', - // your LND host (do not include https:// or port) - lndHost: process.env.LND_HOST, - // your LND REST API port (default is 8080) - lndPort: '8080', + // server-only: lndHost/lndPort configured in server code },src/components/bitcoinConnect/CoursePaymentButton.js (1)
250-260: Fix payment dialog title to reflect discounted amountTitle shows the full amount even when a discount is applied.
Use a computed value:
// near render const payableAmount = discountApplied ? calculateDiscount(amount).discountedAmount : amount; ... <Payment invoice={invoice.paymentRequest} onPaid={handlePaymentSuccess} paymentMethods="all" title={`Pay ${payableAmount} sats`} />src/components/content/documents/DocumentDetails.js (1)
143-153: ReferenceError at runtime: Buffer is not importedBuffer.from(...) is used but Buffer isn’t imported in this file. This will throw in the browser.
Add the import near the other imports:
+import { Buffer } from 'buffer';
🧹 Nitpick comments (19)
AGENTS.md (1)
6-14: Add runtime/tooling versions and workflow tipsInclude Node.js/PNPM versions (engines), Next.js/Prisma versions, and a note about pre-commit hooks (lint/prettier) to reduce CI churn.
Apply this diff to add a Versions section:
## Build, Test, and Development Commands Install dependencies once with `npm install`. Use `npm run dev` for the hot-reloading Next.js server, or `docker compose up --build` when the Postgres service is required. Apply schema updates with `npx prisma migrate dev` and regenerate clients via `npx prisma generate` (also run automatically on `postinstall`). Before shipping, execute `npm run build` followed by `npm run start` to verify the production bundle. Guard code quality with `npm run lint`, and auto-fix common issues using `npm run lint:fix`. +## Versions & Tooling +- Node.js: specify supported version in `package.json#engines` (e.g., `>=18`) +- Package manager: npm (or pnpm) — keep lockfile committed +- Frameworks: Next.js (version), Prisma (version); keep in sync with CI +- Optional: enable pre-commit hooks for `lint`/`prettier` to reduce CI failuressrc/config/appConfig.js (1)
45-46: Minor copy edit in comment“make you're own” → “make your own”.
- // make you're own lud06 metadata description + // make your own lud06 metadata descriptionsrc/components/content/courses/details/CourseDetails.js (2)
20-22: Remove unused import
PROMO_PRICING_MESSAGEis not used here; will fail lint.-import PromoFreeBadge from '@/components/pricing/PromoFreeBadge'; -import { PROMO_PRICING_MESSAGE } from '@/constants/promoPricing'; +import PromoFreeBadge from '@/components/pricing/PromoFreeBadge';
168-176: Deduplicate the “check + badge” snippetExtract to a tiny component to avoid repetition and keep styling consistent.
Example:
+const PaidUnlocked = () => ( + <div className="flex items-center gap-2"> + <i className="pi pi-check text-sm text-blue-300" /> + <PromoFreeBadge + wrapperClassName="flex items-center gap-1 text-sm" + labelClassName="text-blue-300 font-semibold" + iconClassName="pi pi-question-circle text-xs text-blue-300" + /> + </div> +); ... - return ( - <div className="flex items-center gap-2"> - <i className="pi pi-check text-sm text-blue-300" /> - <PromoFreeBadge ... /> - </div> - ); + return <PaidUnlocked />;src/pages/index.js (4)
109-111: Deduplicate promo copy: use PROMO_PRICING_MESSAGE constantAvoid drift by sourcing the banner text from the shared constant.
Apply:
@@ -import { Message } from 'primereact/message'; -import { Badge } from 'primereact/badge'; +import { Message } from 'primereact/message'; +import { Badge } from 'primereact/badge'; +import { PROMO_PRICING_MESSAGE } from '@/constants/promoPricing'; @@ - const bannerMessage = - 'All content is free for a limited time while we finish building out PlebDevs Platform V2. Keep an eye out, coming soon (tm)!'; + const bannerMessage = PROMO_PRICING_MESSAGE;Also applies to: 11-12
190-206: Unused CSS selector: .announcement-wrapperThe media rule targets .announcement-wrapper, but no element uses it. Either add the class to the wrapper div or remove the rule.
Minimal fix (apply the class):
- <div className="w-full px-4 md:px-12 mt-6"> + <div className="w-full px-4 md:px-12 mt-6 announcement-wrapper">Also applies to: 289-297
280-282: Overly broad CSS rule may leak styles
.announcement-badge :global(*) { color: #f8f8ff !important; }will force color on all nested nodes and risks unintended overrides.Remove the broad rule:
- .announcement-badge :global(*) { - color: #f8f8ff !important; - }
197-205: Consider reusing PromoFreeBadge for consistencyOptional: swap the Badge+custom styles for the shared PromoFreeBadge to keep promo messaging consistent across surfaces.
src/constants/promoPricing.js (1)
1-4: Add an explicit promo toggle and env overrideRelying only on a numeric constant makes future rollback easy to miss. Provide
IS_PROMO_ACTIVEwith env override and keep the message centralized.export const PROMO_FREE_PRICE_SATS = 0; export const PROMO_PRICING_MESSAGE = 'All content is free for a limited time while we finish building out PlebDevs Platform V2. Keep an eye out, coming soon (tm)!'; export const PROMO_TOOLTIP_POSITION = 'top'; +export const IS_PROMO_ACTIVE = + (process.env.NEXT_PUBLIC_PROMO_ACTIVE ?? 'true').toLowerCase() === 'true';src/components/bitcoinConnect/CoursePaymentButton.js (3)
219-225: Remove debug log and rely on existing guardThe unauthenticated path already toasts and redirects in fetchInvoice.
- if (status === 'unauthenticated') { - console.log('unauthenticated'); - router.push('/auth/signin'); - } else { - fetchInvoice(); - } + if (status === 'unauthenticated') { + router.push('/auth/signin'); + return; + } + fetchInvoice();
106-113: Mark promo purchases explicitly (optional)Consider including a
promo: PROMO_FREE_PRICE_SATS === 0flag inpurchaseDatafor analytics and future audits.
73-103: Backend: 0-sats accepted — add promo tagging & course API validationConfirmed: course and resource endpoints accept amountPaid = 0 (course parses with parseInt; resource explicitly validates and allows 0). The Purchase model has no promo metadata, so promotional grants aren’t recorded as such.
- Files to review: src/pages/api/purchase/course.js (parses amountPaid), src/pages/api/purchase/resource.js (validation example), src/db/models/userModels.js (addCoursePurchaseToUser / addResourcePurchaseToUser), prisma/schema.prisma (Purchase model).
- Action items: add promo metadata (e.g., isPromo:Boolean and/or promoCode:String) to Purchase schema and include it when creating purchases; add input validation in src/pages/api/purchase/course.js to mirror resource.js (reject missing/NaN amounts).
- Optional: persist invoice preimage/comment for audit (client sends preimage like "promo-free-course").
src/components/bitcoinConnect/ResourcePaymentButton.js (2)
121-127: Remove debug log and reuse existing guardMinor cleanup.
- if (status === 'unauthenticated') { - console.log('unauthenticated'); - router.push('/auth/signin'); - } else { - fetchInvoice(); - } + if (status === 'unauthenticated') { + router.push('/auth/signin'); + return; + } + fetchInvoice();
53-66: Backend accepts 0 sats but does not mark promo unlocks
- /api/purchase/resource accepts amountPaid = 0 (only rejects undefined/null; parseInt(0) → 0) and addResourcePurchaseToUser persists amountPaid: 0 (src/pages/api/purchase/resource.js, src/db/models/userModels.js).
- There is no distinct promo flag or stored preimage on purchases (prisma schema/migration only records amountPaid). Add a boolean (e.g., promo: true) or promoIdentifier/preimage to the purchased record and update the API + DB migration and front-end payload to distinguish promo unlocks.
src/components/content/videos/VideoDetails.js (2)
179-186: DRY up the repeated “check + PromoFreeBadge” blockThe same JSX appears in three branches. Extract a tiny component to cut duplication and ease future tweaks.
Example (outside this file):
// components/pricing/AccessPromoBadge.js export default function AccessPromoBadge({ color = 'text-blue-300' }) { return ( <div className={`flex items-center gap-2 ${color}`}> <i className="pi pi-check text-sm" aria-hidden="true" /> <PromoFreeBadge wrapperClassName="flex items-center gap-1 text-sm" labelClassName={`${color} font-semibold`} iconClassName={`pi pi-question-circle text-xs ${color}`} /> </div> ); }Then replace each block with:
<AccessPromoBadge />Also applies to: 198-205, 211-218
242-245: Hide decorative icon from screen readers (a11y nit)Mark the big gift icon as decorative.
Apply:
- <i className="pi pi-gift text-[100px] text-green-400"></i> + <i className="pi pi-gift text-[100px] text-green-400" aria-hidden="true"></i>src/components/content/documents/DocumentDetails.js (2)
179-186: Deduplicate the “check + PromoFreeBadge” JSXSame suggestion as in VideoDetails: extract a small AccessPromoBadge to avoid copy/paste.
See the example component provided in the VideoDetails comment and reuse it here for parity.
Also applies to: 198-205, 211-218
234-237: Hide decorative icon from screen readers (a11y nit)Mark the gift icon as decorative.
Apply:
- <i className="pi pi-gift text-[60px] text-green-400"></i> + <i className="pi pi-gift text-[60px] text-green-400" aria-hidden="true"></i>src/components/pricing/PromoFreeBadge.js (1)
33-38: Optional: expose tooltip meaning to assistive techIf you want AT users to get the same hint, add an aria-label. Otherwise, keep it decorative.
Example:
- <i + <i id={tooltipId} className={`${iconClassName} cursor-help`} data-pr-tooltip={PROMO_PRICING_MESSAGE} data-pr-position={tooltipPosition} + aria-label="Promo pricing details" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
AGENTS.md(1 hunks)src/components/bitcoinConnect/CoursePaymentButton.js(5 hunks)src/components/bitcoinConnect/ResourcePaymentButton.js(4 hunks)src/components/content/carousels/templates/CombinedTemplate.js(2 hunks)src/components/content/carousels/templates/CourseTemplate.js(2 hunks)src/components/content/carousels/templates/DocumentTemplate.js(2 hunks)src/components/content/carousels/templates/VideoTemplate.js(2 hunks)src/components/content/combined/CombinedDetails.js(4 hunks)src/components/content/courses/details/CourseDetails.js(2 hunks)src/components/content/courses/details/DesktopCourseDetails.js(3 hunks)src/components/content/courses/details/MobileCourseDetails.js(3 hunks)src/components/content/documents/DocumentDetails.js(4 hunks)src/components/content/dropdowns/ContentDropdownItem.js(2 hunks)src/components/content/videos/VideoDetails.js(4 hunks)src/components/pricing/PromoFreeBadge.js(1 hunks)src/config/appConfig.js(1 hunks)src/constants/promoPricing.js(1 hunks)src/pages/api/purchase/resource.js(1 hunks)src/pages/index.js(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (15)
src/components/content/carousels/templates/CombinedTemplate.js (1)
src/components/pricing/PromoFreeBadge.js (2)
isMobile(25-25)PromoFreeBadge(12-42)
src/components/bitcoinConnect/ResourcePaymentButton.js (5)
src/components/bitcoinConnect/CoursePaymentButton.js (4)
session(28-28)router(30-30)handlePaymentSuccess(106-133)fetchInvoice(74-104)src/hooks/useToast.js (1)
showToast(11-13)src/constants/promoPricing.js (4)
PROMO_FREE_PRICE_SATS(1-1)PROMO_FREE_PRICE_SATS(1-1)PROMO_PRICING_MESSAGE(2-3)PROMO_PRICING_MESSAGE(2-3)src/components/buttons/GenericButton.js (1)
GenericButton(4-35)src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)
src/components/content/carousels/templates/VideoTemplate.js (1)
src/components/pricing/PromoFreeBadge.js (2)
isMobile(25-25)PromoFreeBadge(12-42)
src/components/content/courses/details/MobileCourseDetails.js (1)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)
src/components/content/courses/details/DesktopCourseDetails.js (1)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)
src/components/content/dropdowns/ContentDropdownItem.js (1)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)
src/components/content/courses/details/CourseDetails.js (1)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)
src/components/content/carousels/templates/CourseTemplate.js (1)
src/components/pricing/PromoFreeBadge.js (2)
isMobile(25-25)PromoFreeBadge(12-42)
src/components/bitcoinConnect/CoursePaymentButton.js (5)
src/components/bitcoinConnect/ResourcePaymentButton.js (4)
session(24-24)router(26-26)handlePaymentSuccess(85-112)fetchInvoice(53-83)src/hooks/useToast.js (1)
showToast(11-13)src/constants/promoPricing.js (4)
PROMO_FREE_PRICE_SATS(1-1)PROMO_FREE_PRICE_SATS(1-1)PROMO_PRICING_MESSAGE(2-3)PROMO_PRICING_MESSAGE(2-3)src/components/buttons/GenericButton.js (1)
GenericButton(4-35)src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)
src/components/content/documents/DocumentDetails.js (2)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)src/constants/promoPricing.js (2)
PROMO_PRICING_MESSAGE(2-3)PROMO_PRICING_MESSAGE(2-3)
src/components/content/combined/CombinedDetails.js (3)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)src/components/bitcoinConnect/ResourcePaymentButton.js (1)
ResourcePaymentButton(20-165)src/constants/promoPricing.js (2)
PROMO_PRICING_MESSAGE(2-3)PROMO_PRICING_MESSAGE(2-3)
src/components/content/carousels/templates/DocumentTemplate.js (1)
src/components/pricing/PromoFreeBadge.js (2)
isMobile(25-25)PromoFreeBadge(12-42)
src/pages/api/purchase/resource.js (2)
src/pages/api/purchase/course.js (1)
updatedUser(16-19)src/db/models/userModels.js (2)
addResourcePurchaseToUser(97-116)addResourcePurchaseToUser(97-116)
src/components/content/videos/VideoDetails.js (2)
src/components/pricing/PromoFreeBadge.js (1)
PromoFreeBadge(12-42)src/constants/promoPricing.js (2)
PROMO_PRICING_MESSAGE(2-3)PROMO_PRICING_MESSAGE(2-3)
src/components/pricing/PromoFreeBadge.js (2)
src/constants/promoPricing.js (6)
PROMO_TOOLTIP_POSITION(4-4)PROMO_TOOLTIP_POSITION(4-4)PROMO_FREE_PRICE_SATS(1-1)PROMO_FREE_PRICE_SATS(1-1)PROMO_PRICING_MESSAGE(2-3)PROMO_PRICING_MESSAGE(2-3)src/hooks/useWindowWidth.js (1)
useWindowWidth(3-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (23)
src/config/appConfig.js (1)
14-15: Pubkey addition LGTMNew author pubkey appended correctly.
src/components/content/carousels/templates/VideoTemplate.js (2)
21-21: Consistent badge import LGTM
109-119: Verify PrimeReact Message supportscontentprop in your versionSome versions expect
text/children;contentis version-dependent.If needed, switch to children or
text:- <Message + <Message className={`${isMobile ? 'py-1 text-xs' : 'py-2'} whitespace-nowrap flex items-center gap-2`} severity="success" icon="pi pi-lock-open" - content={ - <PromoFreeBadge - iconClassName="pi pi-question-circle text-xs text-green-300" - labelClassName="font-semibold text-green-400" - /> - } + > + <PromoFreeBadge + iconClassName="pi pi-question-circle text-xs text-green-300" + labelClassName="font-semibold text-green-400" + /> + </Message>src/components/content/courses/details/MobileCourseDetails.js (2)
8-8: Badge import LGTM
152-156: Uniform Promo badge presentation LGTMMatches desktop/detail patterns.
src/components/content/carousels/templates/DocumentTemplate.js (2)
20-20: Consistent badge import LGTM
109-119: VerifyMessagecontentsupport in your PrimeReactSame as VideoTemplate; ensure your version supports it or switch to children.
See suggested diff in VideoTemplate comment; apply similarly here if needed.
src/components/content/courses/details/CourseDetails.js (1)
155-163: Paid/unlocked state UI simplification LGTMCompact icon + badge reads well.
src/components/content/carousels/templates/CombinedTemplate.js (2)
20-20: Badge import LGTM
110-115: Confirm PrimeReactMessageAPIEnsure
contentprop is supported; otherwise use children pattern as in prior suggestion.src/pages/api/purchase/resource.js (1)
26-29: LGTM — parsed amount persisted; verification complete
- Course purchase also parses amountPaid (src/pages/api/purchase/course.js) and both addResourcePurchaseToUser / addCoursePurchaseToUser persist amountPaid (src/db/models/userModels.js).
- PrimeReact ^10.7.0 supports Message's content prop with JSX; multiple components already use content={...} (e.g. src/pages/index.js, src/components/content/carousels/templates/*).
src/components/content/dropdowns/ContentDropdownItem.js (1)
85-91: LGTM: unified promo badgeConsistent Message + PromoFreeBadge usage across surfaces looks good.
Confirm this should always show for all items during promo, regardless of historical price; if not, gate behind a promo flag.
src/components/content/courses/details/DesktopCourseDetails.js (2)
165-170: LGTM: price row uses PromoFreeBadgeConsistent with promo UX elsewhere.
191-196: Verify Start/Continue disabled state under promoThe button disables when paidCourse && (!decryptionPerformed || paidCourse === 'loading'). During promo, ensure users aren’t blocked if decryption isn’t performed.
Would you like a small change to also enable the button when promo is active?
src/components/content/carousels/templates/CourseTemplate.js (1)
130-140: LGTM: single Message with PromoFreeBadgeClean replacement of split paid/free UI; props match other surfaces.
src/components/bitcoinConnect/CoursePaymentButton.js (2)
77-86: Auth guard + promo short‑circuit: goodFlow matches the promo UX; finally ensures loading reset.
101-103: Good: loading reset in finallyPrevents stuck spinners on early returns/errors.
src/components/content/combined/CombinedDetails.js (2)
174-181: LGTM: purchased/decrypted promo indicatorsConsistent PromoFreeBadge and checkmark across states.
Also applies to: 192-199, 204-210
227-234: Promo CTA block looks good; ensure author.lud16 presenceIf
author?.lud16is missing, non‑promo purchases would fail. Promo short‑circuit hides it, but verify for post‑promo.Also applies to: 229-229
src/components/bitcoinConnect/ResourcePaymentButton.js (2)
62-66: Promo short‑circuit: goodMatches CoursePaymentButton behavior and UX.
80-82: Good: loading reset in finallyPrevents spinner from sticking on early returns.
src/components/content/videos/VideoDetails.js (1)
22-24: Centralized promo messaging import — LGTMGood move pulling in PromoFreeBadge and PROMO_PRICING_MESSAGE for consistency across surfaces.
src/components/content/documents/DocumentDetails.js (1)
21-22: Centralized promo messaging import — LGTMConsistent promo UI and copy via shared component/constant looks good.
Summary by CodeRabbit
New Features
UI/UX
Bug Fixes
Documentation
Chores