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 31 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, components, 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`<br>`app/components/settings/billing-page/payment-history-section.hbs`, `.ts`<br>`app/components/settings/billing-page/renewal-section.hbs`, `.ts`<br>`app/components/settings/billing-page/support-section.hbs`, `.ts`<br>`app/components/settings/billing-page/membership-section/status-pill.hbs`, `.ts` | Added new Ember components and templates for membership status, payment history, renewal, support sections, and a status pill UI element 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`<br>`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`<br>`app/templates/membership.hbs`                      | Deleted legacy membership route and template. |
| `app/components/membership-page/*` (all files)                                   | Removed all legacy membership page components and templates, including actions, plan section, payment method section, recent payments, upcoming payment, and section wrapper components. |
| `tests/acceptance/settings-page/billing-test.js`<br>`tests/pages/settings/billing-page.ts` | Added new acceptance tests and page objects for the billing settings page. |
| `tests/acceptance/manage-membership-test.js`<br>`tests/pages/membership-page.js`  | Removed legacy membership acceptance tests and page object. |
| `tests/acceptance/course-page/view-course-stages-test.js`<br>`tests/acceptance/header-test.js`<br>`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)

```mermaid
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
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

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! 🐇✨


<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between bc73fbae0d6b3a92b86be765c118ee6f58a04e7e and 263cab5fa1821850d32fb98e5393d61ba718b0cf.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `app/components/settings/billing-page/membership-section/status-pill.hbs` (1 hunks)
* `app/components/settings/billing-page/membership-section/status-pill.ts` (1 hunks)
* `app/components/settings/billing-page/renewal-section.hbs` (1 hunks)
* `app/components/settings/billing-page/support-section.hbs` (1 hunks)
* `tests/acceptance/settings-page/billing-test.js` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* app/components/settings/billing-page/membership-section/status-pill.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* app/components/settings/billing-page/renewal-section.hbs
* app/components/settings/billing-page/support-section.hbs
* app/components/settings/billing-page/membership-section/status-pill.hbs
* tests/acceptance/settings-page/billing-test.js

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=codecrafters-io/frontend&utm_content=2811):

- 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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link

github-actions bot commented Apr 23, 2025

Test Results

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

Results for commit 263cab5. ± 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 57.89474% 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 56.25% 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 2.02kB (-0.01%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
client-array-push 36.58MB -2.02kB (-0.01%) ⬇️

Affected Assets, Files, and Routes:

view changes for bundle: client-array-push

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/chunk.*.js -82 bytes 26.43kB -0.31%
assets/chunk.*.js -1.94kB 2.84MB -0.07%

Files in assets/chunk.*.js:

  • ./controllers/settings/billing.ts → Total Size: 104 bytes

  • ./routes/pay.ts → Total Size: 1.98kB

  • ./controllers/settings.ts → Total Size: 1.33kB

@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.

@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

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: 2

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

4-23: Add missing renewalSection to page object

The billing page includes a renewal section component that needs test coverage, but it's missing from this page object.

#!/bin/bash
# Verify the renewal section exists in the template
rg -A 3 "data-test-renewal-section" --type hbs

Add a renewalSection entry to the page object, matching the structure of other sections:

 export default createPage({
   membershipSection: {
     scope: '[data-test-membership-section]',
   },
 
+  renewalSection: {
+    scope: '[data-test-renewal-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]',
   },
🧹 Nitpick comments (3)
app/components/settings/billing-page/membership-section/status-pill.hbs (1)

15-15: Fix trailing whitespace at end of file

The file has a formatting issue - there's a trailing space at the end of the last line.

-</div> 
+</div>
🧰 Tools
🪛 GitHub Actions: Test

[warning] Prettier formatting warning: Code style issues found. Run Prettier with --write to fix.

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

4-23: Organize page object properties alphabetically

Per project convention, properties in page objects should be arranged alphabetically.

 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]',
   },
 
   supportSection: {
     scope: '[data-test-support-section]',
     contactButtonHref: attribute('href', '[data-test-support-contact-button]'),
   },
 
   visit: visitable('/settings/billing'),
 });

Should be organized as:

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]',
  },
  
  supportSection: {
    contactButtonHref: attribute('href', '[data-test-support-contact-button]'),
    scope: '[data-test-support-section]',
  },
  
  visit: visitable('/settings/billing'),
});
tests/acceptance/settings-page/billing-test.js (1)

58-58: Fix formatting for multi-line assertion

The assertion on line 58 needs to be formatted according to the project's Prettier configuration.

-  assert.strictEqual(billingPage.supportSection.contactButtonHref, 'mailto:[email protected]?subject=Billing help (account: rohitpaulk)', 'contact button href is correct');
+  assert.strictEqual(
+    billingPage.supportSection.contactButtonHref,
+    'mailto:[email protected]?subject=Billing help (account: rohitpaulk)',
+    'contact button href is correct',
+  );
🧰 Tools
🪛 ESLint

