зеркало из https://github.com/mozilla/fxa.git
Merge pull request #17334 from mozilla/FXA-7579
fix(libs): Add Coupon component
This commit is contained in:
Коммит
c33d786bc6
|
@ -3,9 +3,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
import { CouponForm } from '@fxa/payments/ui';
|
||||
import {
|
||||
getApp,
|
||||
fetchCMSData,
|
||||
getApp,
|
||||
getCartAction,
|
||||
PurchaseDetails,
|
||||
SubscriptionTitle,
|
||||
|
@ -67,6 +68,12 @@ export default async function RootLayout({
|
|||
cms.defaultPurchase.data.attributes.purchaseDetails.data.attributes
|
||||
}
|
||||
/>
|
||||
<CouponForm
|
||||
cartId={cart.id}
|
||||
cartVersion={cart.version}
|
||||
promoCode={cart.couponCode}
|
||||
readOnly={false}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<div className="page-body rounded-t-lg tablet:rounded-t-none">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { headers } from 'next/headers';
|
||||
import { PaymentSection } from '@fxa/payments/ui';
|
||||
import { BaseButton, ButtonVariant, PaymentSection } from '@fxa/payments/ui';
|
||||
import {
|
||||
getApp,
|
||||
getCartOrRedirectAction,
|
||||
|
@ -16,7 +16,6 @@ import {
|
|||
getCMSContent,
|
||||
} from 'apps/payments/next/app/_lib/apiClient';
|
||||
import { auth, signIn } from 'apps/payments/next/auth';
|
||||
import { PrimaryButton } from 'libs/payments/ui/src/lib/client/components/PrimaryButton';
|
||||
import { CheckoutParams } from '../layout';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
@ -110,7 +109,14 @@ export default async function Checkout({ params }: { params: CheckoutParams }) {
|
|||
name="email"
|
||||
className="w-full border rounded-md border-black/30 p-3 placeholder:text-grey-500 placeholder:font-normal focus:border focus:!border-black/30 focus:!shadow-[0_0_0_3px_rgba(10,132,255,0.3)] focus-visible:outline-none data-[invalid=true]:border-alert-red data-[invalid=true]:text-alert-red data-[invalid=true]:shadow-inputError"
|
||||
/>
|
||||
<PrimaryButton type="submit"> Set email</PrimaryButton>
|
||||
<BaseButton
|
||||
className="mt-10 w-full"
|
||||
type="submit"
|
||||
variant={ButtonVariant.Primary}
|
||||
>
|
||||
{' '}
|
||||
Set email
|
||||
</BaseButton>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -38,10 +38,10 @@ export default function Index() {
|
|||
<h2 className="text-xl mt-8">Without auth</h2>
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>VPN - Monthly</h2>
|
||||
<h2>123Done Pro - Monthly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/vpn/checkout/monthly/new"
|
||||
href="/en/123donepro/checkout/monthly/new"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
|
|
|
@ -25,7 +25,7 @@ body {
|
|||
}
|
||||
|
||||
.page-body {
|
||||
@apply component-card border-t-0 mb-6 pt-4 px-4 pb-14 text-grey-600 desktop:px-12 desktop:pb-12;
|
||||
@apply bg-white rounded-b-lg shadow-sm shadow-grey-300 border-t-0 mb-6 pt-4 px-4 pb-14 text-grey-600 desktop:px-12 desktop:pb-12;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
|
||||
// Use this file to export React client components (e.g. those with 'use client' directive) or other non-server utilities
|
||||
|
||||
export * from './lib/utils/helpers';
|
||||
export * from './lib/client/components/BaseButton';
|
||||
export * from './lib/client/components/CheckoutForm';
|
||||
export * from './lib/client/components/CheckoutCheckbox';
|
||||
export * from './lib/client/components/CouponForm';
|
||||
export * from './lib/client/components/PaymentSection';
|
||||
export * from './lib/client/components/SubmitButton';
|
||||
export * from './lib/client/providers/Providers';
|
||||
export * from './lib/utils/helpers';
|
||||
export * from './lib/utils/types';
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
'use server';
|
||||
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
|
||||
import { UpdateCart } from '@fxa/payments/cart';
|
||||
import { getApp } from '../nestapp/app';
|
||||
import { UpdateCartActionArgs } from '../nestapp/validators/UpdateCartActionArgs';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
|
||||
export const updateCartAction = async (
|
||||
cartId: string,
|
||||
|
@ -23,4 +25,9 @@ export const updateCartAction = async (
|
|||
cartDetails,
|
||||
})
|
||||
);
|
||||
|
||||
revalidatePath(
|
||||
'/[locale]/[offeringId]/checkout/[interval]/[cartId]/start',
|
||||
'page'
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export enum ButtonVariant {
|
||||
Primary,
|
||||
Secondary,
|
||||
}
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
export function BaseButton({ children, variant, ...props }: ButtonProps) {
|
||||
let variantStyles = '';
|
||||
switch (variant) {
|
||||
case ButtonVariant.Primary:
|
||||
variantStyles = 'bg-blue-500 hover:bg-blue-700 text-white';
|
||||
break;
|
||||
case ButtonVariant.Secondary:
|
||||
variantStyles = 'bg-grey-100 hover:bg-grey-200 text-black';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={`flex items-center justify-center font-semibold h-12 rounded-md p-4 z-10 aria-disabled:relative aria-disabled:after:absolute aria-disabled:after:content-[''] aria-disabled:after:top-0 aria-disabled:after:left-0 aria-disabled:after:w-full aria-disabled:after:h-full aria-disabled:after:bg-white aria-disabled:after:opacity-50 aria-disabled:after:z-30 aria-disabled:border-none ${props.className} ${variantStyles}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
'use client';
|
||||
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { PayPalButtons } from '@paypal/react-paypal-js';
|
||||
import * as Form from '@radix-ui/react-form';
|
||||
import {
|
||||
PaymentElement,
|
||||
|
@ -14,12 +15,11 @@ import { StripePaymentElementChangeEvent } from '@stripe/stripe-js';
|
|||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { BaseButton, ButtonVariant, CheckoutCheckbox } from '@fxa/payments/ui';
|
||||
import LockImage from '@fxa/shared/assets/images/lock.svg';
|
||||
import { CheckoutCheckbox } from '../CheckoutCheckbox';
|
||||
import { PrimaryButton } from '../PrimaryButton';
|
||||
import { checkoutCartWithStripe } from '../../../actions/checkoutCartWithStripe';
|
||||
import { handleStripeErrorAction } from '../../../actions/handleStripeError';
|
||||
import { PayPalButtons } from '@paypal/react-paypal-js';
|
||||
|
||||
interface CheckoutFormProps {
|
||||
cmsCommonContent: {
|
||||
|
@ -251,15 +251,17 @@ export function CheckoutForm({
|
|||
className="mt-6"
|
||||
/>
|
||||
) : (
|
||||
<PrimaryButton
|
||||
<BaseButton
|
||||
className="mt-10 w-full"
|
||||
type="submit"
|
||||
variant={ButtonVariant.Primary}
|
||||
aria-disabled={
|
||||
!stripeFieldsComplete || !nonStripeFieldsComplete || loading
|
||||
}
|
||||
>
|
||||
<Image src={LockImage} className="h-4 w-4 mx-3" alt="" />
|
||||
<Localized id="next-new-user-submit">Subscribe Now</Localized>
|
||||
</PrimaryButton>
|
||||
</BaseButton>
|
||||
)}
|
||||
</Form.Submit>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
## Component - CouponForm
|
||||
|
||||
next-coupon-enter-code =
|
||||
.placeholder = Enter Code
|
||||
|
||||
# Title of container where a user can input a coupon code to get a discount on a subscription.
|
||||
next-coupon-promo-code = Promo Code
|
||||
|
||||
# Title of container showing discount coupon code applied to a subscription.
|
||||
next-coupon-promo-code-applied = Promo Code Applied
|
||||
|
||||
next-coupon-remove = Remove
|
||||
next-coupon-submit = Apply
|
|
@ -0,0 +1,155 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use client';
|
||||
|
||||
import { Localized } from '@fluent/react';
|
||||
import { ButtonVariant } from '../BaseButton';
|
||||
import { SubmitButton } from '../SubmitButton';
|
||||
import { updateCartAction } from '../../../actions/updateCart';
|
||||
import { getFallbackTextByFluentId } from '../../../utils/error-ftl-messages';
|
||||
|
||||
interface WithCouponProps {
|
||||
cartId: string;
|
||||
cartVersion: number;
|
||||
couponCode: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
const WithCoupon = ({
|
||||
cartId,
|
||||
cartVersion,
|
||||
couponCode,
|
||||
readOnly,
|
||||
}: WithCouponProps) => {
|
||||
async function removeCoupon() {
|
||||
await updateCartAction(cartId, cartVersion, { couponCode: '' });
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="m-0 mb-4 font-semibold text-grey-600">
|
||||
<Localized id="next-coupon-promo-code-applied">
|
||||
Promo Code Applied
|
||||
</Localized>
|
||||
</h2>
|
||||
|
||||
<form
|
||||
action={removeCoupon}
|
||||
className="flex gap-4 justify-between items-center"
|
||||
data-testid="coupon-hascoupon"
|
||||
>
|
||||
<span className="break-all">{couponCode}</span>
|
||||
{readOnly ? null : (
|
||||
<span>
|
||||
<SubmitButton
|
||||
className="w-24"
|
||||
variant={ButtonVariant.Secondary}
|
||||
data-testid="coupon-remove-button"
|
||||
>
|
||||
<Localized id="next-coupon-remove">Remove</Localized>
|
||||
</SubmitButton>
|
||||
</span>
|
||||
)}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface WithoutCouponProps {
|
||||
cartId: string;
|
||||
cartVersion: number;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
const WithoutCoupon = ({
|
||||
cartId,
|
||||
cartVersion,
|
||||
readOnly,
|
||||
}: WithoutCouponProps) => {
|
||||
async function applyCoupon(formData: FormData) {
|
||||
const promotionCode = formData.get('coupon') as string;
|
||||
|
||||
await updateCartAction(cartId, cartVersion, {
|
||||
couponCode: promotionCode,
|
||||
});
|
||||
}
|
||||
|
||||
const error = null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="m-0 mb-4 font-semibold text-grey-600">
|
||||
<Localized id="next-coupon-promo-code">Promo Code</Localized>
|
||||
</h2>
|
||||
|
||||
<form action={applyCoupon} data-testid="coupon-form">
|
||||
<div className="flex gap-4 justify-between items-center">
|
||||
<Localized attrs={{ placeholder: true }} id="next-coupon-enter-code">
|
||||
<input
|
||||
className="w-full border rounded-md border-black/30 p-3 placeholder:text-grey-500 placeholder:font-normal focus:border focus:!border-black/30 focus:!shadow-[0_0_0_3px_rgba(10,132,255,0.3)] focus-visible:outline-none data-[invalid=true]:border-alert-red data-[invalid=true]:text-alert-red data-[invalid=true]:shadow-inputError"
|
||||
type="text"
|
||||
name="coupon"
|
||||
data-testid="coupon-input"
|
||||
placeholder="Enter code"
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</Localized>
|
||||
<div>
|
||||
<SubmitButton
|
||||
className="w-20"
|
||||
variant={ButtonVariant.Primary}
|
||||
type="submit"
|
||||
data-testid="coupon-button"
|
||||
disabled={readOnly}
|
||||
>
|
||||
<Localized id="next-coupon-submit">Apply</Localized>
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-red-700 mt-4" data-testid="coupon-error">
|
||||
<Localized id={error}>{getFallbackTextByFluentId(error)}</Localized>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface CouponFormProps {
|
||||
cartId: string;
|
||||
cartVersion: number;
|
||||
promoCode: string | null;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export function CouponForm({
|
||||
cartId,
|
||||
cartVersion,
|
||||
promoCode,
|
||||
readOnly,
|
||||
}: CouponFormProps) {
|
||||
const hasCouponCode = !!promoCode;
|
||||
return (
|
||||
<div className="bg-white rounded-b-lg shadow-sm shadow-grey-300 mt-6 p-4 rounded-t-lg text-base tablet:my-8">
|
||||
{hasCouponCode ? (
|
||||
<WithCoupon
|
||||
cartId={cartId}
|
||||
cartVersion={cartVersion}
|
||||
couponCode={promoCode}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : (
|
||||
<WithoutCoupon
|
||||
cartId={cartId}
|
||||
cartVersion={cartVersion}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CouponForm;
|
|
@ -1,18 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
interface PrimaryButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PrimaryButton({ children, ...props }: PrimaryButtonProps) {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={`flex items-center justify-center bg-blue-500 font-semibold h-12 rounded-md text-white w-full p-4 mt-6 hover:bg-blue-700 z-10 aria-disabled:relative aria-disabled:after:absolute aria-disabled:after:content-[''] aria-disabled:after:top-0 aria-disabled:after:left-0 aria-disabled:after:w-full aria-disabled:after:h-full aria-disabled:after:bg-white aria-disabled:after:opacity-50 aria-disabled:after:z-30 aria-disabled:border-none ${props.className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
import spinnerWhiteImage from '@fxa/shared/assets/images/spinnerwhite.svg';
|
||||
import { BaseButton, ButtonVariant } from '../BaseButton';
|
||||
|
||||
interface SubmitButtonProps {
|
||||
children: React.ReactNode;
|
||||
variant: ButtonVariant;
|
||||
}
|
||||
|
||||
export function SubmitButton({
|
||||
children,
|
||||
variant,
|
||||
disabled,
|
||||
className,
|
||||
...otherProps
|
||||
}: SubmitButtonProps & React.HTMLProps<HTMLButtonElement>) {
|
||||
const { pending } = useFormStatus();
|
||||
const isSubmitting = pending;
|
||||
|
||||
return (
|
||||
<BaseButton
|
||||
variant={variant}
|
||||
{...otherProps}
|
||||
disabled={isSubmitting || disabled}
|
||||
type="submit"
|
||||
className={className}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<Image
|
||||
src={spinnerWhiteImage}
|
||||
alt=""
|
||||
className="animate-spin h-9 w-9"
|
||||
/>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
}
|
|
@ -2,9 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Optional } from '@nestjs/common';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsNumber, IsString, ValidateNested } from 'class-validator';
|
||||
import {
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
export class UpdateCartActionCartTaxAddress {
|
||||
@IsString()
|
||||
|
@ -16,20 +20,20 @@ export class UpdateCartActionCartTaxAddress {
|
|||
|
||||
export class UpdateCartActionCartDetailsArgs {
|
||||
@IsString()
|
||||
@Optional()
|
||||
@IsOptional()
|
||||
uid?: string;
|
||||
|
||||
@Optional()
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => UpdateCartActionCartTaxAddress)
|
||||
taxAddress?: UpdateCartActionCartTaxAddress;
|
||||
|
||||
@IsString()
|
||||
@Optional()
|
||||
@IsOptional()
|
||||
couponCode?: string;
|
||||
|
||||
@IsString()
|
||||
@Optional()
|
||||
@IsOptional()
|
||||
email?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@ next-plan-details-list-price = List Price
|
|||
next-plan-details-tax = Taxes and Fees
|
||||
next-plan-details-total-label = Total
|
||||
|
||||
# Title of container where a user can input a coupon code to get a discount on a subscription.
|
||||
next-coupon-promo-code = Promo Code
|
||||
|
||||
## Purchase details - shared by multiple components, including purchase details and payment form
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ export async function PurchaseDetails(props: PurchaseDetailsProps) {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="component-card text-sm px-4 rounded-t-none tablet:rounded-t-lg">
|
||||
<div className="bg-white rounded-b-lg shadow-sm shadow-grey-300 text-sm px-4 rounded-t-none tablet:rounded-t-lg">
|
||||
<div className="flex gap-4 my-0 py-4 row-divider-grey-200">
|
||||
<Image
|
||||
src={webIcon}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import {
|
||||
BASIC_ERROR,
|
||||
CouponErrorMessageType,
|
||||
getFallbackTextByFluentId,
|
||||
} from './error-ftl-messages';
|
||||
|
||||
describe('getFallbackTextByFluentId', () => {
|
||||
it('returns default basic error message if no error id is provided', () => {
|
||||
expect(getFallbackTextByFluentId('')).toEqual(
|
||||
'Something went wrong. Please try again later.'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns default basic error message if provided error id does not match any key in dictionary', () => {
|
||||
expect(getFallbackTextByFluentId('foo-bar')).toEqual(
|
||||
'Something went wrong. Please try again later.'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns error message for provided error', () => {
|
||||
expect(getFallbackTextByFluentId(BASIC_ERROR)).toEqual(
|
||||
'Something went wrong. Please try again later.'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns coupon error message', () => {
|
||||
expect(getFallbackTextByFluentId(CouponErrorMessageType.Expired)).toEqual(
|
||||
'The code you entered has expired.'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export enum CouponErrorMessageType {
|
||||
Expired = 'next-coupon-error-expired',
|
||||
Generic = 'next-coupon-error-generic',
|
||||
Invalid = 'next-coupon-error-invalid',
|
||||
LimitReached = 'next-coupon-error-limit-reached',
|
||||
}
|
||||
|
||||
const BASIC_ERROR = 'next-basic-error-message';
|
||||
|
||||
// Dictionary of fluentIds and corresponding human-readable error messages
|
||||
// Each error ID key should have a matching error ID property in errorToErrorMessageIdMap
|
||||
const getFallbackTextByFluentId = (key: string) => {
|
||||
switch (key) {
|
||||
// coupon error messages
|
||||
case CouponErrorMessageType.Expired:
|
||||
return 'The code you entered has expired.';
|
||||
case CouponErrorMessageType.Generic:
|
||||
return 'An error occurred processing the code. Please try again.';
|
||||
case CouponErrorMessageType.Invalid:
|
||||
return 'The code you entered is invalid.';
|
||||
case CouponErrorMessageType.LimitReached:
|
||||
return 'The code you entered has reached its limit.';
|
||||
|
||||
// generic messages for groups of similar errors
|
||||
case BASIC_ERROR:
|
||||
default:
|
||||
return 'Something went wrong. Please try again later.';
|
||||
}
|
||||
};
|
||||
|
||||
// BASIC_ERROR, COUNTRY_CURRENCY_MISMATCH and PAYMENT_ERROR_1 are exported for errors.test.tsx
|
||||
export { getFallbackTextByFluentId, BASIC_ERROR };
|
|
@ -1,6 +1,7 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export enum SupportedPages {
|
||||
START = 'start',
|
||||
PROCESSING = 'processing',
|
||||
|
|
|
@ -16,3 +16,4 @@ export { handleStripeErrorAction } from './lib/actions/handleStripeError';
|
|||
export { getCartAction } from './lib/actions/getCart';
|
||||
export { getCartOrRedirectAction } from './lib/actions/getCartOrRedirect';
|
||||
export { setupCartAction } from './lib/actions/setupCart';
|
||||
export { updateCartAction } from './lib/actions/updateCart';
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.component-card {
|
||||
@apply bg-white rounded-b-lg shadow-sm shadow-grey-300;
|
||||
}
|
||||
|
||||
.component-card.rounded-plan {
|
||||
@apply rounded-lg tablet:mt-0;
|
||||
}
|
|
@ -8,4 +8,3 @@
|
|||
@import './links.css';
|
||||
@import './portal.css';
|
||||
@import './tooltips.css';
|
||||
@import './containers.css';
|
||||
|
|
Загрузка…
Ссылка в новой задаче