Skip to content

Add a billing tab to the settings #2811

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 28 commits into
base: main
Choose a base branch
from

Conversation

Arpan-206
Copy link
Contributor

@Arpan-206 Arpan-206 commented Apr 23, 2025

Checklist:

  • I've thoroughly self-reviewed my changes
  • I've added tests for my changes, unless they affect admin-only areas (or are otherwise not worth testing)
  • I've verified any visual changes using Percy (add a commit with [percy] in the message to trigger)

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive Billing page under Settings with Membership, Renewal, Payment History, and Support sections displaying detailed subscription and payment info.
    • Added color-coded badges and contextual messages for membership states including VIP access, active, inactive, and no membership.
    • Enabled viewing of payment history with invoice downloads and refund status indicators.
    • Added a support contact button with dynamic email linking.
  • Bug Fixes

    • Updated navigation and redirects from the old Membership page to the new Billing page for consistency.
  • Chores

    • Removed the old Membership page, associated routes, templates, page objects, and tests.
    • Added a permanent redirect from /membership to /settings/billing.
  • Tests

    • Added acceptance tests for the Billing page covering membership states, payment history, and support.
    • Updated existing tests to reflect navigation changes from Membership to Billing page.

Copy link

linear bot commented Apr 23, 2025

Copy link
Contributor

coderabbitai bot commented Apr 23, 2025

Walkthrough

This update migrates the membership management functionality from the standalone /membership route to a new, consolidated billing section under /settings/billing. It introduces new Ember components and templates for membership status, renewal, support, and payment history, all organized within the billing settings page. The routing structure, navigation destinations, and UI flows are updated accordingly, with redirects and controller logic adjusted to support the new organization. Associated tests and page objects are refactored or replaced to align with the new billing page, and legacy membership files and tests are removed.

Changes

File(s) / Path(s) Change Summary
app/components/settings/billing-page/membership-section.hbs, .ts
app/components/settings/billing-page/payment-history-section.hbs, .ts
app/components/settings/billing-page/renewal-section.hbs, .ts
app/components/settings/billing-page/support-section.hbs, .ts
Added new Ember components and templates for membership status, payment history, renewal, and support sections on the billing page, each with typed arguments and UI logic.
app/templates/settings/billing.hbs Added new billing settings page template, organizing the new billing-related components and layout.
app/controllers/settings/billing.ts Added new controller for the billing settings route with typed model including charges.
app/controllers/settings.ts Added 'Billing' tab to settings page navigation.
app/router.ts Added nested 'billing' route under 'settings'.
app/components/billing-status-badge/member-badge.hbs
app/components/header/account-dropdown.ts
Changed navigation targets from 'membership' to 'settings.billing' for member badge and account dropdown actions.
app/routes/pay.ts Updated post-payment route transition from 'membership' to 'settings.billing'.
vercel.json Added permanent redirect from /membership to /settings/billing.
app/routes/membership.js
app/templates/membership.hbs
Deleted legacy membership route and template.
tests/acceptance/settings-page/billing-test.js
tests/pages/settings/billing-page.ts
Added new acceptance tests and page objects for the billing settings page.
tests/acceptance/manage-membership-test.js
tests/pages/membership-page.js
Removed legacy membership acceptance tests and page object.
tests/acceptance/course-page/view-course-stages-test.js
tests/acceptance/header-test.js
tests/acceptance/pay-test.js
Updated tests to expect billing page (/settings/billing) instead of membership page.
tests/support/authentication-helpers.js Added helper for signing in as a VIP user for tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AppRouter
    participant SettingsController
    participant BillingController
    participant BillingPageComponents

    User->>AppRouter: Navigates to /settings/billing
    AppRouter->>SettingsController: Loads settings context
    AppRouter->>BillingController: Loads billing model (user, charges)
    BillingController->>BillingPageComponents: Passes user and charges
    BillingPageComponents->>User: Renders Membership, Renewal, Support, Payment History sections
Loading
sequenceDiagram
    participant User
    participant MemberBadge
    participant Router

    User->>MemberBadge: Clicks member badge
    MemberBadge->>Router: Transitions to 'settings.billing'
    Router->>User: Displays billing settings page
Loading

Suggested labels

enhancement

Suggested reviewers

  • rohitpaulk

Poem

In burrows deep, the billing grows,
Membership, payments, all in neat rows.
No more hopping to /membership’s den,
Now /settings/billing is where you begin!
With badges, support, and history clear,
This rabbit’s billing joy is here! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between bd80766 and ac350c2.

