Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SkeletonProductCard } from "@/components/organisms/ProductCard/SkeletonProductCard"
import { PRODUCT_LIMIT } from "@/const"

const ProductListingLoadingView = () => (
<div className="flex flex-wrap gap-4">
{Array.from({ length: PRODUCT_LIMIT }).map((_, idx) => (
<SkeletonProductCard key={idx} />
))}
</div>
)

export default ProductListingLoadingView
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const ProductListingNoResultsView = () => (
<div className="text-center w-full my-10">
<h2 className="uppercase text-primary heading-lg">No results</h2>
<p className="mt-4 text-lg">
Sorry, we can&apos;t find any results for your criteria
</p>
</div>
)

export default ProductListingNoResultsView
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { BaseHit, Hit } from "instantsearch.js"
import { ProductCard } from "@/components/organisms"

interface Props {
products: Hit<BaseHit>[]
apiProducts: HttpTypes.StoreProduct[] | null
}

const ProductListingProductsView = ({ products, apiProducts }: Props) => (
<div className="w-full">
<ul className="flex flex-wrap gap-4">
{products.map(
(hit) =>
apiProducts?.find((p) => p.id === hit.objectID) && (
<li key={hit.objectID} className="w-full lg:w-[calc(25%-1rem)] min-w-[250px]">
<ProductCard
api_product={apiProducts?.find((p) => p.id === hit.objectID)}
product={hit}
className="w-full h-full lg:w-full min-w-0"
/>
</li>
)
)}
</ul>
</div>
)

export default ProductListingProductsView
6 changes: 6 additions & 0 deletions src/components/molecules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import { ParcelAccordion } from "./ParcelAccordion/ParcelAccordion"
import { AddressForm } from "./AddressForm/AddressForm"
import { ReviewForm } from "./ReviewForm/ReviewForm"
import { ProfileDetails } from "./ProfileDetails/ProfileDetails"
import ProductListingLoadingView from "./ProductListingLoadingView/ProductListingLoadingView"
import ProductListingNoResultsView from "./ProductListingNoResultsView/ProductListingNoResultsView"
import ProductListingProductsView from "./ProductListingProductsView/ProductListingProductsView"

