diff --git a/.gitignore b/.gitignore index a0caaaaac9..959b63145c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ coverage/ .history .unlighthouse .bigcommerce +.worktrees diff --git a/core/app/[locale]/(default)/(auth)/change-password/page-data.ts b/core/app/[locale]/(default)/(auth)/change-password/page-data.ts index 43a72f2d3a..e6b1f6ed83 100644 --- a/core/app/[locale]/(default)/(auth)/change-password/page-data.ts +++ b/core/app/[locale]/(default)/(auth)/change-password/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -24,10 +25,14 @@ const ChangePasswordQuery = graphql(` } `); -export const getChangePasswordQuery = cache(async () => { +async function getCachedChangePasswordQuery(locale: string) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: ChangePasswordQuery, - fetchOptions: { next: { revalidate } }, + locale, }); const passwordComplexitySettings = @@ -36,4 +41,8 @@ export const getChangePasswordQuery = cache(async () => { return { passwordComplexitySettings, }; +} + +export const getChangePasswordQuery = cache(async (locale: string) => { + return getCachedChangePasswordQuery(locale); }); diff --git a/core/app/[locale]/(default)/(auth)/change-password/page.tsx b/core/app/[locale]/(default)/(auth)/change-password/page.tsx index 1e7e251a6a..70de8e2e8b 100644 --- a/core/app/[locale]/(default)/(auth)/change-password/page.tsx +++ b/core/app/[locale]/(default)/(auth)/change-password/page.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/jsx-no-bind */ import { Metadata } from 'next'; import { getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { ResetPasswordSection } from '@/vibes/soul/sections/reset-password-section'; import { getChangePasswordQuery } from '~/app/[locale]/(default)/(auth)/change-password/page-data'; @@ -26,19 +27,20 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function ChangePassword({ params, searchParams }: Props) { - const { locale } = await params; - - setRequestLocale(locale); +interface ContentProps { + searchParams: Promise<{ c?: string; t?: string }>; + locale: string; +} +async function ChangePasswordContent({ searchParams, locale }: ContentProps) { const { c: customerEntityId, t: token } = await searchParams; - const t = await getTranslations('Auth.ChangePassword'); + const t = await getTranslations({ locale, namespace: 'Auth.ChangePassword' }); if (!customerEntityId || !token) { return redirect({ href: '/login', locale }); } - const { passwordComplexitySettings } = await getChangePasswordQuery(); + const { passwordComplexitySettings } = await getChangePasswordQuery(locale); return ( ); } + +export default async function ChangePassword({ params, searchParams }: Props) { + const { locale } = await params; + + setRequestLocale(locale); + + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/(auth)/layout.tsx b/core/app/[locale]/(default)/(auth)/layout.tsx index cdccae9970..b56d804dfa 100644 --- a/core/app/[locale]/(default)/(auth)/layout.tsx +++ b/core/app/[locale]/(default)/(auth)/layout.tsx @@ -1,4 +1,5 @@ -import { PropsWithChildren } from 'react'; +import { setRequestLocale } from 'next-intl/server'; +import { PropsWithChildren, Suspense } from 'react'; import { isLoggedIn } from '~/auth'; import { redirect } from '~/i18n/routing'; @@ -7,9 +8,8 @@ interface Props extends PropsWithChildren { params: Promise<{ locale: string }>; } -export default async function Layout({ children, params }: Props) { +async function AuthCheck({ locale, children }: { locale: string; children: React.ReactNode }) { const loggedIn = await isLoggedIn(); - const { locale } = await params; if (loggedIn) { redirect({ href: '/account/orders', locale }); @@ -17,3 +17,15 @@ export default async function Layout({ children, params }: Props) { return children; } + +export default async function Layout({ children, params }: Props) { + const { locale } = await params; + + setRequestLocale(locale); + + return ( + + {children} + + ); +} diff --git a/core/app/[locale]/(default)/(auth)/login/page.tsx b/core/app/[locale]/(default)/(auth)/login/page.tsx index dadb1eeb5c..80eeae2a4c 100644 --- a/core/app/[locale]/(default)/(auth)/login/page.tsx +++ b/core/app/[locale]/(default)/(auth)/login/page.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/jsx-no-bind */ import { Metadata } from 'next'; import { getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { ButtonLink } from '@/vibes/soul/primitives/button-link'; import { SignInSection } from '@/vibes/soul/sections/sign-in-section'; @@ -27,13 +28,16 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function Login({ params, searchParams }: Props) { - const { locale } = await params; +async function LoginContent({ + searchParams, + locale, +}: { + searchParams: Props['searchParams']; + locale: string; +}) { const { redirectTo = '/account/orders', error } = await searchParams; - setRequestLocale(locale); - - const t = await getTranslations('Auth.Login'); + const t = await getTranslations({ locale, namespace: 'Auth.Login' }); const vanityUrl = buildConfig.get('urls').vanityUrl; const redirectUrl = new URL(redirectTo, vanityUrl); @@ -75,3 +79,15 @@ export default async function Login({ params, searchParams }: Props) { ); } + +export default async function Login({ params, searchParams }: Props) { + const { locale } = await params; + + setRequestLocale(locale); + + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts b/core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts index 16f806330a..e7e092e828 100644 --- a/core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts +++ b/core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts @@ -35,5 +35,3 @@ export async function GET(_: Request, { params }: { params: Promise<{ token: str redirect(`/login?error=InvalidToken`); } } - -export const dynamic = 'force-dynamic'; diff --git a/core/app/[locale]/(default)/(auth)/register/page-data.ts b/core/app/[locale]/(default)/(auth)/register/page-data.ts index 461237ffa1..59f09b61c0 100644 --- a/core/app/[locale]/(default)/(auth)/register/page-data.ts +++ b/core/app/[locale]/(default)/(auth)/register/page-data.ts @@ -1,8 +1,9 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; +import { revalidate } from '~/client/revalidate-target'; import { FormFieldsFragment } from '~/data-transformers/form-field-transformer/fragment'; const RegisterCustomerQuery = graphql( @@ -61,8 +62,10 @@ interface Props { }; } -export const getRegisterCustomerQuery = cache(async ({ address, customer }: Props) => { - const customerAccessToken = await getSessionCustomerAccessToken(); +async function getCachedRegisterCustomerQuery(locale: string, { address, customer }: Props) { + 'use cache'; + + cacheLife({ revalidate }); const response = await client.fetch({ document: RegisterCustomerQuery, @@ -72,8 +75,7 @@ export const getRegisterCustomerQuery = cache(async ({ address, customer }: Prop customerFilters: customer?.filters, customerSortBy: customer?.sortBy, }, - fetchOptions: { cache: 'no-store' }, - customerAccessToken, + locale, }); const addressFields = response.data.site.settings?.formFields.shippingAddress; @@ -92,4 +94,8 @@ export const getRegisterCustomerQuery = cache(async ({ address, customer }: Prop countries, passwordComplexitySettings, }; +} + +export const getRegisterCustomerQuery = cache(async (locale: string, props: Props) => { + return getCachedRegisterCustomerQuery(locale, props); }); diff --git a/core/app/[locale]/(default)/(auth)/register/page.tsx b/core/app/[locale]/(default)/(auth)/register/page.tsx index bf8ebf1357..ea5fdc8a91 100644 --- a/core/app/[locale]/(default)/(auth)/register/page.tsx +++ b/core/app/[locale]/(default)/(auth)/register/page.tsx @@ -51,7 +51,7 @@ export default async function Register({ params }: Props) { const t = await getTranslations('Auth.Register'); - const registerCustomerData = await getRegisterCustomerQuery({ + const registerCustomerData = await getRegisterCustomerQuery(locale, { address: { sortBy: 'SORT_ORDER' }, customer: { sortBy: 'SORT_ORDER' }, }); diff --git a/core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts b/core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts index 9bb605d215..eef464748d 100644 --- a/core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts +++ b/core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -38,13 +39,49 @@ const BrandPageQuery = graphql(` } `); -export const getBrandPageData = cache(async (entityId: number, customerAccessToken?: string) => { +// Don't cache metadata +export const getBrandPageMetadata = cache( + async (locale: string, entityId: number, customerAccessToken?: string) => { + const response = await client.fetch({ + document: BrandPageQuery, + variables: { entityId }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); + + return response.data.site; + }, +); + +async function getCachedBrandPageData(locale: string, entityId: number) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: BrandPageQuery, variables: { entityId }, - customerAccessToken, - fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, + locale, }); return response.data.site; -}); +} + +export const getBrandPageData = cache( + async (locale: string, entityId: number, customerAccessToken?: string) => { + if (customerAccessToken) { + const response = await client.fetch({ + document: BrandPageQuery, + variables: { entityId }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); + + return response.data.site; + } + + return getCachedBrandPageData(locale, entityId); + }, +); diff --git a/core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx b/core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx index 4278def31c..2a03eaddfc 100644 --- a/core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx +++ b/core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; import { createLoader, SearchParams } from 'nuqs/server'; -import { cache } from 'react'; +import { cache, Suspense } from 'react'; import { Streamable } from '@/vibes/soul/lib/streamable'; import { createCompareLoader } from '@/vibes/soul/primitives/compare-drawer/loader'; @@ -19,7 +19,7 @@ import { MAX_COMPARE_LIMIT } from '../../../compare/page-data'; import { getCompareProducts as getCompareProductsData } from '../../fetch-compare-products'; import { fetchFacetedSearch } from '../../fetch-faceted-search'; -import { getBrandPageData } from './page-data'; +import { getBrandPageData, getBrandPageMetadata } from './page-data'; const getCachedBrand = cache((brandId: string) => { return { @@ -30,9 +30,14 @@ const getCachedBrand = cache((brandId: string) => { const compareLoader = createCompareLoader(); const createBrandSearchParamsLoader = cache( - async (brandId: string, customerAccessToken?: string) => { + async (locale: string, brandId: string, customerAccessToken?: string) => { const cachedBrand = getCachedBrand(brandId); - const brandSearch = await fetchFacetedSearch(cachedBrand, undefined, customerAccessToken); + const brandSearch = await fetchFacetedSearch( + locale, + cachedBrand, + undefined, + customerAccessToken, + ); const brandFacets = brandSearch.facets.items.filter( (facet) => facet.__typename !== 'BrandSearchFilter', ); @@ -73,7 +78,7 @@ export async function generateMetadata(props: Props): Promise { const brandId = Number(slug); - const { brand } = await getBrandPageData(brandId, customerAccessToken); + const { brand } = await getBrandPageMetadata(locale, brandId, customerAccessToken); if (!brand) { return notFound(); @@ -89,7 +94,7 @@ export async function generateMetadata(props: Props): Promise { }; } -export default async function Brand(props: Props) { +async function BrandContent(props: Props) { const { locale, slug } = await props.params; const customerAccessToken = await getSessionCustomerAccessToken(); @@ -99,7 +104,7 @@ export default async function Brand(props: Props) { const brandId = Number(slug); - const { brand, settings } = await getBrandPageData(brandId, customerAccessToken); + const { brand, settings } = await getBrandPageData(locale, brandId, customerAccessToken); if (!brand) { return notFound(); @@ -114,10 +119,11 @@ export default async function Brand(props: Props) { const searchParams = await props.searchParams; const currencyCode = await getPreferredCurrencyCode(); - const loadSearchParams = await createBrandSearchParamsLoader(slug, customerAccessToken); + const loadSearchParams = await createBrandSearchParamsLoader(locale, slug, customerAccessToken); const parsedSearchParams = loadSearchParams?.(searchParams) ?? {}; const search = await fetchFacetedSearch( + locale, { ...searchParams, ...parsedSearchParams, @@ -162,10 +168,15 @@ export default async function Brand(props: Props) { const streamableFilters = Streamable.from(async () => { const searchParams = await props.searchParams; - const loadSearchParams = await createBrandSearchParamsLoader(slug, customerAccessToken); + const loadSearchParams = await createBrandSearchParamsLoader(locale, slug, customerAccessToken); const parsedSearchParams = loadSearchParams?.(searchParams) ?? {}; const cachedBrand = getCachedBrand(slug); - const categorySearch = await fetchFacetedSearch(cachedBrand, undefined, customerAccessToken); + const categorySearch = await fetchFacetedSearch( + locale, + cachedBrand, + undefined, + customerAccessToken, + ); const refinedSearch = await streamableFacetedSearch; const allFacets = categorySearch.facets.items.filter( @@ -195,7 +206,7 @@ export default async function Brand(props: Props) { const compareIds = { entityIds: compare ? compare.map((id: string) => Number(id)) : [] }; - const products = await getCompareProductsData(compareIds, customerAccessToken); + const products = await getCompareProductsData(locale, compareIds, customerAccessToken); return products.map((product) => ({ id: product.entityId.toString(), @@ -244,3 +255,11 @@ export default async function Brand(props: Props) { /> ); } + +export default function Brand(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts b/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts index 6c2c4633fe..fa644bf105 100644 --- a/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts +++ b/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -58,13 +59,49 @@ const CategoryPageQuery = graphql( [BreadcrumbsCategoryFragment], ); -export const getCategoryPageData = cache(async (entityId: number, customerAccessToken?: string) => { +// Don't cache metadata +export const getCategoryPageMetadata = cache( + async (locale: string, entityId: number, customerAccessToken?: string) => { + const response = await client.fetch({ + document: CategoryPageQuery, + variables: { entityId }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); + + return response.data.site; + }, +); + +async function getCachedCategoryPageData(locale: string, entityId: number) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: CategoryPageQuery, variables: { entityId }, - customerAccessToken, - fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, + locale, }); return response.data.site; -}); +} + +export const getCategoryPageData = cache( + async (locale: string, entityId: number, customerAccessToken?: string) => { + if (customerAccessToken) { + const response = await client.fetch({ + document: CategoryPageQuery, + variables: { entityId }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); + + return response.data.site; + } + + return getCachedCategoryPageData(locale, entityId); + }, +); diff --git a/core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx b/core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx index ee143281b5..1af63c9041 100644 --- a/core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx +++ b/core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx @@ -3,7 +3,7 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; import { createLoader, SearchParams } from 'nuqs/server'; -import { cache } from 'react'; +import { cache, Suspense } from 'react'; import { Stream, Streamable } from '@/vibes/soul/lib/streamable'; import { createCompareLoader } from '@/vibes/soul/primitives/compare-drawer/loader'; @@ -21,7 +21,7 @@ import { getCompareProducts } from '../../fetch-compare-products'; import { fetchFacetedSearch } from '../../fetch-faceted-search'; import { CategoryViewed } from './_components/category-viewed'; -import { getCategoryPageData } from './page-data'; +import { getCategoryPageData, getCategoryPageMetadata } from './page-data'; const getCachedCategory = cache((categoryId: number) => { return { @@ -32,9 +32,14 @@ const getCachedCategory = cache((categoryId: number) => { const compareLoader = createCompareLoader(); const createCategorySearchParamsLoader = cache( - async (categoryId: number, customerAccessToken?: string) => { + async (locale: string, categoryId: number, customerAccessToken?: string) => { const cachedCategory = getCachedCategory(categoryId); - const categorySearch = await fetchFacetedSearch(cachedCategory, undefined, customerAccessToken); + const categorySearch = await fetchFacetedSearch( + locale, + cachedCategory, + undefined, + customerAccessToken, + ); const categoryFacets = categorySearch.facets.items.filter( (facet) => facet.__typename !== 'CategorySearchFilter', ); @@ -75,7 +80,7 @@ export async function generateMetadata(props: Props): Promise { const categoryId = Number(slug); - const { category } = await getCategoryPageData(categoryId, customerAccessToken); + const { category } = await getCategoryPageMetadata(locale, categoryId, customerAccessToken); if (!category) { return notFound(); @@ -96,7 +101,7 @@ export async function generateMetadata(props: Props): Promise { }; } -export default async function Category(props: Props) { +async function CategoryContent(props: Props) { const { slug, locale } = await props.params; const customerAccessToken = await getSessionCustomerAccessToken(); @@ -107,6 +112,7 @@ export default async function Category(props: Props) { const categoryId = Number(slug); const { category, settings, categoryTree } = await getCategoryPageData( + locale, categoryId, customerAccessToken, ); @@ -130,12 +136,14 @@ export default async function Category(props: Props) { const currencyCode = await getPreferredCurrencyCode(); const loadSearchParams = await createCategorySearchParamsLoader( + locale, categoryId, customerAccessToken, ); const parsedSearchParams = loadSearchParams?.(searchParams) ?? {}; const search = await fetchFacetedSearch( + locale, { ...searchParams, ...parsedSearchParams, @@ -182,12 +190,18 @@ export default async function Category(props: Props) { const searchParams = await props.searchParams; const loadSearchParams = await createCategorySearchParamsLoader( + locale, categoryId, customerAccessToken, ); const parsedSearchParams = loadSearchParams?.(searchParams) ?? {}; const cachedCategory = getCachedCategory(categoryId); - const categorySearch = await fetchFacetedSearch(cachedCategory, undefined, customerAccessToken); + const categorySearch = await fetchFacetedSearch( + locale, + cachedCategory, + undefined, + customerAccessToken, + ); const refinedSearch = await streamableFacetedSearch; const allFacets = categorySearch.facets.items.filter( @@ -234,7 +248,7 @@ export default async function Category(props: Props) { const compareIds = { entityIds: compare ? compare.map((id: string) => Number(id)) : [] }; - const products = await getCompareProducts(compareIds, customerAccessToken); + const products = await getCompareProducts(locale, compareIds, customerAccessToken); return products.map((product) => ({ id: product.entityId.toString(), @@ -289,3 +303,11 @@ export default async function Category(props: Props) { ); } + +export default function Category(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/(faceted)/fetch-compare-products.ts b/core/app/[locale]/(default)/(faceted)/fetch-compare-products.ts index 8033dc8235..e54bc447f1 100644 --- a/core/app/[locale]/(default)/(faceted)/fetch-compare-products.ts +++ b/core/app/[locale]/(default)/(faceted)/fetch-compare-products.ts @@ -1,5 +1,6 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { VariablesOf } from 'gql.tada'; +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { z } from 'zod'; @@ -42,21 +43,46 @@ const CompareProductsQuery = graphql(` type Variables = VariablesOf; +async function getCachedCompareProducts(locale: string, variables: Variables) { + 'use cache'; + + cacheLife({ revalidate }); + + const parsedVariables = CompareProductsSchema.parse(variables); + + if (parsedVariables.entityIds.length === 0) { + return []; + } + + const response = await client.fetch({ + document: CompareProductsQuery, + variables: { ...parsedVariables, first: MAX_COMPARE_LIMIT }, + locale, + }); + + return removeEdgesAndNodes(response.data.site.products); +} + export const getCompareProducts = cache( - async (variables: Variables, customerAccessToken?: string) => { - const parsedVariables = CompareProductsSchema.parse(variables); + async (locale: string, variables: Variables, customerAccessToken?: string) => { + if (customerAccessToken) { + const parsedVariables = CompareProductsSchema.parse(variables); - if (parsedVariables.entityIds.length === 0) { - return []; - } + if (parsedVariables.entityIds.length === 0) { + return []; + } - const response = await client.fetch({ - document: CompareProductsQuery, - variables: { ...parsedVariables, first: MAX_COMPARE_LIMIT }, - customerAccessToken, - fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, - }); + const response = await client.fetch({ + document: CompareProductsQuery, + variables: { ...parsedVariables, first: MAX_COMPARE_LIMIT }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); + + return removeEdgesAndNodes(response.data.site.products); + } - return removeEdgesAndNodes(response.data.site.products); + return getCachedCompareProducts(locale, variables); }, ); diff --git a/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts b/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts index 115d639d1c..793cc5adb3 100644 --- a/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts +++ b/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts @@ -1,4 +1,5 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { z } from 'zod'; @@ -178,69 +179,145 @@ interface ProductSearch { filters: SearchProductsFiltersInput; } +async function getCachedProductSearchResults( + locale: string, + { limit = 9, after, before, sort, filters }: ProductSearch, + currencyCode: CurrencyCode | undefined, +) { + 'use cache'; + + cacheLife({ revalidate: 300 }); + + const filterArgs = { filters, sort }; + const paginationArgs = before ? { last: limit, before } : { first: limit, after }; + + const response = await client.fetch({ + document: GetProductSearchResultsQuery, + variables: { ...filterArgs, ...paginationArgs, currencyCode }, + locale, + }); + + const { site } = response.data; + const searchResults = site.search.searchProducts; + + const items = removeEdgesAndNodes(searchResults.products).map((product) => ({ + ...product, + })); + + return { + facets: { + items: removeEdgesAndNodes(searchResults.filters).map((node) => { + switch (node.__typename) { + case 'BrandSearchFilter': + return { + ...node, + brands: removeEdgesAndNodes(node.brands), + }; + + case 'CategorySearchFilter': + return { + ...node, + categories: removeEdgesAndNodes(node.categories), + }; + + case 'ProductAttributeSearchFilter': + return { + ...node, + attributes: removeEdgesAndNodes(node.attributes), + }; + + case 'RatingSearchFilter': + return { + ...node, + ratings: removeEdgesAndNodes(node.ratings), + }; + + default: + return node; + } + }), + }, + products: { + collectionInfo: searchResults.products.collectionInfo, + pageInfo: searchResults.products.pageInfo, + items, + }, + }; +} + const getProductSearchResults = cache( + // We need to make sure the reference passed into this function is the same if we want it to be memoized. async ( + locale: string, { limit = 9, after, before, sort, filters }: ProductSearch, currencyCode?: CurrencyCode, customerAccessToken?: string, ) => { - const filterArgs = { filters, sort }; - const paginationArgs = before ? { last: limit, before } : { first: limit, after }; + if (customerAccessToken) { + const filterArgs = { filters, sort }; + const paginationArgs = before ? { last: limit, before } : { first: limit, after }; - const response = await client.fetch({ - document: GetProductSearchResultsQuery, - variables: { ...filterArgs, ...paginationArgs, currencyCode }, - customerAccessToken, - fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate: 300 } }, - }); + const response = await client.fetch({ + document: GetProductSearchResultsQuery, + variables: { ...filterArgs, ...paginationArgs, currencyCode }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); - const { site } = response.data; + const { site } = response.data; + const searchResults = site.search.searchProducts; - const searchResults = site.search.searchProducts; + const items = removeEdgesAndNodes(searchResults.products).map((product) => ({ + ...product, + })); - const items = removeEdgesAndNodes(searchResults.products).map((product) => ({ - ...product, - })); + return { + facets: { + items: removeEdgesAndNodes(searchResults.filters).map((node) => { + switch (node.__typename) { + case 'BrandSearchFilter': + return { + ...node, + brands: removeEdgesAndNodes(node.brands), + }; - return { - facets: { - items: removeEdgesAndNodes(searchResults.filters).map((node) => { - switch (node.__typename) { - case 'BrandSearchFilter': - return { - ...node, - brands: removeEdgesAndNodes(node.brands), - }; - - case 'CategorySearchFilter': - return { - ...node, - categories: removeEdgesAndNodes(node.categories), - }; - - case 'ProductAttributeSearchFilter': - return { - ...node, - attributes: removeEdgesAndNodes(node.attributes), - }; - - case 'RatingSearchFilter': - return { - ...node, - ratings: removeEdgesAndNodes(node.ratings), - }; - - default: - return node; - } - }), - }, - products: { - collectionInfo: searchResults.products.collectionInfo, - pageInfo: searchResults.products.pageInfo, - items, - }, - }; + case 'CategorySearchFilter': + return { + ...node, + categories: removeEdgesAndNodes(node.categories), + }; + + case 'ProductAttributeSearchFilter': + return { + ...node, + attributes: removeEdgesAndNodes(node.attributes), + }; + + case 'RatingSearchFilter': + return { + ...node, + ratings: removeEdgesAndNodes(node.ratings), + }; + + default: + return node; + } + }), + }, + products: { + collectionInfo: searchResults.products.collectionInfo, + pageInfo: searchResults.products.pageInfo, + items, + }, + }; + } + + return getCachedProductSearchResults( + locale, + { limit, after, before, sort, filters }, + currencyCode, + ); }, ); @@ -406,6 +483,7 @@ export const PublicToPrivateParams = PublicSearchParamsSchema.catchall(SearchPar export const fetchFacetedSearch = cache( // We need to make sure the reference passed into this function is the same if we want it to be memoized. async ( + locale: string, params: z.input, currencyCode?: CurrencyCode, customerAccessToken?: string, @@ -413,6 +491,7 @@ export const fetchFacetedSearch = cache( const { after, before, limit = 9, sort, filters } = PublicToPrivateParams.parse(params); return getProductSearchResults( + locale, { after, before, diff --git a/core/app/[locale]/(default)/(faceted)/search/page-data.ts b/core/app/[locale]/(default)/(faceted)/search/page-data.ts index 37f571c49b..4fd9ca8f8a 100644 --- a/core/app/[locale]/(default)/(faceted)/search/page-data.ts +++ b/core/app/[locale]/(default)/(faceted)/search/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -29,11 +30,19 @@ const SearchPageQuery = graphql(` } `); -export const getSearchPageData = cache(async () => { +async function getCachedSearchPageData(locale: string) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: SearchPageQuery, - fetchOptions: { next: { revalidate } }, + locale, }); return response.data.site; +} + +export const getSearchPageData = cache(async (locale: string) => { + return getCachedSearchPageData(locale); }); diff --git a/core/app/[locale]/(default)/(faceted)/search/page.tsx b/core/app/[locale]/(default)/(faceted)/search/page.tsx index bc86471c32..d5f8007a34 100644 --- a/core/app/[locale]/(default)/(faceted)/search/page.tsx +++ b/core/app/[locale]/(default)/(faceted)/search/page.tsx @@ -22,14 +22,14 @@ import { getSearchPageData } from './page-data'; const compareLoader = createCompareLoader(); const createSearchSearchParamsLoader = cache( - async (searchParams: SearchParams, customerAccessToken?: string) => { + async (locale: string, searchParams: SearchParams, customerAccessToken?: string) => { const searchTerm = typeof searchParams.term === 'string' ? searchParams.term : ''; if (!searchTerm) { return null; } - const search = await fetchFacetedSearch(searchParams, undefined, customerAccessToken); + const search = await fetchFacetedSearch(locale, searchParams, undefined, customerAccessToken); const searchFacets = search.facets.items; const transformedSearchFacets = await facetsTransformer({ refinedFacets: searchFacets, @@ -76,7 +76,7 @@ export default async function Search(props: Props) { const t = await getTranslations('Faceted'); - const { settings } = await getSearchPageData(); + const { settings } = await getSearchPageData(locale); const showRating = Boolean(settings?.reviews.enabled && settings.display.showProductRating); @@ -89,12 +89,14 @@ export default async function Search(props: Props) { const currencyCode = await getPreferredCurrencyCode(); const loadSearchParams = await createSearchSearchParamsLoader( + locale, searchParams, customerAccessToken, ); const parsedSearchParams = loadSearchParams?.(searchParams) ?? {}; const search = await fetchFacetedSearch( + locale, { ...searchParams, ...parsedSearchParams, @@ -186,11 +188,12 @@ export default async function Search(props: Props) { } const loadSearchParams = await createSearchSearchParamsLoader( + locale, searchParams, customerAccessToken, ); const parsedSearchParams = loadSearchParams?.(searchParams) ?? {}; - const categorySearch = await fetchFacetedSearch({}, undefined, customerAccessToken); + const categorySearch = await fetchFacetedSearch(locale, {}, undefined, customerAccessToken); const refinedSearch = await streamableFacetedSearch; const allFacets = categorySearch.facets.items.filter( @@ -221,7 +224,7 @@ export default async function Search(props: Props) { const compareIds = { entityIds: compare ? compare.map((id: string) => Number(id)) : [] }; - const products = await getCompareProductsData(compareIds, customerAccessToken); + const products = await getCompareProductsData(locale, compareIds, customerAccessToken); return products.map((product) => ({ id: product.entityId.toString(), diff --git a/core/app/[locale]/(default)/account/addresses/_actions/create-address.ts b/core/app/[locale]/(default)/account/addresses/_actions/create-address.ts index e6f67f0cea..6d7df51ab1 100644 --- a/core/app/[locale]/(default)/account/addresses/_actions/create-address.ts +++ b/core/app/[locale]/(default)/account/addresses/_actions/create-address.ts @@ -1,6 +1,6 @@ import { BigCommerceAPIError, BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { parseWithZod } from '@conform-to/zod'; -import { unstable_expireTag as expireTag } from 'next/cache'; +import { revalidateTag } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { z } from 'zod'; @@ -234,7 +234,7 @@ export async function createAddress( }; } - expireTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { addresses: [ diff --git a/core/app/[locale]/(default)/account/addresses/_actions/delete-address.ts b/core/app/[locale]/(default)/account/addresses/_actions/delete-address.ts index 4760fb1668..f1b1a1314e 100644 --- a/core/app/[locale]/(default)/account/addresses/_actions/delete-address.ts +++ b/core/app/[locale]/(default)/account/addresses/_actions/delete-address.ts @@ -1,6 +1,6 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { parseWithZod } from '@conform-to/zod'; -import { unstable_expireTag as expireTag } from 'next/cache'; +import { revalidateTag } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { z } from 'zod'; @@ -78,7 +78,7 @@ export async function deleteAddress(prevState: Awaited, formData: FormDat }; } - expireTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { addresses: prevState.addresses.filter( diff --git a/core/app/[locale]/(default)/account/addresses/_actions/update-address.ts b/core/app/[locale]/(default)/account/addresses/_actions/update-address.ts index 8547f4d7b1..0435290bae 100644 --- a/core/app/[locale]/(default)/account/addresses/_actions/update-address.ts +++ b/core/app/[locale]/(default)/account/addresses/_actions/update-address.ts @@ -1,6 +1,6 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { parseWithZod } from '@conform-to/zod'; -import { unstable_expireTag as expireTag } from 'next/cache'; +import { revalidateTag } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { z } from 'zod'; @@ -247,7 +247,7 @@ export async function updateAddress( }; } - expireTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { addresses: prevState.addresses.map((address) => diff --git a/core/app/[locale]/(default)/account/addresses/page-data.ts b/core/app/[locale]/(default)/account/addresses/page-data.ts index 66433985ff..f44e833967 100644 --- a/core/app/[locale]/(default)/account/addresses/page-data.ts +++ b/core/app/[locale]/(default)/account/addresses/page-data.ts @@ -1,7 +1,6 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { PaginationFragment } from '~/client/fragments/pagination'; import { graphql } from '~/client/graphql'; @@ -70,13 +69,17 @@ interface Pagination { } export const getCustomerAddresses = cache( - async ({ before = '', after = '', limit = 10 }: Pagination) => { - const customerAccessToken = await getSessionCustomerAccessToken(); + async ( + locale: string, + { before = '', after = '', limit = 10 }: Pagination, + customerAccessToken?: string, + ) => { const paginationArgs = before ? { last: limit, before } : { first: limit, after }; const response = await client.fetch({ document: GetCustomerAddressesQuery, variables: { ...paginationArgs }, + locale, customerAccessToken, fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, }); diff --git a/core/app/[locale]/(default)/account/addresses/page.tsx b/core/app/[locale]/(default)/account/addresses/page.tsx index 49c02451a6..1fa0b82cc5 100644 --- a/core/app/[locale]/(default)/account/addresses/page.tsx +++ b/core/app/[locale]/(default)/account/addresses/page.tsx @@ -1,8 +1,10 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { Address, AddressListSection } from '@/vibes/soul/sections/address-list-section'; +import { getSessionCustomerAccessToken } from '~/auth'; import { formFieldTransformer, injectCountryCodeOptions, @@ -36,18 +38,25 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function Addresses({ params, searchParams }: Props) { +async function AddressesContent({ params, searchParams }: Props) { const { locale } = await params; setRequestLocale(locale); - const t = await getTranslations('Account.Addresses'); - const { before, after } = await searchParams; - - const data = await getCustomerAddresses({ - ...(after && { after }), - ...(before && { before }), - }); + const [customerAccessToken, t, { before, after }] = await Promise.all([ + getSessionCustomerAccessToken(), + getTranslations('Account.Addresses'), + searchParams, + ]); + + const data = await getCustomerAddresses( + locale, + { + ...(after && { after }), + ...(before && { before }), + }, + customerAccessToken, + ); if (!data) { notFound(); @@ -111,3 +120,11 @@ export default async function Addresses({ params, searchParams }: Props) { /> ); } + +export default function Addresses(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/account/orders/[id]/page-data.tsx b/core/app/[locale]/(default)/account/orders/[id]/page-data.tsx index 9fd6c21afc..c69981298a 100644 --- a/core/app/[locale]/(default)/account/orders/[id]/page-data.tsx +++ b/core/app/[locale]/(default)/account/orders/[id]/page-data.tsx @@ -1,7 +1,6 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -155,9 +154,7 @@ const CustomerOrderDetails = graphql( [OrderItemFragment, OrderGiftCertificateItemFragment], ); -export const getCustomerOrderDetails = cache(async (id: number) => { - const customerAccessToken = await getSessionCustomerAccessToken(); - +export const getCustomerOrderDetails = cache(async (id: number, customerAccessToken?: string) => { const response = await client.fetch({ document: CustomerOrderDetails, variables: { diff --git a/core/app/[locale]/(default)/account/orders/[id]/page.tsx b/core/app/[locale]/(default)/account/orders/[id]/page.tsx index 20e166389f..8adb7ab81b 100644 --- a/core/app/[locale]/(default)/account/orders/[id]/page.tsx +++ b/core/app/[locale]/(default)/account/orders/[id]/page.tsx @@ -1,8 +1,10 @@ import { notFound } from 'next/navigation'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { Streamable } from '@/vibes/soul/lib/streamable'; import { OrderDetailsSection } from '@/vibes/soul/sections/order-details-section'; +import { getSessionCustomerAccessToken } from '~/auth'; import { orderDetailsTransformer } from '~/data-transformers/order-details-transformer'; import { getCustomerOrderDetails } from './page-data'; @@ -14,16 +16,19 @@ interface Props { }>; } -export default async function OrderDetails(props: Props) { +async function OrderDetailsContent(props: Props) { const { id, locale } = await props.params; setRequestLocale(locale); - const t = await getTranslations('Account.Orders.Details'); - const format = await getFormatter(); + const [t, format] = await Promise.all([ + getTranslations('Account.Orders.Details'), + getFormatter(), + ]); const streamableOrder = Streamable.from(async () => { - const order = await getCustomerOrderDetails(Number(id)); + const customerAccessToken = await getSessionCustomerAccessToken(); + const order = await getCustomerOrderDetails(Number(id), customerAccessToken); if (!order) { notFound(); @@ -44,3 +49,11 @@ export default async function OrderDetails(props: Props) { /> ); } + +export default function OrderDetails(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/account/orders/page-data.ts b/core/app/[locale]/(default)/account/orders/page-data.ts index a2b240f4f4..85b3f32c9f 100644 --- a/core/app/[locale]/(default)/account/orders/page-data.ts +++ b/core/app/[locale]/(default)/account/orders/page-data.ts @@ -1,7 +1,6 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { PaginationFragment } from '~/client/fragments/pagination'; import { graphql, VariablesOf } from '~/client/graphql'; @@ -89,14 +88,11 @@ interface CustomerOrdersArgs { } export const getCustomerOrders = cache( - async ({ - before = '', - after = '', - filterByStatus, - filterByDateRange, - limit = 5, - }: CustomerOrdersArgs) => { - const customerAccessToken = await getSessionCustomerAccessToken(); + async ( + locale: string, + { before = '', after = '', filterByStatus, filterByDateRange, limit = 5 }: CustomerOrdersArgs, + customerAccessToken?: string, + ) => { const paginationArgs = before ? { last: limit, before } : { first: limit, after }; const filtersArgs = { filters: { @@ -107,6 +103,7 @@ export const getCustomerOrders = cache( const response = await client.fetch({ document: CustomerAllOrders, variables: { ...paginationArgs, ...filtersArgs }, + locale, customerAccessToken, fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, errorPolicy: 'auth', diff --git a/core/app/[locale]/(default)/account/orders/page.tsx b/core/app/[locale]/(default)/account/orders/page.tsx index 65d4167426..609678a587 100644 --- a/core/app/[locale]/(default)/account/orders/page.tsx +++ b/core/app/[locale]/(default)/account/orders/page.tsx @@ -1,6 +1,8 @@ import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { Order, OrderList } from '@/vibes/soul/sections/order-list'; +import { getSessionCustomerAccessToken } from '~/auth'; import { ordersTransformer } from '~/data-transformers/orders-transformer'; import { defaultPageInfo, pageInfoTransformer } from '~/data-transformers/page-info-transformer'; @@ -15,12 +17,16 @@ interface Props { }>; } -async function getOrders(after?: string, before?: string): Promise { - const format = await getFormatter(); - const customerOrdersDetails = await getCustomerOrders({ - ...(after && { after }), - ...(before && { before }), - }); +async function getOrders(locale: string, after?: string, before?: string): Promise { + const [format, customerAccessToken] = await Promise.all([ + getFormatter(), + getSessionCustomerAccessToken(), + ]); + const customerOrdersDetails = await getCustomerOrders( + locale, + { ...(after && { after }), ...(before && { before }) }, + customerAccessToken, + ); if (!customerOrdersDetails) { return []; @@ -31,16 +37,18 @@ async function getOrders(after?: string, before?: string): Promise { return ordersTransformer(orders, format); } -async function getPaginationInfo(after?: string, before?: string) { - const customerOrdersDetails = await getCustomerOrders({ - ...(after && { after }), - ...(before && { before }), - }); +async function getPaginationInfo(locale: string, after?: string, before?: string) { + const customerAccessToken = await getSessionCustomerAccessToken(); + const customerOrdersDetails = await getCustomerOrders( + locale, + { ...(after && { after }), ...(before && { before }) }, + customerAccessToken, + ); return pageInfoTransformer(customerOrdersDetails?.pageInfo ?? defaultPageInfo); } -export default async function Orders({ params, searchParams }: Props) { +async function OrdersContent({ params, searchParams }: Props) { const { locale } = await params; setRequestLocale(locale); @@ -53,11 +61,19 @@ export default async function Orders({ params, searchParams }: Props) { emptyStateActionLabel={t('EmptyState.cta')} emptyStateTitle={t('EmptyState.title')} orderNumberLabel={t('orderNumber')} - orders={getOrders(after, before)} - paginationInfo={getPaginationInfo(after, before)} + orders={getOrders(locale, after, before)} + paginationInfo={getPaginationInfo(locale, after, before)} title={t('title')} totalLabel={t('totalPrice')} viewDetailsLabel={t('viewDetails')} /> ); } + +export default function Orders(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/account/settings/_actions/update-customer.ts b/core/app/[locale]/(default)/account/settings/_actions/update-customer.ts index 8d678d562c..90f673bd55 100644 --- a/core/app/[locale]/(default)/account/settings/_actions/update-customer.ts +++ b/core/app/[locale]/(default)/account/settings/_actions/update-customer.ts @@ -2,7 +2,7 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { parseWithZod } from '@conform-to/zod'; -import { unstable_expireTag } from 'next/cache'; +import { revalidateTag } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { updateAccountSchema } from '@/vibes/soul/sections/account-settings/schema'; @@ -75,7 +75,7 @@ export const updateCustomer: UpdateAccountAction = async (prevState, formData) = }; } - unstable_expireTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { account: submission.value, diff --git a/core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts b/core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts index b984b53601..2ecf52d63c 100644 --- a/core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts +++ b/core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts @@ -3,7 +3,7 @@ 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 { revalidateTag } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { z } from 'zod'; @@ -120,7 +120,7 @@ export const updateNewsletterSubscription = async ( }; } - unstable_expireTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { lastResult: submission.reply(), diff --git a/core/app/[locale]/(default)/account/settings/page-data.tsx b/core/app/[locale]/(default)/account/settings/page-data.tsx index 136ef5c991..5c26cf569e 100644 --- a/core/app/[locale]/(default)/account/settings/page-data.tsx +++ b/core/app/[locale]/(default)/account/settings/page-data.tsx @@ -1,6 +1,5 @@ import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -67,37 +66,37 @@ interface Props { }; } -export const getAccountSettingsQuery = cache(async ({ address, customer }: Props = {}) => { - const customerAccessToken = await getSessionCustomerAccessToken(); +export const getAccountSettingsQuery = cache( + async ({ address, customer }: Props = {}, customerAccessToken?: string) => { + const response = await client.fetch({ + document: AccountSettingsQuery, + variables: { + addressFilters: address?.filters, + addressSortBy: address?.sortBy, + customerFilters: customer?.filters, + customerSortBy: customer?.sortBy, + }, + fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, + customerAccessToken, + }); - const response = await client.fetch({ - document: AccountSettingsQuery, - variables: { - addressFilters: address?.filters, - addressSortBy: address?.sortBy, - customerFilters: customer?.filters, - customerSortBy: customer?.sortBy, - }, - fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, - customerAccessToken, - }); + const addressFields = response.data.site.settings?.formFields.shippingAddress; + const customerFields = response.data.site.settings?.formFields.customer; + const customerInfo = response.data.customer; + const newsletterSettings = response.data.site.settings?.newsletter; + const passwordComplexitySettings = + response.data.site.settings?.customers?.passwordComplexitySettings; - const addressFields = response.data.site.settings?.formFields.shippingAddress; - const customerFields = response.data.site.settings?.formFields.customer; - const customerInfo = response.data.customer; - const newsletterSettings = response.data.site.settings?.newsletter; - const passwordComplexitySettings = - response.data.site.settings?.customers?.passwordComplexitySettings; - - if (!addressFields || !customerFields || !customerInfo) { - return null; - } + if (!addressFields || !customerFields || !customerInfo) { + return null; + } - return { - addressFields, - customerFields, - customerInfo, - newsletterSettings, - passwordComplexitySettings, - }; -}); + return { + addressFields, + customerFields, + customerInfo, + newsletterSettings, + passwordComplexitySettings, + }; + }, +); diff --git a/core/app/[locale]/(default)/account/settings/page.tsx b/core/app/[locale]/(default)/account/settings/page.tsx index cad145dc6f..6f0d011388 100644 --- a/core/app/[locale]/(default)/account/settings/page.tsx +++ b/core/app/[locale]/(default)/account/settings/page.tsx @@ -2,8 +2,10 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { AccountSettingsSection } from '@/vibes/soul/sections/account-settings'; +import { getSessionCustomerAccessToken } from '~/auth'; import { changePassword } from './_actions/change-password'; import { updateCustomer } from './_actions/update-customer'; @@ -24,14 +26,16 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function Settings({ params }: Props) { +async function SettingsContent({ params }: Props) { const { locale } = await params; setRequestLocale(locale); - const t = await getTranslations('Account.Settings'); - - const accountSettings = await getAccountSettingsQuery(); + const [t, customerAccessToken] = await Promise.all([ + getTranslations('Account.Settings'), + getSessionCustomerAccessToken(), + ]); + const accountSettings = await getAccountSettingsQuery({}, customerAccessToken); if (!accountSettings) { notFound(); @@ -69,3 +73,11 @@ export default async function Settings({ params }: Props) { /> ); } + +export default function Settings(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/account/wishlists/[id]/page-data.ts b/core/app/[locale]/(default)/account/wishlists/[id]/page-data.ts index ae14ab4173..602f3729bc 100644 --- a/core/app/[locale]/(default)/account/wishlists/[id]/page-data.ts +++ b/core/app/[locale]/(default)/account/wishlists/[id]/page-data.ts @@ -1,11 +1,10 @@ import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; +import type { CurrencyCode } from '~/components/header/fragment'; import { WishlistPaginatedItemsFragment } from '~/components/wishlist/fragment'; -import { getPreferredCurrencyCode } from '~/lib/currency'; const WishlistDetailsQuery = graphql( ` @@ -37,23 +36,30 @@ interface Pagination { after: string | null; } -export const getCustomerWishlist = cache(async (entityId: number, pagination: Pagination) => { - const { before, after, limit = 9 } = pagination; - const customerAccessToken = await getSessionCustomerAccessToken(); - const currencyCode = await getPreferredCurrencyCode(); - const paginationArgs = before ? { last: limit, before } : { first: limit, after }; - const response = await client.fetch({ - document: WishlistDetailsQuery, - variables: { ...paginationArgs, currencyCode, entityId }, - customerAccessToken, - fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, - }); +export const getCustomerWishlist = cache( + async ( + locale: string, + entityId: number, + pagination: Pagination, + customerAccessToken?: string, + currencyCode?: CurrencyCode, + ) => { + const { before, after, limit = 9 } = pagination; + const paginationArgs = before ? { last: limit, before } : { first: limit, after }; + const response = await client.fetch({ + document: WishlistDetailsQuery, + variables: { ...paginationArgs, currencyCode, entityId }, + locale, + customerAccessToken, + fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, + }); - const wishlist = response.data.customer?.wishlists.edges?.[0]?.node; + const wishlist = response.data.customer?.wishlists.edges?.[0]?.node; - if (!wishlist) { - return null; - } + if (!wishlist) { + return null; + } - return wishlist; -}); + return wishlist; + }, +); diff --git a/core/app/[locale]/(default)/account/wishlists/[id]/page.tsx b/core/app/[locale]/(default)/account/wishlists/[id]/page.tsx index 6ea2baa869..9b067182f6 100644 --- a/core/app/[locale]/(default)/account/wishlists/[id]/page.tsx +++ b/core/app/[locale]/(default)/account/wishlists/[id]/page.tsx @@ -2,14 +2,18 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; import { SearchParams } from 'nuqs'; import { createSearchParamsCache, parseAsInteger, parseAsString } from 'nuqs/server'; +import { Suspense } from 'react'; import { Streamable } from '@/vibes/soul/lib/streamable'; import { CursorPaginationInfo } from '@/vibes/soul/primitives/cursor-pagination'; import { Wishlist, WishlistDetails } from '@/vibes/soul/sections/wishlist-details'; +import { getSessionCustomerAccessToken } from '~/auth'; import { ExistingResultType } from '~/client/util'; +import type { CurrencyCode } from '~/components/header/fragment'; import { defaultPageInfo, pageInfoTransformer } from '~/data-transformers/page-info-transformer'; import { wishlistDetailsTransformer } from '~/data-transformers/wishlists-transformer'; import { redirect } from '~/i18n/routing'; +import { getPreferredCurrencyCode } from '~/lib/currency'; import { isMobileUser } from '~/lib/user-agent'; import { removeWishlistItem } from '../_actions/remove-wishlist-item'; @@ -39,11 +43,19 @@ async function getWishlist( pt: ExistingResultType>, searchParamsPromise: Promise, locale: string, + customerAccessToken?: string, + currencyCode?: CurrencyCode, ): Promise { const entityId = Number(id); const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); const formatter = await getFormatter(); - const wishlist = await getCustomerWishlist(entityId, searchParamsParsed); + const wishlist = await getCustomerWishlist( + locale, + entityId, + searchParamsParsed, + customerAccessToken, + currencyCode, + ); if (!wishlist) { return redirect({ href: '/account/wishlists/', locale }); @@ -52,10 +64,22 @@ async function getWishlist( return wishlistDetailsTransformer(wishlist, t, pt, formatter); } -const getAnalyticsData = async (id: string, searchParamsPromise: Promise) => { +const getAnalyticsData = async ( + locale: string, + id: string, + searchParamsPromise: Promise, + customerAccessToken?: string, + currencyCode?: CurrencyCode, +) => { const entityId = Number(id); const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); - const wishlist = await getCustomerWishlist(entityId, searchParamsParsed); + const wishlist = await getCustomerWishlist( + locale, + entityId, + searchParamsParsed, + customerAccessToken, + currencyCode, + ); if (!wishlist) { return []; @@ -77,23 +101,36 @@ const getAnalyticsData = async (id: string, searchParamsPromise: Promise, + customerAccessToken?: string, + currencyCode?: CurrencyCode, ): Promise { const entityId = Number(id); const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); - const wishlist = await getCustomerWishlist(entityId, searchParamsParsed); + const wishlist = await getCustomerWishlist( + locale, + entityId, + searchParamsParsed, + customerAccessToken, + currencyCode, + ); return pageInfoTransformer(wishlist?.items.pageInfo ?? defaultPageInfo); } -export default async function WishlistPage({ params, searchParams }: Props) { +async function WishlistPageContent({ params, searchParams }: Props) { const { locale, id } = await params; setRequestLocale(locale); - const t = await getTranslations('Wishlist'); - const pt = await getTranslations('Product.ProductDetails'); + const [t, pt, customerAccessToken, currencyCode] = await Promise.all([ + getTranslations('Wishlist'), + getTranslations('Product.ProductDetails'), + getSessionCustomerAccessToken(), + getPreferredCurrencyCode(), + ]); const wishlistActions = (wishlist?: Wishlist) => { if (!wishlist) { return ; @@ -127,17 +164,33 @@ export default async function WishlistPage({ params, searchParams }: Props) { }; return ( - getAnalyticsData(id, searchParams))}> + + getAnalyticsData(locale, id, searchParams, customerAccessToken, currencyCode), + )} + > getPaginationInfo(id, searchParams))} + paginationInfo={Streamable.from(() => + getPaginationInfo(locale, id, searchParams, customerAccessToken, currencyCode), + )} prevHref="/account/wishlists" removeAction={removeWishlistItem} removeButtonTitle={t('removeButtonTitle')} - wishlist={Streamable.from(() => getWishlist(id, t, pt, searchParams, locale))} + wishlist={Streamable.from(() => + getWishlist(id, t, pt, searchParams, locale, customerAccessToken, currencyCode), + )} /> ); } + +export default function WishlistPage(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/account/wishlists/_actions/change-wishlist-visibility.ts b/core/app/[locale]/(default)/account/wishlists/_actions/change-wishlist-visibility.ts index 46ff104696..3f8f8547da 100644 --- a/core/app/[locale]/(default)/account/wishlists/_actions/change-wishlist-visibility.ts +++ b/core/app/[locale]/(default)/account/wishlists/_actions/change-wishlist-visibility.ts @@ -63,7 +63,7 @@ export async function toggleWishlistVisibility( }; } - revalidateTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { lastResult: submission.reply(), diff --git a/core/app/[locale]/(default)/account/wishlists/_actions/delete-wishlist.ts b/core/app/[locale]/(default)/account/wishlists/_actions/delete-wishlist.ts index b63ec01a89..8a5498671e 100644 --- a/core/app/[locale]/(default)/account/wishlists/_actions/delete-wishlist.ts +++ b/core/app/[locale]/(default)/account/wishlists/_actions/delete-wishlist.ts @@ -60,7 +60,7 @@ export async function deleteWishlist( }; } - revalidateTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); // Server toast has to be used here since the item is being deleted. When revalidateTag is called, // the wishlist items will update, and the element node containing the useEffect will be removed. diff --git a/core/app/[locale]/(default)/account/wishlists/_actions/new-wishlist.ts b/core/app/[locale]/(default)/account/wishlists/_actions/new-wishlist.ts index 1df54efdc6..dbf86eb612 100644 --- a/core/app/[locale]/(default)/account/wishlists/_actions/new-wishlist.ts +++ b/core/app/[locale]/(default)/account/wishlists/_actions/new-wishlist.ts @@ -58,7 +58,7 @@ export async function newWishlist(prevState: Awaited, formData: FormData) }; } - revalidateTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { lastResult: submission.reply(), diff --git a/core/app/[locale]/(default)/account/wishlists/_actions/remove-wishlist-item.ts b/core/app/[locale]/(default)/account/wishlists/_actions/remove-wishlist-item.ts index 5bee617081..39fcad84da 100644 --- a/core/app/[locale]/(default)/account/wishlists/_actions/remove-wishlist-item.ts +++ b/core/app/[locale]/(default)/account/wishlists/_actions/remove-wishlist-item.ts @@ -63,7 +63,7 @@ export async function removeWishlistItem( }; } - revalidateTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); // Server toast has to be used here since the item is being deleted. When revalidateTag is called, // the wishlist items will update, and the element node containing the useEffect will be removed. diff --git a/core/app/[locale]/(default)/account/wishlists/_actions/rename-wishlist.ts b/core/app/[locale]/(default)/account/wishlists/_actions/rename-wishlist.ts index 0446c4e6b0..33d7c45ecd 100644 --- a/core/app/[locale]/(default)/account/wishlists/_actions/rename-wishlist.ts +++ b/core/app/[locale]/(default)/account/wishlists/_actions/rename-wishlist.ts @@ -60,7 +60,7 @@ export async function renameWishlist( }; } - revalidateTag(TAGS.customer); + revalidateTag(TAGS.customer, { expire: 0 }); return { lastResult: submission.reply(), diff --git a/core/app/[locale]/(default)/account/wishlists/page-data.ts b/core/app/[locale]/(default)/account/wishlists/page-data.ts index 02a1c3f7b1..fef85399e0 100644 --- a/core/app/[locale]/(default)/account/wishlists/page-data.ts +++ b/core/app/[locale]/(default)/account/wishlists/page-data.ts @@ -1,11 +1,10 @@ import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; +import type { CurrencyCode } from '~/components/header/fragment'; import { WishlistsFragment } from '~/components/wishlist/fragment'; -import { getPreferredCurrencyCode } from '~/lib/currency'; const WishlistsPageQuery = graphql( ` @@ -33,22 +32,28 @@ interface Pagination { after: string | null; } -export const getCustomerWishlists = cache(async ({ limit = 9, before, after }: Pagination) => { - const customerAccessToken = await getSessionCustomerAccessToken(); - const currencyCode = await getPreferredCurrencyCode(); - const paginationArgs = before ? { last: limit, before } : { first: limit, after }; - const response = await client.fetch({ - document: WishlistsPageQuery, - variables: { ...paginationArgs, currencyCode }, - customerAccessToken, - fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, - }); +export const getCustomerWishlists = cache( + async ( + locale: string, + { limit = 9, before, after }: Pagination, + customerAccessToken?: string, + currencyCode?: CurrencyCode, + ) => { + const paginationArgs = before ? { last: limit, before } : { first: limit, after }; + const response = await client.fetch({ + document: WishlistsPageQuery, + variables: { ...paginationArgs, currencyCode }, + locale, + customerAccessToken, + fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } }, + }); - const wishlists = response.data.customer?.wishlists; + const wishlists = response.data.customer?.wishlists; - if (!wishlists) { - return null; - } + if (!wishlists) { + return null; + } - return wishlists; -}); + return wishlists; + }, +); diff --git a/core/app/[locale]/(default)/account/wishlists/page.tsx b/core/app/[locale]/(default)/account/wishlists/page.tsx index 5fba791cfa..0e803dc2d1 100644 --- a/core/app/[locale]/(default)/account/wishlists/page.tsx +++ b/core/app/[locale]/(default)/account/wishlists/page.tsx @@ -1,15 +1,19 @@ import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; import { SearchParams } from 'nuqs'; import { createSearchParamsCache, parseAsInteger, parseAsString } from 'nuqs/server'; +import { Suspense } from 'react'; import { Streamable } from '@/vibes/soul/lib/streamable'; import { CursorPaginationInfo } from '@/vibes/soul/primitives/cursor-pagination'; import * as Skeleton from '@/vibes/soul/primitives/skeleton'; import { Wishlist } from '@/vibes/soul/sections/wishlist-details'; import { WishlistsSection } from '@/vibes/soul/sections/wishlists-section'; +import { getSessionCustomerAccessToken } from '~/auth'; import { ExistingResultType } from '~/client/util'; +import type { CurrencyCode } from '~/components/header/fragment'; import { defaultPageInfo, pageInfoTransformer } from '~/data-transformers/page-info-transformer'; import { wishlistsTransformer } from '~/data-transformers/wishlists-transformer'; +import { getPreferredCurrencyCode } from '~/lib/currency'; import { isMobileUser } from '~/lib/user-agent'; import { NewWishlistButton } from './_components/new-wishlist-button'; @@ -36,12 +40,20 @@ const searchParamsCache = createSearchParamsCache({ }); async function listWishlists( + locale: string, searchParamsPromise: Promise, t: ExistingResultType>, + customerAccessToken?: string, + currencyCode?: CurrencyCode, ): Promise { const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); const formatter = await getFormatter(); - const wishlists = await getCustomerWishlists(searchParamsParsed); + const wishlists = await getCustomerWishlists( + locale, + searchParamsParsed, + customerAccessToken, + currencyCode, + ); if (!wishlists) { return []; @@ -51,21 +63,33 @@ async function listWishlists( } async function getPaginationInfo( + locale: string, searchParamsPromise: Promise, + customerAccessToken?: string, + currencyCode?: CurrencyCode, ): Promise { const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); - const wishlists = await getCustomerWishlists(searchParamsParsed); + const wishlists = await getCustomerWishlists( + locale, + searchParamsParsed, + customerAccessToken, + currencyCode, + ); return pageInfoTransformer(wishlists?.pageInfo ?? defaultPageInfo); } -export default async function Wishlists({ params, searchParams }: Props) { +async function WishlistsContent({ params, searchParams }: Props) { const { locale } = await params; setRequestLocale(locale); - const t = await getTranslations('Wishlist'); - const isMobile = await isMobileUser(); + const [t, isMobile, customerAccessToken, currencyCode] = await Promise.all([ + getTranslations('Wishlist'), + isMobileUser(), + getSessionCustomerAccessToken(), + getPreferredCurrencyCode(), + ]); const newWishlistModal = getNewWishlistModal(t); return ( @@ -121,10 +145,22 @@ export default async function Wishlists({ params, searchParams }: Props) { ); }, }} - paginationInfo={Streamable.from(() => getPaginationInfo(searchParams))} + paginationInfo={Streamable.from(() => + getPaginationInfo(locale, searchParams, customerAccessToken, currencyCode), + )} title={t('title')} viewWishlistLabel={t('viewWishlist')} - wishlists={Streamable.from(() => listWishlists(searchParams, t))} + wishlists={Streamable.from(() => + listWishlists(locale, searchParams, t, customerAccessToken, currencyCode), + )} /> ); } + +export default function Wishlists(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/blog/[blogId]/page-data.ts b/core/app/[locale]/(default)/blog/[blogId]/page-data.ts index 472c44059a..1643f5c1b2 100644 --- a/core/app/[locale]/(default)/blog/[blogId]/page-data.ts +++ b/core/app/[locale]/(default)/blog/[blogId]/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -38,11 +39,15 @@ const BlogPageQuery = graphql(` type Variables = VariablesOf; -export const getBlogPageData = cache(async (variables: Variables) => { +async function getCachedBlogPageData(locale: string, variables: Variables) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: BlogPageQuery, variables, - fetchOptions: { next: { revalidate } }, + locale, }); const { blog } = response.data.site.content; @@ -52,4 +57,8 @@ export const getBlogPageData = cache(async (variables: Variables) => { } return blog; +} + +export const getBlogPageData = cache(async (locale: string, variables: Variables) => { + return getCachedBlogPageData(locale, variables); }); diff --git a/core/app/[locale]/(default)/blog/[blogId]/page.tsx b/core/app/[locale]/(default)/blog/[blogId]/page.tsx index 6b8da45019..5177b6937a 100644 --- a/core/app/[locale]/(default)/blog/[blogId]/page.tsx +++ b/core/app/[locale]/(default)/blog/[blogId]/page.tsx @@ -1,8 +1,9 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; -import { cache } from 'react'; +import { cache, Suspense } from 'react'; +import { Streamable } from '@/vibes/soul/lib/streamable'; import { BlogPostContent, BlogPostContentBlogPost } from '@/vibes/soul/sections/blog-post-content'; import { Breadcrumb } from '@/vibes/soul/sections/breadcrumbs'; import { getMetadataAlternates } from '~/lib/seo/canonical'; @@ -23,7 +24,7 @@ export async function generateMetadata({ params }: Props): Promise { const variables = cachedBlogPageDataVariables(blogId); - const blog = await getBlogPageData(variables); + const blog = await getBlogPageData(locale, variables); const blogPost = blog?.post; if (!blogPost) { @@ -45,11 +46,11 @@ export async function generateMetadata({ params }: Props): Promise { async function getBlogPost(props: Props): Promise { const format = await getFormatter(); - const { blogId } = await props.params; + const { blogId, locale } = await props.params; const variables = cachedBlogPageDataVariables(blogId); - const blog = await getBlogPageData(variables); + const blog = await getBlogPageData(locale, variables); const blogPost = blog?.post; if (!blog || !blogPost) { @@ -76,11 +77,11 @@ async function getBlogPost(props: Props): Promise { async function getBlogPostBreadcrumbs(props: Props): Promise { const t = await getTranslations('Blog'); - const { blogId } = await props.params; + const { blogId, locale } = await props.params; const variables = cachedBlogPageDataVariables(blogId); - const blog = await getBlogPageData(variables); + const blog = await getBlogPageData(locale, variables); const blogPost = blog?.post; if (!blog || !blogPost) { @@ -103,12 +104,23 @@ async function getBlogPostBreadcrumbs(props: Props): Promise { ]; } -export default async function Blog(props: Props) { +async function BlogPostPageContent(props: Props) { const { locale } = await props.params; setRequestLocale(locale); return ( - + getBlogPost(props))} + breadcrumbs={Streamable.from(() => getBlogPostBreadcrumbs(props))} + /> + ); +} + +export default function Blog(props: Props) { + return ( + + + ); } diff --git a/core/app/[locale]/(default)/blog/page-data.ts b/core/app/[locale]/(default)/blog/page-data.ts index d51cf024cd..6fc29e0a6d 100644 --- a/core/app/[locale]/(default)/blog/page-data.ts +++ b/core/app/[locale]/(default)/blog/page-data.ts @@ -1,4 +1,5 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; +import { cacheLife } from 'next/cache'; import { getFormatter } from 'next-intl/server'; import { cache } from 'react'; @@ -72,49 +73,80 @@ interface Pagination { after: string | null; } -export const getBlog = cache(async () => { +async function getCachedBlog(locale: string) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: BlogQuery, - fetchOptions: { next: { revalidate } }, + locale, }); return response.data.site.content.blog; +} + +export const getBlog = cache(async (locale: string) => { + return getCachedBlog(locale); }); -export const getBlogPosts = cache( - async ({ tag, limit = 9, before, after }: BlogPostsFiltersInput & Pagination) => { - const filterArgs = tag ? { filters: { tags: [tag] } } : {}; - const paginationArgs = before ? { last: limit, before } : { first: limit, after }; +async function getCachedBlogPosts( + locale: string, + { tag, limit = 9, before, after }: BlogPostsFiltersInput & Pagination, +) { + 'use cache'; - const response = await client.fetch({ - document: BlogPostsPageQuery, - variables: { ...filterArgs, ...paginationArgs }, - fetchOptions: { next: { revalidate } }, - }); + cacheLife({ revalidate }); + + const filterArgs = tag ? { filters: { tags: [tag] } } : {}; + const paginationArgs = before ? { last: limit, before } : { first: limit, after }; + + const response = await client.fetch({ + document: BlogPostsPageQuery, + variables: { ...filterArgs, ...paginationArgs }, + locale, + }); - const { blog } = response.data.site.content; + const { blog } = response.data.site.content; - if (!blog) { + if (!blog) { + return null; + } + + return { + pageInfo: blog.posts.pageInfo, + posts: removeEdgesAndNodes(blog.posts).map((post) => ({ + id: String(post.entityId), + author: post.author, + content: post.plainTextSummary, + dateUtc: post.publishedDate.utc, + image: post.thumbnailImage + ? { + src: post.thumbnailImage.url, + alt: post.thumbnailImage.altText, + } + : undefined, + href: post.path, + title: post.name, + })), + }; +} + +export const getBlogPosts = cache( + async (locale: string, { tag, limit = 9, before, after }: BlogPostsFiltersInput & Pagination) => { + const raw = await getCachedBlogPosts(locale, { tag, limit, before, after }); + + if (!raw) { return null; } const format = await getFormatter(); return { - pageInfo: blog.posts.pageInfo, - posts: removeEdgesAndNodes(blog.posts).map((post) => ({ - id: String(post.entityId), - author: post.author, - content: post.plainTextSummary, - date: format.dateTime(new Date(post.publishedDate.utc)), - image: post.thumbnailImage - ? { - src: post.thumbnailImage.url, - alt: post.thumbnailImage.altText, - } - : undefined, - href: post.path, - title: post.name, + pageInfo: raw.pageInfo, + posts: raw.posts.map(({ dateUtc, ...post }) => ({ + ...post, + date: format.dateTime(new Date(dateUtc)), })), }; }, diff --git a/core/app/[locale]/(default)/blog/page.tsx b/core/app/[locale]/(default)/blog/page.tsx index 1b8d90cf91..44f940a718 100644 --- a/core/app/[locale]/(default)/blog/page.tsx +++ b/core/app/[locale]/(default)/blog/page.tsx @@ -3,6 +3,7 @@ import { notFound } from 'next/navigation'; import { getTranslations, setRequestLocale } from 'next-intl/server'; import { SearchParams } from 'nuqs'; import { createSearchParamsCache, parseAsInteger, parseAsString } from 'nuqs/server'; +import { Suspense } from 'react'; import { Streamable } from '@/vibes/soul/lib/streamable'; import { FeaturedBlogPostList } from '@/vibes/soul/sections/featured-blog-post-list'; @@ -29,7 +30,7 @@ export async function generateMetadata({ params }: Props): Promise { const { locale } = await params; const t = await getTranslations({ locale, namespace: 'Blog' }); - const blog = await getBlog(); + const blog = await getBlog(locale); const description = blog?.description && blog.description.length > 150 @@ -43,9 +44,9 @@ export async function generateMetadata({ params }: Props): Promise { }; } -async function listBlogPosts(searchParamsPromise: Promise) { +async function listBlogPosts(locale: string, searchParamsPromise: Promise) { const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); - const blogPosts = await getBlogPosts(searchParamsParsed); + const blogPosts = await getBlogPosts(locale, searchParamsParsed); const posts = blogPosts?.posts ?? []; return posts; @@ -63,14 +64,14 @@ async function getEmptyStateSubtitle(): Promise { return t('subtitle'); } -async function getPaginationInfo(searchParamsPromise: Promise) { +async function getPaginationInfo(locale: string, searchParamsPromise: Promise) { const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); - const blogPosts = await getBlogPosts(searchParamsParsed); + const blogPosts = await getBlogPosts(locale, searchParamsParsed); return pageInfoTransformer(blogPosts?.pageInfo ?? defaultPageInfo); } -export default async function Blog(props: Props) { +async function BlogContent(props: Props) { const { locale } = await props.params; setRequestLocale(locale); @@ -79,7 +80,7 @@ export default async function Blog(props: Props) { const searchParamsParsed = searchParamsCache.parse(await props.searchParams); const { tag } = searchParamsParsed; - const blog = await getBlog(); + const blog = await getBlog(locale); if (!blog) { return notFound(); @@ -103,10 +104,18 @@ export default async function Blog(props: Props) { description={blog.description} emptyStateSubtitle={Streamable.from(getEmptyStateSubtitle)} emptyStateTitle={Streamable.from(getEmptyStateTitle)} - paginationInfo={Streamable.from(() => getPaginationInfo(props.searchParams))} + paginationInfo={Streamable.from(() => getPaginationInfo(locale, props.searchParams))} placeholderCount={6} - posts={Streamable.from(() => listBlogPosts(props.searchParams))} + posts={Streamable.from(() => listBlogPosts(locale, props.searchParams))} title={blog.name} /> ); } + +export default function Blog(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/cart/_actions/add-shipping-cost.ts b/core/app/[locale]/(default)/cart/_actions/add-shipping-cost.ts index cd6566f838..5550033fb9 100644 --- a/core/app/[locale]/(default)/cart/_actions/add-shipping-cost.ts +++ b/core/app/[locale]/(default)/cart/_actions/add-shipping-cost.ts @@ -49,7 +49,7 @@ export const addShippingCost = async ({ const result = response.data.checkout.selectCheckoutShippingOption?.checkout; - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return result; }; diff --git a/core/app/[locale]/(default)/cart/_actions/add-shipping-info.ts b/core/app/[locale]/(default)/cart/_actions/add-shipping-info.ts index 87200afc1f..fd29033f17 100644 --- a/core/app/[locale]/(default)/cart/_actions/add-shipping-info.ts +++ b/core/app/[locale]/(default)/cart/_actions/add-shipping-info.ts @@ -68,7 +68,7 @@ export const addCheckoutShippingConsignments = async ({ fetchOptions: { cache: 'no-store' }, }); - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return response.data.checkout.addCheckoutShippingConsignments?.checkout; }; @@ -135,7 +135,7 @@ export const updateCheckoutShippingConsignment = async ({ fetchOptions: { cache: 'no-store' }, }); - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return response.data.checkout.updateCheckoutShippingConsignment?.checkout; }; diff --git a/core/app/[locale]/(default)/cart/_actions/apply-coupon-code.ts b/core/app/[locale]/(default)/cart/_actions/apply-coupon-code.ts index 604e9e3d5c..ed0347eb3c 100644 --- a/core/app/[locale]/(default)/cart/_actions/apply-coupon-code.ts +++ b/core/app/[locale]/(default)/cart/_actions/apply-coupon-code.ts @@ -45,7 +45,7 @@ export const applyCouponCode = async ({ checkoutEntityId, couponCode }: Props) = const checkout = response.data.checkout.applyCheckoutCoupon?.checkout; - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return checkout; }; diff --git a/core/app/[locale]/(default)/cart/_actions/apply-gift-certificate.ts b/core/app/[locale]/(default)/cart/_actions/apply-gift-certificate.ts index b2133c65c6..23113eef8b 100644 --- a/core/app/[locale]/(default)/cart/_actions/apply-gift-certificate.ts +++ b/core/app/[locale]/(default)/cart/_actions/apply-gift-certificate.ts @@ -47,7 +47,7 @@ export const applyGiftCertificate = async ({ checkoutEntityId, giftCertificateCo const checkout = response.data.checkout.applyCheckoutGiftCertificate?.checkout; - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return checkout; }; diff --git a/core/app/[locale]/(default)/cart/_actions/remove-coupon-code.ts b/core/app/[locale]/(default)/cart/_actions/remove-coupon-code.ts index 9d0ef45efa..b27a956fdd 100644 --- a/core/app/[locale]/(default)/cart/_actions/remove-coupon-code.ts +++ b/core/app/[locale]/(default)/cart/_actions/remove-coupon-code.ts @@ -45,7 +45,7 @@ export const removeCouponCode = async ({ checkoutEntityId, couponCode }: Props) const checkout = response.data.checkout.unapplyCheckoutCoupon?.checkout; - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return checkout; }; diff --git a/core/app/[locale]/(default)/cart/_actions/remove-gift-certificate.ts b/core/app/[locale]/(default)/cart/_actions/remove-gift-certificate.ts index 1d358ae1a9..040d97a1fc 100644 --- a/core/app/[locale]/(default)/cart/_actions/remove-gift-certificate.ts +++ b/core/app/[locale]/(default)/cart/_actions/remove-gift-certificate.ts @@ -47,7 +47,7 @@ export const removeGiftCertificate = async ({ checkoutEntityId, giftCertificateC const checkout = response.data.checkout.unapplyCheckoutGiftCertificate?.checkout; - revalidateTag(TAGS.checkout); + revalidateTag(TAGS.checkout, { expire: 0 }); return checkout; }; diff --git a/core/app/[locale]/(default)/cart/_actions/remove-item.ts b/core/app/[locale]/(default)/cart/_actions/remove-item.ts index f02a24dd66..fd4ee07fe8 100644 --- a/core/app/[locale]/(default)/cart/_actions/remove-item.ts +++ b/core/app/[locale]/(default)/cart/_actions/remove-item.ts @@ -1,6 +1,6 @@ 'use server'; -import { unstable_expireTag } from 'next/cache'; +import { revalidateTag } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { getSessionCustomerAccessToken } from '~/auth'; @@ -62,7 +62,7 @@ export async function removeItem({ await clearCartId(); } - unstable_expireTag(TAGS.cart); + revalidateTag(TAGS.cart, { expire: 0 }); return cart; } diff --git a/core/app/[locale]/(default)/cart/_actions/update-coupon-code.ts b/core/app/[locale]/(default)/cart/_actions/update-coupon-code.ts index 01611007f7..55ffc640dd 100644 --- a/core/app/[locale]/(default)/cart/_actions/update-coupon-code.ts +++ b/core/app/[locale]/(default)/cart/_actions/update-coupon-code.ts @@ -3,9 +3,10 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { SubmissionResult } from '@conform-to/react'; import { parseWithZod } from '@conform-to/zod'; -import { getTranslations } from 'next-intl/server'; +import { getLocale, getTranslations } from 'next-intl/server'; import { couponCodeActionFormDataSchema } from '@/vibes/soul/sections/cart/schema'; +import { getSessionCustomerAccessToken } from '~/auth'; import { getCartId } from '~/lib/cart'; import { getCart } from '../page-data'; @@ -34,7 +35,11 @@ export const updateCouponCode = async ( return { ...prevState, lastResult: submission.reply({ formErrors: [t('cartNotFound')] }) }; } - const cart = await getCart({ cartId }); + const [locale, customerAccessToken] = await Promise.all([ + getLocale(), + getSessionCustomerAccessToken(), + ]); + const cart = await getCart(locale, { cartId }, customerAccessToken); const checkout = cart.site.checkout; if (!checkout) { diff --git a/core/app/[locale]/(default)/cart/_actions/update-gift-certificate.ts b/core/app/[locale]/(default)/cart/_actions/update-gift-certificate.ts index b7f2c52ac3..bf1aa88e42 100644 --- a/core/app/[locale]/(default)/cart/_actions/update-gift-certificate.ts +++ b/core/app/[locale]/(default)/cart/_actions/update-gift-certificate.ts @@ -3,9 +3,10 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { SubmissionResult } from '@conform-to/react'; import { parseWithZod } from '@conform-to/zod'; -import { getTranslations } from 'next-intl/server'; +import { getLocale, getTranslations } from 'next-intl/server'; import { giftCertificateCodeActionFormDataSchema } from '@/vibes/soul/sections/cart/schema'; +import { getSessionCustomerAccessToken } from '~/auth'; import { getCartId } from '~/lib/cart'; import { getCart } from '../page-data'; @@ -36,7 +37,11 @@ export const updateGiftCertificate = async ( return { ...prevState, lastResult: submission.reply({ formErrors: [t('cartNotFound')] }) }; } - const cart = await getCart({ cartId }); + const [locale, customerAccessToken] = await Promise.all([ + getLocale(), + getSessionCustomerAccessToken(), + ]); + const cart = await getCart(locale, { cartId }, customerAccessToken); const checkout = cart.site.checkout; if (!checkout) { diff --git a/core/app/[locale]/(default)/cart/_actions/update-quantity.ts b/core/app/[locale]/(default)/cart/_actions/update-quantity.ts index 6a36c88c60..ebae4492ed 100644 --- a/core/app/[locale]/(default)/cart/_actions/update-quantity.ts +++ b/core/app/[locale]/(default)/cart/_actions/update-quantity.ts @@ -1,6 +1,6 @@ 'use server'; -import { unstable_expirePath } from 'next/cache'; +import { revalidatePath } from 'next/cache'; import { getTranslations } from 'next-intl/server'; import { getSessionCustomerAccessToken } from '~/auth'; @@ -87,7 +87,7 @@ export const updateQuantity = async ({ throw new Error(t('failedToUpdateQuantity')); } - unstable_expirePath('/cart'); + revalidatePath('/cart'); return cart; }; diff --git a/core/app/[locale]/(default)/cart/_actions/update-shipping-info.ts b/core/app/[locale]/(default)/cart/_actions/update-shipping-info.ts index 662ba0f7a6..809a3e6d43 100644 --- a/core/app/[locale]/(default)/cart/_actions/update-shipping-info.ts +++ b/core/app/[locale]/(default)/cart/_actions/update-shipping-info.ts @@ -2,10 +2,11 @@ import { BigCommerceGQLError } from '@bigcommerce/catalyst-client'; import { parseWithZod } from '@conform-to/zod'; -import { getTranslations } from 'next-intl/server'; +import { getLocale, getTranslations } from 'next-intl/server'; import { shippingActionFormDataSchema } from '@/vibes/soul/sections/cart/schema'; import { ShippingFormState } from '@/vibes/soul/sections/cart/shipping-form'; +import { getSessionCustomerAccessToken } from '~/auth'; import { getCartId } from '~/lib/cart'; import { getCart } from '../page-data'; @@ -32,7 +33,11 @@ export const updateShippingInfo = async ( return { ...prevState, lastResult: submission.reply({ formErrors: [t('cartNotFound')] }) }; } - const cart = await getCart({ cartId }); + const [locale, customerAccessToken] = await Promise.all([ + getLocale(), + getSessionCustomerAccessToken(), + ]); + const cart = await getCart(locale, { cartId }, customerAccessToken); const checkout = cart.site.checkout; if (!checkout || !cart.site.cart) { diff --git a/core/app/[locale]/(default)/cart/loading.tsx b/core/app/[locale]/(default)/cart/loading.tsx index 1f0e533b4f..7704b74980 100644 --- a/core/app/[locale]/(default)/cart/loading.tsx +++ b/core/app/[locale]/(default)/cart/loading.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useTranslations } from 'next-intl'; import { CartSkeleton } from '@/vibes/soul/sections/cart'; diff --git a/core/app/[locale]/(default)/cart/page-data.ts b/core/app/[locale]/(default)/cart/page-data.ts index c6e47636dc..8792940db4 100644 --- a/core/app/[locale]/(default)/cart/page-data.ts +++ b/core/app/[locale]/(default)/cart/page-data.ts @@ -1,6 +1,6 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; -import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -295,12 +295,15 @@ const CartPageQuery = graphql( type Variables = VariablesOf; -export const getCart = async (variables: Variables) => { - const customerAccessToken = await getSessionCustomerAccessToken(); - +export const getCart = async ( + locale: string, + variables: Variables, + customerAccessToken?: string, +) => { const { data } = await client.fetch({ document: CartPageQuery, variables, + locale, customerAccessToken, fetchOptions: { cache: 'no-store', @@ -336,11 +339,19 @@ const SupportedShippingDestinationsQuery = graphql(` } `); -export const getShippingCountries = cache(async () => { +async function getCachedShippingCountries(locale: string) { + 'use cache'; + + cacheLife({ revalidate }); + const { data } = await client.fetch({ document: SupportedShippingDestinationsQuery, - fetchOptions: { next: { revalidate } }, + locale, }); return data.site.settings?.shipping?.supportedShippingDestinations.countries ?? []; +} + +export const getShippingCountries = cache(async (locale: string) => { + return getCachedShippingCountries(locale); }); diff --git a/core/app/[locale]/(default)/cart/page.tsx b/core/app/[locale]/(default)/cart/page.tsx index bc07f3d473..8839730db9 100644 --- a/core/app/[locale]/(default)/cart/page.tsx +++ b/core/app/[locale]/(default)/cart/page.tsx @@ -1,9 +1,11 @@ import { Metadata } from 'next'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { Streamable } from '@/vibes/soul/lib/streamable'; import { Cart as CartComponent, CartEmptyState } from '@/vibes/soul/sections/cart'; import { CartAnalyticsProvider } from '~/app/[locale]/(default)/cart/_components/cart-analytics-provider'; +import { getSessionCustomerAccessToken } from '~/auth'; import { getCartId } from '~/lib/cart'; import { getPreferredCurrencyCode } from '~/lib/currency'; import { exists } from '~/lib/utils'; @@ -32,8 +34,8 @@ export async function generateMetadata({ params }: Props): Promise { }; } -const getAnalyticsData = async (cartId: string) => { - const data = await getCart({ cartId }); +const getAnalyticsData = async (locale: string, cartId: string, customerAccessToken?: string) => { + const data = await getCart(locale, { cartId }, customerAccessToken); const cart = data.site.cart; @@ -60,15 +62,12 @@ const getAnalyticsData = async (cartId: string) => { }; // eslint-disable-next-line complexity -export default async function Cart({ params }: Props) { +async function CartContent({ params }: Props) { const { locale } = await params; setRequestLocale(locale); - const t = await getTranslations('Cart'); - const tGiftCertificates = await getTranslations('GiftCertificates'); - const format = await getFormatter(); - const cartId = await getCartId(); + const [t, cartId] = await Promise.all([getTranslations('Cart'), getCartId()]); if (!cartId) { return ( @@ -80,8 +79,13 @@ export default async function Cart({ params }: Props) { ); } - const currencyCode = await getPreferredCurrencyCode(); - const data = await getCart({ cartId, currencyCode }); + const [tGiftCertificates, format, currencyCode, customerAccessToken] = await Promise.all([ + getTranslations('GiftCertificates'), + getFormatter(), + getPreferredCurrencyCode(), + getSessionCustomerAccessToken(), + ]); + const data = await getCart(locale, { cartId, currencyCode }, customerAccessToken); const cart = data.site.cart; const checkout = data.site.checkout; @@ -227,7 +231,7 @@ export default async function Cart({ params }: Props) { checkout?.shippingConsignments?.find((consignment) => consignment.selectedShippingOption) || checkout?.shippingConsignments?.[0]; - const shippingCountries = await getShippingCountries(); + const shippingCountries = await getShippingCountries(locale); const countries = shippingCountries.map((country) => ({ value: country.code, @@ -260,7 +264,9 @@ export default async function Cart({ params }: Props) { return ( <> - getAnalyticsData(cartId))}> + getAnalyticsData(locale, cartId, customerAccessToken))} + > {checkoutUrl ? : null} ); } + +export default function Cart(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/compare/page-data.ts b/core/app/[locale]/(default)/compare/page-data.ts index 9b0acac0f7..c7c046d9b3 100644 --- a/core/app/[locale]/(default)/compare/page-data.ts +++ b/core/app/[locale]/(default)/compare/page-data.ts @@ -1,4 +1,5 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -55,23 +56,59 @@ const ComparedProductsQuery = graphql( [ProductCardFragment], ); +async function getCachedComparedProducts( + locale: string, + productIds: number[], + currencyCode: CurrencyCode | undefined, +) { + 'use cache'; + + cacheLife({ revalidate }); + + if (productIds.length === 0) { + return []; + } + + const { data } = await client.fetch({ + document: ComparedProductsQuery, + variables: { + entityIds: productIds, + first: productIds.length ? MAX_COMPARE_LIMIT : 0, + currencyCode, + }, + locale, + }); + + return removeEdgesAndNodes(data.site.products); +} + export const getComparedProducts = cache( - async (productIds: number[] = [], currencyCode?: CurrencyCode, customerAccessToken?: string) => { - if (productIds.length === 0) { - return []; - } + async ( + locale: string, + productIds: number[] = [], + currencyCode?: CurrencyCode, + customerAccessToken?: string, + ) => { + if (customerAccessToken) { + if (productIds.length === 0) { + return []; + } - const { data } = await client.fetch({ - document: ComparedProductsQuery, - variables: { - entityIds: productIds, - first: productIds.length ? MAX_COMPARE_LIMIT : 0, - currencyCode, - }, - customerAccessToken, - fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, - }); + const { data } = await client.fetch({ + document: ComparedProductsQuery, + variables: { + entityIds: productIds, + first: productIds.length ? MAX_COMPARE_LIMIT : 0, + currencyCode, + }, + customerAccessToken, + locale, + fetchOptions: { cache: 'no-store' }, + }); + + return removeEdgesAndNodes(data.site.products); + } - return removeEdgesAndNodes(data.site.products); + return getCachedComparedProducts(locale, productIds, currencyCode); }, ); diff --git a/core/app/[locale]/(default)/compare/page.tsx b/core/app/[locale]/(default)/compare/page.tsx index ac851ddb1a..e99acf7944 100644 --- a/core/app/[locale]/(default)/compare/page.tsx +++ b/core/app/[locale]/(default)/compare/page.tsx @@ -64,7 +64,12 @@ export default async function Compare(props: Props) { const parsed = CompareParamsSchema.parse(searchParams); const productIds = parsed.ids?.filter((id) => !Number.isNaN(id)); - const products = await getComparedProducts(productIds, currencyCode, customerAccessToken); + const products = await getComparedProducts( + locale, + productIds, + currencyCode, + customerAccessToken, + ); const format = await getFormatter(); return products.map((product) => ({ @@ -97,7 +102,12 @@ export default async function Compare(props: Props) { const parsed = CompareParamsSchema.parse(searchParams); const productIds = parsed.ids?.filter((id) => !Number.isNaN(id)); - const products = await getComparedProducts(productIds, currencyCode, customerAccessToken); + const products = await getComparedProducts( + locale, + productIds, + currencyCode, + customerAccessToken, + ); return products.map((product) => { return { diff --git a/core/app/[locale]/(default)/gift-certificates/balance/page.tsx b/core/app/[locale]/(default)/gift-certificates/balance/page.tsx index 0320e48acb..5c41063f99 100644 --- a/core/app/[locale]/(default)/gift-certificates/balance/page.tsx +++ b/core/app/[locale]/(default)/gift-certificates/balance/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { GiftCertificateCheckBalanceSection } from '@/vibes/soul/sections/gift-certificate-balance-section'; import { redirect } from '~/i18n/routing'; @@ -25,14 +26,14 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function GiftCertificates(props: Props) { +async function GiftCertificatesContent(props: Props) { const { locale } = await props.params; setRequestLocale(locale); const t = await getTranslations('GiftCertificates'); const currencyCode = await getPreferredCurrencyCode(); - const data = await getGiftCertificatesData(currencyCode); + const data = await getGiftCertificatesData(locale, currencyCode); if (!data.giftCertificatesEnabled) { return redirect({ href: '/', locale }); @@ -63,3 +64,11 @@ export default async function GiftCertificates(props: Props) { /> ); } + +export default function GiftCertificates(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/gift-certificates/page-data.ts b/core/app/[locale]/(default)/gift-certificates/page-data.ts index 6905ee0b7f..0c287b532d 100644 --- a/core/app/[locale]/(default)/gift-certificates/page-data.ts +++ b/core/app/[locale]/(default)/gift-certificates/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -26,11 +27,15 @@ const GiftCertificatesRootQuery = graphql( [StoreLogoFragment], ); -export const getGiftCertificatesData = cache(async (currencyCode?: CurrencyCode) => { +async function getCachedGiftCertificatesData(locale: string, currencyCode?: CurrencyCode) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: GiftCertificatesRootQuery, variables: { currencyCode }, - fetchOptions: { next: { revalidate } }, + locale, }); return { @@ -38,4 +43,10 @@ export const getGiftCertificatesData = cache(async (currencyCode?: CurrencyCode) defaultCurrency: response.data.site.settings?.currency.defaultCurrency ?? undefined, logo: response.data.site.settings ? logoTransformer(response.data.site.settings) : '', }; -}); +} + +export const getGiftCertificatesData = cache( + async (locale: string, currencyCode?: CurrencyCode) => { + return getCachedGiftCertificatesData(locale, currencyCode); + }, +); diff --git a/core/app/[locale]/(default)/gift-certificates/page.tsx b/core/app/[locale]/(default)/gift-certificates/page.tsx index 5c50984fb3..4e1320d6cd 100644 --- a/core/app/[locale]/(default)/gift-certificates/page.tsx +++ b/core/app/[locale]/(default)/gift-certificates/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'; +import { Suspense } from 'react'; import { GiftCertificatesSection } from '@/vibes/soul/sections/gift-certificates-section'; import { redirect } from '~/i18n/routing'; @@ -23,7 +24,7 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function GiftCertificates(props: Props) { +async function GiftCertificatesContent(props: Props) { const { locale } = await props.params; setRequestLocale(locale); @@ -31,7 +32,7 @@ export default async function GiftCertificates(props: Props) { const t = await getTranslations('GiftCertificates'); const format = await getFormatter(); const currencyCode = await getPreferredCurrencyCode(); - const data = await getGiftCertificatesData(currencyCode); + const data = await getGiftCertificatesData(locale, currencyCode); if (!data.giftCertificatesEnabled) { return redirect({ href: '/', locale }); @@ -55,3 +56,11 @@ export default async function GiftCertificates(props: Props) { /> ); } + +export default function GiftCertificates(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/gift-certificates/purchase/page-data.ts b/core/app/[locale]/(default)/gift-certificates/purchase/page-data.ts index 609584722a..e9156f6fc2 100644 --- a/core/app/[locale]/(default)/gift-certificates/purchase/page-data.ts +++ b/core/app/[locale]/(default)/gift-certificates/purchase/page-data.ts @@ -1,3 +1,4 @@ +import { cacheLife } from 'next/cache'; import { cache } from 'react'; import { client } from '~/client'; @@ -29,11 +30,15 @@ const GiftCertificatePurchaseSettingsQuery = graphql( [GiftCertificateSettingsFragment, StoreLogoFragment], ); -export const getGiftCertificatePurchaseData = cache(async (currencyCode?: CurrencyCode) => { +async function getCachedGiftCertificatePurchaseData(locale: string, currencyCode?: CurrencyCode) { + 'use cache'; + + cacheLife({ revalidate }); + const response = await client.fetch({ document: GiftCertificatePurchaseSettingsQuery, variables: { currencyCode }, - fetchOptions: { next: { revalidate } }, + locale, }); return { @@ -42,4 +47,10 @@ export const getGiftCertificatePurchaseData = cache(async (currencyCode?: Curren storeName: response.data.site.settings?.storeName ?? undefined, defaultCurrency: response.data.site.settings?.currency.defaultCurrency ?? undefined, }; -}); +} + +export const getGiftCertificatePurchaseData = cache( + async (locale: string, currencyCode?: CurrencyCode) => { + return getCachedGiftCertificatePurchaseData(locale, currencyCode); + }, +); diff --git a/core/app/[locale]/(default)/gift-certificates/purchase/page.tsx b/core/app/[locale]/(default)/gift-certificates/purchase/page.tsx index 29e4158c75..d2cd3a55ed 100644 --- a/core/app/[locale]/(default)/gift-certificates/purchase/page.tsx +++ b/core/app/[locale]/(default)/gift-certificates/purchase/page.tsx @@ -1,6 +1,7 @@ import { ResultOf } from 'gql.tada'; import { Metadata } from 'next'; import { getFormatter, getTranslations } from 'next-intl/server'; +import { Suspense } from 'react'; import { Field, FieldGroup } from '@/vibes/soul/form/dynamic-form/schema'; import { GiftCertificatePurchaseSection } from '@/vibes/soul/sections/gift-certificate-purchase-section'; @@ -147,13 +148,13 @@ function getExpiryDate( } } -export default async function GiftCertificatePurchasePage({ params }: Props) { +async function GiftCertificatePurchasePageContent({ params }: Props) { const { locale } = await params; const t = await getTranslations({ locale, namespace: 'GiftCertificates' }); const format = await getFormatter(); const currencyCode = await getPreferredCurrencyCode(); - const data = await getGiftCertificatePurchaseData(currencyCode); + const data = await getGiftCertificatePurchaseData(locale, currencyCode); if (!data.giftCertificateSettings?.isEnabled) { return redirect({ href: '/', locale }); @@ -192,3 +193,11 @@ export default async function GiftCertificatePurchasePage({ params }: Props) { /> ); } + +export default function GiftCertificatePurchasePage(props: Props) { + return ( + + + + ); +} diff --git a/core/app/[locale]/(default)/layout.tsx b/core/app/[locale]/(default)/layout.tsx index 95522f608e..b50a5560de 100644 --- a/core/app/[locale]/(default)/layout.tsx +++ b/core/app/[locale]/(default)/layout.tsx @@ -1,5 +1,5 @@ import { setRequestLocale } from 'next-intl/server'; -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, Suspense } from 'react'; import { Footer } from '~/components/footer'; import { Header } from '~/components/header'; @@ -15,11 +15,15 @@ export default async function DefaultLayout({ params, children }: Props) { return ( <> -
+ +
+
{children}
-