Skip to content

Commit

Permalink
Add currency selector
Browse files Browse the repository at this point in the history
  • Loading branch information
bookernath committed Jan 16, 2025
1 parent 072dd18 commit 6e87363
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 14 deletions.
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
9 changes: 8 additions & 1 deletion core/app/[locale]/(default)/compare/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { cn } from '~/lib/utils';

import { AddToCart } from './_components/add-to-cart';
import { AddToCartFragment } from './_components/add-to-cart/fragment';
import { getPreferredCurrencyCode } from '~/lib/currency';

const MAX_COMPARE_LIMIT = 10;

Expand All @@ -38,7 +39,11 @@ 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 @@ -98,6 +103,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 @@ -107,6 +113,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 @@ -14,10 +14,11 @@ import { Subscribe } from '~/components/subscribe';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';

import { Slideshow } from './_components/slideshow';
import { getPreferredCurrencyCode } from '~/lib/currency';

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 @@ -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 @@ -10,6 +10,7 @@ import { FeaturedProductsCarouselFragment } from '~/components/featured-products

import { ProductSchemaFragment } from './_components/product-schema/fragment';
import { ProductViewedFragment } from './_components/product-viewed/fragment';
import { getPreferredCurrencyCode } from '~/lib/currency';

const MultipleChoiceFieldFragment = graphql(`
fragment MultipleChoiceFieldFragment on MultipleChoiceOption {
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 @@ -14,6 +14,7 @@ import { ProductSchema } from './_components/product-schema';
import { ProductViewed } from './_components/product-viewed';
import { Reviews } from './_components/reviews';
import { getProductData } from './page-data';
import { getPreferredCurrencyCode } from '~/lib/currency';

const getOptionValueIds = ({ searchParams }: { searchParams: Awaited<Props['searchParams']> }) => {
const { slug, ...options } = searchParams;
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
2 changes: 1 addition & 1 deletion core/client/fragments/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { graphql } from '../graphql';

export const PricingFragment = graphql(`
fragment PricingFragment on Product {
prices {
prices(currencyCode: $currencyCode) {
price {
value
currencyCode
Expand Down
37 changes: 37 additions & 0 deletions core/components/header/_actions/switch-currency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use server';

import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';
import { getTranslations } from 'next-intl/server';
import { z } from 'zod';

const currencySchema = z.object({
code: z.string().length(3).toUpperCase(),
});

export const switchCurrency = async (_prevState: SubmissionResult | null, payload: FormData) => {
const t = await getTranslations('Components.Header.Currency');

const submission = parseWithZod(payload, { schema: currencySchema });

if (submission.status !== 'success') {
return submission.reply({ formErrors: [t('invalidCurrency')] });
}

cookies().set({
name: 'currencyCode',
value: submission.value.code,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
});

await Promise.resolve();

revalidatePath('/');

return submission.reply({ resetForm: true });
};
7 changes: 7 additions & 0 deletions core/components/header/fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,12 @@ export const HeaderFragment = graphql(`
}
}
}
currencies(first: 10) {
edges {
node {
code
}
}
}
}
`);
33 changes: 27 additions & 6 deletions core/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { logoTransformer } from '~/data-transformers/logo-transformer';
import { routing } from '~/i18n/routing';

import { search } from './_actions/search';
import { switchCurrency } from './_actions/switch-currency';
import { switchLocale } from './_actions/switch-locale';
import { HeaderFragment } from './fragment';
import { getPreferredCurrencyCode } from '~/lib/currency';

const GetCartCountQuery = graphql(`
query GetCartCountQuery($cartId: String) {
Expand All @@ -35,6 +37,7 @@ const getLayoutData = cache(async () => {

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

Expand Down Expand Up @@ -80,12 +83,7 @@ const getCartCount = async () => {
document: GetCartCountQuery,
variables: { cartId },
customerAccessToken,
fetchOptions: {
cache: 'no-store',
next: {
tags: [TAGS.cart],
},
},
fetchOptions: { cache: 'no-store' },
});

if (!response.data.site.cart) {
Expand All @@ -95,15 +93,35 @@ const getCartCount = async () => {
return response.data.site.cart.lineItems.totalQuantity;
};

const getCurrencies = async () => {
const data = await getLayoutData();

if (!data.currencies?.edges) {
return [];
}

const currencies = data.currencies.edges
.map(({ node }) => ({
code: node.code,
}))

return currencies;
};

export const Header = async () => {
const t = await getTranslations('Components.Header');
const locale = await getLocale();
const currencyCode = await getPreferredCurrencyCode();

const locales = routing.locales.map((enabledLocales) => ({
id: enabledLocales,
label: enabledLocales.toLocaleUpperCase(),
}));

const currencies = await getCurrencies();
// todo handle default currency properly once added to API
const activeCurrencyCode = currencyCode ?? currencies[0]?.code;

return (
<HeaderSection
navigation={{
Expand All @@ -124,6 +142,9 @@ export const Header = async () => {
activeLocaleId: locale,
locales,
localeAction: switchLocale,
currencies,
activeCurrencyCode,
currencyAction: switchCurrency,
}}
/>
);
Expand Down
15 changes: 15 additions & 0 deletions core/lib/currency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use server';

import { cookies } from "next/headers";

export async function getPreferredCurrencyCode(): Promise<string | undefined> {
const cookieStore = await cookies();
const currencyCode = cookieStore.get('currencyCode')?.value;

return currencyCode;
}

export async function setPreferredCurrencyCode(currencyCode: string): Promise<void> {
const cookieStore = await cookies();
cookieStore.set('currencyCode', currencyCode);
}
Loading

0 comments on commit 6e87363

Please sign in to comment.