export {
PrimeCategoryNavbar,
Expand Down Expand Up @@ -68,4 +71,7 @@ export {
AddressForm,
ReviewForm,
ProfileDetails,
ProductListingLoadingView,
ProductListingNoResultsView,
ProductListingProductsView,
}
9 changes: 6 additions & 3 deletions src/components/organisms/ProductCard/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import Image from "next/image"
import { Button } from "@/components/atoms"
import { HttpTypes } from "@medusajs/types"
import { BaseHit, Hit } from "instantsearch.js"
import clsx from "clsx"
import { cn } from "@/lib/utils"
import LocalizedClientLink from "@/components/molecules/LocalizedLink/LocalizedLink"
import { getProductPrice } from "@/lib/helpers/get-product-price"

export const ProductCard = ({
product,
api_product,
className,
}: {
product: Hit<HttpTypes.StoreProduct> | Partial<Hit<BaseHit>>
api_product?: HttpTypes.StoreProduct | null
className?: string
}) => {
if (!api_product) {
return null
Expand All @@ -27,8 +29,9 @@ export const ProductCard = ({

return (
<div
className={clsx(
"relative group border rounded-sm flex flex-col justify-between p-1 w-full lg:w-[calc(25%-1rem)] min-w-[250px]"
className={cn(
"relative group border rounded-sm flex flex-col justify-between p-1 w-full lg:w-[calc(25%-1rem)] min-w-[250px]",
className
)}
>
<div className="relative w-full h-full bg-primary aspect-square">
Expand Down
106 changes: 58 additions & 48 deletions src/components/sections/ProductListing/AlgoliaProductsListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
import { HttpTypes } from "@medusajs/types"
import {
AlgoliaProductSidebar,
ProductCard,
ProductListingActiveFilters,
ProductsPagination,
} from "@/components/organisms"
import {
ProductListingLoadingView,
ProductListingNoResultsView,
ProductListingProductsView,
} from "@/components/molecules"
import { client } from "@/lib/client"
import { Configure, useHits } from "react-instantsearch"
import { InstantSearchNext } from "react-instantsearch-nextjs"
import { useSearchParams } from "next/navigation"
import { getFacedFilters } from "@/lib/helpers/get-faced-filters"
import { PRODUCT_LIMIT } from "@/const"
import { ProductListingSkeleton } from "@/components/organisms/ProductListingSkeleton/ProductListingSkeleton"
import { useEffect, useState } from "react"
import { useEffect, useMemo, useState } from "react"
import { listProducts } from "@/lib/data/products"
import { getProductPrice } from "@/lib/helpers/get-product-price"

Expand All @@ -31,10 +35,11 @@ export const AlgoliaProductsListing = ({
seller_handle?: string
currency_code: string
}) => {
const searchParamas = useSearchParams()
const searchParams = useSearchParams()

const facetFilters: string = getFacedFilters(searchParamas)
const query: string = searchParamas.get("query") || ""
const facetFilters: string = getFacedFilters(searchParams)
const query: string = searchParams.get("query") || ""
const page: number = +(searchParams.get("page") || 1)

const filters = `${
seller_handle
Expand All @@ -52,7 +57,12 @@ export const AlgoliaProductsListing = ({

return (
<InstantSearchNext searchClient={client} indexName="products">
<Configure query={query} filters={filters} />
<Configure
query={query}
filters={filters}
hitsPerPage={PRODUCT_LIMIT}
page={page - 1}
/>
<ProductsListing
locale={locale}
currency_code={currency_code}
Expand All @@ -74,13 +84,19 @@ const ProductsListing = ({
const [apiProducts, setApiProducts] = useState<
HttpTypes.StoreProduct[] | null
>(null)
const [isLoadingProducts, setIsLoadingProducts] = useState(false)
const { items, results } = useHits()

const searchParamas = useSearchParams()
const searchParams = useSearchParams()

const itemsKey = useMemo(
() => items.map((item) => item.objectID).join(","),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any chance for items to be undefined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useHits returns empty array if there's no items

[items]
)

async function handleSetProducts() {
try {
setApiProducts(null)
setIsLoadingProducts(true)
const { response } = await listProducts({
countryCode: locale,
queryParams: {
Expand All @@ -99,34 +115,40 @@ const ProductsListing = ({
)
} catch (error) {
setApiProducts(null)
} finally {
setIsLoadingProducts(false)
}
}

useEffect(() => {
handleSetProducts()
}, [items.length])
if (items.length > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any chance for items to be undefined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useHits returns empty array if there's no items

handleSetProducts()
} else {
setApiProducts([])
setIsLoadingProducts(false)
}
}, [itemsKey])

if (!results?.processingTimeMS) return <ProductListingSkeleton />

const page: number = +(searchParamas.get("page") || 1)
const isLoading = isLoadingProducts && items.length > 0

const filteredProducts = items.filter((pr) =>
apiProducts?.some((p: any) => p.id === pr.objectID)
apiProducts?.some((p) => p.id === pr.objectID)
)

const products = filteredProducts
.filter((pr) =>
apiProducts?.some(
(p: any) => p.id === pr.objectID && filterProductsByCurrencyCode(p)
)
const products = filteredProducts.filter((pr) =>
apiProducts?.some(
(p) => p.id === pr.objectID && filterProductsByCurrencyCode(p)
)
.slice((page - 1) * PRODUCT_LIMIT, page * PRODUCT_LIMIT)
)

const count = filteredProducts?.length || 0
const pages = Math.ceil(count / PRODUCT_LIMIT) || 1
const count = results?.nbHits || 0
const pages = results?.nbPages || 1

function filterProductsByCurrencyCode(product: HttpTypes.StoreProduct) {
const minPrice = searchParamas.get("min_price")
const maxPrice = searchParamas.get("max_price")
const minPrice = searchParams.get("min_price")
const maxPrice = searchParams.get("max_price")

if ([minPrice, maxPrice].some((price) => typeof price === "string")) {
const variantsWithCurrencyCode = product?.variants?.filter(
Expand Down Expand Up @@ -173,35 +195,23 @@ const ProductsListing = ({
<div className="w-[280px] flex-shrink-0 hidden md:block">
<AlgoliaProductSidebar />
</div>
<div className="w-full">
{!items.length ? (
<div className="text-center w-full my-10">
<h2 className="uppercase text-primary heading-lg">no results</h2>
<p className="mt-4 text-lg">
Sorry, we can&apos;t find any results for your criteria
</p>
</div>
) : (
<div className="w-full">
<ul className="flex flex-wrap gap-4">
{products.map(
(hit) =>
apiProducts?.find((p: any) => p.id === hit.objectID) && (
<ProductCard
api_product={apiProducts?.find(
(p: any) => p.id === hit.objectID
)}
key={hit.objectID}
product={hit}
/>
)
)}
</ul>
</div>
<div className="w-full flex flex-col">
{isLoading && <ProductListingLoadingView />}

{!isLoading && !items.length && <ProductListingNoResultsView />}

{!isLoading && items.length > 0 && (
<ProductListingProductsView
products={products}
apiProducts={apiProducts}
/>
)}

<div className="mt-auto">
<ProductsPagination pages={pages} />
</div>
</div>
</div>
<ProductsPagination pages={pages} />
</div>
)
}