Skip to content

Fixed offer end date for repeating offers in Portal #26713

Merged
sagzy merged 1 commit intomainfrom
fix/date-mismatch-2
Mar 5, 2026
Merged

Fixed offer end date for repeating offers in Portal #26713
sagzy merged 1 commit intomainfrom
fix/date-mismatch-2

Conversation

@sagzy
Copy link
Copy Markdown
Contributor

@sagzy sagzy commented Mar 5, 2026

closes https://linear.app/ghost/issue/BER-3413/account-page-date-mismatch-between-discount-end-billing-period

  • When a member redeems a 1-month free offer, Stripe sets the discount start date to now and end date to now + 1 month, i.e. anchors them to the redemption date, not the billing date
  • In Portal, to avoid confusion, we want to anchor the "- Ends {date}" to a billing date to avoid confusion
  • For example, today is 5 Mar 2026 and my subscription renews on 3 Apr 2026. I hit cancel, redeem a free-month offer that applies to my next payment. I expect to see "$0.00/month - Ends 3 Apr 2026", not "$0.00/month - Ends 5 Apr 2026"

Before:
image

After:
CleanShot 2026-03-05 at 11 07 31@2x

@sagzy sagzy changed the title Fixed offer end / renewal date mismatch in Portal account page Fixed offer end date in Portal account page Mar 5, 2026
@sagzy sagzy changed the title Fixed offer end date in Portal account page Fixed offer end date for repeating offers in Portal Mar 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 5, 2026

Walkthrough

Bumps apps/portal package from 2.65.3 to 2.65.4. paid-account-actions now imports and uses addMonths and currentPeriodEnd with discount.duration_in_months to compute repeating discount end labels; JSDoc updated. fixtures-generator adds a durationInMonths parameter and conditionally includes duration_in_months for repeating discounts. addMonths gains validation for non-integer or <1 month values and preserves time components when adding months. Unit tests updated to reflect new discount date semantics and addMonths edge cases.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fixed offer end date for repeating offers in Portal' accurately summarizes the main change—correcting how the end date for repeating discount offers is calculated and displayed to users.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the issue (date mismatch), the fix (anchoring to billing date instead of redemption date), and includes a concrete example with before/after screenshots.

✏️ 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 fix/date-mismatch-2

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

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ec9e2ed93

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sagzy sagzy force-pushed the fix/date-mismatch-2 branch from 3ec9e2e to 2517c19 Compare March 5, 2026 10:11
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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/portal/src/utils/helpers.js`:
- Around line 944-946: The addMonths function currently returns the raw date
input when numberOfMonths is invalid, which can leak non-Date types; update
addMonths (check the numberOfMonths parameter) to return null for invalid input
(keeping the return type Date|null), and ensure the function always returns a
Date instance for valid inputs by cloning/parsing the incoming date before
modifying it (refer to addMonths, numberOfMonths, and the date variable to
locate the logic to change).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78dfaf15-9e11-4646-8ec5-389f72c36add

📥 Commits

Reviewing files that changed from the base of the PR and between c144a8f and 3ec9e2ed93c6419c841824d5a8fd6f4e8673fc95.

📒 Files selected for processing (5)
  • apps/portal/package.json
  • apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js
  • apps/portal/src/utils/fixtures-generator.js
  • apps/portal/src/utils/helpers.js
  • apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js (1)

240-339: Add a yearly repeating-offer case to lock date math behavior.

The updated assertions only cover monthly cadence. Add one interval: 'year' repeating scenario so interval-related end-date regressions are caught early.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js`
around lines 240 - 339, Add a new unit test (similar to "displays discounted
price with \"Ends {date}\" for repeating offers") that uses getSubscriptionData
/ getNextPaymentData / getDiscountData with interval: 'year' and a repeating
offer to verify the displayed "Ends {date}" uses yearly math; create a member
with currentPeriodEnd and discount end dates spaced by the expected repeating
duration in years, call setup({site, member}) and assert the original price,
offer label, discounted price, and that the rendered "Ends {date}" matches the
year-based end date (using queryByText) to prevent interval-related regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js`:
- Around line 237-242: The repeating-offer end-date math currently uses
addMonths(currentPeriodEnd, discount.duration_in_months - 1) which assumes a
1-month billing cadence; update the logic to multiply the number of billing
cycles by the plan's cadence in months (use plan.interval and
plan.interval_count or subscription.interval_count/plan.interval to compute
billingCycleMonths, e.g., billingCycleMonths = (plan.interval === 'year' ? 12 :
1) * (plan.interval_count || 1)), then compute monthsToAdd = billingCycleMonths
* (discount.duration_in_months - 1) and pass that to addMonths to produce
offerEndDate so durationLabel (and getDateString) is cadence-aware.

In
`@apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js`:
- Around line 288-290: The failing assertion checks the wrong end date for the
repeating-offer; update the expectations in paid-account-actions.test.js so the
repeating discount text matches the component's current-period–anchored date
computation: swap the assertions to expect queryByText('$4.00/month — Ends 5 May
2099') toBeInTheDocument() and expect queryByText('$4.00/month — Ends 3 May
2099') not.toBeInTheDocument(); locate the checks using queryByText in the
paid-account-actions test for the repeating offer and adjust them accordingly.

---

Nitpick comments:
In
`@apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js`:
- Around line 240-339: Add a new unit test (similar to "displays discounted
price with \"Ends {date}\" for repeating offers") that uses getSubscriptionData
/ getNextPaymentData / getDiscountData with interval: 'year' and a repeating
offer to verify the displayed "Ends {date}" uses yearly math; create a member
with currentPeriodEnd and discount end dates spaced by the expected repeating
duration in years, call setup({site, member}) and assert the original price,
offer label, discounted price, and that the rendered "Ends {date}" matches the
year-based end date (using queryByText) to prevent interval-related regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2241afaa-ad63-4de1-86b1-abb9e8cb70f3

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec9e2ed93c6419c841824d5a8fd6f4e8673fc95 and 2517c19f5090d610d0d7401379b0ea147949294d.

📒 Files selected for processing (6)
  • apps/portal/package.json
  • apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js
  • apps/portal/src/utils/fixtures-generator.js
  • apps/portal/src/utils/helpers.js
  • apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js
  • apps/portal/test/utils/helpers.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/portal/package.json
  • apps/portal/src/utils/helpers.js

@sagzy sagzy force-pushed the fix/date-mismatch-2 branch from 2517c19 to a0c5b11 Compare March 5, 2026 10:26
@sagzy sagzy requested a review from mike182uk March 5, 2026 10:34
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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js`:
- Around line 237-242: The repeating-offer end-date calculation calls
addMonths(currentPeriodEnd, ...) even when currentPeriodEnd can be null,
producing an incorrect epoch date; update the branch handling discount.duration
=== 'repeating' to first check currentPeriodEnd is truthy before calling
addMonths (use the existing currentPeriodEnd guard pattern), only compute
offerEndDate and set durationLabel (via getDateString) when currentPeriodEnd is
present and addMonths returns a valid date, and leave durationLabel unchanged
otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 371a9629-bad1-4472-b6a3-96ced7dda6db

