Files
website/components/BusinessDetail.tsx
T

456 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}