📒 Files selected for processing (1)
  • app/components/settings/billing-page/payment-history-section.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/settings/billing-page/payment-history-section.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Apr 23, 2025

Test Results

  1 files  ±0    1 suites  ±0   7m 59s ⏱️ - 1m 24s
625 tests +1  580 ✅ +5  45 💤 ±0  0 ❌ ±0 
625 runs  +1  580 ✅ +9  45 💤 ±0  0 ❌  - 4 

Results for commit ac350c2. ± Comparison against base commit 8f14f91.

This pull request removes 9 and adds 10 tests. Note that renamed tests count towards both.
Chrome 135.0 ‑ Acceptance | course-page | view-course-stages-test: member badge redirects to /membership
Chrome 135.0 ‑ Acceptance | header-test: member badge redirects to /membership
Chrome 135.0 ‑ Acceptance | manage-membership-test: subscriber can manage membership
Chrome 135.0 ‑ Acceptance | manage-membership-test: subscriber can update payment method
Chrome 135.0 ‑ Acceptance | manage-membership-test: subscriber can view recent payments
Chrome 135.0 ‑ Acceptance | manage-membership-test: subscriber can view upcoming payments
Chrome 135.0 ‑ Acceptance | manage-membership-test: subscriber that is a partner has correct membership plan copy
Chrome 135.0 ‑ Acceptance | manage-membership-test: subscriber that is a partner with expiry has correct membership plan copy
Chrome 135.0 ‑ Acceptance | pay-test: user should be redirected to /membership if user is authenticated and has an active subscription
Chrome 135.0 ‑ Acceptance | course-page | view-course-stages-test: member badge redirects to /settings/billing
Chrome 135.0 ‑ Acceptance | header-test: member badge redirects to /settings/billing
Chrome 135.0 ‑ Acceptance | pay-test: user should be redirected to /settings/billing if user is authenticated and has an active subscription
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: membership section shows VIP access for subscriber with VIP access
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: membership section shows correct plan for non-subscriber
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: membership section shows correct plan for subscriber with active subscription
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: payment history section shows charges after creation
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: payment history section shows empty state initially
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: payment history section shows refunded charges correctly
Chrome 135.0 ‑ Acceptance | settings-page | billing-test: support section is visible

♻️ This comment has been updated with latest results.

Copy link

codecov bot commented Apr 23, 2025

Codecov Report

Attention: Patch coverage is 55.55556% with 8 lines in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...s/settings/billing-page/payment-history-section.ts 53.33% 7 Missing ⚠️
app/components/header/account-dropdown.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link

codecov bot commented Apr 23, 2025

Bundle Report

