diff --git a/.env.example b/.env.example index 5eee90256..f4c548926 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ NEXT_PUBLIC_SALEOR_API_URL=https://storefront1.saleor.cloud/graphql/ # make sure to add it on production for correct canonical URLs NEXT_PUBLIC_STOREFRONT_URL=http://localhost:3000 + +# Token used for fetching channels +SALEOR_APP_TOKEN= diff --git a/src/app/(main)/cart/CheckoutLink.tsx b/src/app/[channel]/(main)/cart/CheckoutLink.tsx similarity index 100% rename from src/app/(main)/cart/CheckoutLink.tsx rename to src/app/[channel]/(main)/cart/CheckoutLink.tsx diff --git a/src/app/(main)/cart/DeleteLineButton.tsx b/src/app/[channel]/(main)/cart/DeleteLineButton.tsx similarity index 100% rename from src/app/(main)/cart/DeleteLineButton.tsx rename to src/app/[channel]/(main)/cart/DeleteLineButton.tsx diff --git a/src/app/(main)/cart/actions.ts b/src/app/[channel]/(main)/cart/actions.ts similarity index 100% rename from src/app/(main)/cart/actions.ts rename to src/app/[channel]/(main)/cart/actions.ts diff --git a/src/app/(main)/cart/page.tsx b/src/app/[channel]/(main)/cart/page.tsx similarity index 92% rename from src/app/(main)/cart/page.tsx rename to src/app/[channel]/(main)/cart/page.tsx index 93dbafaf6..17fcc086d 100644 --- a/src/app/(main)/cart/page.tsx +++ b/src/app/[channel]/(main)/cart/page.tsx @@ -1,17 +1,16 @@ -import { cookies } from "next/headers"; import Image from "next/image"; -import Link from "next/link"; import { CheckoutLink } from "./CheckoutLink"; import { DeleteLineButton } from "./DeleteLineButton"; import * as Checkout from "@/lib/checkout"; import { formatMoney, getHrefForVariant } from "@/lib/utils"; +import { LinkWithChannel } from "@/ui/atoms/LinkWithChannel"; export const metadata = { title: "Shopping Cart · Saleor Storefront example", }; -export default async function Page() { - const checkoutId = cookies().get("checkoutId")?.value || ""; +export default async function Page({ params }: { params: { channel: string } }) { + const checkoutId = Checkout.getIdFromCookies(params.channel); const checkout = await Checkout.find(checkoutId); @@ -22,12 +21,12 @@ export default async function Page() {

Looks like you haven’t added any items to the cart yet.

