132 lines
5.6 KiB
TypeScript
132 lines
5.6 KiB
TypeScript
import {useState} from 'react'
|
|
|
|
import {ChevronRight} from 'lucide-react'
|
|
|
|
import Button from '@/components/button'
|
|
import {HOME_CATEGORY_META_MAP} from '@/constant'
|
|
import type {ProductCategory, ProductItem} from '@/types'
|
|
|
|
type GoodsCategoryListProps = {
|
|
categories: ProductCategory[]
|
|
loading?: boolean
|
|
emptyText?: string
|
|
showMore?: boolean
|
|
onMoreClick?: (categoryId: ProductCategory['id']) => void
|
|
onRedeem: (product: ProductItem, categoryId: ProductCategory['id']) => void
|
|
}
|
|
|
|
function GoodsImage({
|
|
imageUrl,
|
|
}: {
|
|
imageUrl?: string
|
|
}) {
|
|
const [hasError, setHasError] = useState(false)
|
|
const showFallback = !imageUrl || hasError
|
|
|
|
return (
|
|
<div className="relative h-[116px] w-full overflow-hidden rounded-t-[10px] sm:h-[128px]">
|
|
{showFallback ? (
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-[radial-gradient(circle_at_top,rgba(250,106,0,0.18),transparent_58%),linear-gradient(180deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02))] px-[12px] text-center">
|
|
<div className="h-[30px] w-[30px] rounded-[10px] border border-white/10 bg-white/6"></div>
|
|
<div className="mt-[10px] text-[12px] tracking-[0.08em] text-white/42">NO IMAGE</div>
|
|
</div>
|
|
) : null}
|
|
|
|
{imageUrl ? (
|
|
<img
|
|
src={imageUrl}
|
|
alt=""
|
|
className={`h-full w-full object-cover ${showFallback ? 'hidden' : 'block'}`}
|
|
onError={() => setHasError(true)}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function GoodsCategoryList({
|
|
categories,
|
|
loading = false,
|
|
emptyText = 'No goods available yet.',
|
|
showMore = false,
|
|
onMoreClick,
|
|
onRedeem,
|
|
}: GoodsCategoryListProps) {
|
|
if (loading) {
|
|
return (
|
|
<div className="pb-[24px] mt-[20px]">
|
|
<div className="liquid-glass-bg px-[16px] py-[18px] text-[14px] text-white/60">
|
|
Loading ...
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!categories.length) {
|
|
return (
|
|
<div className="pb-[24px]">
|
|
<div className="liquid-glass-bg px-[16px] py-[18px] text-[14px] text-white/60">
|
|
{emptyText}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="pb-[24px]">
|
|
{categories.map((category) => {
|
|
const CategoryIcon = HOME_CATEGORY_META_MAP[category.id].icon
|
|
|
|
return (
|
|
<div key={category.id} className="mt-[20px]">
|
|
<div className="mb-[10px] flex items-center justify-between gap-[12px]">
|
|
<div className="flex min-w-0 items-center gap-[10px]">
|
|
<div
|
|
className="flex h-[36px] w-[36px] items-center justify-center rounded-[12px] bg-[#FA6A00]/15 text-[#FE9F00]">
|
|
<CategoryIcon className="h-[18px] w-[18px]" aria-hidden="true"/>
|
|
</div>
|
|
<div className="truncate text-[15px] font-semibold text-white">{category.name}</div>
|
|
</div>
|
|
{showMore && onMoreClick ? (
|
|
<button
|
|
type="button"
|
|
className="flex shrink-0 items-center gap-[3px] text-[12px] font-light text-[#FA6A00] underline cursor-pointer"
|
|
onClick={() => onMoreClick(category.id)}
|
|
>
|
|
more
|
|
<ChevronRight className="h-[14px] w-[14px]" aria-hidden="true"/>
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
{category.items.map((product) => (
|
|
<div
|
|
key={product.id}
|
|
className="liquid-glass-bg flex min-h-[260px] w-full flex-col items-stretch justify-start overflow-hidden"
|
|
>
|
|
<GoodsImage imageUrl={product.imageUrl}/>
|
|
<div
|
|
className="flex flex-1 flex-col items-start justify-between gap-[12px] p-[12px] sm:p-[14px]"
|
|
>
|
|
<div className="space-y-[4px]">
|
|
<div className="text-[16px] font-medium text-white">{product.title}</div>
|
|
<div className="text-[13px] text-white/52">{product.subtitle}</div>
|
|
</div>
|
|
<div className="text-[16px] font-semibold text-[#FA6A00]">{product.score}</div>
|
|
<Button
|
|
className="h-[36px] w-full text-[13px]"
|
|
onClick={() => onRedeem(product, category.id)}
|
|
>
|
|
{product.ctaLabel}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|