Changes will decrease total bundle size by 36.58MB (-100.0%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
client-array-push (removed) -36.58MB (-100.0%) ⬇️

@Arpan-206 Arpan-206 marked this pull request as ready for review April 25, 2025 16:04
Copy link
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: 6

🧹 Nitpick comments (5)
app/templates/settings/billing.hbs (1)

20-23: Add handling for empty payment history

The PaymentHistorySection should handle the case where a user has no payment history. Consider adding a conditional to display an appropriate message when the charges array is empty.

 <Settings::FormSection @title="Payment history" @description="Details of your previous payments">
   {{#let this.model.charges as |charges|}}
-    <Settings::BillingPage::PaymentHistorySection @charges={{charges}} />
+    {{#if charges.length}}
+      <Settings::BillingPage::PaymentHistorySection @charges={{charges}} />
+    {{else}}
+      <div class="text-gray-500 py-4">
+        No payment history available.
+      </div>
+    {{/if}}
   {{/let}}
 </Settings::FormSection>
app/components/settings/billing-page/payment-history-section.hbs (1)

14-21: Consider adding a tooltip for refund information.

For partially refunded charges, consider adding a tooltip that provides additional context about when/why the refund was processed to improve user experience.

  {{#if (gt charge.amountRefunded 0)}}
    {{#if charge.isFullyRefunded}}
-     <span data-test-refund-text>(refunded)</span>
+     <span data-test-refund-text title="Fully refunded on {{date-format charge.refundedAt format='PPP'}}">(refunded)</span>
    {{else}}
-     <span data-test-refund-text>(<span class="font-semibold">{{charge.refundedAmountDisplayString}}</span>
+     <span data-test-refund-text title="Partially refunded on {{date-format charge.refundedAt format='PPP'}}">(<span class="font-semibold">{{charge.refundedAmountDisplayString}}</span>
        refunded)</span>
    {{/if}}
  {{/if}}
app/components/settings/billing-page/membership-section.hbs (1)

63-65: Consider adding tracking for membership conversions.

The "Start membership" buttons are key conversion points. Consider adding analytics tracking to measure user engagement with these buttons.

-<PrimaryLinkButton @size="small" @route="pay" class="mt-3">
+<PrimaryLinkButton @size="small" @route="pay" class="mt-3" data-test-start-membership-button {{on "click" this.trackMembershipButtonClick}}>
  Start membership →
</PrimaryLinkButton>

Also applies to: 80-82

tests/acceptance/settings-page/billing-test.js (1)

67-69: Consider mocking email client interaction.

Instead of commenting out the contact button test, consider mocking the email client interaction or testing that the correct mailto URL is generated.

-  // await billingPage.supportSection.clickContactButton();
-  // Commented out because it opens the email client, if behavior changes, uncomment
+  // Test that the correct mailto URL is used
+  const contactButton = document.querySelector('[data-test-support-contact-button]');
+  assert.ok(contactButton.href.startsWith('mailto:'), 'Contact button has mailto link');
+  assert.ok(contactButton.href.includes('subject='), 'Contact button includes subject');
mirage/models/charge.js (1)

1-14: Charge model structure looks good

The model correctly defines the user relationship and includes appropriate fields for a payment charge record.

Consider these improvements:

  1. Add more detailed documentation about valid values for the status field
  2. Consider whether 'succeeded' is the most appropriate default status (versus 'pending')
  3. Clarify the comment on line 9 to better explain the specific linting issue
import { Model, belongsTo } from 'miragejs';

export default Model.extend({
  user: belongsTo(),
  amount: 0,
  amountRefunded: 0,
  currency: 'usd',
  createdAt() {
-    return new Date(); // Done this way due to linting error and shared objects
+    return new Date(); // Returns a new Date object each time to avoid shared reference issues across instances
  },
  invoiceId: null,
-  status: 'succeeded',
+  status: 'succeeded', // Possible values: 'succeeded', 'pending', 'failed', etc.
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0813f20 and d2a4e06.

📒 Files selected for processing (17)
  • app/components/settings/billing-page/membership-section.hbs (1 hunks)
  • app/components/settings/billing-page/membership-section.ts (1 hunks)
  • app/components/settings/billing-page/payment-history-section.hbs (1 hunks)
  • app/components/settings/billing-page/payment-history-section.ts (1 hunks)
  • app/components/settings/billing-page/renewal-section.hbs (1 hunks)
  • app/components/settings/billing-page/renewal-section.ts (1 hunks)
  • app/components/settings/billing-page/support-section.hbs (1 hunks)
  • app/components/settings/billing-page/support-section.ts (1 hunks)
  • app/controllers/settings.ts (1 hunks)
  • app/controllers/settings/billing.ts (1 hunks)
  • app/router.ts (1 hunks)
  • app/routes/settings/billing.ts (1 hunks)
  • app/templates/settings/billing.hbs (1 hunks)
  • mirage/models/charge.js (1 hunks)
  • tests/acceptance/settings-page/billing-test.js (1 hunks)
  • tests/pages/settings/billing-page.ts (1 hunks)
  • tests/support/authentication-helpers.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
app/components/settings/billing-page/renewal-section.ts (3)
app/components/settings/billing-page/membership-section.ts (1)
  • Registry (15-17)
app/components/settings/billing-page/payment-history-section.ts (1)
  • Registry (15-17)
app/components/settings/billing-page/support-section.ts (1)
  • Registry (14-16)
tests/acceptance/settings-page/billing-test.js (1)
tests/support/authentication-helpers.js (4)
  • signInAsSubscriber (51-61)
  • signIn (1-14)
  • signInAsVipUser (63-69)
  • user (102-102)
app/components/settings/billing-page/payment-history-section.ts (3)
app/components/settings/billing-page/membership-section.ts (1)
  • Registry (15-17)
app/components/settings/billing-page/renewal-section.ts (1)
  • Registry (10-12)
app/components/settings/billing-page/support-section.ts (1)
  • Registry (14-16)
app/components/settings/billing-page/membership-section.ts (4)
app/models/user.ts (1)
  • UserModel (28-218)
app/components/settings/billing-page/payment-history-section.ts (1)
  • Registry (15-17)
app/components/settings/billing-page/renewal-section.ts (1)
  • Registry (10-12)
app/components/settings/billing-page/support-section.ts (1)
  • Registry (14-16)
app/components/settings/billing-page/support-section.ts (3)
app/components/settings/billing-page/membership-section.ts (1)
  • Registry (15-17)
app/components/settings/billing-page/payment-history-section.ts (1)
  • Registry (15-17)
app/components/settings/billing-page/renewal-section.ts (1)
  • Registry (10-12)
🔇 Additional comments (13)
app/controllers/settings.ts (1)

18-18: LGTM! The billing tab is correctly added to the settings controller.

This change properly integrates the new billing route into the settings interface navigation, maintaining consistency with the existing tab structure.

app/components/settings/billing-page/renewal-section.ts (1)

1-13: RenewalSectionComponent setup looks solid

The component class and its Glint registry declaration follow the established pattern, matching other billing‐page sections and providing correct typing for the root <div>. No changes needed here.

app/controllers/settings/billing.ts (1)

1-11: BillingController model typing is correct

The BillingController properly extends Controller and declares a strongly typed model combining SettingsModelType with charges: ChargeModel[]. This aligns with the route and ensures type safety.

app/components/settings/billing-page/payment-history-section.ts (1)

1-18: PaymentHistorySectionComponent matches the pattern

The component signature correctly types Args.charges and the registry declaration mirrors other billing‐page sections. Everything is consistent and ready for use in the template.

app/components/settings/billing-page/support-section.hbs (1)

1-12: Support section template is well-structured

The Handlebars markup cleanly renders the mailto link with dynamic @username and the styled support message. It follows accessibility and styling conventions in use elsewhere.

app/components/settings/billing-page/membership-section.ts (1)

1-18: MembershipSectionComponent conforms to conventions

The component signature properly types Args.user: UserModel and the registry entry is consistent with other billing‐page sections. No further updates required.

app/components/settings/billing-page/support-section.ts (1)

3-17: LGTM! Well-structured component signature

The component signature is well-defined with proper typing for the username argument and correct Glint registry setup for type safety.

app/components/settings/billing-page/payment-history-section.hbs (1)

1-45: Well-structured payment history section with appropriate conditional rendering.

The payment history section effectively handles different states (no charges, succeeded/failed charges, and refund scenarios) with clear visual indicators. The conditional logic for displaying "Download Invoice" links or failure messages is implemented correctly.

app/components/settings/billing-page/membership-section.hbs (1)

1-83: Complete and well-structured membership section with clear visual indicators.

The membership section effectively handles all user states (VIP, active, inactive, and no membership) with appropriate visual indicators and messaging.

tests/pages/settings/billing-page.ts (1)

1-30: Well-structured page object with appropriate test selectors.

The page object correctly implements the necessary selectors and properties for testing the billing page components.

tests/acceptance/settings-page/billing-test.js (1)

1-126: Comprehensive test coverage for billing functionality.

The tests cover all major scenarios for the billing page including different membership states and payment history display. Good use of test helpers and Mirage models.

app/router.ts (1)

84-84: LGTM: Clean addition of the billing route

The addition of the billing route under settings is well-placed and follows the existing routing pattern. This aligns perfectly with the PR objective of adding a billing tab to the settings section.

tests/support/authentication-helpers.js (1)

63-69: LGTM: Well-implemented VIP user authentication helper

The new helper function follows the same pattern as other authentication helpers in the file. It correctly sets the VIP status and expiration date, which will be useful for testing the billing features with different user states.

Copy link
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: 0

♻️ Duplicate comments (1)
app/components/settings/billing-page/membership-section.hbs (1)

1-96: Move test attribute to parent to avoid duplication

The data-test-membership-section attribute is currently duplicated in each conditional branch (lines 3, 22, 43, 60, 80). Following the previous review suggestion, move this to the parent FormSection component.

-<Settings::FormSection @title="Membership" @description="Your CodeCrafters membership details">
+<Settings::FormSection @title="Membership" @description="Your CodeCrafters membership details" data-test-membership-section>
  {{#if (and @user.isVip @user.hasActiveSubscription)}}
-    <div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500" data-test-membership-section>
+    <div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500">

And similarly remove it from the other status divs at lines 22, 43, 60, and 80.

🧹 Nitpick comments (2)
app/components/settings/billing-page/membership-section.hbs (2)

21-42: Ensure consistent class usage across status badges

The active membership badge has an extra class has-active-plan that isn't present in other badge elements. Either add a comment explaining why this class is only needed here, or ensure consistent class usage across all badge conditions.

-    <div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500 has-active-plan" data-test-membership-section>
+    <div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500" data-test-membership-section>

2-20: Consider consistent messaging about membership between VIP+membership and membership-only sections

The text formatting differs between the VIP+membership section and the membership-only section. For consistency, consider using the same format for presenting membership information.

For example, change lines 15-19 to match the format in lines 29-38:

      <p>
-        You are currently subscribed to the
-        <b class="font-semibold">{{@user.activeSubscription.pricingPlanName}}</b>
-        plan{{#if @user.activeSubscription.cancelAt}}, valid until
-          <b class="font-semibold">{{date-format @user.activeSubscription.cancelAt format="PPPp"}}</b>{{/if}}.
+        {{#if @user.activeSubscription.cancelAt}}
+          Your CodeCrafters membership is valid until
+          <b class="font-semibold">{{date-format @user.activeSubscription.cancelAt format="PPPp"}}</b>.
+        {{else}}
+          You are currently subscribed to the
+          <b class="font-semibold">{{@user.activeSubscription.pricingPlanName}}</b>
+          plan.
+        {{/if}}
      </p>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between e44227a and 31426ec.

📒 Files selected for processing (1)
  • app/components/settings/billing-page/membership-section.hbs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (2)
app/components/settings/billing-page/membership-section.hbs (2)

2-58:

✅ Verification successful

Address VIP and membership interaction concerns

The component correctly prioritizes showing combined VIP+membership status first, then active membership, then VIP-only. However, a previous comment raised concerns about users potentially being confused by seeing only VIP status when they also have purchased a membership that might be expiring.


🏁 Script executed:

#!/bin/bash
# Check how the user model handles overlapping VIP and subscription statuses
echo "Checking user model properties related to VIP and subscription status:"
fd "user.ts" --type file | xargs cat
echo "\nChecking how isVip is determined:"
rg -A 5 "isVip" --type ts

Length of output: 16267


VIP & Membership Display Logic Verified

I’ve confirmed that isVip is a direct boolean attribute on UserModel and that hasActiveSubscription accurately reflects an active plan. The Handlebars template already prioritizes the combined VIP+membership state, then membership-only, then VIP-only, matching the intended behavior. No changes are needed.


59-79:

❓ Verification inconclusive

Verify the expired subscription logic displays correctly

This section correctly uses @user.expiredSubscription as per the previous review comment. However, ensure that expired subscriptions are properly identified and displayed to users, especially when they also have VIP status.


🏁 Script executed:

#!/bin/bash
# Check how expiredSubscription is defined and how it interacts with VIP status
rg -A 3 -B 3 "expiredSubscription" --type ts

Length of output: 379


#!/bin/bash

Show the full expiredSubscription getter

rg -C 10 "get expiredSubscription" app/models/user.ts

Inspect hasActiveSubscription to see VIP interactions

rg -C 5 "get hasActiveSubscription" app/models/user.ts


🏁 Script executed:

#!/bin/bash
# Retrieve the full expiredSubscription getter
rg -C 10 "get expiredSubscription" app/models/user.ts

# Retrieve the full hasActiveSubscription getter for VIP logic context
rg -C 10 "get hasActiveSubscription" app/models/user.ts

Length of output: 1116


#!/bin/bash

Retrieve the full activeSubscription getter for context

rg -C 10 "get activeSubscription" app/models/user.ts

Search for any VIP-related fields or getters in the User model

rg -i "vip" -C 5 app/models/user.ts


Ensure VIP status is included in the subscription logic

The expiredSubscription getter only returns a value when hasActiveSubscription is false, but hasActiveSubscription currently only checks for activeSubscription. If VIP membership is represented outside of activeSubscription, a VIP user with an expired past subscription would still hit the “Membership inactive” UI. Please:

  • Review get activeSubscription and any VIP flags (e.g. isVIP) in app/models/user.ts.
  • If VIP isn’t surfaced via activeSubscription, update hasActiveSubscription to include it, for example:
    get hasActiveSubscription() {
      return !!this.activeSubscription || this.isVIP;
    }
  • Verify in app/components/settings/billing-page/membership-section.hbs that VIP users never fall through to the expired-subscription branch.

@Arpan-206
Copy link
Contributor Author

@rohitpaulk for now the get help button just opens an email client but I assume we'd want a different action. Can you tell me what

@Arpan-206 email client is good for now! I think in the earlier comment I was mentioning that there's a way to test this - ember-window-mock allows you to test whether a redirect was performed, and mailto:... are just links so I think those can be tested the same way we test for redirects.

I've updated to test but window mock isn't working because it doesn't actually change the window location just opens the related app, but this should cover it decently

Copy link
Member

@rohitpaulk rohitpaulk left a comment

Choose a reason for hiding this comment

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

Added one round of comments + more detailed spec for the membership section

<div class="inline-flex items-center gap-2 rounded-full bg-teal-50 dark:bg-teal-900 px-2 py-1 border border-teal-500 dark:border-teal-400">
{{svg-jar "check-circle" class="h-5 w-5 text-teal-500"}}
<div class="text-teal-500 font-semibold uppercase text-sm">
VIP Access + Membership Active
Copy link
Member

Choose a reason for hiding this comment

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

This is going to look pretty long & ugly - I'll share a spec on how to refine shortly

Copy link
Member

Choose a reason for hiding this comment

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

  • If a user has an active membership
    • Pill is green, says "Membership active"
    • Text says "You have access to all CodeCrafters content, valid until XYZ"
    • If (a) the user also has VIP access and (b) The VIP access date is longer than the subscription end date, let's keep the above text but cross it out and also say "🎉 You have VIP access to all CodeCrafters content, valid until XYZ".
  • Else, If a user is VIP
    • Pill is green, says "VIP Access"
    • Text says "You have VIP access to...."
  • Else, If a user has an expired subscription:
    • Pill is red and says "Membership inactive"
  • Else, if a user doesn't have any memberships, active or expired
    • Pill is yellow and says "No membership found"

We won't use any icons inside the pills - rely solely on color and text to convey meaning.

Copy link
Member

Choose a reason for hiding this comment

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

Let me know if you think there any cases where this logic would result in confusion from the user's POV btw - can discuss

</p>
</div>
{{else if @user.hasActiveSubscription}}
<div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500 has-active-plan">
Copy link
Member

Choose a reason for hiding this comment

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

Are we still using has-active-plan?

</PrimaryLinkButton>
{{else}}
<div class="inline-flex items-center gap-2 rounded-full bg-yellow-50 px-2 py-1 border border-yellow-500">
{{svg-jar "question-mark-circle" class="h-5 w-5 text-yellow-500"}}
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 the design for this didn't have SVGs if I'm not wrong, we were relying on just color & text to convey meaning

</div>
{{else if @user.expiredSubscription}}
<div class="inline-flex items-center gap-2 rounded-full bg-red-50 px-2 py-1 border border-red-500">
{{svg-jar "x-circle" class="h-5 w-5 text-red-500"}}
Copy link
Member

Choose a reason for hiding this comment

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

Same as previous comment, we don't need SVGs here - the color & text is enough to convey the meaning we want, icons would just be distracting here

</div>
{{else if this.errorMessage}}
<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-400 rounded-md p-4">
<div class="flex">
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<div class="flex">
<div class="flex gap-3">

Instead of using ml-3 on the second child, prefer doing this instead - it's cleaner and prevents mistakes

<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-400 rounded-md p-4">
<div class="flex">
<div class="flex-shrink-0">
{{svg-jar "x-circle" class="h-5 w-5 text-red-400 dark:text-red-300"}}
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 this doesn't seem consistent - below I see text-red-700 dark:text-red-300, and here it's text-red-400 dark:text-red-300. Which are we going with?

Usually the direct inverse brightness works for dark mode (300 vs. 700, 800 vs 200), we've only had to make minor +/- 100 changes based on some situations so far.

</div>
</div>
{{else if (gt this.charges.length 0)}}
<div class="grid grid-cols-3 gap-y-3" data-test-payment-history-section>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<div class="grid grid-cols-3 gap-y-3" data-test-payment-history-section>
<div class="grid grid-cols-3 gap-y-3">

(This can go in the outer FormSection component rather than be duplicated)

}

export default class SupportSectionComponent extends Component<Signature> {
get supportEmail() {
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 what is this used for?

Copy link
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: 0

♻️ Duplicate comments (1)
tests/pages/settings/billing-page.ts (1)

4-24: 🛠️ Refactor suggestion

Add missing renewalSection to the page object

There appears to be a renewal-section component in the application, but it's not represented in this page object.

Add the renewalSection to the page object:

export default createPage({
  membershipSection: {
    scope: '[data-test-membership-section]',
  },

  paymentHistorySection: {
    charges: collection('[data-test-payment-history-item]', {
      amount: text('[data-test-amount]'),
      failed: hasClass('text-red-600'),
    }),
    scope: '[data-test-payment-history-section]',
  },

+ renewalSection: {
+   scope: '[data-test-renewal-section]',
+ },
+
  supportSection: {
    clickOnContactButton: clickable('[data-test-support-contact-button]'),
    scope: '[data-test-support-section]',
  },

  visit: visitable('/settings/billing'),
});

Make sure to add the appropriate data-test-renewal-section attribute to the renewal section component's template if it's not already there.

🧰 Tools
🪛 ESLint

[error] 15-16: Delete ··⏎··

(prettier/prettier)

🧹 Nitpick comments (6)
tests/pages/settings/billing-page.ts (3)

4-7: Consider adding properties to the membershipSection

The membershipSection is currently only scoped but doesn't expose any properties for testing elements within this section. If there are membership status badges or other elements that need testing, consider adding them with appropriate data-test attributes.


9-16: Remove extra whitespace on line 15

There's unnecessary whitespace that should be removed to maintain consistent formatting.

    charges: collection('[data-test-payment-history-item]', {
      amount: text('[data-test-amount]'),
      failed: hasClass('text-red-600'),
    }),
    scope: '[data-test-payment-history-section]',
-    
  },
🧰 Tools
🪛 ESLint

[error] 15-16: Delete ··⏎··

(prettier/prettier)


4-24: Consider alphabetical ordering of page object sections

For better maintainability and consistency with other files, consider arranging the sections in alphabetical order.

export default createPage({
  membershipSection: {
    scope: '[data-test-membership-section]',
  },

  paymentHistorySection: {
    charges: collection('[data-test-payment-history-item]', {
      amount: text('[data-test-amount]'),
      failed: hasClass('text-red-600'),
    }),
    scope: '[data-test-payment-history-section]',
  },

+ renewalSection: {
+   scope: '[data-test-renewal-section]',
+ },
+
  supportSection: {
    clickOnContactButton: clickable('[data-test-support-contact-button]'),
    scope: '[data-test-support-section]',
  },

  visit: visitable('/settings/billing'),
});
🧰 Tools
🪛 ESLint

[error] 15-16: Delete ··⏎··

(prettier/prettier)

app/components/settings/billing-page/membership-section.hbs (3)

26-30: Add dark mode support for VIP section badge

The active subscription badge has dark mode classes but the VIP badge is missing them. For UI consistency, dark mode classes should be added to match other sections.

-<div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500">
+<div class="inline-flex items-center gap-2 rounded-full bg-teal-50 dark:bg-teal-900 px-2 py-1 border border-teal-500 dark:border-teal-400">

42-46: Add dark mode support for inactive membership badge

The inactive membership badge is missing dark mode support classes. For consistency with the active membership section, dark mode classes should be added.

-<div class="inline-flex items-center gap-2 rounded-full bg-red-50 px-2 py-1 border border-red-500">
+<div class="inline-flex items-center gap-2 rounded-full bg-red-50 dark:bg-red-900 px-2 py-1 border border-red-500 dark:border-red-400">

61-65: Add dark mode support for no membership badge

The "No membership found" badge is missing dark mode support classes. For consistency across all membership status badges, dark mode classes should be added.

-<div class="inline-flex items-center gap-2 rounded-full bg-yellow-50 px-2 py-1 border border-yellow-500">
+<div class="inline-flex items-center gap-2 rounded-full bg-yellow-50 dark:bg-yellow-900 px-2 py-1 border border-yellow-500 dark:border-yellow-400">
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 96700ab and 324665f.

📒 Files selected for processing (6)
  • app/components/settings/billing-page/membership-section.hbs (1 hunks)
  • app/components/settings/billing-page/payment-history-section.hbs (1 hunks)
  • app/components/settings/billing-page/payment-history-section.ts (1 hunks)
  • app/components/settings/billing-page/support-section.ts (1 hunks)
  • tests/acceptance/settings-page/billing-test.js (1 hunks)
  • tests/pages/settings/billing-page.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/components/settings/billing-page/payment-history-section.hbs
  • app/components/settings/billing-page/support-section.ts
  • app/components/settings/billing-page/payment-history-section.ts
  • tests/acceptance/settings-page/billing-test.js
🧰 Additional context used
🪛 ESLint
tests/pages/settings/billing-page.ts

[error] 15-16: Delete ··⏎··

(prettier/prettier)

🔇 Additional comments (2)
tests/pages/settings/billing-page.ts (1)

1-24: Good foundation for the billing settings page object!

The page object defines the basic structure needed for testing the billing page, with proper data-test selectors for the main sections.

🧰 Tools
🪛 ESLint

[error] 15-16: Delete ··⏎··

(prettier/prettier)

app/components/settings/billing-page/membership-section.hbs (1)

1-76: Membership section looks good overall

The membership section correctly implements the requirements from previous review comments:

  • Uses the proper conditional logic for different membership states
  • Handles the interaction between membership and VIP status appropriately
  • Uses color-coded badges and text to convey membership status
  • Provides clear calls to action for users without active memberships

The implementation follows the design guidelines and addresses all the feedback from previous reviews.

Copy link
Member

@rohitpaulk rohitpaulk left a comment

Choose a reason for hiding this comment

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

Added one more round of comments - think this can be the last, let's aim to wrap up and ship for the next review! Make sure to self-review in detail before opening for review, ensure that there are no inconsistencies.

Also, I still don't see the change where we remove the components used in the old membership page

<Settings::FormSection @title="Membership" @description="Your CodeCrafters membership details" data-test-membership-section>
{{#if @user.hasActiveSubscription}}
<div class="inline-flex items-center gap-2 rounded-full bg-teal-50 dark:bg-teal-900 px-2 py-1 border border-teal-500 dark:border-teal-400">
{{svg-jar "circle" class="h-5 w-5 text-teal-500"}}
Copy link
Member

Choose a reason for hiding this comment

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

Instead of svg let's just do HTML (h-5 w-5 rounded-full), that's what we use for simple cases like this

{{/if}}
</div>
{{else if @user.isVip}}
<div class="inline-flex items-center gap-2 rounded-full bg-teal-50 px-2 py-1 border border-teal-500">
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 no dark mode styles. The same green pill above has different styles added.

Maybe you'd want to extract this into a component? Something like BillingPage::MembershipSection::StatusPill?

Start membership →
</PrimaryLinkButton>
{{else}}
<div class="inline-flex items-center gap-2 rounded-full bg-yellow-50 px-2 py-1 border border-yellow-500">
Copy link
Member

Choose a reason for hiding this comment

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

No dark mode styles (bg-yellow-50 will look extremely bright in dark mode)

@@ -0,0 +1,63 @@
<Settings::FormSection @title="Payment history" @description="Details of your previous payments" data-test-payment-history-section>
{{#if this.isLoading}}
<div class="flex justify-center py-8">
Copy link
Member

Choose a reason for hiding this comment

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

Let's stay consistent with loading states that we have in other places:

Screenshot 2025-04-30 at 15 28 39 Screenshot 2025-04-30 at 15 28 32 Screenshot 2025-04-30 at 15 28 22

(I searched for isLoading across *.hbs files to find out what we do in other cases)

@@ -0,0 +1,14 @@
<Settings::FormSection @title="Support" @description="How to get help with billing">
<a href="mailto:[email protected]?subject=Billing help (account: {{@user.username}})" class="inline-block rounded" data-test-support-section>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<a href="mailto:[email protected]?subject=Billing help (account: {{@user.username}})" class="inline-block rounded" data-test-support-section>
<a href="mailto:[email protected]?subject=Billing help (account: {{@user.username}})" class="inline-block rounded">

(Place on the outer FormSection, to be consistent with other components)

Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't be needed, just use HTML with rounded-full

await billingPage.visit();

assert.ok(billingPage.supportSection.isVisible, 'support section is visible');
assert.ok(billingPage.supportSection.clickOnContactButton, 'click on contact button');
Copy link
Member

Choose a reason for hiding this comment

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

This isn't asserting anything clickOnContactButton is a function and hence will always be truthy


assert.ok(billingPage.supportSection.isVisible, 'support section is visible');
assert.ok(billingPage.supportSection.clickOnContactButton, 'click on contact button');
assert.dom('[data-test-support-section]').hasAttribute('href', /^mailto:[email protected]/);
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this just an attribute on the button? Why are we asserting against data-test-support-section here?

Copy link
Member

Choose a reason for hiding this comment

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

Also, page objects do support checking attributes: https://ember-cli-page-object.js.org/docs/v2.3.x/api/attribute. So you could use that to define something like contactButtonLink or contactButtonHref

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