📥 Commits

Reviewing files that changed from the base of the PR and between 2517c19f5090d610d0d7401379b0ea147949294d and a0c5b112aecc5f832cf8b57abd56f39fe678ef40.

📒 Files selected for processing (6)
  • apps/portal/package.json
  • apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js
  • apps/portal/src/utils/fixtures-generator.js
  • apps/portal/src/utils/helpers.js
  • apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js
  • apps/portal/test/utils/helpers.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/portal/package.json
  • apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js

closes https://linear.app/ghost/issue/BER-3413/account-page-date-mismatch-between-discount-end-billing-period

- When a member redeems a 1-month free offer, Stripe sets the discount start date to now and end date to now + 1 month, i.e. anchors them to the redemption date, not the billing date
- In Portal, to avoid confusion, we want to anchor the "- Ends {date}" to a billing date to avoid confusion
- For example, today is 5 Mar 2026 and my subscription renews on 7 Mar 2026. I hit cancel, redeem a free-month offer that applies to my next payment. I expect to see "$0.00/month - Ends 7 Mar 2026", not "$0.00/month - Ends 5 Apr 2026"
@sagzy sagzy force-pushed the fix/date-mismatch-2 branch from a0c5b11 to eaf86db Compare March 5, 2026 10:41
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/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js (1)

239-239: Consider: Defensive handling for undefined duration_in_months.

If discount.duration_in_months is unexpectedly undefined, the expression discount.duration_in_months - 1 evaluates to NaN, causing addMonths to return the original currentPeriodEnd. This silently falls back to showing the current period end date as the offer end date.

While this is a safe fallback, you may want to add an explicit guard or log a warning for debugging purposes if this scenario indicates a data issue upstream.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js`
at line 239, The computation of offerEndDate using addMonths(currentPeriodEnd,
discount.duration_in_months - 1) can produce NaN when
discount.duration_in_months is undefined; update the offerEndDate calculation to
defensively handle undefined/invalid duration_in_months by validating/coercing
it to a safe integer (e.g., defaulting to 1 or 0) and/or logging a warning when
discount.duration_in_months is missing; locate the assignment to offerEndDate
and the discount object usage in this module (references: offerEndDate,
discount.duration_in_months, addMonths, currentPeriodEnd) and add the guard +
optional processLogger/console.warn so the code falls back deterministically
while surfacing the unexpected data.
🤖 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/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js`:
- Line 239: The computation of offerEndDate using addMonths(currentPeriodEnd,
discount.duration_in_months - 1) can produce NaN when
discount.duration_in_months is undefined; update the offerEndDate calculation to
defensively handle undefined/invalid duration_in_months by validating/coercing
it to a safe integer (e.g., defaulting to 1 or 0) and/or logging a warning when
discount.duration_in_months is missing; locate the assignment to offerEndDate
and the discount object usage in this module (references: offerEndDate,
discount.duration_in_months, addMonths, currentPeriodEnd) and add the guard +
optional processLogger/console.warn so the code falls back deterministically
while surfacing the unexpected data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e3bfa86d-05c1-4886-b377-3291e58f9237

📥 Commits

Reviewing files that changed from the base of the PR and between a0c5b112aecc5f832cf8b57abd56f39fe678ef40 and eaf86db.

📒 Files selected for processing (6)
  • apps/portal/package.json
  • apps/portal/src/components/pages/AccountHomePage/components/paid-account-actions.js
  • apps/portal/src/utils/fixtures-generator.js
  • apps/portal/src/utils/helpers.js
  • apps/portal/test/unit/components/pages/AccountHomePage/paid-account-actions.test.js
  • apps/portal/test/utils/helpers.test.js
✅ Files skipped from review due to trivial changes (1)
  • apps/portal/package.json

@sagzy sagzy merged commit d7bb54f into main Mar 5, 2026
29 checks passed
@sagzy sagzy deleted the fix/date-mismatch-2 branch March 5, 2026 11:59
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