зеркало из https://github.com/mozilla/fxa.git
feat(next): add consent checkbox
Because: * Add consent checkbox, reminder error tooltip and payment section with default disabled state. This commit: * Adds a new client component for the consent checkbox and implements a Radix holdover component for the error tooltip. * Adds PaymentSection client component. Closes: #FXA-7806
This commit is contained in:
Родитель
1203c6a3c4
Коммит
6409c5e422
|
@ -2,3 +2,8 @@
|
|||
next-new-user-step-1-2 = 1. Create a { -product-mozilla-account }
|
||||
next-new-user-sign-in-link-2 = Already have a { -product-mozilla-account }? <a>Sign in</a>
|
||||
next-payment-confirm-with-legal-links-static-3 = I authorize { -brand-mozilla } to charge my payment method for the amount shown, according to <termsOfServiceLink>Terms of Service</termsOfServiceLink> and <privacyNoticeLink>Privacy Notice</privacyNoticeLink>, until I cancel my subscription.
|
||||
|
||||
next-payment-method-header = Choose your payment method
|
||||
# This message is used to indicate the second step in a multi step process.
|
||||
payment-method-header-second-step-next = 2. { next-payment-method-header }
|
||||
next-payment-method-first-approve = First you’ll need to approve your subscription
|
||||
|
|
|
@ -10,8 +10,11 @@ import { DEFAULT_LOCALE } from '@fxa/shared/l10n';
|
|||
import { auth, signIn } from 'apps/payments/next/auth';
|
||||
import { headers } from 'next/headers';
|
||||
import { CheckoutParams } from '../layout';
|
||||
import { StripeWrapper } from '@fxa/payments/ui';
|
||||
import { getFakeCartData } from 'apps/payments/next/app/_lib/apiClient';
|
||||
import {
|
||||
getFakeCartData,
|
||||
getContentfulContent,
|
||||
} from 'apps/payments/next/app/_lib/apiClient';
|
||||
import { PaymentSection } from '@fxa/payments/ui';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
|
@ -31,169 +34,100 @@ export default async function Checkout({ params }: { params: CheckoutParams }) {
|
|||
);
|
||||
//TODO - Replace with cartPromise as part of FXA-8903
|
||||
const fakeCartDataPromise = getFakeCartData(params.cartId);
|
||||
const [session, l10n, cart, fakeCart] = await Promise.all([
|
||||
const cmsPromise = getContentfulContent(params.offeringId, locale);
|
||||
const [session, l10n, cart, fakeCart, cms] = await Promise.all([
|
||||
sessionPromise,
|
||||
l10nPromise,
|
||||
cartPromise,
|
||||
fakeCartDataPromise,
|
||||
cmsPromise,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
className="h-min-[640px]"
|
||||
aria-label="Section under construction"
|
||||
>
|
||||
{!session && (
|
||||
<>
|
||||
<h4 className="font-semibold text-grey-600 text-lg">
|
||||
{l10n.getString(
|
||||
'next-new-user-step-1-2',
|
||||
'1. Create a Mozilla account'
|
||||
)}
|
||||
</h4>
|
||||
<section>
|
||||
{!session && (
|
||||
<>
|
||||
<h4 className="font-semibold text-grey-600 text-lg mt-10">
|
||||
{l10n.getString(
|
||||
'next-new-user-step-1-2',
|
||||
'1. Create a Mozilla account'
|
||||
)}
|
||||
</h4>
|
||||
|
||||
<form
|
||||
action={async () => {
|
||||
'use server';
|
||||
await signIn('fxa');
|
||||
}}
|
||||
>
|
||||
<div className="text-grey-400 text-sm">
|
||||
{l10n.getFragmentWithSource(
|
||||
'next-new-user-sign-in-link-2',
|
||||
{
|
||||
elems: {
|
||||
a: (
|
||||
<button className="underline text-grey-400 hover:text-grey-400">
|
||||
Sign in
|
||||
</button>
|
||||
),
|
||||
},
|
||||
},
|
||||
<button className="underline text-grey-400 hover:text-grey-400">
|
||||
Sign in
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr className="mx-auto my-4 w-full border-grey-200" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<section className="flex flex-col gap-2 mb-8">
|
||||
<div>
|
||||
<h3 className="text-xl">Temporary L10n Section</h3>
|
||||
<p className="text-sm">
|
||||
Temporary section to illustrate various translations using the
|
||||
Localizer classes
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Regular translation - no variables</h4>
|
||||
<p className="text-sm">
|
||||
{l10n.getString('app-footer-mozilla-logo-label', 'testing2')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Regular translation - with variables</h4>
|
||||
<p className="text-sm">
|
||||
{l10n.getString(
|
||||
'app-page-title-2',
|
||||
{ title: 'Test Title' },
|
||||
'testing2'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Regular translation - With Selector</h4>
|
||||
<p className="text-sm">
|
||||
{l10n.getString(
|
||||
'next-plan-price-interval-day',
|
||||
{ intervalCount: 2, amount: 20 },
|
||||
'testing2'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Regular translation - With Currency</h4>
|
||||
<p className="text-sm">
|
||||
{l10n.getString(
|
||||
'list-positive-amount',
|
||||
{
|
||||
amount: l10n.getLocalizedCurrency(502, 'usd'),
|
||||
},
|
||||
`${l10n.getLocalizedCurrencyString(502, 'usd')}`
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Regular translation - With Date</h4>
|
||||
<p className="text-sm">
|
||||
{l10n.getString(
|
||||
'list-positive-amount',
|
||||
{
|
||||
amount: l10n.getLocalizedCurrency(502, 'usd'),
|
||||
},
|
||||
`${l10n.getLocalizedCurrencyString(502, 'usd')}`
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Get Fragment with Fallback element</h4>
|
||||
<p className="text-sm">
|
||||
<form
|
||||
action={async () => {
|
||||
'use server';
|
||||
await signIn('fxa');
|
||||
}}
|
||||
>
|
||||
<p className="text-grey-400 text-sm mt-2 mb-4">
|
||||
{l10n.getFragmentWithSource(
|
||||
'next-payment-legal-link-stripe-3',
|
||||
'next-new-user-sign-in-link-2',
|
||||
{
|
||||
elems: {
|
||||
stripePrivacyLink: (
|
||||
<a href="https://stripe.com/privacy">
|
||||
Stripe privacy policy
|
||||
</a>
|
||||
),
|
||||
},
|
||||
},
|
||||
<a href="https://stripe.com/privacy">Stripe privacy policy</a>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Get Element - With reference</h4>
|
||||
<p className="text-sm">
|
||||
{l10n.getFragmentWithSource(
|
||||
'next-payment-confirm-with-legal-links-static-3',
|
||||
{
|
||||
elems: {
|
||||
termsOfServiceLink: (
|
||||
<a href="https://stripe.com/privacy">
|
||||
Stripe privacy policy
|
||||
</a>
|
||||
),
|
||||
privacyNoticeLink: (
|
||||
<a href="https://stripe.com/privacy">
|
||||
Stripe privacy policy
|
||||
</a>
|
||||
a: (
|
||||
<button className="underline text-grey-400 hover:text-grey-400">
|
||||
Sign in
|
||||
</button>
|
||||
),
|
||||
},
|
||||
},
|
||||
<>
|
||||
I authorize Mozilla to charge my payment method for the amount
|
||||
shown, according to{' '}
|
||||
<a href="https://www.example.com">Terms of Service</a> and{' '}
|
||||
<a href="https://www.example.com">Privacy Notice</a>, until I
|
||||
cancel my subscription.
|
||||
Already have a Mozilla account?
|
||||
<button className="underline text-grey-400 hover:text-grey-400">
|
||||
Sign in
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<hr className="mx-auto w-full border-grey-200" />
|
||||
|
||||
<div className="h-64 text-center flex items-center justify-center">
|
||||
{'<placeholder>Passwordless signup</placeholder>'}
|
||||
</div>
|
||||
</section>
|
||||
<StripeWrapper
|
||||
amount={fakeCart.amount}
|
||||
currency={fakeCart.nextInvoice.currency}
|
||||
cart={cart}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
|
||||
<hr className="mx-auto w-full border-grey-200" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!session ? (
|
||||
<h4
|
||||
className="font-semibold text-grey-600 text-lg mt-14 mb-5"
|
||||
data-testid="header-prefix"
|
||||
>
|
||||
{l10n.getString(
|
||||
'payment-method-header-second-step-next',
|
||||
'2. Choose your payment method2'
|
||||
)}
|
||||
</h4>
|
||||
) : (
|
||||
<h4
|
||||
className="font-semibold text-grey-600 text-lg mt-14 mb-5"
|
||||
data-testid="header"
|
||||
>
|
||||
{l10n.getString(
|
||||
'next-payment-method-header',
|
||||
'Choose your payment method'
|
||||
)}
|
||||
</h4>
|
||||
)}
|
||||
<p className="font-semibold my-3 text-grey-600 text-start">
|
||||
{l10n.getString(
|
||||
'next-payment-method-first-approve',
|
||||
`First you'll need to approve your subscription`
|
||||
)}
|
||||
</p>
|
||||
|
||||
<PaymentSection
|
||||
cmsCommonContent={cms.commonContent}
|
||||
paymentsInfo={{
|
||||
amount: fakeCart.amount,
|
||||
currency: fakeCart.nextInvoice.currency,
|
||||
}}
|
||||
cart={cart}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,3 +7,5 @@
|
|||
export * from './lib/utils/helpers';
|
||||
export * from './lib/client/components/StripeWrapper';
|
||||
export * from './lib/client/components/Providers';
|
||||
export * from './lib/client/components/CheckoutCheckbox';
|
||||
export * from './lib/client/components/PaymentSection';
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
## Component - Payment Consent Checkbox
|
||||
|
||||
next-payment-confirm-with-legal-links-static-3 = I authorize { -brand-mozilla } to charge my payment method for the amount shown, according to <termsOfServiceLink>Terms of Service</termsOfServiceLink> and <privacyNoticeLink>Privacy Notice</privacyNoticeLink>, until I cancel my subscription.
|
||||
|
||||
next-payment-confirm-checkbox-error = You need to complete this before moving forward
|
|
@ -0,0 +1,110 @@
|
|||
/* 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 * as HoverCard from '@radix-ui/react-hover-card';
|
||||
import { useState } from 'react';
|
||||
import { Localized } from '@fluent/react';
|
||||
|
||||
interface CheckoutCheckboxProps {
|
||||
isRequired: boolean;
|
||||
termsOfService: string;
|
||||
privacyNotice: string;
|
||||
notifyCheckboxChange: (isChecked: boolean) => void;
|
||||
}
|
||||
|
||||
export function CheckoutCheckbox({
|
||||
isRequired,
|
||||
termsOfService,
|
||||
privacyNotice,
|
||||
notifyCheckboxChange,
|
||||
}: CheckoutCheckboxProps) {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
|
||||
const changeHandler = () => {
|
||||
const newValue = !isChecked;
|
||||
setIsChecked(newValue);
|
||||
notifyCheckboxChange(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<HoverCard.Root open={isRequired && !isChecked}>
|
||||
<label className="flex gap-5 items-center mt-6">
|
||||
<HoverCard.Trigger>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="confirm"
|
||||
className="grow-0 shrink-0 basis-4 scale-150 cursor-pointer"
|
||||
checked={isChecked}
|
||||
onChange={changeHandler}
|
||||
/>
|
||||
</HoverCard.Trigger>
|
||||
<Localized
|
||||
id="next-payment-confirm-with-legal-links-static-3"
|
||||
elems={{
|
||||
termsOfServiceLink: (
|
||||
<a
|
||||
href={termsOfService}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
Terms of Service
|
||||
</a>
|
||||
),
|
||||
privacyNoticeLink: (
|
||||
<a
|
||||
href={privacyNotice}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
Privacy Notice
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<span className="font-normal text-sm block">
|
||||
I authorize Mozilla to charge my payment method for the amount
|
||||
shown, according to{' '}
|
||||
<a
|
||||
href={termsOfService}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
Terms of Service
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a
|
||||
href={privacyNotice}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
Privacy Notice
|
||||
</a>
|
||||
, until I cancel my subscription.
|
||||
</span>
|
||||
</Localized>
|
||||
</label>
|
||||
<HoverCard.Portal>
|
||||
<HoverCard.Content
|
||||
className="animate-slide-up z-20"
|
||||
sideOffset={20}
|
||||
align="start"
|
||||
alignOffset={50}
|
||||
arrowPadding={20}
|
||||
>
|
||||
<Localized id="next-payment-confirm-checkbox-error">
|
||||
<div className="text-white text-sm bg-alert-red py-1.5 px-4">
|
||||
You need to complete this before moving forward
|
||||
</div>
|
||||
</Localized>
|
||||
<HoverCard.Arrow className="fill-alert-red" height={11} width={22} />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Portal>
|
||||
</HoverCard.Root>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
## Checkout Form
|
||||
|
||||
next-new-user-submit = Subscribe Now
|
||||
|
||||
# Label for the Full Name input
|
||||
payment-name-label = Name as it appears on your card
|
||||
|
||||
# Full Name input
|
||||
next-payment-name =
|
||||
.placeholder = Full Name
|
||||
|
|
@ -16,8 +16,10 @@ import LockImage from '@fxa/shared/assets/images/lock.svg';
|
|||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import * as Form from '@radix-ui/react-form';
|
||||
import { Localized } from '@fluent/react';
|
||||
|
||||
interface CheckoutFormProps {
|
||||
readOnly: boolean;
|
||||
cart: {
|
||||
id: string;
|
||||
version: number;
|
||||
|
@ -25,7 +27,7 @@ interface CheckoutFormProps {
|
|||
};
|
||||
}
|
||||
|
||||
export function CheckoutForm({ cart }: CheckoutFormProps) {
|
||||
export function CheckoutForm({ readOnly, cart }: CheckoutFormProps) {
|
||||
const router = useRouter();
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
|
@ -63,7 +65,7 @@ export function CheckoutForm({ cart }: CheckoutFormProps) {
|
|||
) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!stripe || !elements) {
|
||||
if (!stripe || !elements || readOnly) {
|
||||
// Stripe.js hasn't yet loaded.
|
||||
// Make sure to disable form submission until Stripe.js has loaded.
|
||||
return;
|
||||
|
@ -131,20 +133,25 @@ export function CheckoutForm({ cart }: CheckoutFormProps) {
|
|||
{!isPaymentElementLoading && (
|
||||
<Form.Field name="name" serverInvalid={hasFullNameError}>
|
||||
<Form.Label className="font-medium text-sm text-grey-400 block mb-1 text-start">
|
||||
Name as it appears on your card
|
||||
<Localized id="payment-name-label">
|
||||
Name as it appears on your card
|
||||
</Localized>
|
||||
</Form.Label>
|
||||
<Form.Control asChild>
|
||||
<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"
|
||||
data-testid="name"
|
||||
placeholder="Full Name"
|
||||
value={fullName}
|
||||
onChange={(e) => {
|
||||
setFullName(e.target.value);
|
||||
setHasFullNameError(!e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Localized id="next-payment-name" attrs={{ placeholder: true }}>
|
||||
<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"
|
||||
data-testid="name"
|
||||
placeholder="Full Name"
|
||||
readOnly={readOnly}
|
||||
value={fullName}
|
||||
onChange={(e) => {
|
||||
setFullName(e.target.value);
|
||||
setHasFullNameError(!e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Localized>
|
||||
</Form.Control>
|
||||
{hasFullNameError && (
|
||||
<Form.Message asChild>
|
||||
|
@ -155,23 +162,25 @@ export function CheckoutForm({ cart }: CheckoutFormProps) {
|
|||
)}
|
||||
</Form.Field>
|
||||
)}
|
||||
<PaymentElement />
|
||||
<PaymentElement options={{ readOnly }} />
|
||||
{!isPaymentElementLoading && (
|
||||
<Form.Submit asChild>
|
||||
<button
|
||||
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 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"
|
||||
type="submit"
|
||||
aria-disabled={
|
||||
!stripeFieldsComplete || !nonStripeFieldsComplete || loading
|
||||
}
|
||||
>
|
||||
<Image
|
||||
src={LockImage}
|
||||
className="h-4 w-4 my-0 mx-3 relative top-0.5"
|
||||
alt=""
|
||||
/>
|
||||
Subscribe Now
|
||||
</button>
|
||||
<Localized id="next-new-user-submit">
|
||||
<button
|
||||
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 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"
|
||||
type="submit"
|
||||
aria-disabled={
|
||||
!stripeFieldsComplete || !nonStripeFieldsComplete || loading
|
||||
}
|
||||
>
|
||||
<Image
|
||||
src={LockImage}
|
||||
className="h-4 w-4 my-0 mx-3 relative top-0.5"
|
||||
alt=""
|
||||
/>
|
||||
Subscribe Now
|
||||
</button>
|
||||
</Localized>
|
||||
</Form.Submit>
|
||||
)}
|
||||
</Form.Root>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
## Payment Section
|
||||
|
||||
next-new-user-card-title = Enter your card information
|
|
@ -0,0 +1,70 @@
|
|||
/* 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 { useState } from 'react';
|
||||
import { CheckoutCheckbox } from './CheckoutCheckbox';
|
||||
import { Providers } from './Providers';
|
||||
import { StripeWrapper } from './StripeWrapper';
|
||||
import { Localized } from '@fluent/react';
|
||||
|
||||
interface PaymentFormProps {
|
||||
cmsCommonContent: {
|
||||
termsOfServiceUrl: string;
|
||||
privacyNoticeUrl: string;
|
||||
};
|
||||
paymentsInfo: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
cart: {
|
||||
id: string;
|
||||
version: number;
|
||||
email: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export function PaymentSection({
|
||||
cmsCommonContent,
|
||||
paymentsInfo,
|
||||
cart,
|
||||
}: PaymentFormProps) {
|
||||
const [formEnabled, setFormEnabled] = useState(false);
|
||||
const [showConsentError, setShowConsentError] = useState(false);
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<CheckoutCheckbox
|
||||
isRequired={showConsentError}
|
||||
termsOfService={cmsCommonContent.termsOfServiceUrl}
|
||||
privacyNotice={cmsCommonContent.privacyNoticeUrl}
|
||||
notifyCheckboxChange={(consentCheckbox) => {
|
||||
setFormEnabled(consentCheckbox);
|
||||
setShowConsentError(true);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={
|
||||
formEnabled
|
||||
? 'mt-14'
|
||||
: 'mt-14 relative cursor-not-allowed focus:border-blue-400 focus:outline-none focus:shadow-input-blue-focus after:absolute after:content-[""] after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:opacity-50 after:z-10'
|
||||
}
|
||||
aria-disabled={!formEnabled}
|
||||
onClick={() => setShowConsentError(true)}
|
||||
>
|
||||
<Localized id="next-new-user-card-title">
|
||||
<p className="font-semibold text-grey-600 text-start mt-3 mb-6">
|
||||
Enter your card information
|
||||
</p>
|
||||
</Localized>
|
||||
<StripeWrapper
|
||||
readOnly={!formEnabled}
|
||||
amount={paymentsInfo.amount}
|
||||
currency={paymentsInfo.currency}
|
||||
cart={cart}
|
||||
/>
|
||||
</div>
|
||||
</Providers>
|
||||
);
|
||||
}
|
|
@ -3,13 +3,12 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use client';
|
||||
|
||||
import { Localized } from '@fluent/react';
|
||||
import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js';
|
||||
import { Elements } from '@stripe/react-stripe-js';
|
||||
import { CheckoutForm } from './CheckoutForm';
|
||||
import { Providers } from './Providers';
|
||||
|
||||
interface StripeWrapperProps {
|
||||
readOnly: boolean;
|
||||
amount: number;
|
||||
currency: string;
|
||||
cart: {
|
||||
|
@ -19,7 +18,12 @@ interface StripeWrapperProps {
|
|||
};
|
||||
}
|
||||
|
||||
export function StripeWrapper({ amount, currency, cart }: StripeWrapperProps) {
|
||||
export function StripeWrapper({
|
||||
readOnly,
|
||||
amount,
|
||||
currency,
|
||||
cart,
|
||||
}: StripeWrapperProps) {
|
||||
// TODO - Load from config
|
||||
const stripePromise = loadStripe(
|
||||
'pk_test_VNpCidC0a2TJJB3wqXq7drhN00sF8r9mhs'
|
||||
|
@ -59,11 +63,8 @@ export function StripeWrapper({ amount, currency, cart }: StripeWrapperProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Localized id="next-pay-with-heading-card-only"></Localized>
|
||||
<Elements stripe={stripePromise} options={options}>
|
||||
<CheckoutForm cart={cart} />
|
||||
</Elements>
|
||||
</Providers>
|
||||
<Elements stripe={stripePromise} options={options}>
|
||||
<CheckoutForm readOnly={readOnly} cart={cart} />
|
||||
</Elements>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -84,10 +84,15 @@ module.exports = {
|
|||
'0%': { transform: 'rotate(0)' },
|
||||
'100%': { transform: 'rotate(360deg)' },
|
||||
},
|
||||
'slide-up': {
|
||||
'0%': { opacity: 0, transform: 'translateY(10px)' },
|
||||
'100%': { opacity: 1, transform: 'translateY(0)' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'delayed-fade-in': 'fade-in 1s linear 5s forwards',
|
||||
spin: 'rotate 0.8s linear infinite',
|
||||
'slide-up': 'slide-up 0.6s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
},
|
||||
listStyleType: {
|
||||
circle: 'circle',
|
||||
|
@ -270,7 +275,7 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
plugins: [
|
||||
plugin(function ({ addUtilities }) {
|
||||
plugin(function({ addUtilities }) {
|
||||
const customUtilities = {
|
||||
'.clip-auto': {
|
||||
clip: 'auto',
|
||||
|
@ -279,7 +284,7 @@ module.exports = {
|
|||
|
||||
addUtilities(customUtilities, ['responsive', 'hover', 'focus']);
|
||||
}),
|
||||
plugin(function ({ addComponents }) {
|
||||
plugin(function({ addComponents }) {
|
||||
const carets = {
|
||||
'.caret-top': {
|
||||
borderLeft: '0.75rem solid transparent',
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"@opentelemetry/sdk-trace-node": "^1.23.0",
|
||||
"@opentelemetry/sdk-trace-web": "^1.23.0",
|
||||
"@radix-ui/react-form": "^0.0.3",
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@sentry/browser": "^7.113.0",
|
||||
"@sentry/integrations": "^7.113.0",
|
||||
"@sentry/node": "^7.113.0",
|
||||
|
|
123
yarn.lock
123
yarn.lock
|
@ -14878,6 +14878,30 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-dismissable-layer@npm:1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "@radix-ui/react-dismissable-layer@npm:1.0.5"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@radix-ui/primitive": 1.0.1
|
||||
"@radix-ui/react-compose-refs": 1.0.1
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
"@radix-ui/react-use-callback-ref": 1.0.1
|
||||
"@radix-ui/react-use-escape-keydown": 1.0.3
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: e73cf4bd3763f4d55b1bea7486a9700384d7d94dc00b1d5a75e222b2f1e4f32bc667a206ca4ed3baaaf7424dce7a239afd0ba59a6f0d89c3462c4e6e8d029a04
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-focus-guards@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-focus-guards@npm:1.0.1"
|
||||
|
@ -14940,6 +14964,34 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-hover-card@npm:^1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "@radix-ui/react-hover-card@npm:1.0.7"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@radix-ui/primitive": 1.0.1
|
||||
"@radix-ui/react-compose-refs": 1.0.1
|
||||
"@radix-ui/react-context": 1.0.1
|
||||
"@radix-ui/react-dismissable-layer": 1.0.5
|
||||
"@radix-ui/react-popper": 1.1.3
|
||||
"@radix-ui/react-portal": 1.0.4
|
||||
"@radix-ui/react-presence": 1.0.1
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
"@radix-ui/react-use-controllable-state": 1.0.1
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 812c348d8331348774b0460cd9058fdb34e0a4e167cc3ab7350d60d0ac374c673e8159573919da299f58860b8eeb9d43c21ccb679cf6db70f5db0386359871ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-id@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-id@npm:1.0.1"
|
||||
|
@ -15005,6 +15057,35 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-popper@npm:1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "@radix-ui/react-popper@npm:1.1.3"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@floating-ui/react-dom": ^2.0.0
|
||||
"@radix-ui/react-arrow": 1.0.3
|
||||
"@radix-ui/react-compose-refs": 1.0.1
|
||||
"@radix-ui/react-context": 1.0.1
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
"@radix-ui/react-use-callback-ref": 1.0.1
|
||||
"@radix-ui/react-use-layout-effect": 1.0.1
|
||||
"@radix-ui/react-use-rect": 1.0.1
|
||||
"@radix-ui/react-use-size": 1.0.1
|
||||
"@radix-ui/rect": 1.0.1
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: b18a15958623f9222b6ed3e24b9fbcc2ba67b8df5a5272412f261de1592b3f05002af1c8b94c065830c3c74267ce00cf6c1d70d4d507ec92ba639501f98aa348
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-portal@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@radix-ui/react-portal@npm:1.0.3"
|
||||
|
@ -15025,6 +15106,47 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-portal@npm:1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "@radix-ui/react-portal@npm:1.0.4"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: c4cf35e2f26a89703189d0eef3ceeeb706ae0832e98e558730a5e929ca7c72c7cb510413a24eca94c7732f8d659a1e81942bec7b90540cb73ce9e4885d040b64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-presence@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-presence@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@radix-ui/react-compose-refs": 1.0.1
|
||||
"@radix-ui/react-use-layout-effect": 1.0.1
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: ed2ff9faf9e4257a4065034d3771459e5a91c2d840b2fcec94661761704dbcb65bcdd927d28177a2a129b3dab5664eb90a9b88309afe0257a9f8ba99338c0d95
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-primitive@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@radix-ui/react-primitive@npm:1.0.3"
|
||||
|
@ -38715,6 +38837,7 @@ fsevents@~2.1.1:
|
|||
"@opentelemetry/sdk-trace-node": ^1.23.0
|
||||
"@opentelemetry/sdk-trace-web": ^1.23.0
|
||||
"@radix-ui/react-form": ^0.0.3
|
||||
"@radix-ui/react-hover-card": ^1.0.7
|
||||
"@sentry/browser": ^7.113.0
|
||||
"@sentry/integrations": ^7.113.0
|
||||
"@sentry/node": ^7.113.0
|
||||
|
|
Загрузка…
Ссылка в новой задаче