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

feat(core): Add currency selector to header #1912

Merged
merged 6 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/gold-ducks-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Add currency selector to header
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PaginationFragment } from '~/client/fragments/pagination';
import { graphql, VariablesOf } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { ProductCardFragment } from '~/components/product-card/fragment';
import { getPreferredCurrencyCode } from '~/lib/currency';

const GetProductSearchResultsQuery = graphql(
`
Expand All @@ -18,6 +19,7 @@ const GetProductSearchResultsQuery = graphql(
$before: String
$filters: SearchProductsFiltersInput!
$sort: SearchProductsSortInput
$currencyCode: currencyCode
) {
site {
search {
Expand Down Expand Up @@ -168,12 +170,13 @@ interface ProductSearch {
const getProductSearchResults = cache(
async ({ limit = 9, after, before, sort, filters }: ProductSearch) => {
const customerAccessToken = await getSessionCustomerAccessToken();
const currencyCode = await getPreferredCurrencyCode();
const filterArgs = { filters, sort };
const paginationArgs = before ? { last: limit, before } : { first: limit, after };

const response = await client.fetch({
document: GetProductSearchResultsQuery,
variables: { ...filterArgs, ...paginationArgs },
variables: { ...filterArgs, ...paginationArgs, currencyCode },
customerAccessToken,
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate: 300 } },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { cookies } from 'next/headers';
import { getLocale, getTranslations } from 'next-intl/server';
import { z } from 'zod';

import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { redirect } from '~/i18n/routing';
import { getCartId } from '~/lib/cart';

const CheckoutRedirectMutation = graphql(`
mutation CheckoutRedirectMutation($cartId: String!) {
Expand All @@ -30,13 +30,12 @@ export const redirectToCheckout = async (
): Promise<SubmissionResult | null> => {
const locale = await getLocale();
const t = await getTranslations('Cart.Errors');
const cookieStore = await cookies();

const customerAccessToken = await getSessionCustomerAccessToken();

const submission = parseWithZod(formData, { schema: z.object({}) });

const cartId = cookieStore.get('cartId')?.value;
const cartId = await getCartId();

if (!cartId) {
return submission.reply({ formErrors: [t('cartNotFound')] });
Expand Down
7 changes: 3 additions & 4 deletions core/app/[locale]/(default)/cart/_actions/remove-item.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use server';

import { unstable_expireTag } from 'next/cache';
import { cookies } from 'next/headers';
import { getTranslations } from 'next-intl/server';

import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql, VariablesOf } from '~/client/graphql';
import { TAGS } from '~/client/tags';
import { clearCartId, getCartId } from '~/lib/cart';

const DeleteCartLineItemMutation = graphql(`
mutation DeleteCartLineItemMutation($input: DeleteCartLineItemInput!) {
Expand All @@ -31,8 +31,7 @@ export async function removeItem({

const customerAccessToken = await getSessionCustomerAccessToken();

const cookieStore = await cookies();
const cartId = cookieStore.get('cartId')?.value;
const cartId = await getCartId();

if (!cartId) {
throw new Error(t('cartNotFound'));
Expand Down Expand Up @@ -60,7 +59,7 @@ export async function removeItem({
// so we need to remove the cartId cookie
// TODO: We need to figure out if it actually failed.
if (!cart) {
cookieStore.delete('cartId');
await clearCartId();
}

unstable_expireTag(TAGS.cart);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use server';

import { unstable_expirePath } from 'next/cache';
import { cookies } from 'next/headers';
import { getTranslations } from 'next-intl/server';

import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql, VariablesOf } from '~/client/graphql';
import { getCartId } from '~/lib/cart';

import { removeItem } from './remove-item';

Expand Down Expand Up @@ -44,8 +44,7 @@ export const updateQuantity = async ({

const customerAccessToken = await getSessionCustomerAccessToken();

const cookieStore = await cookies();
const cartId = cookieStore.get('cartId')?.value;
const cartId = await getCartId();

if (!cartId) {
throw new Error(t('cartNotFound'));
Expand Down
1 change: 1 addition & 0 deletions core/app/[locale]/(default)/cart/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const CartPageQuery = graphql(
site {
cart(entityId: $cartId) {
entityId
version
currencyCode
lineItems {
physicalItems {
Expand Down
5 changes: 3 additions & 2 deletions core/app/[locale]/(default)/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Metadata } from 'next';
import { cookies } from 'next/headers';
import { getFormatter, getTranslations } from 'next-intl/server';

import { Cart as CartComponent, CartEmptyState } from '@/vibes/soul/sections/cart';
import { getCartId } from '~/lib/cart';

import { redirectToCheckout } from './_actions/redirect-to-checkout';
import { updateLineItem } from './_actions/update-line-item';
Expand All @@ -20,7 +20,7 @@ export async function generateMetadata(): Promise<Metadata> {
export default async function Cart() {
const t = await getTranslations('Cart');
const format = await getFormatter();
const cartId = (await cookies()).get('cartId')?.value;
const cartId = await getCartId();

if (!cartId) {
return (
Expand Down Expand Up @@ -98,6 +98,7 @@ export default async function Cart() {
cta: { label: t('Empty.cta'), href: '/shop-all' },
}}
incrementLineItemLabel={t('increment')}
key={`${cart.entityId}-${cart.version}`}
lineItemAction={updateLineItem}
lineItems={formattedLineItems}
summary={{
Expand Down
14 changes: 3 additions & 11 deletions core/app/[locale]/(default)/compare/_actions/add-to-cart.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use server';

import { unstable_expireTag } from 'next/cache';
import { cookies } from 'next/headers';

import {
addCartLineItem,
Expand All @@ -10,12 +9,12 @@ import {
import { assertCreateCartErrors, createCart } from '~/client/mutations/create-cart';
import { getCart } from '~/client/queries/get-cart';
import { TAGS } from '~/client/tags';
import { getCartId, setCartId } from '~/lib/cart';

export const addToCart = async (data: FormData) => {
const productEntityId = Number(data.get('product_id'));

const cookieStore = await cookies();
const cartId = cookieStore.get('cartId')?.value;
const cartId = await getCartId();

let cart;

Expand Down Expand Up @@ -55,14 +54,7 @@ export const addToCart = async (data: FormData) => {
return { status: 'error', error: 'Failed to add product to cart.' };
}

cookieStore.set({
name: 'cartId',
value: cart.entityId,
httpOnly: true,
sameSite: 'lax',
secure: true,
path: '/',
});
await setCartId(cart.entityId);

unstable_expireTag(TAGS.cart);

Expand Down
5 changes: 4 additions & 1 deletion core/app/[locale]/(default)/compare/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Link } from '~/components/link';
import { SearchForm } from '~/components/search-form';
import { Button } from '~/components/ui/button';
import { Rating } from '~/components/ui/rating';
import { getPreferredCurrencyCode } from '~/lib/currency';
import { cn } from '~/lib/utils';

import { AddToCart } from './_components/add-to-cart';
Expand All @@ -39,7 +40,7 @@ const CompareParamsSchema = z.object({

const ComparePageQuery = graphql(
`
query ComparePageQuery($entityIds: [Int!], $first: Int) {
query ComparePageQuery($entityIds: [Int!], $first: Int, $currencyCode: currencyCode) {
site {
products(entityIds: $entityIds, first: $first) {
edges {
Expand Down Expand Up @@ -99,6 +100,7 @@ export default async function Compare(props: Props) {
const t = await getTranslations('Compare');
const format = await getFormatter();
const customerAccessToken = await getSessionCustomerAccessToken();
const currencyCode = await getPreferredCurrencyCode();

const parsed = CompareParamsSchema.parse(searchParams);
const productIds = parsed.ids?.filter((id) => !Number.isNaN(id));
Expand All @@ -108,6 +110,7 @@ export default async function Compare(props: Props) {
variables: {
entityIds: productIds ?? [],
first: productIds?.length ? MAX_COMPARE_LIMIT : 0,
currencyCode,
},
customerAccessToken,
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
Expand Down
6 changes: 4 additions & 2 deletions core/app/[locale]/(default)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { FeaturedProductsCarouselFragment } from '~/components/featured-products
import { FeaturedProductsListFragment } from '~/components/featured-products-list/fragment';
import { Subscribe } from '~/components/subscribe';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
import { getPreferredCurrencyCode } from '~/lib/currency';

import { Slideshow } from './_components/slideshow';

const HomePageQuery = graphql(
`
query HomePageQuery {
query HomePageQuery($currencyCode: currencyCode) {
site {
featuredProducts(first: 12) {
edges {
Expand All @@ -41,10 +42,11 @@ const HomePageQuery = graphql(

const getPageData = cache(async () => {
const customerAccessToken = await getSessionCustomerAccessToken();

const currencyCode = await getPreferredCurrencyCode();
const { data } = await client.fetch({
document: HomePageQuery,
customerAccessToken,
variables: { currencyCode },
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { unstable_expireTag } from 'next/cache';
import { cookies } from 'next/headers';
import { getTranslations } from 'next-intl/server';
import { ReactNode } from 'react';

Expand All @@ -15,6 +14,7 @@ import { createCart } from '~/client/mutations/create-cart';
import { getCart } from '~/client/queries/get-cart';
import { TAGS } from '~/client/tags';
import { Link } from '~/components/link';
import { getCartId, setCartId } from '~/lib/cart';

type CartSelectedOptionsInput = ReturnType<typeof graphql.scalar<'CartSelectedOptionsInput'>>;

Expand Down Expand Up @@ -43,8 +43,7 @@ export const addToCart = async (
const productEntityId = Number(submission.value.id);
const quantity = Number(submission.value.quantity);

const cookieStore = await cookies();
const cartId = cookieStore.get('cartId')?.value;
const cartId = await getCartId();

let cart;

Expand Down Expand Up @@ -217,14 +216,7 @@ export const addToCart = async (
};
}

cookieStore.set({
name: 'cartId',
value: cart.entityId,
httpOnly: true,
sameSite: 'lax',
secure: true,
path: '/',
});
await setCartId(cart.entityId);

unstable_expireTag(TAGS.cart);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const ProductSchemaFragment = graphql(`
defaultImage {
url: urlTemplate(lossy: true)
}
prices {
prices(currencyCode: $currencyCode) {
price {
value
currencyCode
Expand Down
5 changes: 4 additions & 1 deletion core/app/[locale]/(default)/product/[slug]/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PricingFragment } from '~/client/fragments/pricing';
import { graphql, VariablesOf } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { FeaturedProductsCarouselFragment } from '~/components/featured-products-carousel/fragment';
import { getPreferredCurrencyCode } from '~/lib/currency';

import { ProductSchemaFragment } from './_components/product-schema/fragment';
import { ProductViewedFragment } from './_components/product-viewed/fragment';
Expand Down Expand Up @@ -211,6 +212,7 @@ const ProductPageQuery = graphql(
$entityId: Int!
$optionValueIds: [OptionValueId!]
$useDefaultOptionSelections: Boolean
$currencyCode: currencyCode
) {
site {
product(
Expand Down Expand Up @@ -254,10 +256,11 @@ type Variables = VariablesOf<typeof ProductPageQuery>;

export const getProductData = cache(async (variables: Variables) => {
const customerAccessToken = await getSessionCustomerAccessToken();
const currencyCode = await getPreferredCurrencyCode();

const { data } = await client.fetch({
document: ProductPageQuery,
variables,
variables: { ...variables, currencyCode },
customerAccessToken,
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
});
Expand Down
3 changes: 3 additions & 0 deletions core/app/[locale]/(default)/product/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ProductDetail } from '@/vibes/soul/sections/product-detail';
import { pricesTransformer } from '~/data-transformers/prices-transformer';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
import { productOptionsTransformer } from '~/data-transformers/product-options-transformer';
import { getPreferredCurrencyCode } from '~/lib/currency';

import { addToCart } from './_actions/add-to-cart';
import { ProductSchema } from './_components/product-schema';
Expand Down Expand Up @@ -204,6 +205,7 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
export default async function Product(props: Props) {
const searchParams = await props.searchParams;
const params = await props.params;
const currencyCode = await getPreferredCurrencyCode();

const { locale, slug } = params;

Expand All @@ -219,6 +221,7 @@ export default async function Product(props: Props) {
entityId: productId,
optionValueIds,
useDefaultOptionSelections: true,
currencyCode,
});

return (
Expand Down
5 changes: 4 additions & 1 deletion core/app/[locale]/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { Footer } from '~/components/footer/footer';
import { Header } from '~/components/header';
import { ProductCardFragment } from '~/components/product-card/fragment';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
import { getPreferredCurrencyCode } from '~/lib/currency';

const NotFoundQuery = graphql(
`
query NotFoundQuery {
query NotFoundQuery($currencyCode: currencyCode) {
site {
featuredProducts(first: 10) {
edges {
Expand All @@ -31,8 +32,10 @@ const NotFoundQuery = graphql(

async function getFeaturedProducts(): Promise<CarouselProduct[]> {
const format = await getFormatter();
const currencyCode = await getPreferredCurrencyCode();
const { data } = await client.fetch({
document: NotFoundQuery,
variables: { currencyCode },
fetchOptions: { next: { revalidate } },
});

Expand Down
Loading
Loading