Skip to content
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

Add subscription data section in account settings #1838

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 42 additions & 116 deletions packages/host/app/components/operator-mode/profile-info-popover.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +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,
LoadingIndicator,
} from '@cardstack/boxel-ui/components';
import { Avatar, BoxelButton } 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 config from '@cardstack/host/config/environment';
import BillingService from '@cardstack/host/services/billing-service';
import MatrixService from '@cardstack/host/services/matrix-service';
Expand Down Expand Up @@ -140,125 +134,57 @@ export default class ProfileInfoPopover extends Component<ProfileInfoPopoverSign
<ProfileInfo />
{{! TODO: Remove config.APP.stripeBillingEnabled once the API integration for credit info is completed. }}
{{#if config.APP.stripeBillingEnabled}}
<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|>
<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>
@kind='secondary-light'
@size='small'
@disabled={{subscriptionData.isLoading}}
data-test-upgrade-plan-button
{{on 'click' this.billingService.managePlan}}
>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>
</WithSubscriptionData>
{{/if}}
</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,179 @@
import { fn } from '@ember/helper';

import { on } from '@ember/modifier';
import Owner from '@ember/owner';
import { service } from '@ember/service';
import Component from '@glimmer/component';

import { task } from 'ember-concurrency';

import window from 'ember-window-mock';

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
@kind='secondary-light'
@size='extra-small'
@disabled={{subscriptionData.isLoading}}
{{on 'click' this.billingService.managePlan}}
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.fetchPaymentLinks.isRunning}}
<LoadingIndicator />
{{else}}
{{#each
this.billingService.paymentLinks
as |paymentLink index|
}}
<div class='payment-link' data-test-payment-link={{index}}>
<span><IconHexagon width='16px' height='16px' />
{{paymentLink.creditReloadAmount}}</span>
<BoxelButton
@kind='secondary-light'
@size='extra-small'
{{on 'click' (fn this.pay paymentLink.url)}}
FadhlanR marked this conversation as resolved.
Show resolved Hide resolved
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;

constructor(owner: Owner, args: any) {
super(owner, args);
this.fetchPaymentLinks.perform();
}

private fetchPaymentLinks = task(async () => {
return await this.billingService.fetchPaymentLinks();
});

private pay(paymentLinkURL: string) {
window.open(paymentLinkURL);
}
}
Loading
Loading