Skip to content

Hid offer type selector for yearly retention offers#26606

Merged
minimaluminium merged 1 commit intomainfrom
retention-offer-yearly-hide-button-select
Feb 26, 2026
Merged

Hid offer type selector for yearly retention offers#26606
minimaluminium merged 1 commit intomainfrom
retention-offer-yearly-hide-button-select

Conversation

@minimaluminium
Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/BER-3364/use-a-stripe-coupon-instead-of-a-trial-for-free-month-offers

When cadence is yearly, the "Free month(s)" option is already excluded, leaving the type selector with only "Percentage discount" — a radio-style control with a single option. This hides the entire type selector container for yearly retention offers since there's no choice to make.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 26, 2026

Walkthrough

The change modifies the Details section rendering logic in the retention offer editor component. Previously, a bordered container with two ButtonSelect options (percent and free_months) was always rendered. Now, the container and percent-option ButtonSelect are conditionally rendered only when cadence is set to monthly. For yearly cadence, the percent option is not displayed. This alters the available offer-type options based on the selected cadence and changes the surrounding DOM structure in the monthly cadence view.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: hiding the offer type selector for yearly retention offers, which matches the core modification in the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining the rationale for hiding the type selector when cadence is yearly and referencing the relevant issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch retention-offer-yearly-hide-button-select

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/growth/offers/edit-retention-offer-modal.tsx (1)

260-283: Implementation looks correct; consider defensive handling for edge case.

The conditional rendering correctly hides the type selector when cadence === 'yearly', aligning with the PR objective. For yearly offers, only the percentage discount fields will render since getDefaultState sets type: 'percent' for yearly cadence (line 71).

Potential edge case: If an existing yearly offer was somehow stored with the free_months pattern (e.g., via API or legacy data), getRetentionOfferFormState would load it with type: 'free_months'. In this scenario:

  • The type selector remains hidden (yearly)
  • The free_months input field renders (lines 330-344)
  • User has no way to switch to percentage discount

This may be unlikely in practice, but consider adding defensive enforcement in getRetentionOfferFormState to force type: 'percent' for yearly cadence:

🛡️ Optional defensive fix in getRetentionOfferFormState
 const getRetentionOfferFormState = (offer: Offer | null, cadence: 'monthly' | 'yearly' = 'monthly'): RetentionOfferFormState => {
     const defaultState = getDefaultState(cadence);

     if (!offer) {
         return defaultState;
     }

     const isFreeMonths = isFreeMonthsPattern(offer);
+    // For yearly cadence, force percent type since free_months is not supported
+    const resolvedType = cadence === 'yearly' ? 'percent' : (isFreeMonths ? 'free_months' : 'percent');
     const isPercentOffer = offer.type === 'percent' && !isFreeMonths;
     const repeatingDurationInMonths = offer.duration === 'repeating' && offer.duration_in_months ? offer.duration_in_months : defaultState.durationInMonths;

     return {
         enabled: offer.status === 'active',
         displayTitle: offer.display_title || '',
         displayDescription: offer.display_description || '',
-        type: isFreeMonths ? 'free_months' : 'percent',
+        type: resolvedType,
         percentAmount: isPercentOffer ? offer.amount : defaultState.percentAmount,
         duration: isPercentOffer ? offer.duration : defaultState.duration,
         durationInMonths: repeatingDurationInMonths,
         freeMonths: isFreeMonths ? (offer.duration_in_months || defaultState.freeMonths) : defaultState.freeMonths
     };
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/admin-x-settings/src/components/settings/growth/offers/edit-retention-offer-modal.tsx`
around lines 260 - 283, Ensure yearly offers cannot load with free_months by
adding a defensive check in getRetentionOfferFormState: when cadence ===
'yearly', override or coerce the loaded form state's type to 'percent'
(mirroring getDefaultState behavior) so the UI (formState/type and dependent
fields) always reflects a percent-only offer; update any state normalization
there to set type = 'percent' and clear free_months-related fields if present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/admin-x-settings/src/components/settings/growth/offers/edit-retention-offer-modal.tsx`:
- Around line 260-283: Ensure yearly offers cannot load with free_months by
adding a defensive check in getRetentionOfferFormState: when cadence ===
'yearly', override or coerce the loaded form state's type to 'percent'
(mirroring getDefaultState behavior) so the UI (formState/type and dependent
fields) always reflects a percent-only offer; update any state normalization
there to set type = 'percent' and clear free_months-related fields if present.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f036ba1 and 835ebf3.

📒 Files selected for processing (1)
  • apps/admin-x-settings/src/components/settings/growth/offers/edit-retention-offer-modal.tsx

@minimaluminium minimaluminium merged commit aa447ca into main Feb 26, 2026
32 checks passed
@minimaluminium minimaluminium deleted the retention-offer-yearly-hide-button-select branch February 26, 2026 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants