diff --git a/examples/algolia/next.config.js b/examples/algolia/next.config.js index 0ccb3ba7..be97d613 100644 --- a/examples/algolia/next.config.js +++ b/examples/algolia/next.config.js @@ -4,8 +4,17 @@ * @type {import('next').NextConfig} **/ const nextConfig = { + experimental: { + serverActions: true, + }, images: { - domains: ["files-eu.epusercontent.com", "files-na.epusercontent.com"], + formats: ["image/avif", "image/webp"], + remotePatterns: [ + { + protocol: "https", + hostname: "**.epusercontent.com", + }, + ], }, i18n: { locales: ["en"], @@ -16,11 +25,6 @@ const nextConfig = { ...config.resolve.fallback, fs: false, }; - config.module.rules.push({ - test: /\.svg$/i, - issuer: /\.[jt]sx?$/, - use: ["@svgr/webpack"], - }); return config; }, diff --git a/examples/algolia/package.json b/examples/algolia/package.json index 0164ac44..f464c58f 100644 --- a/examples/algolia/package.json +++ b/examples/algolia/package.json @@ -22,24 +22,25 @@ "dependencies": { "@algolia/react-instantsearch-widget-color-refinement-list": "^1.4.7", "@elasticpath/react-shopper-hooks": "0.5.1", + "@elasticpath/shopper-common": "0.1.1", "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@moltin/sdk": "^25.0.2", "algoliasearch": "^4.14.2", "clsx": "^1.2.1", - "cookies-next": "^2.1.1", - "dequal": "^2.0.3", + "cookies-next": "^4.0.0", "focus-visible": "^5.2.0", "formik": "^2.2.9", - "next": "^12.2.5", + "next": "^13.5.6", "pure-react-carousel": "^1.29.0", "rc-slider": "^10.3.0", "react": "^18.2.0", "react-device-detect": "^2.2.2", "react-dom": "^18.2.0", - "react-instantsearch-hooks-server": "6.38.1", - "react-instantsearch-hooks-web": "6.38.1", + "react-instantsearch": "^7.2.0", + "react-instantsearch-hooks": "^6.47.3", "react-toastify": "^9.1.3", + "server-only": "^0.0.1", "zod": "^3.22.4", "zod-formik-adapter": "^1.2.0" }, @@ -57,6 +58,7 @@ "@vitest/coverage-istanbul": "^0.34.5", "autoprefixer": "^10.4.14", "babel-loader": "^8.2.5", + "encoding": "^0.1.13", "eslint": "^8.49.0", "eslint-config-next": "^13.5.2", "eslint-config-prettier": "^9.0.0", diff --git a/examples/algolia/public/150-placeholder.png b/examples/algolia/public/150-placeholder.png deleted file mode 100644 index 61cd0d97..00000000 Binary files a/examples/algolia/public/150-placeholder.png and /dev/null differ diff --git a/examples/algolia/public/icons/empty.svg b/examples/algolia/public/icons/empty.svg deleted file mode 100644 index 48a29670..00000000 --- a/examples/algolia/public/icons/empty.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/algolia/public/icons/ep-icon.svg b/examples/algolia/public/icons/ep-icon.svg deleted file mode 100644 index 6250de53..00000000 --- a/examples/algolia/public/icons/ep-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/examples/algolia/public/icons/ep-logo.svg b/examples/algolia/public/icons/ep-logo.svg deleted file mode 100644 index c2498f42..00000000 --- a/examples/algolia/public/icons/ep-logo.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/examples/algolia/public/icons/github.svg b/examples/algolia/public/icons/github.svg deleted file mode 100644 index 21a72e8c..00000000 --- a/examples/algolia/public/icons/github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/algolia/src/app/about/page.tsx b/examples/algolia/src/app/about/page.tsx new file mode 100644 index 00000000..edda2df8 --- /dev/null +++ b/examples/algolia/src/app/about/page.tsx @@ -0,0 +1,5 @@ +import Blurb from "../../components/shared/blurb"; + +export default function About() { + return ; +} diff --git a/examples/algolia/src/app/cart/page.tsx b/examples/algolia/src/app/cart/page.tsx new file mode 100644 index 00000000..4dc53f4b --- /dev/null +++ b/examples/algolia/src/app/cart/page.tsx @@ -0,0 +1,42 @@ +"use client"; +import Cart from "../../components/cart/Cart"; +import CartIcon from "../../components/icons/cart"; +import { useCart } from "@elasticpath/react-shopper-hooks"; +import { resolveShoppingCartProps } from "../../lib/resolve-shopping-cart-props"; +import Link from "next/link"; + +export default function CartPage() { + const { removeCartItem, state } = useCart(); + const shoppingCartProps = resolveShoppingCartProps(state, removeCartItem); + + return ( +
+ {shoppingCartProps && ( + <> +

Your Shopping Cart

+ + + )} + {(state.kind === "empty-cart-state" || + state.kind === "uninitialised-cart-state" || + state.kind === "loading-cart-state") && ( +
+ +

+ Empty Cart +

+

Your cart is empty

+
+ + Start shopping + +
+
+ )} +
+ ); +} diff --git a/examples/algolia/src/pages/configuration-error.tsx b/examples/algolia/src/app/configuration-error/page.tsx similarity index 60% rename from examples/algolia/src/pages/configuration-error.tsx rename to examples/algolia/src/app/configuration-error/page.tsx index cc321880..5b9a5dba 100644 --- a/examples/algolia/src/pages/configuration-error.tsx +++ b/examples/algolia/src/app/configuration-error/page.tsx @@ -1,22 +1,35 @@ import Link from "next/link"; -import { GetServerSideProps, NextPage } from "next"; +import { Metadata } from "next"; -interface IConfigurationError { - from?: string; - issues?: Record; -} +export const metadata: Metadata = { + title: "Configuration Error", + description: "Configuration error page", +}; + +type Props = { + searchParams: { [key: string]: string | string[] | undefined }; +}; + +export default function ConfigurationErrorPage({ searchParams }: Props) { + const { + "missing-env-variable": missingEnvVariables, + authentication, + from, + } = searchParams; + + const issues: { [key: string]: string | string[] } = { + ...(missingEnvVariables && { missingEnvVariables }), + ...(authentication && { authentication }), + }; + const fromProcessed = Array.isArray(from) ? from[0] : from; -export const ConfigurationError: NextPage = ({ - issues, - from, -}: IConfigurationError) => { return (
There is a problem with the stores setup Refresh @@ -38,9 +51,9 @@ export const ConfigurationError: NextPage = ({
    {(Array.isArray(issue) ? issue : [issue]).map( - (messsage) => ( -
  • - {decodeURIComponent(messsage)} + (message) => ( +
  • + {decodeURIComponent(message)}
  • ), )} @@ -53,23 +66,4 @@ export const ConfigurationError: NextPage = ({
); -}; - -export default ConfigurationError; - -export const getServerSideProps: GetServerSideProps = async ({ query }) => { - const { - "missing-env-variable": missingEnvVariables, - authentication, - from, - } = query; - return { - props: { - issues: { - ...(missingEnvVariables && { missingEnvVariables }), - ...(authentication && { authentication }), - }, - from: Array.isArray(from) ? from[0] : from, - }, - }; -}; +} diff --git a/examples/algolia/src/app/error.tsx b/examples/algolia/src/app/error.tsx new file mode 100644 index 00000000..f4724026 --- /dev/null +++ b/examples/algolia/src/app/error.tsx @@ -0,0 +1,31 @@ +"use client"; +import Link from "next/link"; + +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + + +
+ + {error.digest} - Internal server error. + + + Back to home + + +
+ + + ); +} diff --git a/examples/algolia/src/app/faq/page.tsx b/examples/algolia/src/app/faq/page.tsx new file mode 100644 index 00000000..7aac9fba --- /dev/null +++ b/examples/algolia/src/app/faq/page.tsx @@ -0,0 +1,5 @@ +import Blurb from "../../components/shared/blurb"; + +export default function FAQ() { + return ; +} diff --git a/examples/algolia/src/app/layout.tsx b/examples/algolia/src/app/layout.tsx new file mode 100644 index 00000000..b4b06802 --- /dev/null +++ b/examples/algolia/src/app/layout.tsx @@ -0,0 +1,59 @@ +import { Inter } from "next/font/google"; +import { ReactNode, Suspense } from "react"; +import "../styles/globals.css"; +import Header from "../components/header/Header"; +import { getStoreContext } from "../lib/get-store-context"; +import { getServerSideImplicitClient } from "../lib/epcc-server-side-implicit-client"; +import { Providers } from "./providers"; +import { Toaster } from "../components/toast/toaster"; +import Footer from "../components/footer/Footer"; + +const { SITE_NAME } = process.env; +const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL + ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` + : "http://localhost:3000"; + +export const metadata = { + metadataBase: new URL(baseUrl), + title: { + default: SITE_NAME!, + template: `%s | ${SITE_NAME}`, + }, + robots: { + follow: true, + index: true, + }, +}; + +const inter = Inter({ + subsets: ["latin"], + display: "swap", + variable: "--font-inter", +}); + +export default async function RootLayout({ + children, +}: { + children: ReactNode; +}) { + const client = getServerSideImplicitClient(); + const storeContext = await getStoreContext(client); + + return ( + + + {/* headless ui needs this div - https://github.com/tailwindlabs/headlessui/issues/2752#issuecomment-1745272229 */} +
+ +
+ + +
{children}
+
+
+ +
+ + + ); +} diff --git a/examples/algolia/src/pages/404.tsx b/examples/algolia/src/app/not-found.tsx similarity index 90% rename from examples/algolia/src/pages/404.tsx rename to examples/algolia/src/app/not-found.tsx index 84e2e0e5..d97d88f4 100644 --- a/examples/algolia/src/pages/404.tsx +++ b/examples/algolia/src/app/not-found.tsx @@ -1,6 +1,5 @@ import Link from "next/link"; - -export default function Custom404() { +export default function NotFound() { return (
diff --git a/examples/algolia/src/app/page.tsx b/examples/algolia/src/app/page.tsx new file mode 100644 index 00000000..dfd06746 --- /dev/null +++ b/examples/algolia/src/app/page.tsx @@ -0,0 +1,38 @@ +import PromotionBanner from "../components/promotion-banner/PromotionBanner"; +import FeaturedProducts from "../components/featured-products/FeaturedProducts"; +import { Suspense } from "react"; + +export default async function Home() { + const promotion = { + title: "Your Elastic Path storefront", + description: + "This marks the beginning, embark on the journey of crafting something truly extraordinary, uniquely yours.", + }; + + return ( +
+ +
+
+
+ + + +
+
+
+
+ ); +} diff --git a/examples/algolia/src/app/products/[productId]/page.tsx b/examples/algolia/src/app/products/[productId]/page.tsx new file mode 100644 index 00000000..f0a54aed --- /dev/null +++ b/examples/algolia/src/app/products/[productId]/page.tsx @@ -0,0 +1,48 @@ +import { Metadata } from "next"; +import { ProductDisplay } from "./product-display"; +import { getServerSideImplicitClient } from "../../../lib/epcc-server-side-implicit-client"; +import { getProductById } from "../../../services/products"; +import { notFound } from "next/navigation"; +import { parseProductResponse } from "@elasticpath/shopper-common"; + +export const dynamic = "force-dynamic"; + +type Props = { + params: { productId: string }; +}; + +export async function generateMetadata({ + params: { productId }, +}: Props): Promise { + const client = getServerSideImplicitClient(); + const product = await getProductById(productId, client); + + if (!product) { + notFound(); + } + + return { + title: product.data.attributes.name, + description: product.data.attributes.description, + }; +} + +export default async function ProductPage({ params }: Props) { + const client = getServerSideImplicitClient(); + const product = await getProductById(params.productId, client); + + if (!product) { + notFound(); + } + + const shopperProduct = await parseProductResponse(product, client); + + return ( +
+ +
+ ); +} diff --git a/examples/algolia/src/app/products/[productId]/product-display.tsx b/examples/algolia/src/app/products/[productId]/product-display.tsx new file mode 100644 index 00000000..2664951b --- /dev/null +++ b/examples/algolia/src/app/products/[productId]/product-display.tsx @@ -0,0 +1,39 @@ +"use client"; +import React, { ReactElement, useState } from "react"; +import { ShopperProduct } from "@elasticpath/react-shopper-hooks"; +import { VariationProductDetail } from "../../../components/product/variations/VariationProduct"; +import BundleProductDetail from "../../../components/product/bundles/BundleProduct"; +import { ProductContext } from "../../../lib/product-context"; +import SimpleProductDetail from "../../../components/product/SimpleProduct"; + +export function ProductDisplay({ + product, +}: { + product: ShopperProduct; +}): ReactElement { + const [isChangingSku, setIsChangingSku] = useState(false); + + return ( + + {resolveProductDetailComponent(product)} + + ); +} + +function resolveProductDetailComponent(product: ShopperProduct): JSX.Element { + switch (product.kind) { + case "base-product": + return ; + case "child-product": + return ; + case "simple-product": + return ; + case "bundle-product": + return ; + } +} diff --git a/examples/algolia/src/app/providers.tsx b/examples/algolia/src/app/providers.tsx new file mode 100644 index 00000000..5bcdc503 --- /dev/null +++ b/examples/algolia/src/app/providers.tsx @@ -0,0 +1,17 @@ +"use client"; + +import StoreNextJSProvider from "../lib/providers/store-provider"; +import { ReactNode } from "react"; +import { StoreContext } from "@elasticpath/react-shopper-hooks"; + +export function Providers({ + children, + store, +}: { + children: ReactNode; + store: StoreContext; +}) { + return ( + {children} + ); +} diff --git a/examples/algolia/src/app/search/[[...node]]/layout.tsx b/examples/algolia/src/app/search/[[...node]]/layout.tsx new file mode 100644 index 00000000..9e1fb2bd --- /dev/null +++ b/examples/algolia/src/app/search/[[...node]]/layout.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from "react"; +import Breadcrumb from "../../../components/breadcrumb"; + +export default function SearchLayout({ children }: { children: ReactNode }) { + return ( +
+ + {children} +
+ ); +} diff --git a/examples/algolia/src/app/search/[[...node]]/page.tsx b/examples/algolia/src/app/search/[[...node]]/page.tsx new file mode 100644 index 00000000..a3450b11 --- /dev/null +++ b/examples/algolia/src/app/search/[[...node]]/page.tsx @@ -0,0 +1,13 @@ +import { Search } from "../search"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Search", + description: "Search for products", +}; + +export const dynamic = "force-dynamic"; + +export default function SearchPage() { + return ; +} diff --git a/examples/algolia/src/app/search/search.tsx b/examples/algolia/src/app/search/search.tsx new file mode 100644 index 00000000..c0f077f0 --- /dev/null +++ b/examples/algolia/src/app/search/search.tsx @@ -0,0 +1,77 @@ +"use client"; +import { searchClient } from "../../lib/search-client"; +import { InstantSearchNext } from "react-instantsearch-nextjs"; +import { algoliaEnvData } from "../../lib/resolve-algolia-env"; +import { resolveAlgoliaRouting } from "../../lib/algolia-search-routing"; +import SearchResults from "../../components/search/SearchResults"; +import React from "react"; +import { buildBreadcrumbLookup } from "../../lib/build-breadcrumb-lookup"; +import { useStore } from "@elasticpath/react-shopper-hooks"; +import { + HierarchicalMenuProps, + PaginationProps, + RangeInputProps, + RefinementListProps, + SearchBoxProps, + SortByProps, + useHierarchicalMenu, + usePagination, + useRange, + useRefinementList, + useSearchBox, + useSortBy, +} from "react-instantsearch"; +import { sortByItems } from "../../lib/sort-by-items"; +import { hierarchicalAttributes } from "../../lib/hierarchical-attributes"; + +export function Search() { + const { nav } = useStore(); + const lookup = buildBreadcrumbLookup(nav ?? []); + + return ( + + {/* Virtual widgets are here as a workaround for this issue https://github.com/algolia/instantsearch/issues/5890 */} + + + + + + + + + ); +} + +function VirtualHierarchicalMenu(props: HierarchicalMenuProps) { + useHierarchicalMenu(props); + return null; +} +function VirtualSearchBox(props: SearchBoxProps) { + useSearchBox(props); + return null; +} +function VirtualPagination(props: PaginationProps) { + usePagination(props); + return null; +} +function VirtualSortBy(props: SortByProps) { + useSortBy(props); + return null; +} + +function VirtualRangeInput(props: RangeInputProps) { + useRange(props); + return null; +} + +function VirtualRefinementList(props: RefinementListProps) { + useRefinementList(props); + return null; +} diff --git a/examples/algolia/src/app/shipping/page.tsx b/examples/algolia/src/app/shipping/page.tsx new file mode 100644 index 00000000..7be31b80 --- /dev/null +++ b/examples/algolia/src/app/shipping/page.tsx @@ -0,0 +1,5 @@ +import Blurb from "../../components/shared/blurb"; + +export default function Shipping() { + return ; +} diff --git a/examples/algolia/src/app/terms/page.tsx b/examples/algolia/src/app/terms/page.tsx new file mode 100644 index 00000000..8efeb5e7 --- /dev/null +++ b/examples/algolia/src/app/terms/page.tsx @@ -0,0 +1,5 @@ +import Blurb from "../../components/shared/blurb"; + +export default function Terms() { + return ; +} diff --git a/examples/algolia/src/components/search/NoImage.tsx b/examples/algolia/src/components/NoImage.tsx similarity index 58% rename from examples/algolia/src/components/search/NoImage.tsx rename to examples/algolia/src/components/NoImage.tsx index 8b90b5f8..bf84f253 100644 --- a/examples/algolia/src/components/search/NoImage.tsx +++ b/examples/algolia/src/components/NoImage.tsx @@ -3,8 +3,8 @@ import { EyeSlashIcon } from "@heroicons/react/24/solid"; export const NoImage = (): JSX.Element => { return ( -
- +
+
); }; diff --git a/examples/algolia/src/components/Spinner.tsx b/examples/algolia/src/components/Spinner.tsx index 916be6ba..9bd90a3e 100644 --- a/examples/algolia/src/components/Spinner.tsx +++ b/examples/algolia/src/components/Spinner.tsx @@ -10,9 +10,7 @@ const Spinner = (props: IProps) => { aria-hidden="true" className={`${props.absolute ? "absolute right-0 top-0" : "relative"} ${ props.width - } ${ - props.height - } animate-spin fill-brand-primary text-gray-200 dark:text-gray-600`} + } ${props.height} animate-spin fill-brand-secondary text-brand-primary`} viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg" diff --git a/examples/algolia/src/components/breadcrumb.tsx b/examples/algolia/src/components/breadcrumb.tsx index 91da88f2..950c0130 100644 --- a/examples/algolia/src/components/breadcrumb.tsx +++ b/examples/algolia/src/components/breadcrumb.tsx @@ -1,23 +1,31 @@ -import { BreadcrumbEntry } from "../lib/create-breadcrumb"; +"use client"; +import { createBreadcrumb } from "../lib/create-breadcrumb"; import Link from "next/link"; +import { useStore } from "@elasticpath/react-shopper-hooks"; +import { buildBreadcrumbLookup } from "../lib/build-breadcrumb-lookup"; +import { usePathname } from "next/navigation"; -interface IBreadcrumb { - entries: BreadcrumbEntry[]; -} +export default function Breadcrumb(): JSX.Element { + const { nav } = useStore(); + const pathname = usePathname(); + const lookup = buildBreadcrumbLookup(nav ?? []); + const nodes = pathname.replace("/search/", "")?.split("/"); + const crumbs = createBreadcrumb(nodes, lookup); -export default function Breadcrumb({ entries }: IBreadcrumb): JSX.Element { return (
    - {entries.length > 1 && - entries.map((entry, index, array) => ( + {crumbs.length > 1 && + crumbs.map((entry, index, array) => (
  1. {array.length === index + 1 ? ( {entry.label} ) : ( - - - {entry.label} - + + {entry.label} )} {array.length !== index + 1 && /} diff --git a/examples/algolia/src/components/cart/CartItemList.tsx b/examples/algolia/src/components/cart/CartItemList.tsx index 99fb1052..71c8c0dc 100644 --- a/examples/algolia/src/components/cart/CartItemList.tsx +++ b/examples/algolia/src/components/cart/CartItemList.tsx @@ -19,22 +19,26 @@ export function CartItemList({
    {items.map((item) => (
    -
    +
    {item.image?.href && ( {item.name} )}
    -
    +
    {item.name} @@ -42,16 +46,18 @@ export function CartItemList({ {item.meta.display_price.without_tax.unit.formatted}
    - - { - handleRemoveItem(item.id); - }} - /> +
    + + { + handleRemoveItem(item.id); + }} + /> +
    ))}
    diff --git a/examples/algolia/src/components/cart/CartOrderSummary.tsx b/examples/algolia/src/components/cart/CartOrderSummary.tsx index 077fa4b8..891304ef 100644 --- a/examples/algolia/src/components/cart/CartOrderSummary.tsx +++ b/examples/algolia/src/components/cart/CartOrderSummary.tsx @@ -68,10 +68,10 @@ export function CartOrderSummary({
    - + - +
    diff --git a/examples/algolia/src/components/featured-products/FeaturedProducts.tsx b/examples/algolia/src/components/featured-products/FeaturedProducts.tsx index 2fd5ea4c..d8ca7240 100644 --- a/examples/algolia/src/components/featured-products/FeaturedProducts.tsx +++ b/examples/algolia/src/components/featured-products/FeaturedProducts.tsx @@ -1,13 +1,12 @@ -import React, { useCallback, useEffect, useState } from "react"; -import type { ProductResponseWithImage } from "../../lib/types/product-types"; -import { connectProductsWithMainImages } from "../../lib/product-util"; -import { getProducts } from "../../services/products"; +"use server"; import clsx from "clsx"; import Link from "next/link"; import { ArrowRightIcon, EyeSlashIcon } from "@heroicons/react/24/outline"; import Image from "next/image"; +import { getServerSideImplicitClient } from "../../lib/epcc-server-side-implicit-client"; +import { fetchFeaturedProducts } from "./fetchFeaturedProducts"; -interface IFeaturedProductsBaseProps { +interface IFeaturedProductsProps { title: string; linkProps?: { link: string; @@ -15,48 +14,12 @@ interface IFeaturedProductsBaseProps { }; } -interface IFeaturedProductsProvidedProps extends IFeaturedProductsBaseProps { - type: "provided"; - products: ProductResponseWithImage[]; -} - -interface IFeaturedProductsFetchProps extends IFeaturedProductsBaseProps { - type: "fetch"; -} - -type IFeaturedProductsProps = - | IFeaturedProductsFetchProps - | IFeaturedProductsProvidedProps; - -const FeaturedProducts = (props: IFeaturedProductsProps): JSX.Element => { - const { type, title, linkProps } = props; - - const [products, setProducts] = useState( - type === "provided" ? props.products : [], - ); - - const fetchNodeProducts = useCallback(async () => { - if (type === "fetch") { - const { data, included } = await getProducts(); - let products = data.slice(0, 4); - if (included?.main_images) { - products = connectProductsWithMainImages( - products, - included.main_images, - ); - } - setProducts(products); - } - }, [type]); - - useEffect(() => { - try { - fetchNodeProducts(); - } catch (error) { - console.error(error); - throw error; - } - }, [fetchNodeProducts]); +export default async function FeaturedProducts({ + title, + linkProps, +}: IFeaturedProductsProps) { + const client = getServerSideImplicitClient(); + const products = await fetchFeaturedProducts(client); return (
    { "max-w-7xl my-0 mx-auto", )} > -
    +

    {title}

    {linkProps && ( @@ -80,42 +43,44 @@ const FeaturedProducts = (props: IFeaturedProductsProps): JSX.Element => { )}
    -
    +
      {products.map((product) => ( - -
      - {product.main_image?.link.href ? ( - {product.main_image?.file_name - ) : ( -
      - + +
    • +
      +
      + {product.main_image?.link.href ? ( + {product.main_image?.file_name!} + ) : ( +
      + +
      + )}
      - )} - -

      +

      +

      {product.attributes.name} - -

      +

      +

      {product.meta.display_price?.without_tax.formatted} -

      -
    • +

      + ))} -
      +
    ); -}; - -export default FeaturedProducts; +} diff --git a/examples/algolia/src/components/featured-products/fetchFeaturedProducts.ts b/examples/algolia/src/components/featured-products/fetchFeaturedProducts.ts index 7f2c0bb9..9a5679e6 100644 --- a/examples/algolia/src/components/featured-products/fetchFeaturedProducts.ts +++ b/examples/algolia/src/components/featured-products/fetchFeaturedProducts.ts @@ -1,10 +1,14 @@ -// Fetching the first 4 products of in the catalog to display in the featured-products component -import { connectProductsWithMainImages } from "../../lib/product-util"; import { getProducts } from "../../services/products"; +import { Moltin } from "@moltin/sdk"; +import { ProductResponseWithImage } from "../../lib/types/product-types"; +import { connectProductsWithMainImages } from "../../lib/connect-products-with-main-images"; -export const fetchFeaturedProducts = async () => { +// Fetching the first 4 products of in the catalog to display in the featured-products component +export const fetchFeaturedProducts = async ( + client: Moltin, +): Promise => { const { data: productsResponse, included: productsIncluded } = - await getProducts(); + await getProducts(client); return productsIncluded?.main_images ? connectProductsWithMainImages( diff --git a/examples/algolia/src/components/footer/Footer.tsx b/examples/algolia/src/components/footer/Footer.tsx index f5eae085..46759f53 100644 --- a/examples/algolia/src/components/footer/Footer.tsx +++ b/examples/algolia/src/components/footer/Footer.tsx @@ -1,32 +1,32 @@ -import GithubIcon from "../../../public/icons/github.svg"; -import EpLogo from "../../../public/icons/ep-logo.svg"; import Link from "next/link"; import { PhoneIcon, InformationCircleIcon } from "@heroicons/react/24/solid"; +import { GitHubIcon } from "../icons/github-icon"; +import EpLogo from "../icons/ep-logo"; const Footer = (): JSX.Element => (
    - +
    - - Home + + Home - - Shipping + + Shipping - - FAQ + + FAQ
    - - About + + About - - Terms + + Terms
    @@ -37,41 +37,31 @@ const Footer = (): JSX.Element => ( className="flex items-center justify-center" passHref > - - {" "} - - + {" "} + - - {" "} - - + {" "} + - - - +
    diff --git a/examples/algolia/src/components/header/Header.tsx b/examples/algolia/src/components/header/Header.tsx index 1f7bddff..f7b86fbb 100644 --- a/examples/algolia/src/components/header/Header.tsx +++ b/examples/algolia/src/components/header/Header.tsx @@ -1,33 +1,29 @@ -import { NavigationNode } from "../../lib/build-site-navigation"; import SearchModal from "../search/SearchModal"; import MobileNavBar from "./navigation/MobileNavBar"; -import EpIcon from "../../../public/icons/ep-icon.svg"; import NavBar from "./navigation/NavBar"; import Link from "next/link"; import CartMenu from "./cart/CartMenu"; +import EpIcon from "../icons/ep-icon"; +import { Suspense } from "react"; -interface IHeader { - nav: NavigationNode[]; -} - -const Header = ({ nav }: IHeader): JSX.Element => { - const headerPadding = 4; - +const Header = (): JSX.Element => { return (
    - + + +
    - - -
    - -
    -
    + +
    - + +
    + +
    +
    diff --git a/examples/algolia/src/components/header/cart/CartMenu.tsx b/examples/algolia/src/components/header/cart/CartMenu.tsx index c6d59dad..84234349 100644 --- a/examples/algolia/src/components/header/cart/CartMenu.tsx +++ b/examples/algolia/src/components/header/cart/CartMenu.tsx @@ -1,3 +1,4 @@ +"use client"; import Link from "next/link"; import ModalCartItems from "./ModalCartItem"; import { @@ -92,7 +93,7 @@ function CartPopoverFooter({ const hasCartItems = state.kind === "present-cart-state"; return (
    - + diff --git a/examples/algolia/src/components/header/navigation/MobileNavBar.tsx b/examples/algolia/src/components/header/navigation/MobileNavBar.tsx index 3c313b88..93156749 100644 --- a/examples/algolia/src/components/header/navigation/MobileNavBar.tsx +++ b/examples/algolia/src/components/header/navigation/MobileNavBar.tsx @@ -1,53 +1,24 @@ +"use server"; import Link from "next/link"; -import { NavigationNode } from "../../../lib/build-site-navigation"; - -// TODO conditionally include the search modal - include search? -// import SearchModal from "../../search/SearchModal"; import CartMenu from "../cart/CartMenu"; -import EpIcon from "../../../../public/icons/ep-icon.svg"; -import { useState } from "react"; -import NavMenu from "./NavMenu"; - -interface IMobileNavBar { - nav: NavigationNode[]; -} +import EpIcon from "../../icons/ep-icon"; +import { MobileNavBarButton } from "./MobileNavBarButton"; +import { getServerSideImplicitClient } from "../../../lib/epcc-server-side-implicit-client"; +import { buildSiteNavigation } from "../../../lib/build-site-navigation"; -const MobileNavBar = ({ nav }: IMobileNavBar): JSX.Element => { - const [showMenu, setShowMenu] = useState(false); +export default async function MobileNavBar() { + const client = getServerSideImplicitClient(); + const nav = await buildSiteNavigation(client); return (
    - {/* React */}
    - - +
    - - -
    - -
    -
    + +
    @@ -58,6 +29,4 @@ const MobileNavBar = ({ nav }: IMobileNavBar): JSX.Element => {
    ); -}; - -export default MobileNavBar; +} diff --git a/examples/algolia/src/components/header/navigation/MobileNavBarButton.tsx b/examples/algolia/src/components/header/navigation/MobileNavBarButton.tsx new file mode 100644 index 00000000..7e9392c6 --- /dev/null +++ b/examples/algolia/src/components/header/navigation/MobileNavBarButton.tsx @@ -0,0 +1,33 @@ +"use client"; +import { useState } from "react"; +import NavMenu from "./NavMenu"; +import { NavigationNode } from "@elasticpath/react-shopper-hooks"; + +export function MobileNavBarButton({ nav }: { nav: NavigationNode[] }) { + const [showMenu, setShowMenu] = useState(false); + + return ( + <> + + + + ); +} diff --git a/examples/algolia/src/components/header/navigation/NavBar.tsx b/examples/algolia/src/components/header/navigation/NavBar.tsx index 752abf74..a18dd638 100644 --- a/examples/algolia/src/components/header/navigation/NavBar.tsx +++ b/examples/algolia/src/components/header/navigation/NavBar.tsx @@ -1,48 +1,17 @@ -import { Popover, Transition } from "@headlessui/react"; -import { Fragment } from "react"; -import type { NavigationNode } from "../../../lib/build-site-navigation"; -import NavItemContent from "./NavItemContent"; +"use server"; +import { NavBarPopover } from "./NavBarPopover"; +import { getServerSideImplicitClient } from "../../../lib/epcc-server-side-implicit-client"; +import { buildSiteNavigation } from "@elasticpath/shopper-common"; -interface INavBar { - nav: NavigationNode[]; - headerPadding: number; -} +export default async function NavBar() { + const client = getServerSideImplicitClient(); + const nav = await buildSiteNavigation(client); -const NavBar = ({ nav }: INavBar): JSX.Element => { return (
    - {nav && - nav.map((item: NavigationNode, index) => ( - - {({ close }) => ( - <> - - {item.name} - - - - -
    - -
    -
    -
    - - )} -
    - ))} +
    ); -}; - -export default NavBar; +} diff --git a/examples/algolia/src/components/header/navigation/NavBarPopover.tsx b/examples/algolia/src/components/header/navigation/NavBarPopover.tsx new file mode 100644 index 00000000..b7b445c3 --- /dev/null +++ b/examples/algolia/src/components/header/navigation/NavBarPopover.tsx @@ -0,0 +1,44 @@ +"use client"; +import { Popover, Transition } from "@headlessui/react"; +import { Fragment, ReactElement } from "react"; +import NavItemContent from "./NavItemContent"; +import { NavigationNode } from "../../../lib/build-site-navigation"; + +export function NavBarPopover({ + nav, +}: { + nav: NavigationNode[]; +}): ReactElement { + return ( + <> + {nav && + nav.map((item: NavigationNode) => ( + + {({ close }) => ( + <> + + {item.name} + + + + +
    + +
    +
    +
    + + )} +
    + ))} + + ); +} diff --git a/examples/algolia/src/components/header/navigation/NavItemContent.tsx b/examples/algolia/src/components/header/navigation/NavItemContent.tsx index b25eee2c..de3ee02c 100644 --- a/examples/algolia/src/components/header/navigation/NavItemContent.tsx +++ b/examples/algolia/src/components/header/navigation/NavItemContent.tsx @@ -13,16 +13,23 @@ const NavItemContent = ({ item, onClose }: IProps): JSX.Element => {
    {item.name} {item.children.map((child: NavigationNode) => ( - - onClose()}> - {child.name} - + onClose()} + > + {child.name} ))} - - onClose()}> - Browse All - + onClose()} + > + Browse All
    ); @@ -37,17 +44,13 @@ const NavItemContent = ({ item, onClose }: IProps): JSX.Element => {

    onClose()} > - onClose()} - > - Browse All {item.name} - - + Browse All {item.name} +
    ); diff --git a/examples/algolia/src/components/header/navigation/NavMenu.tsx b/examples/algolia/src/components/header/navigation/NavMenu.tsx index 9469860c..c22dd9ca 100644 --- a/examples/algolia/src/components/header/navigation/NavMenu.tsx +++ b/examples/algolia/src/components/header/navigation/NavMenu.tsx @@ -1,3 +1,4 @@ +"use client"; import { Dispatch, SetStateAction, Fragment, useState } from "react"; import { Transition, Dialog, Disclosure } from "@headlessui/react"; import { NavigationNode } from "@elasticpath/react-shopper-hooks"; diff --git a/examples/algolia/src/components/icons/cart.tsx b/examples/algolia/src/components/icons/cart.tsx new file mode 100644 index 00000000..bbb0fe34 --- /dev/null +++ b/examples/algolia/src/components/icons/cart.tsx @@ -0,0 +1,16 @@ +import clsx from "clsx"; +import { SVGProps } from "react"; + +export default function Cart(props: SVGProps) { + return ( + + + + + ); +} diff --git a/examples/algolia/src/components/icons/ep-icon.tsx b/examples/algolia/src/components/icons/ep-icon.tsx new file mode 100644 index 00000000..ea703d5d --- /dev/null +++ b/examples/algolia/src/components/icons/ep-icon.tsx @@ -0,0 +1,20 @@ +import clsx from "clsx"; +import { SVGProps } from "react"; + +export default function EpIcon(props: SVGProps) { + return ( + + + + ); +} diff --git a/examples/algolia/src/components/icons/ep-logo.tsx b/examples/algolia/src/components/icons/ep-logo.tsx new file mode 100644 index 00000000..21bf4d4f --- /dev/null +++ b/examples/algolia/src/components/icons/ep-logo.tsx @@ -0,0 +1,31 @@ +import clsx from "clsx"; +import { SVGProps } from "react"; + +export default function EpLogo(props: SVGProps) { + return ( + + + + + + + + + + + + ); +} diff --git a/examples/algolia/src/components/icons/github-icon.tsx b/examples/algolia/src/components/icons/github-icon.tsx new file mode 100644 index 00000000..5b4ef518 --- /dev/null +++ b/examples/algolia/src/components/icons/github-icon.tsx @@ -0,0 +1,16 @@ +import * as React from "react"; +import { SVGProps } from "react"; +import clsx from "clsx"; +export function GitHubIcon(props: SVGProps) { + return ( + + + + ); +} diff --git a/examples/algolia/src/components/layouts/MainLayout.tsx b/examples/algolia/src/components/layouts/MainLayout.tsx deleted file mode 100644 index 3160598e..00000000 --- a/examples/algolia/src/components/layouts/MainLayout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import Header from "../header/Header"; -import Footer from "../footer/Footer"; -import type { ReactNode } from "react"; -import type { NavigationNode } from "../../lib/build-site-navigation"; -import { Toaster } from "../toast/toaster"; -import Head from "next/head"; -export const MAIN_LAYOUT_TITLE = "D2C Starter Kit"; - -interface IMainLayout { - nav?: NavigationNode[]; - children: ReactNode; -} - -const MainLayout = ({ nav = [], children }: IMainLayout): JSX.Element => { - return ( - <> - - {MAIN_LAYOUT_TITLE} - - - -
    - {children} -