Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation. Made-with: Cursor
70 lines
3.1 KiB
TypeScript
70 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useState } from "react";
|
|
import { useCart } from "@/contexts/CartContext";
|
|
import { ProductSocialBlock } from "@/components/shop/ProductSocialBlock";
|
|
import { resolveProductImage, type ShopCurrency, type ShopProduct } from "@/lib/shopCatalog";
|
|
import { getProductSocialMetrics } from "@/lib/shopProductSocial";
|
|
|
|
const SYM: Record<ShopCurrency, string> = { BTC: "₿", ETH: "Ξ", USD: "$", XMR: "⏣" };
|
|
|
|
function formatPrice(price: number, currency: ShopCurrency): string {
|
|
if (currency === "BTC") return price.toFixed(6);
|
|
if (currency === "ETH") return price.toFixed(4);
|
|
if (currency === "XMR") return price.toFixed(3);
|
|
return price.toFixed(2);
|
|
}
|
|
|
|
export function VendorShelfRow({ product }: { product: ShopProduct }) {
|
|
const { addToCart, hydrated } = useCart();
|
|
const [flash, setFlash] = useState(false);
|
|
const src = resolveProductImage(product);
|
|
const metrics = getProductSocialMetrics(product.id);
|
|
|
|
return (
|
|
<li className="flex flex-col gap-4 rounded-lg border border-orange-950/70 bg-[#080605] p-4 sm:flex-row">
|
|
<div className="relative h-28 w-full shrink-0 overflow-hidden rounded border border-orange-900/30 bg-black sm:h-28 sm:w-28">
|
|
<Image src={src} alt="" fill className="object-cover" sizes="112px" unoptimized={src.startsWith("/")} />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="font-mono text-[10px] uppercase tracking-wider text-orange-500/60">{product.category}</p>
|
|
<p className="font-semibold text-orange-50">{product.name}</p>
|
|
<div className="mt-2 text-[#c4b5a0]">
|
|
<ProductSocialBlock metrics={metrics} compact />
|
|
</div>
|
|
<p className="mt-2 line-clamp-2 text-xs text-orange-200/65">{product.description}</p>
|
|
<p className="mt-2 font-mono text-sm text-orange-300">
|
|
{SYM[product.currency]}
|
|
{formatPrice(product.price, product.currency)} {product.currency}
|
|
</p>
|
|
<div className="mt-3 space-y-2 border-t border-orange-950/50 pt-3">
|
|
<p className="font-mono text-[9px] font-bold uppercase tracking-widest text-orange-500/50">Verified note</p>
|
|
<p className="text-xs italic text-orange-200/60">“{metrics.testimonials[0]?.text}”</p>
|
|
</div>
|
|
<div className="mt-4 flex flex-wrap gap-2">
|
|
<button
|
|
type="button"
|
|
disabled={!hydrated}
|
|
onClick={() => {
|
|
addToCart(product, 1);
|
|
setFlash(true);
|
|
window.setTimeout(() => setFlash(false), 1500);
|
|
}}
|
|
className="rounded border border-orange-500 bg-orange-500/90 px-4 py-2 font-mono text-xs font-bold uppercase text-black hover:bg-orange-400 disabled:opacity-50"
|
|
>
|
|
{flash ? "Added ✓" : "Add to cart"}
|
|
</button>
|
|
<Link
|
|
href="/checkout"
|
|
className="rounded border border-orange-800 px-4 py-2 font-mono text-xs text-orange-200 hover:border-orange-600"
|
|
>
|
|
Cart
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|