Skip to content

Commit

Permalink
Merge pull request #1838 from cardstack/cs-7044-cs-7555-cs-7556-subsc…
Browse files Browse the repository at this point in the history
…ription-section-in-account-settings

Add subscription data section in account settings
  • Loading branch information
jurgenwerk authored Dec 2, 2024
2 parents 3400152 + 8ca738f commit 9d3f8f8
Show file tree
Hide file tree
Showing 11 changed files with 615 additions and 189 deletions.
1 change: 1 addition & 0 deletions packages/host/app/components/matrix/payment-setup.gts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export default class PaymentSetup extends Component<Signature> {
<BoxelButton
@as='anchor'
@kind='primary'
@disabled={{this.billingService.fetchingStripePaymentLinks}}
@href={{this.stripePaymentLink}}
data-test-setup-payment
class='setup-button'
Expand Down
171 changes: 52 additions & 119 deletions packages/host/app/components/operator-mode/profile-info-popover.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import Component from '@glimmer/component';

import { task } from 'ember-concurrency';
import { Avatar, BoxelButton } from '@cardstack/boxel-ui/components';
import { cn, or } from '@cardstack/boxel-ui/helpers';

import {
Avatar,
BoxelButton,
LoadingIndicator,
} from '@cardstack/boxel-ui/components';
import { cn } from '@cardstack/boxel-ui/helpers';
import { IconHexagon } from '@cardstack/boxel-ui/icons';
import WithSubscriptionData from '@cardstack/host/components/with-subscription-data';

import BillingService from '@cardstack/host/services/billing-service';
import MatrixService from '@cardstack/host/services/matrix-service';
Expand Down Expand Up @@ -137,127 +132,65 @@ export default class ProfileInfoPopover extends Component<ProfileInfoPopoverSign
</header>

<ProfileInfo />
{{! Show credit info if the user has an active plan }}
{{#if this.plan}}
<div class='credit-info' data-test-credit-info>
<div class='info-group'>
<span class='label'>Membership Tier</span>
<span class='value' data-test-membership-tier>
{{#if this.isLoading}}
<LoadingIndicator />
{{else}}
{{this.plan}}
{{/if}}
</span>
</div>
<BoxelButton
@kind='secondary-light'
@size='small'
@disabled={{this.isLoading}}
data-test-upgrade-plan-button
{{on 'click' @toggleProfileSettings}}
>Upgrade Plan</BoxelButton>
<div class='info-group'>
<span class='label'>Monthly Credit</span>
<span
class={{cn 'value' out-of-credit=this.isOutOfPlanCreditAllowance}}
data-test-monthly-credit
>
{{#if this.isLoading}}
<LoadingIndicator />
{{else}}
<IconHexagon width='16px' height='16px' />
{{this.monthlyCreditText}}
{{/if}}
</span>
</div>
<div class='info-group additional-credit'>
<span class='label'>Additional Credit</span>
<span
class={{cn 'value' out-of-credit=this.isOutOfCredit}}
data-test-additional-credit
>{{#if this.isLoading}}
<LoadingIndicator />
{{else}}
<IconHexagon width='16px' height='16px' />
{{this.extraCreditsAvailableInBalance}}
{{/if}}</span>
</div>
<div
class={{cn 'buy-more-credits' out-of-credit=this.isOutOfCredit}}
data-test-buy-more-credits
>
<WithSubscriptionData as |subscriptionData|>
{{! Show credit info if the user has an active plan }}
{{#if subscriptionData.hasActiveSubscription}}
<div class='credit-info' data-test-credit-info>
<div class='info-group'>
<span class='label'>Membership Tier</span>
{{subscriptionData.plan}}
</div>
<BoxelButton
@kind={{if this.isOutOfCredit 'primary' 'secondary-light'}}
@size={{if this.isOutOfCredit 'base' 'small'}}
@disabled={{this.isLoading}}
{{on 'click' @toggleProfileSettings}}
>Buy more credits</BoxelButton>
@as='anchor'
@kind='secondary-light'
@size='small'
@disabled={{or
subscriptionData.isLoading
this.billingService.fetchingStripePaymentLinks
}}
@href={{this.billingService.customerPortalLink.url}}
target='_blank'
data-test-upgrade-plan-button
>Upgrade Plan</BoxelButton>
<div class='info-group'>
<span class='label'>Monthly Credit</span>
{{subscriptionData.monthlyCredit}}
</div>
<div class='info-group additional-credit'>
<span class='label'>Additional Credit</span>
{{subscriptionData.additionalCredit}}
</div>
<div
class={{cn
'buy-more-credits'
out-of-credit=subscriptionData.isOutOfCredit
}}
data-test-buy-more-credits
>
<BoxelButton
@kind={{if
subscriptionData.isOutOfCredit
'primary'
'secondary-light'
}}
@size={{if subscriptionData.isOutOfCredit 'base' 'small'}}
@disabled={{subscriptionData.isLoading}}
{{on 'click' @toggleProfileSettings}}
>Buy more credits</BoxelButton>
</div>
</div>
</div>
{{/if}}
{{/if}}
</WithSubscriptionData>

</div>
</template>

constructor(...args: [any, any]) {
super(...args);
this.fetchCreditInfo.perform();
}

@service private declare billingService: BillingService;
@service declare matrixService: MatrixService;

private fetchCreditInfo = task(async () => {
await this.billingService.fetchSubscriptionData();
});
@service declare billingService: BillingService;

@action private logout() {
this.matrixService.logout();
}

private get isLoading() {
return this.billingService.fetchingSubscriptionData;
}

private get plan() {
return this.billingService.subscriptionData?.plan;
}

private get creditsIncludedInPlanAllowance() {
return this.billingService.subscriptionData?.creditsIncludedInPlanAllowance;
}

private get creditsAvailableInPlanAllowance() {
return this.billingService.subscriptionData
?.creditsAvailableInPlanAllowance;
}

private get extraCreditsAvailableInBalance() {
return this.billingService.subscriptionData?.extraCreditsAvailableInBalance;
}

private get monthlyCreditText() {
return this.creditsAvailableInPlanAllowance != null &&
this.creditsIncludedInPlanAllowance != null
? `${this.creditsAvailableInPlanAllowance} of ${this.creditsIncludedInPlanAllowance} left`
: null;
}

private get isOutOfCredit() {
return (
this.isOutOfPlanCreditAllowance &&
(this.extraCreditsAvailableInBalance == null ||
this.extraCreditsAvailableInBalance == 0)
);
}

private get isOutOfPlanCreditAllowance() {
return (
this.creditsAvailableInPlanAllowance == null ||
this.creditsIncludedInPlanAllowance == null ||
this.creditsAvailableInPlanAllowance <= 0
);
}
}

export class ProfileInfo extends Component<ProfileInfoSignature> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { isValidPassword } from '@cardstack/host/lib/matrix-utils';
import MatrixService from '@cardstack/host/services/matrix-service';

import ProfileEmail from './profile-email';
import ProfileSubscription from './profile-subscription';

interface Signature {
Args: {
Expand Down Expand Up @@ -126,6 +127,7 @@ export default class ProfileSettingsModal extends Component<Signature> {
@changeEmailComplete={{this.completeEmail}}
/>
{{/if}}
<ProfileSubscription />
</form>
{{#if (or (bool this.displayNameError) (bool this.error))}}
<div class='error-message' data-test-profile-save-error>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { service } from '@ember/service';
import Component from '@glimmer/component';

import {
BoxelButton,
FieldContainer,
LoadingIndicator,
} from '@cardstack/boxel-ui/components';
import { IconHexagon } from '@cardstack/boxel-ui/icons';

import WithSubscriptionData from '@cardstack/host/components/with-subscription-data';
import BillingService from '@cardstack/host/services/billing-service';

interface Signature {
Args: {};
Element: HTMLElement;
}

export default class ProfileSubscription extends Component<Signature> {
<template>
<WithSubscriptionData as |subscriptionData|>
<FieldContainer
@label='Membership Tier'
@tag='label'
class='profile-field'
>
<div class='profile-subscription'>
<div class='monthly-credit'>
<div class='plan-name'>{{subscriptionData.plan}}</div>
<div class='credit-info'>
<span class='credit-info__label'>Monthly Credit</span>
{{subscriptionData.monthlyCredit}}
</div>
</div>
<BoxelButton
@as='anchor'
@kind='secondary-light'
@size='extra-small'
@disabled={{this.billingService.fetchingStripePaymentLinks}}
@href={{this.billingService.customerPortalLink.url}}
target='_blank'
data-test-manage-plan-button
>Manage Plan</BoxelButton>
</div>
</FieldContainer>
<FieldContainer
@label='Additional Credit'
@tag='label'
class='profile-field'
>
<div class='additional-credit'>
<div class='profile-subscription'>
<div class='credit-info'>
{{subscriptionData.additionalCredit}}
</div>
</div>
<div class='buy-more-credits'>
<span class='buy-more-credits__title'>Buy more credits</span>
<div class='payment-links'>
{{#if this.billingService.fetchingStripePaymentLinks}}
<LoadingIndicator />
{{else}}
{{#each
this.billingService.extraCreditsPaymentLinks
as |paymentLink index|
}}
<div class='payment-link' data-test-payment-link={{index}}>
<span><IconHexagon width='16px' height='16px' />
{{paymentLink.creditReloadAmount}}</span>
<BoxelButton
@as='anchor'
@kind='secondary-light'
@size='extra-small'
@href={{paymentLink.url}}
target='_blank'
data-test-pay-button={{index}}
>Pay</BoxelButton>
</div>
{{/each}}
{{/if}}
</div>
</div>
</div>
</FieldContainer>
</WithSubscriptionData>
<style scoped>
.profile-field :deep(.invalid) {
box-shadow: none;
}
.profile-field + .profile-field {
margin-top: var(--boxel-sp-xl);
}
.profile-subscription {
display: flex;
justify-content: space-between;
}
.monthly-credit {
display: flex;
flex-direction: column;
gap: var(--boxel-sp);
}
.credit-info {
display: flex;
flex-direction: column;
gap: var(--boxel-sp-xs);
padding-left: var(--boxel-sp-sm);
border-left: 5px solid #c6c6c6;
min-height: 40px;
}
.credit-info__label {
font: var(--boxel-font-xs);
letter-spacing: var(--boxel-lsp-xs);
text-wrap: nowrap;
line-height: 18px;
}
.additional-credit {
display: flex;
flex-direction: column;
gap: var(--boxel-sp);
}
.buy-more-credits {
display: flex;
flex-direction: column;
gap: var(--boxel-sp-sm);
border-top: 1px solid var(--boxel-300);
padding-top: var(--boxel-sp-sm);
}
.buy-more-credits__title {
font: 600 var(--boxel-font-sm);
}
.payment-links {
display: flex;
flex-direction: column;
gap: var(--boxel-sp-sm);
padding-left: var(--boxel-sp-xs);
}
.payment-link {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--boxel-300);
padding: var(--boxel-sp-xxs);
}
.payment-link > span {
color: var(--boxel-dark);
font: 600 var(--boxel-font-sm);
display: flex;
align-items: center;
gap: var(--boxel-sp-4xs);
--icon-color: var(--boxel-teal);
--boxel-loading-indicator-size: var(--boxel-icon-xs);
}
:deep(.buy-more-credits .boxel-loading-indicator) {
width: 100%;
text-align: center;
}
</style>
</template>

@service private declare billingService: BillingService;
}
Loading

0 comments on commit 9d3f8f8

Please sign in to comment.