From 5ec3d76c3af5847604dedfa9c6d1c870246808ef Mon Sep 17 00:00:00 2001 From: Daniel Almaguer Date: Tue, 14 May 2024 14:29:41 -0500 Subject: [PATCH] chore(core): fetch checkout url until click (#912) --- .changeset/lazy-tools-collect.md | 5 ++ .../cart/_actions/redirect-to-checkout.ts | 37 ++++++++++++++ .../cart/_components/checkout-button.tsx | 51 ++++++++++--------- core/app/[locale]/(default)/cart/page.tsx | 13 +++-- .../login/register-customer/page.tsx | 2 +- core/tests/ui/desktop/e2e/cart.spec.ts | 4 +- core/tests/ui/desktop/e2e/coupon.spec.ts | 4 +- .../ui/mobile/checkout-experience.spec.ts | 2 +- 8 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 .changeset/lazy-tools-collect.md create mode 100644 core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts diff --git a/.changeset/lazy-tools-collect.md b/.changeset/lazy-tools-collect.md new file mode 100644 index 000000000..866354dae --- /dev/null +++ b/.changeset/lazy-tools-collect.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +fetch checkout redirect url when user clicks proceed to checkout button diff --git a/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts b/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts new file mode 100644 index 000000000..d33737f99 --- /dev/null +++ b/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts @@ -0,0 +1,37 @@ +'use server'; + +import { redirect } from 'next/navigation'; +import { z } from 'zod'; + +import { client } from '~/client'; +import { graphql } from '~/client/graphql'; + +const CheckoutRedirectMutation = graphql(` + mutation CheckoutRedirectMutation($cartId: String!) { + cart { + createCartRedirectUrls(input: { cartEntityId: $cartId }) { + redirectUrls { + redirectedCheckoutUrl + } + } + } + } +`); + +export const redirectToCheckout = async (formData: FormData) => { + const cartId = z.string().parse(formData.get('cartId')); + + const { data } = await client.fetch({ + document: CheckoutRedirectMutation, + variables: { cartId }, + fetchOptions: { cache: 'no-store' }, + }); + + const url = data.cart.createCartRedirectUrls.redirectUrls?.redirectedCheckoutUrl; + + if (!url) { + throw new Error('Invalid checkout url.'); + } + + redirect(url); +}; diff --git a/core/app/[locale]/(default)/cart/_components/checkout-button.tsx b/core/app/[locale]/(default)/cart/_components/checkout-button.tsx index 4c430a175..a702081d1 100644 --- a/core/app/[locale]/(default)/cart/_components/checkout-button.tsx +++ b/core/app/[locale]/(default)/cart/_components/checkout-button.tsx @@ -1,31 +1,36 @@ -import { client } from '~/client'; -import { graphql } from '~/client/graphql'; -import { Button } from '~/components/ui/button'; +'use client'; + +import { Loader2 as Spinner } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import { useFormStatus } from 'react-dom'; -export const CheckoutButtonMutation = graphql(` - mutation CheckoutButtonMutation($cartId: String!) { - cart { - createCartRedirectUrls(input: { cartEntityId: $cartId }) { - redirectUrls { - redirectedCheckoutUrl - } - } - } - } -`); +import { Button } from '~/components/ui/button'; -export const CheckoutButton = async ({ cartId, label }: { cartId: string; label: string }) => { - const { data } = await client.fetch({ - document: CheckoutButtonMutation, - variables: { cartId }, - fetchOptions: { cache: 'no-store' }, - }); +import { redirectToCheckout } from '../_actions/redirect-to-checkout'; - const checkoutUrl = data.cart.createCartRedirectUrls.redirectUrls?.redirectedCheckoutUrl; +const InternalButton = () => { + const t = useTranslations('Cart'); + const { pending } = useFormStatus(); return ( - ); }; + +export const CheckoutButton = ({ cartId }: { cartId: string }) => { + return ( +
+ + + + ); +}; diff --git a/core/app/[locale]/(default)/cart/page.tsx b/core/app/[locale]/(default)/cart/page.tsx index e0ab777f3..818adcf4a 100644 --- a/core/app/[locale]/(default)/cart/page.tsx +++ b/core/app/[locale]/(default)/cart/page.tsx @@ -1,6 +1,6 @@ import { cookies } from 'next/headers'; -import { getTranslations } from 'next-intl/server'; -import { Suspense } from 'react'; +import { NextIntlClientProvider } from 'next-intl'; +import { getMessages, getTranslations } from 'next-intl/server'; import { getSessionCustomerId } from '~/auth'; import { client } from '~/client'; @@ -50,7 +50,10 @@ export default async function CartPage({ params: { locale } }: Props) { return ; } + const messages = await getMessages({ locale }); + const Cart = messages.Cart ?? {}; const t = await getTranslations({ locale, namespace: 'Cart' }); + const customerId = await getSessionCustomerId(); const { data } = await client.fetch({ @@ -87,9 +90,9 @@ export default async function CartPage({ params: { locale } }: Props) {
{checkout && } - - - + + +
diff --git a/core/app/[locale]/(default)/login/register-customer/page.tsx b/core/app/[locale]/(default)/login/register-customer/page.tsx index 86c240047..89d7d9aa1 100644 --- a/core/app/[locale]/(default)/login/register-customer/page.tsx +++ b/core/app/[locale]/(default)/login/register-customer/page.tsx @@ -56,7 +56,7 @@ export default async function RegisterCustomer({ params: { locale } }: Props) { return (
-

{t('heading')}

+

{t('heading')}

{ await page.getByRole('link', { name: 'Cart Items 1' }).click(); await expect(page.getByRole('heading', { level: 1, name: 'Your cart' })).toBeVisible(); - await expect(page.getByRole('link', { name: 'Proceed to checkout' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Proceed to checkout' })).toBeVisible(); await page.getByRole('button', { name: 'Increase count' }).click(); @@ -37,7 +37,7 @@ test('Proceed to checkout', async ({ page }) => { await expect(page.getByRole('heading', { level: 1, name: 'Your cart' })).toBeVisible(); - await page.getByRole('link', { name: 'Proceed to checkout' }).click(); + await page.getByRole('button', { name: 'Proceed to checkout' }).click(); await expect(page.getByRole('heading', { name: 'Order Summary', level: 3 })).toBeVisible(); await expect(page.getByRole('heading', { name: `1 x ${sampleProduct}`, level: 4 })).toBeVisible(); diff --git a/core/tests/ui/desktop/e2e/coupon.spec.ts b/core/tests/ui/desktop/e2e/coupon.spec.ts index a474b9343..2c32aeaf9 100644 --- a/core/tests/ui/desktop/e2e/coupon.spec.ts +++ b/core/tests/ui/desktop/e2e/coupon.spec.ts @@ -13,7 +13,7 @@ test.beforeEach(async ({ page }) => { await page.getByRole('link', { name: 'Cart Items 1' }).click(); await expect(page.getByRole('heading', { level: 1, name: 'Your cart' })).toBeVisible(); - await expect(page.getByRole('link', { name: 'Proceed to checkout' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Proceed to checkout' })).toBeVisible(); await page.getByRole('button', { name: 'Add' }).nth(1).click(); }); @@ -46,7 +46,7 @@ test('Coupon code fails', async ({ page }) => { }); test('Apply coupon on checkout', async ({ page }) => { - await page.getByRole('link', { name: 'Proceed to checkout' }).click(); + await page.getByRole('button', { name: 'Proceed to checkout' }).click(); await expect(page.getByRole('link', { name: 'Coupon/Gift Certificate' })).toBeVisible(); await expect(page.getByText('Total (USD) $225.00')).toBeVisible(); diff --git a/core/tests/ui/mobile/checkout-experience.spec.ts b/core/tests/ui/mobile/checkout-experience.spec.ts index ee5ec81ce..95aebf461 100644 --- a/core/tests/ui/mobile/checkout-experience.spec.ts +++ b/core/tests/ui/mobile/checkout-experience.spec.ts @@ -20,7 +20,7 @@ test('Checkout experience on ios mobile', async ({ page }) => { await page.getByRole('link', { name: 'Cart Items 1' }).click(); await expect(page.getByRole('heading', { level: 1, name: 'Your cart' })).toBeVisible(); - await page.getByRole('link', { name: 'Proceed to checkout' }).click(); + await page.getByRole('button', { name: 'Proceed to checkout' }).click(); await page.getByLabel('Email').fill(faker.internet.email());