docs: add yearly pricing option with discount#2597
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis pull request introduces a pricing frequency toggle feature. A new Changes
Sequence DiagramsequenceDiagram
actor User
participant PricingTiers
participant Tiers
participant TierCTAButton
participant Checkout
User->>PricingTiers: Load pricing page
PricingTiers->>PricingTiers: Initialize frequency state (default "year")
PricingTiers->>Tiers: Render with tiers + frequency
User->>PricingTiers: Toggle frequency switch
PricingTiers->>PricingTiers: Update frequency state
PricingTiers->>Tiers: Re-render with new frequency
Tiers->>Tiers: Render pricing display (yearly or monthly)
alt frequency === "year"
Tiers->>Tiers: Show monthly equivalent, strikethrough price, "-50%" badge
else frequency === "month"
Tiers->>Tiers: Show standard monthly pricing
end
Tiers->>TierCTAButton: Pass frequency prop
User->>TierCTAButton: Click purchase button
TierCTAButton->>TierCTAButton: Determine checkout slug based on frequency
alt business tier + yearly
TierCTAButton->>Checkout: Use "business-yearly" slug
else other tiers
TierCTAButton->>Checkout: Use tier.id slug
end
TierCTAButton->>Checkout: Redirect to checkout
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/mantine
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-email-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
commit: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
docs/app/pricing/tiers.tsx (1)
97-100: Include billing frequency in the pricing events.This branch adds a second Business checkout path, but the surrounding telemetry still only sends
tier: tier.id. Monthly and yearly clicks will collapse into the same funnel, which makes it harder to validate adoption of the new yearly offer. AddfrequencyorcheckoutSlugto the event payloads.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/pricing/tiers.tsx` around lines 97 - 100, The telemetry currently only sends tier: tier.id so monthly and yearly Business checkouts collapse; update the pricing click/purchase event payload to include the billing frequency or the computed checkoutSlug (use the existing checkoutSlug variable or frequency) before calling authClient.checkout so events distinguish yearly vs monthly (locate the code that builds the event payload which currently references tier.id and add frequency or checkoutSlug to that payload).docs/lib/auth.ts (1)
210-213: Optional: Normalize yearly billing back to the existing Business entitlement.The webhook at
docs/lib/auth.tslines 240-244 persists the product slug directly asplanType. While this meansplanTypecan be either"business"or"business-yearly", the codebase already handles both values correctly through theBUSINESS_PLAN_TYPESset and theisBusinessPlan()helper function indocs/app/pricing/tiers.tsx.However, normalizing
planTypeback to"business"in the webhook and storing billing cadence separately would improve clarity and reduce semantic ambiguity in the stored data. The current approach works but is less explicit about the intent.Suggested normalization in the webhook update path
- const planType = Object.values(PRODUCTS).find( + const productSlug = Object.values(PRODUCTS).find( (p) => p.id === productId, )?.slug; + const planType = + productSlug === "business-yearly" ? "business" : productSlug; await authContext.internalAdapter.updateUser(userId, { planType, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/lib/auth.ts` around lines 210 - 213, The webhook in docs/lib/auth.ts persists the product slug directly into planType (e.g., "business-yearly"); change the update path so that if the selected product slug is "business-yearly" you normalize planType to "business" and persist billing cadence separately (e.g., billingInterval = "yearly" vs "monthly"), otherwise keep planType as the slug for non-business plans; use the existing PRODUCTS mapping and ensure compatibility with BUSINESS_PLAN_TYPES and isBusinessPlan() by storing only the canonical "business" plan in planType and recording cadence metadata alongside it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/app/pricing/PricingTiers.tsx`:
- Around line 24-34: The billing toggle button with role="switch" and
aria-checked (in the component using frequency and setFrequency) lacks an
accessible name; update the button to include an accessible label by either
adding aria-label="Billing frequency toggle" or by giving the visible
“Monthly/Yearly” text an id and wiring aria-labelledby="<that-id>" on the button
so screen readers announce the switch state; keep the existing frequency and
setFrequency handlers intact and ensure the chosen id is unique in the
component.
In `@docs/lib/product-list.ts`:
- Around line 10-17: The "business-yearly" product entry uses the placeholder ID
"NOT-CREATED" in non-production, which breaks sandbox yearly checkout flows;
either replace that placeholder with the actual sandbox Polar product ID for the
yearly plan or gate/disable the yearly option outside production where
"business-yearly" is referenced (e.g., in docs/app/pricing/PricingTiers.tsx
default selection and in docs/lib/auth.ts checkout registration). Update the
"business-yearly" object to contain the sandbox product ID when NODE_ENV !==
"production", or add an environment check in PricingTiers.tsx/auth.ts to hide or
disable the yearly option when the ID is missing.
---
Nitpick comments:
In `@docs/app/pricing/tiers.tsx`:
- Around line 97-100: The telemetry currently only sends tier: tier.id so
monthly and yearly Business checkouts collapse; update the pricing
click/purchase event payload to include the billing frequency or the computed
checkoutSlug (use the existing checkoutSlug variable or frequency) before
calling authClient.checkout so events distinguish yearly vs monthly (locate the
code that builds the event payload which currently references tier.id and add
frequency or checkoutSlug to that payload).
In `@docs/lib/auth.ts`:
- Around line 210-213: The webhook in docs/lib/auth.ts persists the product slug
directly into planType (e.g., "business-yearly"); change the update path so that
if the selected product slug is "business-yearly" you normalize planType to
"business" and persist billing cadence separately (e.g., billingInterval =
"yearly" vs "monthly"), otherwise keep planType as the slug for non-business
plans; use the existing PRODUCTS mapping and ensure compatibility with
BUSINESS_PLAN_TYPES and isBusinessPlan() by storing only the canonical
"business" plan in planType and recording cadence metadata alongside it.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bffda504-dbaf-4074-a6e2-4f58153e5b0a
📒 Files selected for processing (5)
docs/app/pricing/PricingTiers.tsxdocs/app/pricing/page.tsxdocs/app/pricing/tiers.tsxdocs/lib/auth.tsdocs/lib/product-list.ts
| <button | ||
| type="button" | ||
| role="switch" | ||
| aria-checked={frequency === "year"} | ||
| onClick={() => | ||
| setFrequency((f) => (f === "month" ? "year" : "month")) | ||
| } | ||
| className={cn( | ||
| "relative inline-flex h-7 w-12 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors", | ||
| frequency === "year" ? "bg-purple-600" : "bg-stone-300", | ||
| )} |
There was a problem hiding this comment.
Give the billing switch a programmatic label.
The control has role="switch" and aria-checked, but it still has no accessible name, so screen readers will announce an unnamed switch. Add aria-label or wire aria-labelledby to the visible “Monthly/Yearly” text.
Accessibility fix
<button
type="button"
role="switch"
+ aria-label="Toggle billing frequency"
aria-checked={frequency === "year"}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| type="button" | |
| role="switch" | |
| aria-checked={frequency === "year"} | |
| onClick={() => | |
| setFrequency((f) => (f === "month" ? "year" : "month")) | |
| } | |
| className={cn( | |
| "relative inline-flex h-7 w-12 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors", | |
| frequency === "year" ? "bg-purple-600" : "bg-stone-300", | |
| )} | |
| <button | |
| type="button" | |
| role="switch" | |
| aria-label="Toggle billing frequency" | |
| aria-checked={frequency === "year"} | |
| onClick={() => | |
| setFrequency((f) => (f === "month" ? "year" : "month")) | |
| } | |
| className={cn( | |
| "relative inline-flex h-7 w-12 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors", | |
| frequency === "year" ? "bg-purple-600" : "bg-stone-300", | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/app/pricing/PricingTiers.tsx` around lines 24 - 34, The billing toggle
button with role="switch" and aria-checked (in the component using frequency and
setFrequency) lacks an accessible name; update the button to include an
accessible label by either adding aria-label="Billing frequency toggle" or by
giving the visible “Monthly/Yearly” text an id and wiring
aria-labelledby="<that-id>" on the button so screen readers announce the switch
state; keep the existing frequency and setFrequency handlers intact and ensure
the chosen id is unique in the component.
| "business-yearly": { | ||
| id: | ||
| process.env.NODE_ENV === "production" | ||
| ? "ba3965dc-e1ca-494e-b36a-62e2e41615d4" | ||
| : "NOT-CREATED", | ||
| name: "Business Yearly", | ||
| slug: "business-yearly", | ||
| } as const, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Expected: a placeholder non-production id in product-list,
# the slug registered in auth checkout config,
# and the pricing UI defaulting to yearly.
rg -n -C2 '"business-yearly"|NOT-CREATED' docs/lib/product-list.ts docs/lib/auth.ts
rg -n -C2 'useState<Frequency>\("year"\)' docs/app/pricing/PricingTiers.tsxRepository: TypeCellOS/BlockNote
Length of output: 1089
Add sandbox product ID for non-production yearly flow or gate yearly outside production.
The yearly plan defaults in the UI (docs/app/pricing/PricingTiers.tsx, line 10) and is registered in checkout (docs/lib/auth.ts, lines 211–212), but uses the placeholder "NOT-CREATED" for non-production environments (docs/lib/product-list.ts, line 14). This prevents end-to-end testing of the yearly checkout flow in sandbox. Either create the missing Polar sandbox product and populate its ID, or disable the yearly option outside production.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/lib/product-list.ts` around lines 10 - 17, The "business-yearly" product
entry uses the placeholder ID "NOT-CREATED" in non-production, which breaks
sandbox yearly checkout flows; either replace that placeholder with the actual
sandbox Polar product ID for the yearly plan or gate/disable the yearly option
outside production where "business-yearly" is referenced (e.g., in
docs/app/pricing/PricingTiers.tsx default selection and in docs/lib/auth.ts
checkout registration). Update the "business-yearly" object to contain the
sandbox product ID when NODE_ENV !== "production", or add an environment check
in PricingTiers.tsx/auth.ts to hide or disable the yearly option when the ID is
missing.
Summary
business-yearlyPolar product for checkoutSummary by CodeRabbit
New Features
Updates