[error] 58-58: Replace billingPage.supportSection.contactButtonHref,·'mailto:[email protected]?subject=Billing·help·(account:·rohitpaulk)',·'contact·button·href·is·correct' with ⏎······billingPage.supportSection.contactButtonHref,⏎······'mailto:[email protected]?subject=Billing·help·(account:·rohitpaulk)',⏎······'contact·button·href·is·correct',⏎····

(prettier/prettier)

🪛 GitHub Actions: Test

[error] 58-58: Prettier formatting error: Replace line with properly formatted code. Run 'prettier --write' to fix.


[warning] Prettier formatting warning: Code style issues found. Run Prettier with --write to fix.

📜 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 ac350c2 and bc73fba.

📒 Files selected for processing (19)
  • app/components/membership-page/actions-section.hbs (0 hunks)
  • app/components/membership-page/actions-section.js (0 hunks)
  • app/components/membership-page/membership-plan-section.hbs (0 hunks)
  • app/components/membership-page/membership-plan-section.js (0 hunks)
  • app/components/membership-page/payment-method-section.hbs (0 hunks)
  • app/components/membership-page/payment-method-section.js (0 hunks)
  • app/components/membership-page/recent-payments-section.hbs (0 hunks)
  • app/components/membership-page/recent-payments-section.js (0 hunks)
  • app/components/membership-page/section.hbs (0 hunks)
  • app/components/membership-page/section.js (0 hunks)
  • app/components/membership-page/upcoming-payment-section.hbs (0 hunks)
  • app/components/membership-page/upcoming-payment-section.js (0 hunks)
  • app/components/settings/billing-page/membership-section.hbs (1 hunks)
  • app/components/settings/billing-page/membership-section/status-pill.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.hbs (1 hunks)
  • tests/acceptance/settings-page/billing-test.js (1 hunks)
  • tests/pages/settings/billing-page.ts (1 hunks)
💤 Files with no reviewable changes (12)
  • app/components/membership-page/section.js
  • app/components/membership-page/upcoming-payment-section.hbs
  • app/components/membership-page/membership-plan-section.js
  • app/components/membership-page/section.hbs
  • app/components/membership-page/payment-method-section.hbs
  • app/components/membership-page/upcoming-payment-section.js
  • app/components/membership-page/recent-payments-section.js
  • app/components/membership-page/payment-method-section.js
  • app/components/membership-page/recent-payments-section.hbs
  • app/components/membership-page/actions-section.js
  • app/components/membership-page/actions-section.hbs
  • app/components/membership-page/membership-plan-section.hbs
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/components/settings/billing-page/support-section.hbs
  • app/components/settings/billing-page/payment-history-section.hbs
  • app/components/settings/billing-page/membership-section.hbs
  • app/components/settings/billing-page/payment-history-section.ts
🧰 Additional context used
🪛 GitHub Actions: Test
app/components/settings/billing-page/membership-section/status-pill.hbs

[error] 2-2: TS2339: Property 'variant' does not exist on type '{}'.


[error] 3-4: TS2339: Property 'variant' does not exist on type '{}'.


[error] 5-5: TS2345: Argument of type 'void' is not assignable to parameter of type 'Element'.


[error] 8-10: TS2339: Property 'variant' does not exist on type '{}'.


[error] 13-13: TS2345: Argument of type '"default"' and '[]' is not assignable to parameter of type 'never'.


[warning] Prettier formatting warning: Code style issues found. Run Prettier with --write to fix.

tests/acceptance/settings-page/billing-test.js

[error] 58-58: Prettier formatting error: Replace line with properly formatted code. Run 'prettier --write' to fix.


[warning] Prettier formatting warning: Code style issues found. Run Prettier with --write to fix.

🪛 ESLint
tests/acceptance/settings-page/billing-test.js

[error] 58-58: Replace billingPage.supportSection.contactButtonHref,·'mailto:[email protected]?subject=Billing·help·(account:·rohitpaulk)',·'contact·button·href·is·correct' with ⏎······billingPage.supportSection.contactButtonHref,⏎······'mailto:[email protected]?subject=Billing·help·(account:·rohitpaulk)',⏎······'contact·button·href·is·correct',⏎····

(prettier/prettier)

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

59-59: Add window mock test for contact button

The test verifies the href attribute but doesn't test the actual click behavior.

Since this is a mailto link, add a test using ember-window-mock to verify that clicking the button triggers the expected behavior:

test('clicking support contact button opens email client', async function (assert) {
  testScenario(this.server);
  signInAsSubscriber(this.owner, this.server);
  
  await billingPage.visit();
  await billingPage.supportSection.clickOnContactButton();
  
  // Assert that window.location would have changed to the mailto link
  const window = this.owner.lookup('service:window');
  assert.true(
    window.location.href.includes('mailto:[email protected]'),
    'email client should open with correct recipient'
  );
});

You'll need to add a clickOnContactButton action to the supportSection in the page object.

🧰 Tools
🪛 GitHub Actions: Test

[warning] Prettier formatting warning: Code style issues found. Run Prettier with --write to fix.

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