Skip to content

Commit 6e87363

Browse files
committed
Add currency selector
1 parent 072dd18 commit 6e87363

File tree

13 files changed

+192
-14
lines changed

13 files changed

+192
-14
lines changed

core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { PaginationFragment } from '~/client/fragments/pagination';
88
import { graphql, VariablesOf } from '~/client/graphql';
99
import { revalidate } from '~/client/revalidate-target';
1010
import { ProductCardFragment } from '~/components/product-card/fragment';
11+
import { getPreferredCurrencyCode } from '~/lib/currency';
1112

1213
const GetProductSearchResultsQuery = graphql(
1314
`
@@ -18,6 +19,7 @@ const GetProductSearchResultsQuery = graphql(
1819
$before: String
1920
$filters: SearchProductsFiltersInput!
2021
$sort: SearchProductsSortInput
22+
$currencyCode: currencyCode
2123
) {
2224
site {
2325
search {
@@ -168,12 +170,13 @@ interface ProductSearch {
168170
const getProductSearchResults = cache(
169171
async ({ limit = 9, after, before, sort, filters }: ProductSearch) => {
170172
const customerAccessToken = await getSessionCustomerAccessToken();
173+
const currencyCode = await getPreferredCurrencyCode();
171174
const filterArgs = { filters, sort };
172175
const paginationArgs = before ? { last: limit, before } : { first: limit, after };
173176

174177
const response = await client.fetch({
175178
document: GetProductSearchResultsQuery,
176-
variables: { ...filterArgs, ...paginationArgs },
179+
variables: { ...filterArgs, ...paginationArgs, currencyCode },
177180
customerAccessToken,
178181
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate: 300 } },
179182
});

core/app/[locale]/(default)/compare/page.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { cn } from '~/lib/utils';
1616

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

2021
const MAX_COMPARE_LIMIT = 10;
2122

@@ -38,7 +39,11 @@ const CompareParamsSchema = z.object({
3839

3940
const ComparePageQuery = graphql(
4041
`
41-
query ComparePageQuery($entityIds: [Int!], $first: Int) {
42+
query ComparePageQuery(
43+
$entityIds: [Int!],
44+
$first: Int,
45+
$currencyCode: currencyCode
46+
) {
4247
site {
4348
products(entityIds: $entityIds, first: $first) {
4449
edges {
@@ -98,6 +103,7 @@ export default async function Compare(props: Props) {
98103
const t = await getTranslations('Compare');
99104
const format = await getFormatter();
100105
const customerAccessToken = await getSessionCustomerAccessToken();
106+
const currencyCode = await getPreferredCurrencyCode();
101107

102108
const parsed = CompareParamsSchema.parse(searchParams);
103109
const productIds = parsed.ids?.filter((id) => !Number.isNaN(id));
@@ -107,6 +113,7 @@ export default async function Compare(props: Props) {
107113
variables: {
108114
entityIds: productIds ?? [],
109115
first: productIds?.length ? MAX_COMPARE_LIMIT : 0,
116+
currencyCode,
110117
},
111118
customerAccessToken,
112119
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },

core/app/[locale]/(default)/page.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import { Subscribe } from '~/components/subscribe';
1414
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
1515

1616
import { Slideshow } from './_components/slideshow';
17+
import { getPreferredCurrencyCode } from '~/lib/currency';
1718

1819
const HomePageQuery = graphql(
1920
`
20-
query HomePageQuery {
21+
query HomePageQuery($currencyCode: currencyCode) {
2122
site {
2223
featuredProducts(first: 12) {
2324
edges {
@@ -41,10 +42,11 @@ const HomePageQuery = graphql(
4142

4243
const getPageData = cache(async () => {
4344
const customerAccessToken = await getSessionCustomerAccessToken();
44-
45+
const currencyCode = await getPreferredCurrencyCode();
4546
const { data } = await client.fetch({
4647
document: HomePageQuery,
4748
customerAccessToken,
49+
variables: { currencyCode },
4850
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
4951
});
5052

core/app/[locale]/(default)/product/[slug]/_components/product-schema/fragment.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const ProductSchemaFragment = graphql(`
1919
defaultImage {
2020
url: urlTemplate(lossy: true)
2121
}
22-
prices {
22+
prices(currencyCode: $currencyCode) {
2323
price {
2424
value
2525
currencyCode

core/app/[locale]/(default)/product/[slug]/page-data.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { FeaturedProductsCarouselFragment } from '~/components/featured-products
1010

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

1415
const MultipleChoiceFieldFragment = graphql(`
1516
fragment MultipleChoiceFieldFragment on MultipleChoiceOption {
@@ -211,6 +212,7 @@ const ProductPageQuery = graphql(
211212
$entityId: Int!
212213
$optionValueIds: [OptionValueId!]
213214
$useDefaultOptionSelections: Boolean
215+
$currencyCode: currencyCode
214216
) {
215217
site {
216218
product(
@@ -254,10 +256,11 @@ type Variables = VariablesOf<typeof ProductPageQuery>;
254256

255257
export const getProductData = cache(async (variables: Variables) => {
256258
const customerAccessToken = await getSessionCustomerAccessToken();
259+
const currencyCode = await getPreferredCurrencyCode();
257260

258261
const { data } = await client.fetch({
259262
document: ProductPageQuery,
260-
variables,
263+
variables: { ...variables, currencyCode },
261264
customerAccessToken,
262265
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
263266
});

core/app/[locale]/(default)/product/[slug]/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ProductSchema } from './_components/product-schema';
1414
import { ProductViewed } from './_components/product-viewed';
1515
import { Reviews } from './_components/reviews';
1616
import { getProductData } from './page-data';
17+
import { getPreferredCurrencyCode } from '~/lib/currency';
1718

1819
const getOptionValueIds = ({ searchParams }: { searchParams: Awaited<Props['searchParams']> }) => {
1920
const { slug, ...options } = searchParams;
@@ -204,6 +205,7 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
204205
export default async function Product(props: Props) {
205206
const searchParams = await props.searchParams;
206207
const params = await props.params;
208+
const currencyCode = await getPreferredCurrencyCode();
207209

208210
const { locale, slug } = params;
209211

@@ -219,6 +221,7 @@ export default async function Product(props: Props) {
219221
entityId: productId,
220222
optionValueIds,
221223
useDefaultOptionSelections: true,
224+
currencyCode,
222225
});
223226

224227
return (

core/app/[locale]/not-found.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { Footer } from '~/components/footer/footer';
1111
import { Header } from '~/components/header';
1212
import { ProductCardFragment } from '~/components/product-card/fragment';
1313
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
14+
import { getPreferredCurrencyCode } from '~/lib/currency';
1415

1516
const NotFoundQuery = graphql(
1617
`
17-
query NotFoundQuery {
18+
query NotFoundQuery($currencyCode: currencyCode) {
1819
site {
1920
featuredProducts(first: 10) {
2021
edges {
@@ -31,8 +32,10 @@ const NotFoundQuery = graphql(
3132

3233
async function getFeaturedProducts(): Promise<CarouselProduct[]> {
3334
const format = await getFormatter();
35+
const currencyCode = await getPreferredCurrencyCode();
3436
const { data } = await client.fetch({
3537
document: NotFoundQuery,
38+
variables: { currencyCode },
3639
fetchOptions: { next: { revalidate } },
3740
});
3841

core/client/fragments/pricing.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { graphql } from '../graphql';
22

33
export const PricingFragment = graphql(`
44
fragment PricingFragment on Product {
5-
prices {
5+
prices(currencyCode: $currencyCode) {
66
price {
77
value
88
currencyCode
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use server';
2+
3+
import { SubmissionResult } from '@conform-to/react';
4+
import { parseWithZod } from '@conform-to/zod';
5+
import { revalidatePath } from 'next/cache';
6+
import { cookies } from 'next/headers';
7+
import { getTranslations } from 'next-intl/server';
8+
import { z } from 'zod';
9+
10+
const currencySchema = z.object({
11+
code: z.string().length(3).toUpperCase(),
12+
});
13+
14+
export const switchCurrency = async (_prevState: SubmissionResult | null, payload: FormData) => {
15+
const t = await getTranslations('Components.Header.Currency');
16+
17+
const submission = parseWithZod(payload, { schema: currencySchema });
18+
19+
if (submission.status !== 'success') {
20+
return submission.reply({ formErrors: [t('invalidCurrency')] });
21+
}
22+
23+
cookies().set({
24+
name: 'currencyCode',
25+
value: submission.value.code,
26+
httpOnly: true,
27+
secure: process.env.NODE_ENV === 'production',
28+
sameSite: 'lax',
29+
path: '/',
30+
});
31+
32+
await Promise.resolve();
33+
34+
revalidatePath('/');
35+
36+
return submission.reply({ resetForm: true });
37+
};

core/components/header/fragment.ts

+7
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,12 @@ export const HeaderFragment = graphql(`
2929
}
3030
}
3131
}
32+
currencies(first: 10) {
33+
edges {
34+
node {
35+
code
36+
}
37+
}
38+
}
3239
}
3340
`);

core/components/header/index.tsx

+27-6
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import { logoTransformer } from '~/data-transformers/logo-transformer';
1414
import { routing } from '~/i18n/routing';
1515

1616
import { search } from './_actions/search';
17+
import { switchCurrency } from './_actions/switch-currency';
1718
import { switchLocale } from './_actions/switch-locale';
1819
import { HeaderFragment } from './fragment';
20+
import { getPreferredCurrencyCode } from '~/lib/currency';
1921

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

3638
const { data: response } = await client.fetch({
3739
document: LayoutQuery,
40+
customerAccessToken,
3841
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
3942
});
4043

@@ -80,12 +83,7 @@ const getCartCount = async () => {
8083
document: GetCartCountQuery,
8184
variables: { cartId },
8285
customerAccessToken,
83-
fetchOptions: {
84-
cache: 'no-store',
85-
next: {
86-
tags: [TAGS.cart],
87-
},
88-
},
86+
fetchOptions: { cache: 'no-store' },
8987
});
9088

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

96+
const getCurrencies = async () => {
97+
const data = await getLayoutData();
98+
99+
if (!data.currencies?.edges) {
100+
return [];
101+
}
102+
103+
const currencies = data.currencies.edges
104+
.map(({ node }) => ({
105+
code: node.code,
106+
}))
107+
108+
return currencies;
109+
};
110+
98111
export const Header = async () => {
99112
const t = await getTranslations('Components.Header');
100113
const locale = await getLocale();
114+
const currencyCode = await getPreferredCurrencyCode();
101115

102116
const locales = routing.locales.map((enabledLocales) => ({
103117
id: enabledLocales,
104118
label: enabledLocales.toLocaleUpperCase(),
105119
}));
106120

121+
const currencies = await getCurrencies();
122+
// todo handle default currency properly once added to API
123+
const activeCurrencyCode = currencyCode ?? currencies[0]?.code;
124+
107125
return (
108126
<HeaderSection
109127
navigation={{
@@ -124,6 +142,9 @@ export const Header = async () => {
124142
activeLocaleId: locale,
125143
locales,
126144
localeAction: switchLocale,
145+
currencies,
146+
activeCurrencyCode,
147+
currencyAction: switchCurrency,
127148
}}
128149
/>
129150
);

core/lib/currency.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use server';
2+
3+
import { cookies } from "next/headers";
4+
5+
export async function getPreferredCurrencyCode(): Promise<string | undefined> {
6+
const cookieStore = await cookies();
7+
const currencyCode = cookieStore.get('currencyCode')?.value;
8+
9+
return currencyCode;
10+
}
11+
12+
export async function setPreferredCurrencyCode(currencyCode: string): Promise<void> {
13+
const cookieStore = await cookies();
14+
cookieStore.set('currencyCode', currencyCode);
15+
}

0 commit comments

Comments
 (0)