Skip to content

Commit

Permalink
Add currency selector
Browse files Browse the repository at this point in the history
Co-Authored-By: Chancellor Clark <[email protected]>
  • Loading branch information
bookernath and chanceaclark committed Jan 16, 2025
1 parent 072dd18 commit 11e4a96
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 16 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
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 @@ -12,6 +12,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 @@ -38,7 +39,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 @@ -98,6 +99,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 +109,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 @@ -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
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
31 changes: 31 additions & 0 deletions core/components/header/_actions/switch-currency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use server';

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

import { setPreferredCurrencyCode } from '~/lib/currency';

import { currencyCodeSchema } from '../schema';

const currencySwitchSchema = z.object({
code: currencyCodeSchema,
});

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

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

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

await setPreferredCurrencyCode(submission.value.code);

revalidatePath('/');

return submission.reply({ resetForm: true });
};
14 changes: 13 additions & 1 deletion core/components/header/fragment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { graphql } from '~/client/graphql';
import { FragmentOf, graphql } from '~/client/graphql';

export const HeaderFragment = graphql(`
fragment HeaderFragment on Site {
Expand Down Expand Up @@ -29,5 +29,17 @@ export const HeaderFragment = graphql(`
}
}
}
currencies(first: 10) {
edges {
node {
code
}
}
}
}
`);

export type Currency = NonNullable<
NonNullable<FragmentOf<typeof HeaderFragment>>['currencies']['edges']
>[number]['node'];
export type CurrencyCode = Currency['code'];
33 changes: 26 additions & 7 deletions core/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql, readFragment } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { TAGS } from '~/client/tags';
import { logoTransformer } from '~/data-transformers/logo-transformer';
import { routing } from '~/i18n/routing';
import { getPreferredCurrencyCode } from '~/lib/currency';

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

Expand All @@ -35,6 +36,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 +82,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 +92,34 @@ 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 +140,9 @@ export const Header = async () => {
activeLocaleId: locale,
locales,
localeAction: switchLocale,
currencies,
activeCurrencyCode,
currencyAction: switchCurrency,
}}
/>
);
Expand Down
11 changes: 11 additions & 0 deletions core/components/header/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { z } from 'zod';

import type { CurrencyCode } from './fragment';

export const currencyCodeSchema = z
.string()
.length(3)
.toUpperCase()
.refine((val): val is CurrencyCode => /^[A-Z]{3}$/.test(val), {
message: 'Must be a valid currency code',
});
30 changes: 30 additions & 0 deletions core/lib/currency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use server';

import { cookies } from 'next/headers';

import type { CurrencyCode } from '~/components/header/fragment';
import { currencyCodeSchema } from '~/components/header/schema';

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

if (!currencyCode) {
return undefined;
}

const result = currencyCodeSchema.safeParse(currencyCode);

return result.success ? result.data : undefined;
}

export async function setPreferredCurrencyCode(currencyCode: CurrencyCode): Promise<void> {
const cookieStore = await cookies();

cookieStore.set('currencyCode', currencyCode, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
});
}
3 changes: 3 additions & 0 deletions core/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,9 @@
"login": "Login",
"logout": "Log out"
},
"Currency": {
"invalidCurrency": "Invalid currency"
},
"Locale": {
"invalidLocale": "Invalid locale"
},
Expand Down
Loading

0 comments on commit 11e4a96

Please sign in to comment.