Update carousels, card hover, and IranBay branding.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import type { BusinessDetail, BusinessTabId } from "@/data/business";
|
||||
import { businessTabs } from "@/data/business";
|
||||
import styles from "./BusinessDetail.module.css";
|
||||
|
||||
function Stars({ rating }: { rating: string }) {
|
||||
return (
|
||||
<div className={styles.stars} aria-label={`${rating} out of 5`}>
|
||||
{Array.from({ length: 5 }, (_, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={i < 4 ? styles.starFilled : styles.starEmpty}
|
||||
aria-hidden
|
||||
>
|
||||
★
|
||||
</span>
|
||||
))}
|
||||
<span className={styles.rating}>{rating}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CategoryPills({
|
||||
items,
|
||||
active,
|
||||
onChange,
|
||||
}: {
|
||||
items: string[];
|
||||
active: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.pills}>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
className={`filter-chip${item === active ? " is-active" : ""}`}
|
||||
aria-pressed={item === active}
|
||||
onClick={() => onChange(item)}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FiltersBar({
|
||||
fields,
|
||||
tags,
|
||||
}: {
|
||||
fields: { label: string; value: string }[];
|
||||
tags: { label: string; value: string }[];
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.filtersBar}>
|
||||
<div className={styles.filtersLabel}>
|
||||
<img src="/images/icons/search.svg" alt="" width={22} height={22} />
|
||||
<div>
|
||||
<strong>Filters</strong>
|
||||
<span>A better focus</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.filters}>
|
||||
{fields.map((field) => (
|
||||
<label key={field.label} className={styles.filterField}>
|
||||
<span>{field.label}</span>
|
||||
<input defaultValue={field.value} readOnly />
|
||||
</label>
|
||||
))}
|
||||
<button className={`filter-chip ${styles.filterBtn}`} type="button">
|
||||
Filter
|
||||
<img src="/images/icons/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{tags.length ? (
|
||||
<div className={styles.activeTags}>
|
||||
{tags.map((tag) => (
|
||||
<span className={`filter-chip ${styles.activeTag}`} key={tag.label}>
|
||||
<em>{tag.label}:</em> {tag.value}
|
||||
<button type="button" aria-label={`Remove ${tag.label}`}>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BusinessDetailView({
|
||||
business,
|
||||
}: {
|
||||
business: BusinessDetail;
|
||||
}) {
|
||||
const [tab, setTab] = useState<BusinessTabId>("products");
|
||||
const [productCategory, setProductCategory] = useState("All");
|
||||
const [blogCategory, setBlogCategory] = useState("All");
|
||||
const [portfolioCategory, setPortfolioCategory] = useState("All");
|
||||
const [hoveredPortfolio, setHoveredPortfolio] = useState<string | null>(
|
||||
business.portfolios[2]?.id ?? null,
|
||||
);
|
||||
|
||||
const products =
|
||||
productCategory === "All"
|
||||
? business.products
|
||||
: business.products.filter((p) => p.category === productCategory);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<section className={styles.bannerWrap}>
|
||||
<div className={styles.banner}>
|
||||
<h1 className={styles.bannerTitle}>{business.name}</h1>
|
||||
<nav className={styles.crumbs} aria-label="Breadcrumb">
|
||||
{business.breadcrumbs.map((crumb, index) => (
|
||||
<span key={`${crumb}-${index}`}>
|
||||
{index > 0 ? (
|
||||
<img
|
||||
className={styles.crumbChevron}
|
||||
src="/images/icons/chevron.svg"
|
||||
alt=""
|
||||
/>
|
||||
) : null}
|
||||
<span
|
||||
className={
|
||||
index === business.breadcrumbs.length - 1
|
||||
? styles.crumbActive
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{crumb}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.hero}>
|
||||
<Image
|
||||
src={business.hero}
|
||||
alt=""
|
||||
fill
|
||||
priority
|
||||
className={styles.heroImg}
|
||||
sizes="100vw"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className={styles.profile}>
|
||||
<div className={styles.profileBar}>
|
||||
<div className={styles.identity}>
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={business.logo}
|
||||
alt=""
|
||||
width={75}
|
||||
height={75}
|
||||
/>
|
||||
<div>
|
||||
<h2 className={styles.name}>{business.name}</h2>
|
||||
<p className={styles.tagline}>{business.tagline}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.stats}>
|
||||
<div className={styles.stat}>
|
||||
<span className={styles.statLabel}>since</span>
|
||||
<strong className={styles.statValueSm}>{business.since}</strong>
|
||||
</div>
|
||||
<div className={styles.stat}>
|
||||
<span className={styles.statLabel}>province</span>
|
||||
<strong className={styles.statValueSm}>{business.province}</strong>
|
||||
</div>
|
||||
<div className={styles.statWide}>
|
||||
<strong className={styles.statValueLg}>
|
||||
{business.activeProducts}
|
||||
</strong>
|
||||
<span className={styles.statLabelStack}>
|
||||
active
|
||||
<br />
|
||||
product
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.stat}>
|
||||
<span className={styles.statLabel}>Telephone</span>
|
||||
<strong className={styles.statValueSm}>{business.telephone}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className={styles.description}>{business.description}</p>
|
||||
</section>
|
||||
|
||||
<nav className={styles.tabs} aria-label="Business sections">
|
||||
{businessTabs.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={tab === item.id ? styles.tabActive : styles.tab}
|
||||
onClick={() => setTab(item.id)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<section className={styles.panel} aria-live="polite">
|
||||
{tab === "products" ? (
|
||||
<>
|
||||
<CategoryPills
|
||||
items={business.productCategories}
|
||||
active={productCategory}
|
||||
onChange={setProductCategory}
|
||||
/>
|
||||
<FiltersBar
|
||||
fields={[{ label: "Product name", value: "gold" }]}
|
||||
tags={[{ label: "Name", value: "gold" }]}
|
||||
/>
|
||||
<div className={styles.productGrid}>
|
||||
{products.map((product) => (
|
||||
<article key={product.id} className={styles.productCard}>
|
||||
<div className={styles.productMedia}>
|
||||
<Image
|
||||
src={product.image}
|
||||
alt=""
|
||||
fill
|
||||
className={styles.productImg}
|
||||
sizes="(max-width: 720px) 50vw, 292px"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.productBody}>
|
||||
<h3>{product.title}</h3>
|
||||
<Stars rating={product.rating} />
|
||||
<div className={styles.productMeta}>
|
||||
<span className={styles.chip}>
|
||||
<strong>{product.minOrder}</strong>
|
||||
<em>
|
||||
pcs, min
|
||||
<br />
|
||||
order
|
||||
</em>
|
||||
</span>
|
||||
<span className={styles.chip}>
|
||||
<strong>{product.shipsIn}</strong>
|
||||
<em>
|
||||
days
|
||||
<br />
|
||||
to ship
|
||||
</em>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{tab === "contact" ? (
|
||||
<div className={styles.contact}>
|
||||
<div className={styles.contactRows}>
|
||||
<div className={styles.contactRow}>
|
||||
<span className={styles.contactIcon} aria-hidden>
|
||||
⌖
|
||||
</span>
|
||||
<div>
|
||||
<span className={styles.contactLabel}>Address</span>
|
||||
<p>{business.contact.address}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.contactRow}>
|
||||
<span className={styles.contactIcon} aria-hidden>
|
||||
☎
|
||||
</span>
|
||||
<div>
|
||||
<span className={styles.contactLabel}>land line</span>
|
||||
<p>{business.contact.landlines.join(" , ")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.socials}>
|
||||
{business.contact.socials.map((social) => (
|
||||
<a
|
||||
key={social.network}
|
||||
href="#"
|
||||
className={styles.socialChip}
|
||||
>
|
||||
<img src={social.icon} alt="" width={18} height={18} />
|
||||
<span>
|
||||
<em>{social.network}</em> {social.handle}
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.map}>
|
||||
<Image
|
||||
src={business.contact.map}
|
||||
alt="Business location map"
|
||||
fill
|
||||
className={styles.mapImg}
|
||||
sizes="100vw"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tab === "blog" ? (
|
||||
<>
|
||||
<CategoryPills
|
||||
items={business.blogCategories}
|
||||
active={blogCategory}
|
||||
onChange={setBlogCategory}
|
||||
/>
|
||||
<FiltersBar
|
||||
fields={[
|
||||
{ label: "Article Name", value: "gold Scarlet necklace" },
|
||||
{ label: "Date", value: "1402.05.12" },
|
||||
]}
|
||||
tags={[
|
||||
{ label: "Name", value: "gold Scarlet necklace" },
|
||||
{ label: "date", value: "1402.05.12" },
|
||||
]}
|
||||
/>
|
||||
<div className={styles.blogGrid}>
|
||||
{business.posts.map((post) => (
|
||||
<Link
|
||||
key={post.id}
|
||||
href={`/businesses/${business.slug}/blog/${post.slug}`}
|
||||
className={styles.blogCard}
|
||||
>
|
||||
<div className={styles.blogMedia}>
|
||||
<Image
|
||||
src={post.image}
|
||||
alt=""
|
||||
fill
|
||||
className={styles.blogImg}
|
||||
sizes="(max-width: 720px) 50vw, 292px"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.blogBody}>
|
||||
<h3>{post.title}</h3>
|
||||
<p>{post.excerpt}</p>
|
||||
<div className={styles.blogMeta}>
|
||||
<span>
|
||||
<img
|
||||
src="/images/icons/home-outline.svg"
|
||||
alt=""
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
{post.date}
|
||||
</span>
|
||||
<span>
|
||||
<img
|
||||
src="/images/icons/user.svg"
|
||||
alt=""
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
{post.author}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{tab === "gallery" ? (
|
||||
<div className={styles.gallery}>
|
||||
{business.gallery.map((item) => (
|
||||
<figure
|
||||
key={item.id}
|
||||
className={[
|
||||
styles.galleryItem,
|
||||
item.tall ? styles.galleryTall : "",
|
||||
item.medium ? styles.galleryMedium : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
<Image
|
||||
src={item.image}
|
||||
alt=""
|
||||
fill
|
||||
className={styles.galleryImg}
|
||||
sizes="(max-width: 900px) 50vw, 20vw"
|
||||
/>
|
||||
<span className={styles.galleryZoom} aria-hidden>
|
||||
<img src="/images/icons/search.svg" alt="" width={22} height={22} />
|
||||
</span>
|
||||
</figure>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tab === "portfolios" ? (
|
||||
<>
|
||||
<CategoryPills
|
||||
items={business.portfolioCategories}
|
||||
active={portfolioCategory}
|
||||
onChange={setPortfolioCategory}
|
||||
/>
|
||||
<div className={styles.portfolioGrid}>
|
||||
{business.portfolios.map((item) => {
|
||||
const showOverlay =
|
||||
hoveredPortfolio === item.id ||
|
||||
(hoveredPortfolio === null &&
|
||||
item.id === business.portfolios[2]?.id);
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/businesses/${business.slug}/portfolios/${item.slug}`}
|
||||
className={styles.portfolioCard}
|
||||
onMouseEnter={() => setHoveredPortfolio(item.id)}
|
||||
onMouseLeave={() => setHoveredPortfolio(null)}
|
||||
>
|
||||
<div className={styles.portfolioMedia}>
|
||||
<Image
|
||||
src={item.image}
|
||||
alt=""
|
||||
fill
|
||||
className={styles.portfolioImg}
|
||||
sizes="(max-width: 900px) 100vw, 33vw"
|
||||
/>
|
||||
<div
|
||||
className={[
|
||||
styles.portfolioOverlay,
|
||||
showOverlay ? styles.portfolioOverlayVisible : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
<h3>{item.title}</h3>
|
||||
<p>{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user