Skip to content
Merged
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
@@ -1,5 +1,5 @@
import type { Metadata } from "next"
import { CategoryView } from "components/category/category-view"
import { CategoryCLPView } from "components/category/category-clp-view"

export const revalidate = 86400
export const dynamic = "force-static"
Expand All @@ -22,5 +22,5 @@ export async function generateStaticParams() {

export default async function CategoryPage(props: CategoryPageProps) {
const params = await props.params
return <CategoryView searchParams={{ page: params.page }} params={params} basePath="ai" />
return <CategoryCLPView searchParams={{ page: params.page }} params={params} basePath="ai" />
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from "next"
import { isDemoMode } from "utils/demo-utils"
import { getCategories } from "lib/algolia"
import { CategoryView } from "components/category/category-view"
import { CategoryCLPView } from "components/category/category-clp-view"

export const revalidate = 86400
export const dynamic = "force-static"
Expand Down Expand Up @@ -31,5 +31,5 @@ export async function generateStaticParams() {

export default async function CategoryPage(props: CategoryPageProps) {
const params = await props.params
return <CategoryView params={params} basePath="ai" />
return <CategoryCLPView params={params} basePath="ai" />
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Metadata } from "next"
import { SearchParamsType } from "types"
import { CategoryView } from "components/category/category-view"
import { CategoryPLPView } from "components/category/category-plp-view"
import { isDemoMode } from "utils/demo-utils"
import { getCategories } from "lib/algolia"

export const runtime = "nodejs"

export const revalidate = 86400
export const dynamic = "force-static"

interface ProductListingPageProps {
searchParams: Promise<SearchParamsType>
Expand All @@ -19,8 +21,19 @@ export async function generateMetadata(props: ProductListingPageProps): Promise<
}
}

export async function generateStaticParams() {
if (isDemoMode()) return []

const { hits } = await getCategories({
hitsPerPage: 50,
attributesToRetrieve: ["handle"],
})

return hits.map(({ handle }) => ({ slug: handle }))
}

export default async function ProductListingPage(props: ProductListingPageProps) {
const params = await props.params
const searchParams = await props.searchParams
return <CategoryView params={params} searchParams={searchParams} basePath="ai" />
return <CategoryPLPView params={params} searchParams={searchParams} basePath="ai" />
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Metadata } from "next"
import { CategoryView } from "components/category/category-view"
import { CategoryCLPView } from "components/category/category-clp-view"

export const revalidate = 86400
export const dynamic = "force-static"
Expand All @@ -22,5 +22,5 @@ export async function generateStaticParams() {

export default async function CategoryPage(props: CategoryPageProps) {
const params = await props.params
return <CategoryView searchParams={{ page: params.page }} params={params} />
return <CategoryCLPView searchParams={{ page: params.page }} params={params} />
}
21 changes: 7 additions & 14 deletions starters/shopify-algolia/app/(browse)/category/clp/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { Metadata } from "next"
import { isDemoMode } from "utils/demo-utils"
import { getCategories } from "lib/algolia"
import { CategoryView } from "components/category/category-view"
import { CategoryCLPView } from "components/category/category-clp-view"
import { SearchParamsType } from "types"

export const revalidate = 86400
export const dynamic = "force-static"

export const dynamic = "force-dynamic"

interface CategoryPageProps {
params: Promise<{ slug: string }>
searchParams: Promise<SearchParamsType>
}

export async function generateMetadata(props: CategoryPageProps): Promise<Metadata> {
Expand All @@ -18,18 +21,8 @@ export async function generateMetadata(props: CategoryPageProps): Promise<Metada
}
}

export async function generateStaticParams() {
if (isDemoMode()) return []

const { hits } = await getCategories({
hitsPerPage: 50,
attributesToRetrieve: ["handle"],
})

return hits.map(({ handle }) => ({ slug: handle }))
}

export default async function CategoryPage(props: CategoryPageProps) {
const params = await props.params
return <CategoryView params={params} />
const searchParams = await props.searchParams
return <CategoryCLPView params={params} searchParams={searchParams} />
}
21 changes: 18 additions & 3 deletions starters/shopify-algolia/app/(browse)/category/plp/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { Metadata } from "next"
import { SearchParamsType } from "types"
import { CategoryView } from "components/category/category-view"
import { CategoryPLPView } from "components/category/category-plp-view"
import { isDemoMode } from "utils/demo-utils"
import { getCategories } from "lib/algolia"

export const runtime = "nodejs"

export const revalidate = 86400

export const dynamic = "force-dynamic"

interface ProductListingPageProps {
searchParams: Promise<SearchParamsType>
params: Promise<{ slug: string }>
Expand All @@ -19,8 +22,20 @@ export async function generateMetadata(props: ProductListingPageProps): Promise<
}
}

export async function generateStaticParams() {
if (isDemoMode()) return []

const { hits } = await getCategories({
hitsPerPage: 50,
attributesToRetrieve: ["handle"],
})

return hits.map(({ handle }) => ({ slug: handle }))
}

export default async function ProductListingPage(props: ProductListingPageProps) {
const params = await props.params
const searchParams = await props.searchParams
return <CategoryView params={params} searchParams={searchParams} />

return <CategoryPLPView params={params} searchParams={searchParams} />
}
65 changes: 26 additions & 39 deletions starters/shopify-algolia/app/(browse)/favorites/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import { cookies } from "next/headers"
import { Suspense } from "react"
import { ProductCard } from "components/product-card"
import { Skeleton } from "components/ui/skeleton"
import { COOKIE_FAVORITES } from "constants/index"
import { getProduct } from "lib/algolia"
import { filterImagesByVisualOption, getCombinationByMultiOption, getCombinationByVisualOption, getImagesForCarousel, getMultiOptionFromSlug, getVisualOptionFromSlug, removeMultiOptionFromSlug, removeVisualOptionFromSlug } from "utils/visual-variant-utils"
import {
filterImagesByVisualOption,
getCombinationByMultiOption,
getCombinationByVisualOption,
getMultiOptionFromSlug,
getVisualOptionFromSlug,
removeMultiOptionFromSlug,
removeVisualOptionFromSlug,
} from "utils/visual-variant-utils"
import { removeOptionsFromUrl } from "utils/product-options-utils"
import Image from "next/image"
import Link from "next/link"
import { cn } from "utils/cn"
import { type CurrencyType, mapCurrencyToSign } from "utils/map-currency-to-sign"
import { StarIcon } from "components/icons/star-icon"

export const revalidate = 86400

export const dynamicParams = true

// Custom ProductCard for favorites that includes variant badges
function FavoriteProductCard({
product,
variant,
featuredImage,
variantInfo,
href,
priority = false
priority = false,
}: {
product: any
variant: any
Expand All @@ -48,15 +53,12 @@ function FavoriteProductCard({
</div>
<div className="bg-size-200 bg-pos-0 hover:bg-pos-100 flex shrink-0 grow flex-col text-pretty bg-gradient-to-b from-transparent to-primary/5 p-4 transition-all duration-200">
<h3 className="line-clamp-2 text-lg font-semibold transition-colors">{product.title}</h3>
{/* Variant info badges */}

{}
{variantInfo.length > 0 && (
<div className="flex flex-wrap gap-1 pt-2">
{variantInfo.map((info, infoIdx) => (
<span
key={`${info.name}-${infoIdx}`}
className="inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-900"
>
<span key={`${info.name}-${infoIdx}`} className="inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-900">
{info.value}
</span>
))}
Expand Down Expand Up @@ -120,7 +122,6 @@ async function FavoritesView() {
favoritesHandles = JSON.parse(favoritesCookie) as string[]
}

// Create an array to store variant information for each favorite
const variantData: Array<{
product: any
variantHandle: string
Expand All @@ -129,21 +130,18 @@ async function FavoritesView() {
variantInfo: Array<{ name: string; value: string }>
}> = []

// Process each variant handle separately (no deduplication)
for (const variantHandle of favoritesHandles) {
let baseHandle: string
let multiOptions: Record<string, string> = {}
let visualValue: string | null = null

// Extract base product handle and variant options
if (variantHandle.includes('--')) {
if (variantHandle.includes("--")) {
multiOptions = getMultiOptionFromSlug(variantHandle)
baseHandle = removeMultiOptionFromSlug(variantHandle)
} else if (variantHandle.includes('-color_')) {
} else if (variantHandle.includes("-color_")) {
visualValue = getVisualOptionFromSlug(variantHandle)
baseHandle = removeVisualOptionFromSlug(variantHandle)
} else {
// Fallback to legacy approach
baseHandle = removeOptionsFromUrl(variantHandle)
}

Expand All @@ -154,72 +152,62 @@ async function FavoritesView() {
let combination
let variantInfo: Array<{ name: string; value: string }> = []

// Find the specific variant based on the options
if (Object.keys(multiOptions).length > 0) {
combination = getCombinationByMultiOption(product.variants, multiOptions)

// Extract variant info for display

if (combination) {
const variant = product.variants.find((v: any) => v.id === combination.id)
if (variant?.selectedOptions) {
variantInfo = variant.selectedOptions.map((opt: any) => ({
name: opt.name,
value: opt.value
value: opt.value,
}))
}
}
} else if (visualValue) {
combination = getCombinationByVisualOption(product.variants, visualValue)

// Extract variant info for display

if (combination) {
const variant = product.variants.find((v: any) => v.id === combination.id)
if (variant?.selectedOptions) {
variantInfo = variant.selectedOptions.map((opt: any) => ({
name: opt.name,
value: opt.value
value: opt.value,
}))
}
}
} else {
// Use first variant for products without specific variant options
combination = product.variants?.[0]
if (combination) {
const variant = product.variants.find((v: any) => v.id === combination.id)
if (variant?.selectedOptions) {
variantInfo = variant.selectedOptions.map((opt: any) => ({
name: opt.name,
value: opt.value
value: opt.value,
}))
}
}
}

// Get variant-specific image using proper filtering
let featuredImage = product.featuredImage
if (combination) {
const variant = product.variants.find((v: any) => v.id === combination.id)

if (variant?.selectedOptions) {
// Try different option names that might affect images
const visualOptions = ['Color', 'Colour', 'color', 'colour']
const visualOptions = ["Color", "Colour", "color", "colour"]
let filteredImages = product.images

for (const optionName of visualOptions) {
const option = variant.selectedOptions.find((opt: any) =>
opt.name.toLowerCase() === optionName.toLowerCase()
)
const option = variant.selectedOptions.find((opt: any) => opt.name.toLowerCase() === optionName.toLowerCase())
if (option) {
// Use filterImagesByVisualOption to get the correct variant images
const variantImages = filterImagesByVisualOption(product.images, option.value, option.name)
if (variantImages.length > 0 && variantImages !== product.images) {
filteredImages = variantImages
break
}
}
}

// If we found variant-specific images, use the first one

if (filteredImages.length > 0) {
featuredImage = filteredImages[0]
}
Expand All @@ -231,9 +219,8 @@ async function FavoritesView() {
variantHandle,
variant: combination,
featuredImage,
variantInfo
variantInfo,
})

} catch (error) {
console.warn(`Failed to fetch product for handle: ${variantHandle}`, error)
}
Expand Down
13 changes: 6 additions & 7 deletions starters/shopify-algolia/app/(browse)/product/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ProductImages } from "components/product/product-images"
import { RightSection } from "components/product/right-section"
import { FaqAccordionItem, FaqSectionClient } from "components/product/faq-section/faq-section-client"
import { ShopifyRichText } from "components/product/faq-section/shopify-rich-text"
import { nameToSlug } from "utils/slug-name"
import { AddToCartButton } from "components/product/add-to-cart-button"
import { ReviewsSection } from "components/product/reviews-section"

Expand All @@ -45,7 +46,6 @@ interface ProductProps {
params: Promise<{ slug: string }>
}

// TODO: These must take the new params into consideration? Move the variants logic to the query parameters?
export async function generateStaticParams() {
if (isDemoMode()) return []

Expand Down Expand Up @@ -134,7 +134,7 @@ export default async function Product(props: ProductProps) {
<p>{product.description}</p>
<AddToCartButton className="mt-4" product={product} combination={combination} />
<FavoriteMarker handle={slug} />
<FaqSectionClient defaultOpenSections={product.productDetailsMetafield?.value ?? getDefaultFaqAccordionItemValue()}>
<FaqSectionClient defaultOpenSections={[nameToSlug(getDefaultFaqAccordionItemValue()[0])]}>
<FaqAccordionItem title={getDefaultFaqAccordionItemValue()[0]}>
<ShopifyRichText data={product.productDetailsMetafield?.value || getDefaultFaqAccordionItemRichText()} className="prose prose-sm max-w-none" />
</FaqAccordionItem>
Expand Down Expand Up @@ -186,11 +186,10 @@ function makeBreadcrumbs(product: CommerceProduct) {
}
}


function getDefaultFaqAccordionItemRichText() {
return "{\"type\":\"root\",\"children\":[{\"listType\":\"unordered\",\"type\":\"list\",\"children\":[{\"type\":\"list-item\",\"children\":[{\"type\":\"text\",\"value\":\"Super for the muscles\"}]},{\"type\":\"list-item\",\"children\":[{\"type\":\"text\",\"value\":\"Various types and color variants\"}]},{\"type\":\"list-item\",\"children\":[{\"type\":\"text\",\"value\":\"Outdoor, or indoor - you define the place where you want to exercise\"}]},{\"type\":\"list-item\",\"children\":[{\"type\":\"text\",\"value\":\"100% Plastic from \"},{\"type\":\"text\",\"value\":\"recycling the materials\",\"bold\":true}]}]}]}"
function getDefaultFaqAccordionItemRichText() {
return '{"type":"root","children":[{"listType":"unordered","type":"list","children":[{"type":"list-item","children":[{"type":"text","value":"Super for the muscles"}]},{"type":"list-item","children":[{"type":"text","value":"Various types and color variants"}]},{"type":"list-item","children":[{"type":"text","value":"Outdoor, or indoor - you define the place where you want to exercise"}]},{"type":"list-item","children":[{"type":"text","value":"100% Plastic from "},{"type":"text","value":"recycling the materials","bold":true}]}]}]}'
}

function getDefaultFaqAccordionItemValue() {
return ["Product Details"]
function getDefaultFaqAccordionItemValue() {
return ["Product Details"]
}
Loading