зеркало из https://github.com/mozilla/fxa.git
feat(payments): update subscription upgrade UX for new designs
- Reworked SubscriptionUpdate to match new UX mocks - New PlanUpgradeDetails subcomponent for SubscriptionUpdate - try to extract some more common CSS & formatting out of SubscriptionCreate to share with SubscriptionUpdate - test tweaks - l10n string updates fixes #3931
This commit is contained in:
Родитель
3dfdada573
Коммит
cc0b1d69d9
|
@ -131,8 +131,9 @@ sub-redirect-skip-survey = No thanks, just take me to my product.
|
|||
default-input-error = This field is required
|
||||
|
||||
## subscription upgrade
|
||||
product-plan-upgrade-heading = Review your upgrade
|
||||
sub-update-failed = Plan update failed
|
||||
sub-update-title = Billing Information
|
||||
sub-update-title = Billing information
|
||||
sub-update-card-ending = Card Ending { $last }
|
||||
sub-update-card-exp = Expires { $cardExpMonth }/{ $cardExpYear }
|
||||
sub-update-copy =
|
||||
|
@ -161,9 +162,12 @@ sub-update-confirm-year = { $intervalCount ->
|
|||
*[other] I authorize { -brand-name-mozilla }, maker of { -brand-name-firefox } products, to charge my payment method <strong>{ $amount } every { $intervalCount } years</strong>, according to payment terms, until I cancel my subscription.
|
||||
}
|
||||
|
||||
sub-update-submit = Change Plans
|
||||
sub-update-submit = Confirm upgrade
|
||||
sub-update-indicator =
|
||||
.aria-label = upgrade indicator
|
||||
sub-update-current-plan-label = Current plan
|
||||
sub-update-new-plan-label = New plan
|
||||
sub-update-total-label = New total
|
||||
|
||||
## subscription upgrade plan details
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
|
|
@ -69,10 +69,6 @@ form.payment {
|
|||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.terms {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.lock::before {
|
||||
background-image: url('./images/lock.svg');
|
||||
background-position: 0 4px;
|
||||
|
|
|
@ -25,7 +25,11 @@ import {
|
|||
useValidatorState,
|
||||
} from '../../lib/validator';
|
||||
import { useCallbackOnce } from '../../lib/hooks';
|
||||
import { getLocalizedCurrency, formatPlanPricing } from '../../lib/formats';
|
||||
import {
|
||||
getLocalizedCurrency,
|
||||
formatPlanPricing,
|
||||
getDefaultPaymentConfirmText,
|
||||
} from '../../lib/formats';
|
||||
import { AppContext } from '../../lib/AppContext';
|
||||
|
||||
import './index.scss';
|
||||
|
@ -61,22 +65,6 @@ export type PaymentFormProps = {
|
|||
submitNonce: string;
|
||||
};
|
||||
|
||||
function getDefaultPaymentConfirmText(
|
||||
amount: number,
|
||||
currency: string,
|
||||
interval: PlanInterval,
|
||||
intervalCount: number
|
||||
): string {
|
||||
const planPricing = formatPlanPricing(
|
||||
amount,
|
||||
currency,
|
||||
interval,
|
||||
intervalCount
|
||||
);
|
||||
|
||||
return `I authorize Mozilla, maker of Firefox products, to charge my payment method <strong>${planPricing}</strong>, according to payment terms, until I cancel my subscription.`;
|
||||
}
|
||||
|
||||
export const PaymentForm = ({
|
||||
inProgress = false,
|
||||
confirm = true,
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
}
|
||||
|
||||
.terms {
|
||||
margin-top: 0;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -210,3 +210,33 @@ hr {
|
|||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c-card::before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 27px;
|
||||
left: -45px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.visa::before {
|
||||
background: url('./images/visa.svg') no-repeat;
|
||||
}
|
||||
|
||||
.mastercard::before {
|
||||
background: url('./images/mastercard.svg') no-repeat;
|
||||
}
|
||||
|
||||
.american::before {
|
||||
background: url('./images/amex.svg') no-repeat;
|
||||
}
|
||||
|
||||
.discover::before {
|
||||
background: url('./images/discover.svg') no-repeat;
|
||||
}
|
||||
|
|
|
@ -146,3 +146,19 @@ export function formatPlanPricing(
|
|||
return `${formattedAmount} every ${intervalCount} years`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultPaymentConfirmText(
|
||||
amount: number,
|
||||
currency: string,
|
||||
interval: PlanInterval,
|
||||
intervalCount: number
|
||||
): string {
|
||||
const planPricing = formatPlanPricing(
|
||||
amount,
|
||||
currency,
|
||||
interval,
|
||||
intervalCount
|
||||
);
|
||||
|
||||
return `I authorize Mozilla, maker of Firefox products, to charge my payment method <strong>${planPricing}</strong>, according to payment terms, until I cancel my subscription.`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
import React from 'react';
|
||||
import { Localized } from '@fluent/react';
|
||||
import { getLocalizedCurrency, formatPlanPricing } from '../../../lib/formats';
|
||||
import { metadataFromPlan } from '../../../store/utils';
|
||||
|
||||
import ffLogo from '../../../images/firefox-logo.svg';
|
||||
|
||||
import './index.scss';
|
||||
import { Plan } from '../../../store/types';
|
||||
|
||||
export const PlanUpgradeDetails = ({
|
||||
selectedPlan,
|
||||
upgradeFromPlan,
|
||||
isMobile,
|
||||
className = 'default',
|
||||
}: {
|
||||
selectedPlan: Plan;
|
||||
upgradeFromPlan: Plan;
|
||||
isMobile: boolean;
|
||||
className?: string;
|
||||
}) => {
|
||||
const totalPrice = formatPlanPricing(
|
||||
selectedPlan.amount,
|
||||
selectedPlan.currency,
|
||||
selectedPlan.interval,
|
||||
selectedPlan.interval_count
|
||||
);
|
||||
|
||||
const role = isMobile ? undefined : 'complementary';
|
||||
|
||||
return (
|
||||
<section
|
||||
className={`plan-details-component plan-upgrade-details-component ${className}`}
|
||||
{...{ role }}
|
||||
data-testid="plan-upgrade-details-component"
|
||||
>
|
||||
<div className="plan-details-component-inner">
|
||||
<p className="plan-label current-plan-label">
|
||||
<Localized id="sub-update-current-plan-label">Current plan</Localized>
|
||||
</p>
|
||||
<PlanDetailsCard className="from-plan" plan={upgradeFromPlan} />
|
||||
<p className="plan-label new-plan-label">
|
||||
<Localized id="sub-update-new-plan-label">New plan</Localized>
|
||||
</p>
|
||||
<PlanDetailsCard className="to-plan" plan={selectedPlan} />
|
||||
|
||||
<div
|
||||
className="plan-details-total"
|
||||
aria-labelledby="plan-details-product"
|
||||
>
|
||||
<div className="plan-details-total-inner">
|
||||
<Localized id="sub-update-total-label">
|
||||
<p className="label">New total</p>
|
||||
</Localized>
|
||||
<Localized
|
||||
id={`plan-price-${selectedPlan.interval}`}
|
||||
$amount={getLocalizedCurrency(
|
||||
selectedPlan.amount,
|
||||
selectedPlan.currency
|
||||
)}
|
||||
$intervalCount={selectedPlan.interval_count}
|
||||
>
|
||||
<p className="total-price">{totalPrice}</p>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlanDetailsCard = ({
|
||||
plan,
|
||||
className = '',
|
||||
}: {
|
||||
plan: Plan;
|
||||
className?: string;
|
||||
}) => {
|
||||
const { product_name, amount, currency, interval, interval_count } = plan;
|
||||
const { webIconURL } = metadataFromPlan(plan);
|
||||
const planPrice = formatPlanPricing(
|
||||
amount,
|
||||
currency,
|
||||
interval,
|
||||
interval_count
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`container card plan-details-component-card plan-upgrade-details-component-card ${className}`}
|
||||
>
|
||||
<div className="plan-details-header">
|
||||
<div className="plan-details-header-wrap">
|
||||
<div className="plan-details-logo-wrap">
|
||||
<img
|
||||
src={webIconURL || ffLogo}
|
||||
alt={product_name}
|
||||
data-testid="product-logo"
|
||||
/>
|
||||
</div>
|
||||
<div className="plan-details-heading-wrap">
|
||||
<h3
|
||||
id="plan-details-product"
|
||||
className="product-name plan-details-product"
|
||||
>
|
||||
{product_name}
|
||||
</h3>
|
||||
{/* TODO: make this configurable, issue #4741 / FXA-1484 */}
|
||||
<p className="product-description plan-details-description">
|
||||
Full-device VPN
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Localized
|
||||
id={`plan-price-${interval}`}
|
||||
$amount={getLocalizedCurrency(amount, currency)}
|
||||
$intervalCount={interval_count}
|
||||
>
|
||||
<p>{planPrice}</p>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlanUpgradeDetails;
|
|
@ -1,5 +1,27 @@
|
|||
@import '../../../../../fxa-content-server/app/styles/variables';
|
||||
@import '../../../../../fxa-content-server/app/styles/breakpoints';
|
||||
@import '../../../styles/variables';
|
||||
|
||||
.product-upgrade {
|
||||
|
||||
.c-card {
|
||||
left: 45px;
|
||||
}
|
||||
|
||||
.subscription-update-heading {
|
||||
display: none;
|
||||
|
||||
@include min-width('tablet') {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade-details {
|
||||
text-align: center;
|
||||
width: 560px;
|
||||
|
@ -49,10 +71,38 @@
|
|||
}
|
||||
|
||||
form.upgrade {
|
||||
margin: 0 25px;
|
||||
margin: 0;
|
||||
|
||||
> p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plan-upgrade-details-component {
|
||||
|
||||
.plan-details-header {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
p.plan-label {
|
||||
color: #6d6d6e;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 18px;
|
||||
margin: 0 0 8px 0;
|
||||
|
||||
&.new-plan-label {
|
||||
background-image: url('../../../images/down-arrow.png');
|
||||
background-position-x: center;
|
||||
background-position-y: 18px;
|
||||
background-repeat: no-repeat;
|
||||
padding-top: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import MockApp from '../../../../.storybook/components/MockApp';
|
|||
|
||||
import { SignInLayout } from '../../../components/AppLayout';
|
||||
|
||||
import { CUSTOMER, SELECTED_PLAN, UPGRADE_FROM_PLAN } from './mocks';
|
||||
import { CUSTOMER, SELECTED_PLAN, UPGRADE_FROM_PLAN, PROFILE } from './mocks';
|
||||
|
||||
import SubscriptionUpgrade, { SubscriptionUpgradeProps } from './index';
|
||||
|
||||
|
@ -76,6 +76,8 @@ const linkToUpgradeOffer = linkTo(
|
|||
);
|
||||
|
||||
const MOCK_PROPS: SubscriptionUpgradeProps = {
|
||||
isMobile: false,
|
||||
profile: PROFILE,
|
||||
customer: CUSTOMER,
|
||||
selectedPlan: SELECTED_PLAN,
|
||||
upgradeFromPlan: UPGRADE_FROM_PLAN,
|
||||
|
|
|
@ -5,7 +5,7 @@ import '@testing-library/jest-dom/extend-expect';
|
|||
|
||||
import { APIError } from '../../../lib/apiClient';
|
||||
import { Plan } from '../../../store/types';
|
||||
import { PlanDetail } from './index';
|
||||
import { PlanUpgradeDetails, PlanDetailsCard } from './PlanUpgradeDetails';
|
||||
|
||||
jest.mock('../../../lib/sentry');
|
||||
|
||||
|
@ -22,7 +22,7 @@ import {
|
|||
getLocalizedMessage,
|
||||
} from '../../../lib/test-utils';
|
||||
|
||||
import { CUSTOMER, SELECTED_PLAN, UPGRADE_FROM_PLAN } from './mocks';
|
||||
import { CUSTOMER, SELECTED_PLAN, UPGRADE_FROM_PLAN, PROFILE } from './mocks';
|
||||
|
||||
import { SignInLayout } from '../../../components/AppLayout';
|
||||
|
||||
|
@ -132,6 +132,8 @@ describe('routes/Product/SubscriptionUpgrade', () => {
|
|||
describe('Legal', () => {
|
||||
describe('rendering the legal checkbox Localized component', () => {
|
||||
const baseProps = {
|
||||
isMobile: false,
|
||||
profile: PROFILE,
|
||||
customer: CUSTOMER,
|
||||
upgradeFromPlan: UPGRADE_FROM_PLAN,
|
||||
upgradeFromSubscription: CUSTOMER.subscriptions[0],
|
||||
|
@ -359,7 +361,7 @@ describe('routes/Product/SubscriptionUpgrade', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PlanDetail', () => {
|
||||
describe('PlanDetailsCard', () => {
|
||||
const dayBasedId = 'plan-price-day';
|
||||
const weekBasedId = 'plan-price-week';
|
||||
const monthBasedId = 'plan-price-month';
|
||||
|
@ -377,7 +379,7 @@ describe('PlanDetail', () => {
|
|||
function runTests(plan: Plan, expectedMsgId: string, expectedMsg: string) {
|
||||
const props = { plan: plan };
|
||||
|
||||
const testRenderer = TestRenderer.create(<PlanDetail {...props} />);
|
||||
const testRenderer = TestRenderer.create(<PlanDetailsCard {...props} />);
|
||||
const testInstance = testRenderer.root;
|
||||
const planPriceComponent = testInstance.findByProps({
|
||||
id: expectedMsgId,
|
||||
|
@ -577,6 +579,7 @@ const Subject = ({ props = {} }: { props?: object }) => {
|
|||
|
||||
const MOCK_PROPS: SubscriptionUpgradeProps = {
|
||||
customer: CUSTOMER,
|
||||
profile: PROFILE,
|
||||
selectedPlan: SELECTED_PLAN,
|
||||
upgradeFromPlan: UPGRADE_FROM_PLAN,
|
||||
upgradeFromSubscription: CUSTOMER.subscriptions[0],
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
Plan,
|
||||
Customer,
|
||||
CustomerSubscription,
|
||||
PlanInterval,
|
||||
Profile,
|
||||
} from '../../../store/types';
|
||||
import { SelectorReturns } from '../../../store/selectors';
|
||||
import { metadataFromPlan } from '../../../store/utils';
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
getLocalizedDateString,
|
||||
getLocalizedCurrency,
|
||||
formatPlanPricing,
|
||||
getDefaultPaymentConfirmText,
|
||||
} from '../../../lib/formats';
|
||||
import { useCallbackOnce } from '../../../lib/hooks';
|
||||
|
||||
|
@ -25,27 +26,18 @@ import { useValidatorState } from '../../../lib/validator';
|
|||
|
||||
import DialogMessage from '../../../components/DialogMessage';
|
||||
import PaymentLegalBlurb from '../../../components/PaymentLegalBlurb';
|
||||
import { TermsAndPrivacy } from '../../../components/TermsAndPrivacy';
|
||||
|
||||
import PlanUpgradeDetails from './PlanUpgradeDetails';
|
||||
import Header from '../../../components/Header';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
import { ProductProps } from '../index';
|
||||
|
||||
function getDefaultConfirmText(
|
||||
amount: number,
|
||||
currency: string,
|
||||
interval: PlanInterval,
|
||||
intervalCount: number
|
||||
) {
|
||||
const planPricing = formatPlanPricing(
|
||||
amount,
|
||||
currency,
|
||||
interval,
|
||||
intervalCount
|
||||
);
|
||||
return `I authorize Mozilla, maker of Firefox products, to charge my payment method <strong>${planPricing}</strong>, according to payment terms, until I cancel my subscription.`;
|
||||
}
|
||||
|
||||
export type SubscriptionUpgradeProps = {
|
||||
isMobile: boolean;
|
||||
profile: Profile;
|
||||
customer: Customer;
|
||||
selectedPlan: Plan;
|
||||
upgradeFromPlan: Plan;
|
||||
|
@ -56,6 +48,8 @@ export type SubscriptionUpgradeProps = {
|
|||
};
|
||||
|
||||
export const SubscriptionUpgrade = ({
|
||||
isMobile,
|
||||
profile,
|
||||
customer,
|
||||
selectedPlan,
|
||||
upgradeFromPlan,
|
||||
|
@ -94,19 +88,22 @@ export const SubscriptionUpgrade = ({
|
|||
]
|
||||
);
|
||||
|
||||
const {
|
||||
last4: cardLast4,
|
||||
exp_month: cardExpMonth,
|
||||
exp_year: cardExpYear,
|
||||
// TODO https://github.com/mozilla/fxa/issues/3037
|
||||
// brand: cardBrand,
|
||||
} = customer;
|
||||
const { last4: cardLast4, brand: cardBrand } = customer;
|
||||
|
||||
const cardBrandLc = ('' + cardBrand).toLowerCase();
|
||||
|
||||
const mobileUpdateHeading = isMobile ? (
|
||||
<div className="mobile-subscription-update-heading">
|
||||
<div className="subscription-update-heading">
|
||||
<Localized id="product-plan-upgrade-heading">
|
||||
<h2>Review your upgrade</h2>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="product-payment product-upgrade"
|
||||
data-testid="subscription-upgrade"
|
||||
>
|
||||
<>
|
||||
{updateSubscriptionPlanStatus.error && (
|
||||
<DialogMessage
|
||||
className="dialog-error"
|
||||
|
@ -118,156 +115,126 @@ export const SubscriptionUpgrade = ({
|
|||
<p>{updateSubscriptionPlanStatus.error.message}</p>
|
||||
</DialogMessage>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
|
||||
<div className="upgrade-details">
|
||||
<h2>Review your upgrade details</h2>
|
||||
<ol className="upgrade-plans">
|
||||
<li className="from-plan">
|
||||
<PlanDetail plan={upgradeFromPlan} />
|
||||
</li>
|
||||
<Localized id="sub-update-indicator">
|
||||
<li
|
||||
role="img"
|
||||
aria-label="upgrade indicator"
|
||||
className="upgrade-indicator"
|
||||
>
|
||||
|
||||
</li>
|
||||
</Localized>
|
||||
<li className="to-plan">
|
||||
<PlanDetail plan={selectedPlan} />
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<Form
|
||||
data-testid="upgrade-form"
|
||||
className="upgrade"
|
||||
{...{ validator, onSubmit }}
|
||||
>
|
||||
<h3 className="billing-title">
|
||||
<Localized id="sub-update-title">
|
||||
<span className="title">Billing Information</span>
|
||||
</Localized>
|
||||
|
||||
<span className="card-details">
|
||||
{/* TODO: we don't have data from subhub to determine card icon
|
||||
https://github.com/mozilla/fxa/issues/3037
|
||||
<span className="icon"> </span>
|
||||
*/}
|
||||
<Localized id="sub-update-card-ending" $last={cardLast4}>
|
||||
<span className="last">Card Ending {cardLast4}</span>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="sub-update-card-exp"
|
||||
$cardExpMonth={cardExpMonth}
|
||||
$cardExpYear={cardExpYear}
|
||||
>
|
||||
<span className="expires">
|
||||
Expires {cardExpMonth}/{cardExpYear}
|
||||
</span>
|
||||
</Localized>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<Localized
|
||||
id="sub-update-copy"
|
||||
$startingDate={getLocalizedDate(
|
||||
upgradeFromSubscription.current_period_end
|
||||
)}
|
||||
<Header {...{ profile }} />
|
||||
<div className="main-content">
|
||||
<div
|
||||
className="product-payment product-upgrade"
|
||||
data-testid="subscription-upgrade"
|
||||
>
|
||||
<p>
|
||||
Your plan will change immediately, and you’ll be charged an adjusted
|
||||
amount for the rest of your billing cycle. Starting{' '}
|
||||
{getLocalizedDateString(upgradeFromSubscription.current_period_end)}{' '}
|
||||
you’ll be charged the full amount.
|
||||
</p>
|
||||
</Localized>
|
||||
|
||||
<Checkbox
|
||||
data-testid="confirm"
|
||||
name="confirm"
|
||||
onClick={engageOnce}
|
||||
required
|
||||
>
|
||||
<Localized
|
||||
id={`sub-update-confirm-${selectedPlan.interval}`}
|
||||
strong={<strong></strong>}
|
||||
$amount={getLocalizedCurrency(
|
||||
selectedPlan.amount,
|
||||
selectedPlan.currency
|
||||
)}
|
||||
$intervalCount={selectedPlan.interval_count}
|
||||
<div
|
||||
className="subscription-update-heading"
|
||||
data-testid="subscription-update-heading"
|
||||
>
|
||||
<p>
|
||||
{getDefaultConfirmText(
|
||||
selectedPlan.amount,
|
||||
selectedPlan.currency,
|
||||
selectedPlan.interval,
|
||||
selectedPlan.interval_count
|
||||
)}
|
||||
</p>
|
||||
</Localized>
|
||||
</Checkbox>
|
||||
<Localized id="product-plan-upgrade-heading">
|
||||
<h2>Review your upgrade</h2>
|
||||
</Localized>
|
||||
<p className="subheading"></p>
|
||||
</div>
|
||||
|
||||
<div className="button-row">
|
||||
<SubmitButton
|
||||
data-testid="submit"
|
||||
name="submit"
|
||||
disabled={inProgress}
|
||||
>
|
||||
{inProgress ? (
|
||||
<span data-testid="spinner-submit" className="spinner">
|
||||
|
||||
</span>
|
||||
) : (
|
||||
<Localized id="sub-update-submit">
|
||||
<span>Change Plans</span>
|
||||
<div className="payment-details">
|
||||
<h3 className="billing-title">
|
||||
<Localized id="sub-update-title">
|
||||
<span className="title">Billing information</span>
|
||||
</Localized>
|
||||
)}
|
||||
</SubmitButton>
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<Localized
|
||||
id="payment-confirmation-cc-preview"
|
||||
$last4={cardLast4}
|
||||
>
|
||||
<p className={`c-card ${cardBrandLc}`}>
|
||||
{' '}
|
||||
ending in {cardLast4}
|
||||
</p>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
data-testid="upgrade-form"
|
||||
className="payment upgrade"
|
||||
{...{ validator, onSubmit }}
|
||||
>
|
||||
<hr />
|
||||
<Localized
|
||||
id="sub-update-copy"
|
||||
$startingDate={getLocalizedDate(
|
||||
upgradeFromSubscription.current_period_end
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
Your plan will change immediately, and you’ll be charged an
|
||||
adjusted amount for the rest of your billing cycle. Starting
|
||||
{getLocalizedDateString(
|
||||
upgradeFromSubscription.current_period_end
|
||||
)}{' '}
|
||||
you’ll be charged the full amount.
|
||||
</p>
|
||||
</Localized>
|
||||
|
||||
<hr />
|
||||
|
||||
<Localized
|
||||
id={`sub-update-confirm-${selectedPlan.interval}`}
|
||||
strong={<strong></strong>}
|
||||
$amount={getLocalizedCurrency(
|
||||
selectedPlan.amount,
|
||||
selectedPlan.currency
|
||||
)}
|
||||
$intervalCount={selectedPlan.interval_count}
|
||||
>
|
||||
<Checkbox
|
||||
data-testid="confirm"
|
||||
name="confirm"
|
||||
onClick={engageOnce}
|
||||
required
|
||||
>
|
||||
{getDefaultPaymentConfirmText(
|
||||
selectedPlan.amount,
|
||||
selectedPlan.currency,
|
||||
selectedPlan.interval,
|
||||
selectedPlan.interval_count
|
||||
)}
|
||||
</Checkbox>
|
||||
</Localized>
|
||||
|
||||
<hr />
|
||||
|
||||
<div className="button-row">
|
||||
<SubmitButton
|
||||
data-testid="submit"
|
||||
name="submit"
|
||||
disabled={inProgress}
|
||||
>
|
||||
{inProgress ? (
|
||||
<span data-testid="spinner-submit" className="spinner">
|
||||
|
||||
</span>
|
||||
) : (
|
||||
<Localized id="sub-update-submit">
|
||||
<span>Confirm upgrade</span>
|
||||
</Localized>
|
||||
)}
|
||||
</SubmitButton>
|
||||
</div>
|
||||
|
||||
<PaymentLegalBlurb />
|
||||
<TermsAndPrivacy />
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<PaymentLegalBlurb />
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlanDetail = ({ plan }: { plan: Plan }) => {
|
||||
const {
|
||||
product_name: productName,
|
||||
amount,
|
||||
currency,
|
||||
interval,
|
||||
interval_count,
|
||||
} = plan;
|
||||
const { webIconURL } = metadataFromPlan(plan);
|
||||
const planPrice = formatPlanPricing(
|
||||
amount,
|
||||
currency,
|
||||
interval,
|
||||
interval_count
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="upgrade-plan-detail">
|
||||
{webIconURL && (
|
||||
<img src={webIconURL} alt={productName} height="49" width="49" />
|
||||
)}
|
||||
<span className="product-name">{productName}</span>
|
||||
<Localized
|
||||
id={`plan-price-${interval}`}
|
||||
$amount={getLocalizedCurrency(amount, currency)}
|
||||
$intervalCount={interval_count}
|
||||
>
|
||||
<span className="plan-price">{planPrice}</span>
|
||||
</Localized>
|
||||
</div>
|
||||
<PlanUpgradeDetails
|
||||
{...{
|
||||
profile,
|
||||
selectedPlan,
|
||||
upgradeFromPlan,
|
||||
isMobile,
|
||||
showExpandButton: isMobile,
|
||||
}}
|
||||
/>
|
||||
{mobileUpdateHeading}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
.mobile-subscription-update-heading,
|
||||
.mobile-subscription-create-heading {
|
||||
grid-row: 1/2;
|
||||
margin: 0 auto;
|
||||
|
|
|
@ -158,6 +158,7 @@ export const Product = ({
|
|||
return (
|
||||
<SubscriptionUpgrade
|
||||
{...{
|
||||
isMobile,
|
||||
profile: profile.result,
|
||||
customer: customer.result,
|
||||
selectedPlan,
|
||||
|
|
Загрузка…
Ссылка в новой задаче