Initial commit: Meshkee dashboards monorepo.
Includes business, customer, and super-admin apps with shared packages and production deploy scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import { useId } from 'react'
|
||||
import { Pencil, Percent, Sparkles, Trash2 } from 'lucide-react'
|
||||
import {
|
||||
formatVariantCount,
|
||||
type StoreProductListing,
|
||||
} from '../utils/storeProductGroups'
|
||||
import { StoreItemPrice } from './StoreItemPrice'
|
||||
import { Tooltip } from './Tooltip'
|
||||
import styles from './StoreItemCard.module.css'
|
||||
|
||||
interface StoreItemCardProps {
|
||||
listing: StoreProductListing
|
||||
onOpen: (listing: StoreProductListing) => void
|
||||
onEdit: (listing: StoreProductListing) => void
|
||||
onDiscount: (listing: StoreProductListing) => void
|
||||
onFestival: (listing: StoreProductListing) => void
|
||||
onRemove: (listing: StoreProductListing) => void
|
||||
onAddToCart: (listing: StoreProductListing) => void
|
||||
removeTooltip?: string
|
||||
}
|
||||
|
||||
function GradientPlusIcon({ gradientId }: { gradientId: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
className={styles.addToCartIcon}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={gradientId}
|
||||
x1="4"
|
||||
y1="4"
|
||||
x2="20"
|
||||
y2="20"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#22c55e" />
|
||||
<stop offset="0.5" stopColor="var(--primary)" />
|
||||
<stop offset="1" stopColor="#a855f7" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M12 5v14M5 12h14"
|
||||
stroke={`url(#${gradientId})`}
|
||||
strokeWidth="2.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function StoreItemCard({
|
||||
listing,
|
||||
onOpen,
|
||||
onEdit,
|
||||
onDiscount,
|
||||
onFestival,
|
||||
onRemove,
|
||||
onAddToCart,
|
||||
removeTooltip = 'Remove from store',
|
||||
}: StoreItemCardProps) {
|
||||
const plusGradientId = `add-cart-gradient-${useId().replace(/:/g, '')}`
|
||||
|
||||
return (
|
||||
<article className={styles.card}>
|
||||
<button type="button" className={styles.clickable} onClick={() => onOpen(listing)}>
|
||||
<div className={styles.imageWrap}>
|
||||
{listing.productImage ? (
|
||||
<img
|
||||
src={listing.productImage}
|
||||
alt={listing.productTitle}
|
||||
className={styles.image}
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.imagePlaceholder} />
|
||||
)}
|
||||
{listing.showFestival && <span className={styles.festivalBadge}>Festival</span>}
|
||||
{listing.productTotalStock > 0 && (
|
||||
<span className={styles.stockBadge}>{listing.productTotalStock} in stock</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<h3 className={styles.nameEn}>{listing.productTitle}</h3>
|
||||
{listing.productNameFa && <p className={styles.nameFa}>{listing.productNameFa}</p>}
|
||||
<p className={styles.variantLabel}>{formatVariantCount(listing.variantCount)}</p>
|
||||
<StoreItemPrice
|
||||
price={listing.displayPrice}
|
||||
discountedPrice={listing.displayDiscountedPrice}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div className={styles.controls} onClick={(e) => e.stopPropagation()}>
|
||||
<div className={styles.controlsLeft}>
|
||||
<Tooltip label="Edit store items">
|
||||
<button type="button" onClick={() => onEdit(listing)} aria-label="Edit store items">
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip label="Set discounts">
|
||||
<button type="button" onClick={() => onDiscount(listing)} aria-label="Set discounts">
|
||||
<Percent size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip label="Festival reward points">
|
||||
<button
|
||||
type="button"
|
||||
className={listing.showFestival ? styles.festivalActive : undefined}
|
||||
onClick={() => onFestival(listing)}
|
||||
aria-label="Festival reward points"
|
||||
>
|
||||
<Sparkles size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip label={removeTooltip}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.danger}
|
||||
onClick={() => onRemove(listing)}
|
||||
aria-label={removeTooltip}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Tooltip label="Add to shopping cart">
|
||||
<button
|
||||
type="button"
|
||||
className={styles.addToCartBtn}
|
||||
onClick={() => onAddToCart(listing)}
|
||||
aria-label="Add to shopping cart"
|
||||
>
|
||||
<GradientPlusIcon gradientId={plusGradientId} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user