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: ui changes #81

Merged
merged 6 commits into from
Oct 23, 2024
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
57 changes: 35 additions & 22 deletions starters/shopify-meilisearch/app/home/[bucket]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,57 @@
import { Suspense } from "react"
import { BUCKETS } from "constants/index"
import { BestOffersSection } from "views/homepage/best-offers-skeleton"
import { CarouselSectionSkeleton } from "views/homepage/carousel-section"
import { CategoriesSection, CategoriesSectionSkeleton } from "views/homepage/categories-section"
import { EverythingUnderSection } from "views/homepage/everything-under-section"
import { AnnouncementBar } from "components/announcement-bar"
import { HeroSection } from "views/homepage/hero-section"
import { meilisearch } from "clients/search"
import { CommerceProduct } from "types"
import { env } from "env.mjs"
import type { Hits } from "meilisearch"
import { CategoriesSection } from "views/homepage/categories-section"
import { FeaturedProductsSection } from "views/homepage/featured-products-section"
import { PlatformCollection } from "lib/shopify/types"

export const revalidate = 86400

export const dynamic = "force-static"

export const dynamicParams = true

export default function Homepage({ params: { bucket } }: { params: { bucket: string } }) {
export default async function Homepage({ params: { bucket } }: { params: { bucket: string } }) {
const heroTitles = {
a: "Your daily trendsetting deals",
b: "Spring into Savings! Up to 60% Off",
a: "Discover Your Next Favorite Thing",
b: "Shop the best Deals on Top Brands & Unique Finds",
}

const { products } = await fetchFeaturedData()

return (
<div className="flex w-full flex-col">
<HeroSection className="-order-1 md:-order-2" title={heroTitles[bucket as keyof typeof heroTitles]} />
<AnnouncementBar className="-order-2 md:-order-1" />

<Suspense fallback={<CategoriesSectionSkeleton />}>
<CategoriesSection />
</Suspense>

<Suspense fallback={<CarouselSectionSkeleton />}>
<BestOffersSection />
</Suspense>

<Suspense fallback={<CarouselSectionSkeleton />}>
<EverythingUnderSection />
</Suspense>
<AnnouncementBar className="-order-2" />
<HeroSection className="-order-1 self-center md:-order-2" title={heroTitles[bucket]} />
<FeaturedProductsSection products={products} />
<CategoriesSection />
</div>
)
}

export async function generateStaticParams() {
return BUCKETS.HOME.map((bucket) => ({ bucket }))
}

const fetchFeaturedData = async () => {
const results = await meilisearch?.multiSearch({
queries: [
{
indexUid: env.MEILISEARCH_FEATURED_PRODUCTS_INDEX,
q: "",
limit: 6,
attributesToRetrieve: ["id", "title", "featuredImage", "minPrice", "variants", "avgRating", "totalReviews", "vendor", "handle"],
},
{ indexUid: env.MEILISEARCH_CATEGORIES_INDEX, q: "", limit: 4, attributesToRetrieve: ["id", "title", "handle"] },
],
})

return {
products: results[0].hits as Hits<CommerceProduct>,
categories: results[1].hits as Hits<PlatformCollection>,
}
}
2 changes: 0 additions & 2 deletions starters/shopify-meilisearch/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { CartView } from "views/cart/cart-view"
import type { NavItem } from "components/navigation-bar/types"
import { NavigationBar } from "components/navigation-bar/navigation-bar"
import { mobileInlineScript } from "components/navigation-bar/mobile-inline-script"
import { CallToAction } from "components/call-to-action"
import { Footer } from "components/footer"
import { Modals } from "components/modals/modals"

Expand Down Expand Up @@ -245,7 +244,6 @@ export default async function RootLayout({ children }: { children: React.ReactNo

{children}

<CallToAction />
<Footer />
<Modals />

Expand Down
8 changes: 5 additions & 3 deletions starters/shopify-meilisearch/app/product/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ export default async function Product({ params: { slug } }: ProductProps) {
return (
<div className="relative mx-auto max-w-container-md px-4 xl:px-0">
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(generateJsonLd(product, slug)) }}></script>
<div className="mb:pb-8 relative w-fit py-4 md:pt-12">
<BackButton className="mb-8 hidden md:block" />
<div className="mb:pb-8 relative flex w-full items-center justify-center gap-10 py-4 md:pt-12">
<BackButton className="left-2 mb-8 hidden md:block xl:absolute" />
<div className="mx-auto w-full max-w-container-sm">
<Breadcrumbs className="mb-8" items={makeBreadcrumbs(product)} />
</div>
</div>
<main className="mx-auto max-w-container-sm">
<Breadcrumbs className="mb-8" items={makeBreadcrumbs(product)} />
<div className="grid grid-cols-1 gap-4 md:mx-auto md:max-w-screen-xl md:grid-cols-12 md:gap-8">
<ProductTitle
className="md:hidden"
Expand Down
4 changes: 2 additions & 2 deletions starters/shopify-meilisearch/components/breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ export function Breadcrumbs({ items, className }: BreadcrumbsProps) {
<BreadcrumbLink
prefetch={false}
aria-current={isLast ? "page" : undefined}
className={cn("text-neutral-500 hover:underline", isLast && "font-medium underline")}
className={cn("text-sm text-neutral-500 hover:underline", isLast && "font-medium underline")}
href={href}
>
{title}
</BreadcrumbLink>
</BreadcrumbItem>
{!isLast && <BreadcrumbSeparator />}
{!isLast && <BreadcrumbSeparator className="text-transparent [&>svg]:size-2 [&>svg]:fill-black" />}
</React.Fragment>
)
})}
Expand Down
32 changes: 0 additions & 32 deletions starters/shopify-meilisearch/components/call-to-action.tsx