- Explore products - + ); } @@ -57,14 +56,14 @@ export default async function Page() {
-

{item.variant?.product?.name}

- +

{item.variant?.product?.category?.name}

{item.variant.name !== item.variant.id && Boolean(item.variant.name) && (

Variant: {item.variant.name}

diff --git a/src/app/(main)/categories/[slug]/page.tsx b/src/app/[channel]/(main)/categories/[slug]/page.tsx similarity index 82% rename from src/app/(main)/categories/[slug]/page.tsx rename to src/app/[channel]/(main)/categories/[slug]/page.tsx index 4960f69b5..d9ba8eb3c 100644 --- a/src/app/(main)/categories/[slug]/page.tsx +++ b/src/app/[channel]/(main)/categories/[slug]/page.tsx @@ -3,14 +3,13 @@ import { type ResolvingMetadata, type Metadata } from "next"; import { ProductListByCategoryDocument } from "@/gql/graphql"; import { executeGraphQL } from "@/lib/graphql"; import { ProductList } from "@/ui/components/ProductList"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; export const generateMetadata = async ( - { params }: { params: { slug: string } }, + { params }: { params: { slug: string; channel: string } }, parent: ResolvingMetadata, ): Promise => { const { category } = await executeGraphQL(ProductListByCategoryDocument, { - variables: { slug: params.slug, channel: DEFAULT_CHANNEL }, + variables: { slug: params.slug, channel: params.channel }, revalidate: 60, }); @@ -20,9 +19,9 @@ export const generateMetadata = async ( }; }; -export default async function Page({ params }: { params: { slug: string } }) { +export default async function Page({ params }: { params: { slug: string; channel: string } }) { const { category } = await executeGraphQL(ProductListByCategoryDocument, { - variables: { slug: params.slug, channel: DEFAULT_CHANNEL }, + variables: { slug: params.slug, channel: params.channel }, revalidate: 60, }); diff --git a/src/app/(main)/collections/[slug]/page.tsx b/src/app/[channel]/(main)/collections/[slug]/page.tsx similarity index 83% rename from src/app/(main)/collections/[slug]/page.tsx rename to src/app/[channel]/(main)/collections/[slug]/page.tsx index 44c62d3e0..60d39d3de 100644 --- a/src/app/(main)/collections/[slug]/page.tsx +++ b/src/app/[channel]/(main)/collections/[slug]/page.tsx @@ -3,14 +3,13 @@ import { type ResolvingMetadata, type Metadata } from "next"; import { ProductListByCollectionDocument } from "@/gql/graphql"; import { executeGraphQL } from "@/lib/graphql"; import { ProductList } from "@/ui/components/ProductList"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; export const generateMetadata = async ( - { params }: { params: { slug: string } }, + { params }: { params: { slug: string; channel: string } }, parent: ResolvingMetadata, ): Promise => { const { collection } = await executeGraphQL(ProductListByCollectionDocument, { - variables: { slug: params.slug, channel: DEFAULT_CHANNEL }, + variables: { slug: params.slug, channel: params.channel }, revalidate: 60, }); @@ -21,9 +20,9 @@ export const generateMetadata = async ( }; }; -export default async function Page({ params }: { params: { slug: string } }) { +export default async function Page({ params }: { params: { slug: string; channel: string } }) { const { collection } = await executeGraphQL(ProductListByCollectionDocument, { - variables: { slug: params.slug, channel: DEFAULT_CHANNEL }, + variables: { slug: params.slug, channel: params.channel }, revalidate: 60, }); diff --git a/src/app/(main)/layout.tsx b/src/app/[channel]/(main)/layout.tsx similarity index 70% rename from src/app/(main)/layout.tsx rename to src/app/[channel]/(main)/layout.tsx index 353a95ff1..142a16068 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/[channel]/(main)/layout.tsx @@ -7,13 +7,13 @@ export const metadata = { description: "Starter pack for building performant e-commerce experiences with Saleor.", }; -export default function RootLayout(props: { children: ReactNode }) { +export default function RootLayout(props: { children: ReactNode; params: { channel: string } }) { return ( <> -
+
{props.children}
-
+
); diff --git a/src/app/(main)/login/page.tsx b/src/app/[channel]/(main)/login/page.tsx similarity index 100% rename from src/app/(main)/login/page.tsx rename to src/app/[channel]/(main)/login/page.tsx diff --git a/src/app/(main)/orders/page.tsx b/src/app/[channel]/(main)/orders/page.tsx similarity index 100% rename from src/app/(main)/orders/page.tsx rename to src/app/[channel]/(main)/orders/page.tsx diff --git a/src/app/(main)/page.tsx b/src/app/[channel]/(main)/page.tsx similarity index 87% rename from src/app/(main)/page.tsx rename to src/app/[channel]/(main)/page.tsx index a39766182..9591cdde2 100644 --- a/src/app/(main)/page.tsx +++ b/src/app/[channel]/(main)/page.tsx @@ -1,4 +1,3 @@ -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; import { ProductListByCollectionDocument } from "@/gql/graphql"; import { executeGraphQL } from "@/lib/graphql"; import { ProductList } from "@/ui/components/ProductList"; @@ -9,11 +8,11 @@ export const metadata = { "Storefront Next.js Example for building performant e-commerce experiences with Saleor - the composable, headless commerce platform for global brands.", }; -export default async function Page() { +export default async function Page({ params }: { params: { channel: string } }) { const data = await executeGraphQL(ProductListByCollectionDocument, { variables: { slug: "featured-products", - channel: DEFAULT_CHANNEL, + channel: params.channel, }, revalidate: 60, }); diff --git a/src/app/(main)/pages/[slug]/page.tsx b/src/app/[channel]/(main)/pages/[slug]/page.tsx similarity index 100% rename from src/app/(main)/pages/[slug]/page.tsx rename to src/app/[channel]/(main)/pages/[slug]/page.tsx diff --git a/src/app/(main)/products/[slug]/AddButton.tsx b/src/app/[channel]/(main)/products/[slug]/AddButton.tsx similarity index 100% rename from src/app/(main)/products/[slug]/AddButton.tsx rename to src/app/[channel]/(main)/products/[slug]/AddButton.tsx diff --git a/src/app/(main)/products/[slug]/not-found.tsx b/src/app/[channel]/(main)/products/[slug]/not-found.tsx similarity index 100% rename from src/app/(main)/products/[slug]/not-found.tsx rename to src/app/[channel]/(main)/products/[slug]/not-found.tsx diff --git a/src/app/(main)/products/[slug]/page.tsx b/src/app/[channel]/(main)/products/[slug]/page.tsx similarity index 85% rename from src/app/(main)/products/[slug]/page.tsx rename to src/app/[channel]/(main)/products/[slug]/page.tsx index 84f226e62..17f98b12b 100644 --- a/src/app/(main)/products/[slug]/page.tsx +++ b/src/app/[channel]/(main)/products/[slug]/page.tsx @@ -1,6 +1,5 @@ import edjsHTML from "editorjs-html"; import { revalidatePath } from "next/cache"; -import { cookies } from "next/headers"; import { notFound } from "next/navigation"; import { type ResolvingMetadata, type Metadata } from "next"; import xss from "xss"; @@ -14,17 +13,13 @@ import { formatMoney, formatMoneyRange } from "@/lib/utils"; import { CheckoutAddLineDocument, ProductDetailsDocument, ProductListDocument } from "@/gql/graphql"; import * as Checkout from "@/lib/checkout"; import { AvailabilityMessage } from "@/ui/components/AvailabilityMessage"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; - -const shouldUseHttps = - process.env.NEXT_PUBLIC_STOREFRONT_URL?.startsWith("https") || !!process.env.NEXT_PUBLIC_VERCEL_URL; export async function generateMetadata( { params, searchParams, }: { - params: { slug: string }; + params: { slug: string; channel: string }; searchParams: { variant?: string }; }, parent: ResolvingMetadata, @@ -32,7 +27,7 @@ export async function generateMetadata( const { product } = await executeGraphQL(ProductDetailsDocument, { variables: { slug: decodeURIComponent(params.slug), - channel: DEFAULT_CHANNEL, + channel: params.channel, }, revalidate: 60, }); @@ -66,13 +61,10 @@ export async function generateMetadata( }; } -// TODO: re-enable when the bug in Next.js is fixed -// https://github.com/vercel/next.js/issues/50658 - -export async function generateStaticParams() { +export async function generateStaticParams({ params }: { params: { channel: string } }) { const { products } = await executeGraphQL(ProductListDocument, { revalidate: 60, - variables: { first: 20, channel: DEFAULT_CHANNEL }, + variables: { first: 20, channel: params.channel }, withAuth: false, }); @@ -82,13 +74,17 @@ export async function generateStaticParams() { const parser = edjsHTML(); -export default async function Page(props: { params: { slug: string }; searchParams: { variant?: string } }) { - const { params, searchParams } = props; - +export default async function Page({ + params, + searchParams, +}: { + params: { slug: string; channel: string }; + searchParams: { variant?: string }; +}) { const { product } = await executeGraphQL(ProductDetailsDocument, { variables: { slug: decodeURIComponent(params.slug), - channel: DEFAULT_CHANNEL, + channel: params.channel, }, revalidate: 60, }); @@ -107,14 +103,13 @@ export default async function Page(props: { params: { slug: string }; searchPara async function addItem() { "use server"; - const checkout = await Checkout.findOrCreate(cookies().get("checkoutId")?.value); + const checkout = await Checkout.findOrCreate({ + checkoutId: Checkout.getIdFromCookies(params.channel), + channel: params.channel, + }); invariant(checkout, "This should never happen"); - cookies().set("checkoutId", checkout.id, { - secure: shouldUseHttps, - sameSite: "lax", - httpOnly: true, - }); + Checkout.saveIdToCookie(params.channel, checkout.id); if (!selectedVariantID) { return; @@ -204,7 +199,12 @@ export default async function Page(props: { params: { slug: string }; searchPara

{variants && ( - + )} {description && (
diff --git a/src/app/(main)/products/loading.tsx b/src/app/[channel]/(main)/products/loading.tsx similarity index 100% rename from src/app/(main)/products/loading.tsx rename to src/app/[channel]/(main)/products/loading.tsx diff --git a/src/app/(main)/products/page.tsx b/src/app/[channel]/(main)/products/page.tsx similarity index 86% rename from src/app/(main)/products/page.tsx rename to src/app/[channel]/(main)/products/page.tsx index 41c66d1d1..d89e9c65d 100644 --- a/src/app/(main)/products/page.tsx +++ b/src/app/[channel]/(main)/products/page.tsx @@ -3,7 +3,6 @@ import { ProductListPaginatedDocument } from "@/gql/graphql"; import { executeGraphQL } from "@/lib/graphql"; import { Pagination } from "@/ui/components/Pagination"; import { ProductList } from "@/ui/components/ProductList"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; import { ProductsPerPage } from "@/app/config"; export const metadata = { @@ -11,20 +10,22 @@ export const metadata = { description: "All products in Saleor Storefront example", }; -type Props = { +export default async function Page({ + params, + searchParams, +}: { + params: { channel: string }; searchParams: { cursor: string | string[] | undefined; }; -}; - -export default async function Page({ searchParams }: Props) { +}) { const cursor = typeof searchParams.cursor === "string" ? searchParams.cursor : null; const { products } = await executeGraphQL(ProductListPaginatedDocument, { variables: { first: ProductsPerPage, after: cursor, - channel: DEFAULT_CHANNEL, + channel: params.channel, }, revalidate: 60, }); @@ -44,7 +45,7 @@ export default async function Page({ searchParams }: Props) { diff --git a/src/app/(main)/search/page.tsx b/src/app/[channel]/(main)/search/page.tsx similarity index 90% rename from src/app/(main)/search/page.tsx rename to src/app/[channel]/(main)/search/page.tsx index 9cd6aad5c..214ec83fe 100644 --- a/src/app/(main)/search/page.tsx +++ b/src/app/[channel]/(main)/search/page.tsx @@ -3,7 +3,6 @@ import { OrderDirection, ProductOrderField, SearchProductsDocument } from "@/gql import { executeGraphQL } from "@/lib/graphql"; import { Pagination } from "@/ui/components/Pagination"; import { ProductList } from "@/ui/components/ProductList"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; import { ProductsPerPage } from "@/app/config"; export const metadata = { @@ -11,11 +10,13 @@ export const metadata = { description: "Search products in Saleor Storefront example", }; -type Props = { +export default async function Page({ + searchParams, + params, +}: { searchParams: Record<"query" | "cursor", string | string[] | undefined>; -}; - -export default async function Page({ searchParams }: Props) { + params: { channel: string }; +}) { const cursor = typeof searchParams.cursor === "string" ? searchParams.cursor : null; const searchValue = searchParams.query; @@ -38,7 +39,7 @@ export default async function Page({ searchParams }: Props) { after: cursor, sortBy: ProductOrderField.Rating, sortDirection: OrderDirection.Asc, - channel: DEFAULT_CHANNEL, + channel: params.channel, }, revalidate: 60, }); @@ -61,7 +62,7 @@ export default async function Page({ searchParams }: Props) { diff --git a/src/app/[channel]/layout.tsx b/src/app/[channel]/layout.tsx new file mode 100644 index 000000000..ab40ec01b --- /dev/null +++ b/src/app/[channel]/layout.tsx @@ -0,0 +1,29 @@ +import { type ReactNode } from "react"; +import { executeGraphQL } from "@/lib/graphql"; +import { ChannelsListDocument } from "@/gql/graphql"; + +export const generateStaticParams = async () => { + // the `channels` query is protected + // you can either hardcode the channels or use an app token to fetch the channel list here + + if (process.env.SALEOR_APP_TOKEN) { + const channels = await executeGraphQL(ChannelsListDocument, { + withAuth: false, // disable cookie-based auth for this call + headers: { + // and use app token instead + Authorization: `Bearer ${process.env.SALEOR_APP_TOKEN}`, + }, + }); + return ( + channels.channels + ?.filter((channel) => channel.isActive) + .map((channel) => ({ channel: channel.slug })) ?? [] + ); + } else { + return [{ channel: "default-channel" }]; + } +}; + +export default function ChannelLayout({ children }: { children: ReactNode }) { + return children; +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 000000000..722f46db6 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function EmptyPage() { + redirect("/default-channel"); +} diff --git a/src/checkout/lib/regions.ts b/src/checkout/lib/regions.ts index 8267898d8..2b6c5f70d 100644 --- a/src/checkout/lib/regions.ts +++ b/src/checkout/lib/regions.ts @@ -2,6 +2,4 @@ export const locales = ["en-US"] as const; export const DEFAULT_LOCALE = "en-US"; -export const DEFAULT_CHANNEL = "default-channel"; - export type Locale = (typeof locales)[number]; diff --git a/src/checkout/lib/utils/url.ts b/src/checkout/lib/utils/url.ts index f1c82cd75..1a78bd11c 100644 --- a/src/checkout/lib/utils/url.ts +++ b/src/checkout/lib/utils/url.ts @@ -1,5 +1,4 @@ import queryString from "query-string"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; import { type CountryCode } from "@/checkout/graphql"; import { type MightNotExist } from "@/checkout/lib/globalTypes"; @@ -38,10 +37,6 @@ type RawQueryParams = Record & CustomTypedQ export type QueryParams = Record & CustomTypedQueryParams; -const defaultParams: Partial = { - channel: DEFAULT_CHANNEL, -}; - // this is intentional, we know what we'll get from the query but // queryString has no way to type this in such a specific way export const getRawQueryParams = () => queryString.parse(location.search) as unknown as RawQueryParams; @@ -52,7 +47,7 @@ export const getQueryParams = (): QueryParams => { return Object.entries(params).reduce((result, entry) => { const [paramName, paramValue] = entry as [UnmappedQueryParam, ParamBasicValue]; const mappedParamName = queryParamsMap[paramName]; - const mappedParamValue = paramValue || defaultParams[paramName]; + const mappedParamValue = paramValue; return { ...result, diff --git a/src/graphql/ChannelsList.graphql b/src/graphql/ChannelsList.graphql new file mode 100644 index 000000000..299e365e8 --- /dev/null +++ b/src/graphql/ChannelsList.graphql @@ -0,0 +1,13 @@ +query ChannelsList { + channels { + id + name + slug + isActive + currencyCode + countries { + country + code + } + } +} diff --git a/src/lib/checkout.ts b/src/lib/checkout.ts index 08432a7e6..0c1e30b74 100644 --- a/src/lib/checkout.ts +++ b/src/lib/checkout.ts @@ -1,7 +1,23 @@ -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; +import { cookies } from "next/headers"; import { CheckoutCreateDocument, CheckoutFindDocument } from "@/gql/graphql"; import { executeGraphQL } from "@/lib/graphql"; +export function getIdFromCookies(channel: string) { + const cookieName = `checkoutId-${channel}`; + const checkoutId = cookies().get(cookieName)?.value || ""; + return checkoutId; +} + +export function saveIdToCookie(channel: string, checkoutId: string) { + const shouldUseHttps = + process.env.NEXT_PUBLIC_STOREFRONT_URL?.startsWith("https") || !!process.env.NEXT_PUBLIC_VERCEL_URL; + const cookieName = `checkoutId-${channel}`; + cookies().set(cookieName, checkoutId, { + sameSite: "lax", + secure: shouldUseHttps, + }); +} + export async function find(checkoutId: string) { try { const { checkout } = checkoutId @@ -19,13 +35,13 @@ export async function find(checkoutId: string) { } } -export async function findOrCreate(checkoutId?: string) { +export async function findOrCreate({ channel, checkoutId }: { checkoutId?: string; channel: string }) { if (!checkoutId) { - return (await create()).checkoutCreate?.checkout; + return (await create({ channel })).checkoutCreate?.checkout; } const checkout = await find(checkoutId); - return checkout || (await create()).checkoutCreate?.checkout; + return checkout || (await create({ channel })).checkoutCreate?.checkout; } -export const create = () => - executeGraphQL(CheckoutCreateDocument, { cache: "no-cache", variables: { channel: DEFAULT_CHANNEL } }); +export const create = ({ channel }: { channel: string }) => + executeGraphQL(CheckoutCreateDocument, { cache: "no-cache", variables: { channel } }); diff --git a/src/lib/graphql.ts b/src/lib/graphql.ts index 09d3edbc0..6ede85b4b 100644 --- a/src/lib/graphql.ts +++ b/src/lib/graphql.ts @@ -48,6 +48,7 @@ export async function executeGraphQL( return ""; } })(); + console.error(input.body); throw new HTTPError(response, body); } diff --git a/src/ui/atoms/LinkWithChannel.tsx b/src/ui/atoms/LinkWithChannel.tsx new file mode 100644 index 000000000..484d66704 --- /dev/null +++ b/src/ui/atoms/LinkWithChannel.tsx @@ -0,0 +1,19 @@ +"use client"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +import { type ComponentProps } from "react"; + +export const LinkWithChannel = ({ + href, + ...props +}: Omit, "href"> & { href: string }) => { + const { channel } = useParams<{ channel?: string }>(); + + if (!href.startsWith("/")) { + return ; + } + + const encodedChannel = encodeURIComponent(channel ?? ""); + const hrefWithChannel = `/${encodedChannel}${href}`; + return ; +}; diff --git a/src/ui/components/Footer.tsx b/src/ui/components/Footer.tsx index 864e24744..f5d9c1b64 100644 --- a/src/ui/components/Footer.tsx +++ b/src/ui/components/Footer.tsx @@ -1,11 +1,10 @@ -import Link from "next/link"; +import { LinkWithChannel } from "../atoms/LinkWithChannel"; import { MenuGetBySlugDocument } from "@/gql/graphql"; import { executeGraphQL } from "@/lib/graphql"; -import { DEFAULT_CHANNEL } from "@/checkout/lib/regions"; -export async function Footer() { +export async function Footer({ channel }: { channel: string }) { const footerLinks = await executeGraphQL(MenuGetBySlugDocument, { - variables: { slug: "footer", channel: DEFAULT_CHANNEL }, + variables: { slug: "footer", channel }, revalidate: 60 * 60 * 24, }); const currentYear = new Date().getFullYear(); @@ -23,28 +22,34 @@ export async function Footer() { if (child.category) { return (
  • - {child.category.name} + + {child.category.name} +
  • ); } if (child.collection) { return (
  • - {child.collection.name} + + {child.collection.name} +
  • ); } if (child.page) { return (
  • - {child.page.title} + + {child.page.title} +
  • ); } if (child.url) { return (
  • - {child.name} + {child.name}
  • ); } diff --git a/src/ui/components/Header.tsx b/src/ui/components/Header.tsx index bfa5f42ed..930143a49 100644 --- a/src/ui/components/Header.tsx +++ b/src/ui/components/Header.tsx @@ -1,13 +1,13 @@ import { Logo } from "./Logo"; import { Nav } from "./nav/Nav"; -export function Header() { +export function Header({ channel }: { channel: string }) { return (
    -
    diff --git a/src/ui/components/Logo.tsx b/src/ui/components/Logo.tsx index c318cc26d..321a5021e 100644 --- a/src/ui/components/Logo.tsx +++ b/src/ui/components/Logo.tsx @@ -1,7 +1,7 @@ "use client"; -import Link from "next/link"; import { usePathname } from "next/navigation"; +import { LinkWithChannel } from "../atoms/LinkWithChannel"; const companyName = "ACME"; @@ -17,9 +17,9 @@ export const Logo = () => { } return (
    - + {companyName} - +
    ); }; diff --git a/src/ui/components/OrderListItem.tsx b/src/ui/components/OrderListItem.tsx index 1bb2c155a..2b6a8cd82 100644 --- a/src/ui/components/OrderListItem.tsx +++ b/src/ui/components/OrderListItem.tsx @@ -1,5 +1,5 @@ import Image from "next/image"; -import Link from "next/link"; +import { LinkWithChannel } from "../atoms/LinkWithChannel"; import { formatDate, formatMoney, getHrefForVariant } from "@/lib/utils"; import { type OrderDetailsFragment } from "@/gql/graphql"; import { PaymentStatus } from "@/ui/components/PaymentStatus"; @@ -32,12 +32,12 @@ export const OrderListItem = ({ order }: Props) => { {/* TODO: Reveal after implementing the order details page. */} {/*
    - View Order - +
    */}
    @@ -76,7 +76,7 @@ export const OrderListItem = ({ order }: Props) => {
    )}
    - { className="font-medium text-neutral-900" > {product.name} - + {item.variant.name !== item.variant.id && Boolean(item.variant.name) && (

    Variant: {item.variant.name}

    )} diff --git a/src/ui/components/Pagination.tsx b/src/ui/components/Pagination.tsx index f15fdb17d..5b61dd59b 100644 --- a/src/ui/components/Pagination.tsx +++ b/src/ui/components/Pagination.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import Link from "next/link"; +import { LinkWithChannel } from "../atoms/LinkWithChannel"; export async function Pagination({ pageInfo, @@ -12,7 +12,7 @@ export async function Pagination({ }) { return ( ); } diff --git a/src/ui/components/ProductElement.tsx b/src/ui/components/ProductElement.tsx index 7a8b4b89f..43ff2d340 100644 --- a/src/ui/components/ProductElement.tsx +++ b/src/ui/components/ProductElement.tsx @@ -1,4 +1,4 @@ -import Link from "next/link"; +import { LinkWithChannel } from "../atoms/LinkWithChannel"; import { ProductImageWrapper } from "@/ui/atoms/ProductImageWrapper"; import type { ProductListItemFragment } from "@/gql/graphql"; @@ -11,7 +11,7 @@ export function ProductElement({ }: { product: ProductListItemFragment } & { loading: "eager" | "lazy"; priority?: boolean }) { return (
  • - +
    {product?.thumbnail?.url && (
  • - + ); } diff --git a/src/ui/components/VariantSelector.tsx b/src/ui/components/VariantSelector.tsx index 8671b11f1..422d0a06c 100644 --- a/src/ui/components/VariantSelector.tsx +++ b/src/ui/components/VariantSelector.tsx @@ -1,6 +1,6 @@ import { clsx } from "clsx"; -import Link from "next/link"; import { redirect } from "next/navigation"; +import { LinkWithChannel } from "../atoms/LinkWithChannel"; import { type ProductListItemFragment, type VariantDetailsFragment } from "@/gql/graphql"; import { getHrefForVariant } from "@/lib/utils"; @@ -8,13 +8,15 @@ export function VariantSelector({ variants, product, selectedVariant, + channel, }: { variants: readonly VariantDetailsFragment[]; product: ProductListItemFragment; selectedVariant?: VariantDetailsFragment; + channel: string; }) { if (!selectedVariant && variants.length === 1 && variants[0]?.quantityAvailable) { - redirect(getHrefForVariant({ productSlug: product.slug, variantId: variants[0].id })); + redirect("/" + channel + getHrefForVariant({ productSlug: product.slug, variantId: variants[0].id })); } return ( @@ -26,7 +28,7 @@ export function VariantSelector({ const isDisabled = !variant.quantityAvailable; const isCurrentVariant = selectedVariant?.id === variant.id; return ( - {variant.name} - + ); })}
    diff --git a/src/ui/components/nav/Nav.tsx b/src/ui/components/nav/Nav.tsx index 9c21b7320..82640ab22 100644 --- a/src/ui/components/nav/Nav.tsx +++ b/src/ui/components/nav/Nav.tsx @@ -5,15 +5,15 @@ import { NavLinks } from "./components/NavLinks"; import { MobileMenu } from "./components/MobileMenu"; import { SearchBar } from "./components/SearchBar"; -export const Nav = () => { +export const Nav = ({ channel }: { channel: string }) => { return ( diff --git a/src/ui/components/nav/components/CartNavItem.tsx b/src/ui/components/nav/components/CartNavItem.tsx index bc49278ee..f4362d40e 100644 --- a/src/ui/components/nav/components/CartNavItem.tsx +++ b/src/ui/components/nav/components/CartNavItem.tsx @@ -1,17 +1,16 @@ -import { cookies } from "next/headers"; import { ShoppingBagIcon } from "lucide-react"; -import Link from "next/link"; import clsx from "clsx"; import * as Checkout from "@/lib/checkout"; +import { LinkWithChannel } from "@/ui/atoms/LinkWithChannel"; -export const CartNavItem = async () => { - const checkoutId = cookies().get("checkoutId")?.value; +export const CartNavItem = async ({ channel }: { channel: string }) => { + const checkoutId = Checkout.getIdFromCookies(channel); const checkout = checkoutId ? await Checkout.find(checkoutId) : null; const lineCount = checkout ? checkout.lines.reduce((result, line) => result + line.quantity, 0) : 0; return ( - +