Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: heyapi powered sdk shopper #245

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ dist-schema/
# Local Netlify folder
.netlify

*.tsbuildinfo
*.tsbuildinfo

# Bundled sdk specs
packages/sdks/specs/bundled/
9 changes: 4 additions & 5 deletions examples/simple/e2e/product-list-page.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from "@playwright/test";
import { gateway } from "@elasticpath/js-sdk";
import { buildSiteNavigation } from "../src/lib/build-site-navigation";
import { buildSiteNavigation } from "@elasticpath/react-shopper-hooks";

const host = process.env.NEXT_PUBLIC_EPCC_ENDPOINT_URL;
const client_id = process.env.NEXT_PUBLIC_EPCC_CLIENT_ID;
Expand All @@ -19,9 +19,8 @@ test("should be able to use quick view to view full product details", async ({

/* Get the cart id from the cookie */
const allCookies = await page.context().cookies();
const cartId = allCookies.find(
(cookie) => cookie.name === "_store_ep_cart",
)?.value;
const cartId = allCookies.find((cookie) => cookie.name === "_store_ep_cart")
?.value;

const nav = await buildSiteNavigation(client);

Expand All @@ -34,7 +33,7 @@ test("should be able to use quick view to view full product details", async ({
);
}

await page.getByRole("button", {name: "Shop Now"}).click();
await page.getByRole("button", { name: "Shop Now" }).click();

/* Check to make sure the page has navigated to the product list page for Men's / T-Shirts */
await expect(page).toHaveURL(`/search`);
Expand Down
2 changes: 2 additions & 0 deletions examples/simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"dependencies": {
"@elasticpath/js-sdk": "5.0.0",
"@elasticpath/react-shopper-hooks": "workspace:*",
"@epcc-sdk/sdks-shopper": "workspace:*",
"@epcc-sdk/sdks-nextjs": "workspace:*",
"clsx": "^1.2.1",
"cookies-next": "^4.0.0",
"focus-visible": "^5.2.0",
Expand Down
48 changes: 39 additions & 9 deletions examples/simple/src/app/(store)/products/[productId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,72 @@
import { Metadata } from "next";
import { ProductDetailsComponent, ProductProvider } 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/react-shopper-hooks";
import React from "react";
import { getByContextProduct, client } from "@epcc-sdk/sdks-shopper";
import { applyDefaultNextMiddleware } from "@epcc-sdk/sdks-nextjs";
import { epccEnv } from "../../../../lib/resolve-epcc-env";
import { ProductResponse, ShopperCatalogResource } from "@elasticpath/js-sdk";

export const dynamic = "force-dynamic";

type Props = {
params: { productId: string };
};

client.setConfig({
baseUrl: `https://${epccEnv.host}`,
});

applyDefaultNextMiddleware(client);

export async function generateMetadata({
params: { productId },
}: Props): Promise<Metadata> {
const client = getServerSideImplicitClient();
const product = await getProductById(productId, client);
const productResponse = await getByContextProduct({
path: {
product_id: productId,
},
query: {
include: ["main_images", "files", "component_products"],
},
});

if (!product) {
if (!productResponse.data) {
notFound();
}

const product = productResponse.data;

return {
title: product.data.attributes.name,
description: product.data.attributes.description,
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);
const productResponse = await getByContextProduct({
path: {
product_id: params.productId,
},
query: {
include: ["main_images", "files", "component_products"],
},
});

if (!product) {
if (!productResponse.data) {
notFound();
}

const shopperProduct = await parseProductResponse(product, client);
const product = productResponse.data;

// TODO I want to replace the ShopperProduct concept and just use the sdk types that are provided
const shopperProduct = await parseProductResponse(
product as unknown as ShopperCatalogResource<ProductResponse>,
client,
);

return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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";
import { extractProductImage } from "@epcc-sdk/sdks-shopper";

interface IFeaturedProductsProps {
title: string;
Expand All @@ -19,7 +20,13 @@ export default async function FeaturedProducts({
linkProps,
}: IFeaturedProductsProps) {
const client = getServerSideImplicitClient();
const products = await fetchFeaturedProducts(client);
const featuredProductResponse = await fetchFeaturedProducts(client);

if (!featuredProductResponse.response.ok) {
return null;
}

const products = featuredProductResponse.data?.data ?? [];

return (
<div
Expand Down Expand Up @@ -47,39 +54,45 @@ export default async function FeaturedProducts({
role="list"
className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8"
>
{products.map((product) => (
<Link key={product.id} href={`/products/${product.id}`}>
<li className="relative group">
<div className=" aspect-square block w-full overflow-hidden rounded-lg bg-gray-100 focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 focus-within:ring-offset-gray-100">
<div className="relative w-full h-full bg-[#f6f7f9] rounded-lg text-center animate-fadeIn transition duration-300 ease-in-out group-hover:scale-105">
{product.main_image?.link.href ? (
<Image
alt={product.main_image?.file_name!}
src={product.main_image?.link.href}
className="rounded-lg"
sizes="(max-width: 200px)"
fill
style={{
objectFit: "contain",
objectPosition: "center",
}}
/>
) : (
<div className="w-[64px] h-[64px] flex items-center justify-center text-white bg-gray-200 rounded-md shadow-sm object-cover">
<EyeSlashIcon className="w-3 h-3" />
</div>
)}
{products.map((product) => {
const mainImage = extractProductImage(
product,
featuredProductResponse.data?.included?.main_images,
);
return (
<Link key={product.id} href={`/products/${product.id}`}>
<li className="relative group">
<div className=" aspect-square block w-full overflow-hidden rounded-lg bg-gray-100 focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 focus-within:ring-offset-gray-100">
<div className="relative w-full h-full bg-[#f6f7f9] rounded-lg text-center animate-fadeIn transition duration-300 ease-in-out group-hover:scale-105">
{mainImage?.link?.href ? (
<Image
alt={mainImage?.file_name!}
src={mainImage?.link.href}
className="rounded-lg"
sizes="(max-width: 200px)"
fill
style={{
objectFit: "contain",
objectPosition: "center",
}}
/>
) : (
<div className="w-[64px] h-[64px] flex items-center justify-center text-white bg-gray-200 rounded-md shadow-sm object-cover">
<EyeSlashIcon className="w-3 h-3" />
</div>
)}
</div>
</div>
</div>
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900">
{product.attributes.name}
</p>
<p className="pointer-events-none block text-sm font-medium text-gray-500">
{product.meta.display_price?.without_tax?.formatted}
</p>
</li>
</Link>
))}
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900">
{product.attributes?.name}
</p>
<p className="pointer-events-none block text-sm font-medium text-gray-500">
{product.meta?.display_price?.without_tax?.formatted}
</p>
</li>
</Link>
);
})}
</ul>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { getProducts } from "../../services/products";
import { ElasticPath } from "@elasticpath/js-sdk";
import { ProductResponseWithImage } from "../../lib/types/product-types";
import { connectProductsWithMainImages } from "../../lib/connect-products-with-main-images";
import {
getByContextAllProducts,
client as sdkClient,
} from "@epcc-sdk/sdks-shopper";

// Fetching the first 4 products of in the catalog to display in the featured-products component
export const fetchFeaturedProducts = async (
client: ElasticPath,
): Promise<ProductResponseWithImage[]> => {
const { data: productsResponse, included: productsIncluded } =
await getProducts(client);

return productsIncluded?.main_images
? connectProductsWithMainImages(
productsResponse.slice(0, 4), // Only need the first 4 products to feature
productsIncluded?.main_images,
)
: productsResponse;
export const fetchFeaturedProducts = async (client: typeof sdkClient) => {
return await getByContextAllProducts({
client,
query: {
"page[limit]": 4,
"page[offset]": 0,
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import Link from "next/link";
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";
import { Cart } from "../../cart/CartSheet";
import { buildSiteNavigation } from "@elasticpath/react-shopper-hooks";

export default async function MobileNavBar() {
const client = getServerSideImplicitClient();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";
import { ReactElement } from "react";
import { NavigationNode } from "../../../lib/build-site-navigation";
import { NavigationNode } from "@elasticpath/react-shopper-hooks";
import {
NavigationMenu,
NavigationMenuContent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from "next/link";
import { NavigationNode } from "../../../lib/build-site-navigation";
import { NavigationNode } from "@elasticpath/react-shopper-hooks";
import { ArrowRightIcon } from "@heroicons/react/20/solid";

interface IProps {
Expand Down
2 changes: 1 addition & 1 deletion examples/simple/src/lib/build-breadcrumb-lookup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NavigationNode } from "./build-site-navigation";
import { NavigationNode } from "@elasticpath/react-shopper-hooks";
import { BreadcrumbLookup } from "./types/breadcrumb-lookup";

export function buildBreadcrumbLookup(
Expand Down
104 changes: 0 additions & 104 deletions examples/simple/src/lib/build-site-navigation.ts

This file was deleted.

Loading