This file was deleted.

32 changes: 32 additions & 0 deletions starters/shopify-meilisearch/components/category-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { PlatformCollection } from "lib/shopify/types"
import Image from "next/image"
import Link from "next/link"
import { cn } from "utils/cn"

interface CategoryCardProps extends Pick<PlatformCollection, "title" | "image" | "handle"> {
index: number
className?: string
}

export const CategoryCard = ({ handle, image, title, index, className }: CategoryCardProps) => {
const href = `/category/${handle}`
return (
<Link href={href} className={cn("group relative overflow-hidden rounded-lg transition-all hover:shadow-md", className)} prefetch={false}>
<div className="relative aspect-video">
<Image
src={image?.url || `/category-placeholder-${index}.png`}
alt={image?.altText || `${title} category`}
className="transition-transform group-hover:scale-105"
style={{
objectFit: "contain",
}}
fill
/>
<div className="absolute left-0 top-0 z-10 size-full bg-gradient-to-b from-white/90 to-60%" />
</div>
<div className="absolute inset-x-4 top-0 z-20">
<h3 className="ml-3 mt-5 text-xl font-semibold text-black group-hover:text-orange-500">{title}</h3>
</div>
</Link>
)
}
79 changes: 79 additions & 0 deletions starters/shopify-meilisearch/components/compact-product-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 type { CommerceProduct } from "types"
import { StarIcon } from "components/icons/star-icon"

interface ProductCardProps extends Pick<CommerceProduct, "variants" | "handle" | "images" | "title" | "featuredImage" | "minPrice" | "avgRating" | "totalReviews" | "vendor"> {
priority?: boolean
prefetch?: boolean
className?: string
}

export const CompactProductCard = ({
variants,
handle,
title,
featuredImage,
minPrice,
avgRating,
totalReviews,
className,
priority,
vendor,
prefetch = false,
}: ProductCardProps) => {
const noOfVariants = variants?.length
const href = `/product/${handle}`
const linkAria = `Visit product: ${title}`
const variantPrice = variants?.find(Boolean)?.price

return (
<Link
className={cn("group relative flex flex-col overflow-hidden rounded-lg border border-gray-100 transition-all", className)}
aria-label={linkAria}
href={href}
prefetch={prefetch}
>
<div className="relative aspect-square overflow-hidden">
<Image
priority={priority}
className="object-cover transition-transform group-hover:scale-105"
src={featuredImage?.url || "/default-product-image.svg"}
alt={featuredImage?.altText || title}
fill
/>
</div>
<div className="absolute bottom-0 flex w-full shrink-0 grow translate-y-full flex-col overflow-hidden bg-gradient-to-t from-gray-100 to-transparent p-4 transition-transform group-hover:translate-y-0">
{/* remove first word from the title as it includes vendor (this just needs feed update and then can be removed) */}
<h3 className="line-clamp-2 text-lg font-semibold">{title.split(" ").slice(1).join(" ")}</h3>
<div className="mt-auto flex flex-col gap-1">
{!!variantPrice && <span>From {mapCurrencyToSign((variantPrice.currencyCode as CurrencyType) || "USD") + minPrice.toFixed(2)}</span>}

{!!vendor && <p className="text-sm text-gray-500">{vendor}</p>}

<div className="flex items-center gap-1">
{!!avgRating && !!totalReviews && (
<>
<div className="flex items-center space-x-1">
<StarIcon className="size-4 fill-gray-400 stroke-gray-500" />
<span className="text-sm">{avgRating.toFixed(2)}</span>
<span className="text-xs">
({totalReviews} review{totalReviews !== 1 && "s"})
</span>
</div>
</>
)}
{noOfVariants > 0 && (
<p className="text-sm text-gray-500">
{noOfVariants} variant{noOfVariants > 1 ? "s" : ""}
</p>
)}
</div>
</div>
</div>
</Link>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const ExpandableContent = ({ children, className, lines = 2 }: Expandable
{children}
</div>
{isClamped && (
<button className={cn("flex items-center gap-1 bg-transparent text-sm underline")} onClick={() => setIsExpanded((prev) => !prev)}>
<button className={cn("mx-auto flex items-center gap-1 bg-transparent text-sm underline")} onClick={() => setIsExpanded((prev) => !prev)}>
{isExpanded ? "Read less" : "Read more"}
<ChevronIcon className={isExpanded ? "rotate-180" : "rotate-0"} />
</button>
Expand Down
76 changes: 76 additions & 0 deletions starters/shopify-meilisearch/components/featured-product-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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 type { CommerceProduct } from "types"
import { StarIcon } from "components/icons/star-icon"

interface ProductCardProps extends Pick<CommerceProduct, "variants" | "handle" | "images" | "title" | "featuredImage" | "minPrice" | "avgRating" | "totalReviews" | "vendor"> {
priority?: boolean
prefetch?: boolean
className?: string
}

export const FeaturedProductCard = ({
variants,
handle,
title,
featuredImage,
minPrice,
avgRating,
totalReviews,
className,
priority,
vendor,
prefetch = false,
}: ProductCardProps) => {
const noOfVariants = variants?.length
const href = `/product/${handle}`
const linkAria = `Visit product: ${title}`
const variantPrice = variants?.find(Boolean)?.price

return (
<Link className={cn("group flex flex-col overflow-hidden rounded-lg border border-gray-100 transition-all", className)} aria-label={linkAria} href={href} prefetch={prefetch}>
<div className="relative aspect-square overflow-hidden">
<Image
priority={priority}
className="object-cover transition-transform group-hover:scale-105"
src={featuredImage?.url || "/default-product-image.svg"}
alt={featuredImage?.altText || title}
fill
/>
</div>
<div className="flex shrink-0 grow items-start justify-between p-4 transition-colors group-hover:bg-gradient-to-t group-hover:from-gray-100 group-hover:to-transparent">
<div className="flex flex-col gap-1">
{/* remove first word from the title as it includes vendor (this just needs feed update and then can be removed) */}
<h3 className="line-clamp-2 text-lg font-semibold">{title.split(" ").slice(1).join(" ")}</h3>
{!!variantPrice && <span className="block sm:hidden">From {mapCurrencyToSign((variantPrice.currencyCode as CurrencyType) || "USD") + minPrice.toFixed(2)}</span>}

<div className="mt-auto flex flex-col gap-1">
{!!vendor && <p className="text-sm text-gray-500">{vendor}</p>}
<div className="flex items-center gap-1">
{!!avgRating && !!totalReviews && (
<>
<div className="flex items-center space-x-1">
<StarIcon className="size-4 fill-gray-400 stroke-gray-500" />
<span className="text-sm">{avgRating.toFixed(2)}</span>
<span className="text-xs">
({totalReviews} review{totalReviews !== 1 && "s"})
</span>
</div>
</>
)}
{noOfVariants > 0 && (
<p className="text-sm text-gray-500">
{noOfVariants} variant{noOfVariants > 1 ? "s" : ""}
</p>
)}
</div>
</div>
</div>
{!!variantPrice && <span className="hidden sm:block">From {mapCurrencyToSign((variantPrice.currencyCode as CurrencyType) || "USD") + minPrice.toFixed(2)}</span>}
</div>
</Link>
)
}
Loading
Loading