perf: product detail page modified

This commit is contained in:
Afra ‌ 2026-09-02 00:29:50 +03:30
parent 89d2a090e1
commit 418a63734f
3 changed files with 42 additions and 34 deletions

View file

@ -14,8 +14,8 @@ export default async function ProductDetailPage({ params }: PageProps<"/products
if (!product) notFound(); if (!product) notFound();
return ( return (
<main className="flex min-h-full flex-1 flex-col bg-white pb-[140px]"> <main className="flex min-h-full flex-1 flex-col bg-white pb-35">
<div className="relative h-[320px] w-full shrink-0 overflow-hidden bg-muted"> <div className="relative h-80 w-full shrink-0 overflow-hidden bg-muted">
{product.image_url && ( {product.image_url && (
// eslint-disable-next-line @next/next/no-img-element // eslint-disable-next-line @next/next/no-img-element
<img src={product.image_url} alt={product.name} className="h-full w-full object-cover" /> <img src={product.image_url} alt={product.name} className="h-full w-full object-cover" />
@ -24,9 +24,9 @@ export default async function ProductDetailPage({ params }: PageProps<"/products
</div> </div>
<div className="flex flex-col gap-5 px-5 pt-5"> <div className="flex flex-col gap-5 px-5 pt-5">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-6">
<h1 className="text-[24px] font-bold leading-none text-foreground">{product.name}</h1> <h1 className="text-[24px] font-bold leading-none text-foreground">{product.name}</h1>
<Link href={`/stores/${product.store.uuid}`} className="w-fit text-[14px] font-semibold leading-none text-primary-action"> <Link href={`/stores/${product.store.uuid}`} className="w-fit text-[14px] font-medium leading-none text-gray-600">
{product.store.name} {product.store.name}
</Link> </Link>
<p className="text-headline-medium font-extrabold leading-none text-primary-action"> <p className="text-headline-medium font-extrabold leading-none text-primary-action">

View file

@ -2,7 +2,7 @@
import { useEffect, useState, useTransition, type ReactNode } from "react"; import { useEffect, useState, useTransition, type ReactNode } from "react";
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import { Minus, Plus, X, CheckCircle2 } from "lucide-react"; import { Minus, Plus, X, CheckCircle2, Check } from "lucide-react";
import { addToCart } from "@/lib/api/cart"; import { addToCart } from "@/lib/api/cart";
import { toPersianDigits } from "@/lib/format/persian-digits"; import { toPersianDigits } from "@/lib/format/persian-digits";
import type { ProductDetail } from "@/types/api"; import type { ProductDetail } from "@/types/api";
@ -41,17 +41,19 @@ export function ProductPurchasePanel({ product, children }: { product: ProductDe
return ( return (
<> <>
{/* DOM order intentionally mirrors the screenshot's left→right layout, not reading
order — under this app's inherited dir="rtl", the first child renders at the
right edge and the last at the left, so "what should appear on the left" must
come last in JSX (stepper group, then its own "+" trailing last so it lands
leftmost within the group). */}
<div className="flex h-10 items-center justify-between"> <div className="flex h-10 items-center justify-between">
<span
className={`inline-flex items-center gap-1 text-[14px] font-semibold leading-none ${product.is_out_of_stock ? "text-error" : "text-emerald-500"}`}
>
{product.is_out_of_stock ? "ناموجود" : "موجود"}
{!product.is_out_of_stock && <Check className="h-3.5 w-3.5" />}
</span>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<button
type="button"
onClick={() => setQuantity((q) => q + 1)}
aria-label="افزایش تعداد"
className="flex h-10 w-10 items-center justify-center rounded-[20px] border border-gray-300 text-primary-action"
>
<Plus className="h-4 w-4" />
</button>
<span className="text-[18px] font-bold leading-none text-black">{toPersianDigits(quantity)}</span>
<button <button
type="button" type="button"
onClick={() => setQuantity((q) => Math.max(1, q - 1))} onClick={() => setQuantity((q) => Math.max(1, q - 1))}
@ -60,10 +62,16 @@ export function ProductPurchasePanel({ product, children }: { product: ProductDe
> >
<Minus className="h-4 w-4" /> <Minus className="h-4 w-4" />
</button> </button>
<span className="text-[18px] font-bold leading-none text-black">{toPersianDigits(quantity)}</span>
<button
type="button"
onClick={() => setQuantity((q) => q + 1)}
aria-label="افزایش تعداد"
className="flex h-10 w-10 items-center justify-center rounded-[20px] border border-gray-300 text-primary-action"
>
<Plus className="h-4 w-4" />
</button>
</div> </div>
<span className={`text-[14px] font-semibold leading-none ${product.is_out_of_stock ? "text-error" : "text-emerald-500"}`}>
{product.is_out_of_stock ? "ناموجود" : "موجود ●"}
</span>
</div> </div>
<div className="h-px w-full bg-gray-300" /> <div className="h-px w-full bg-gray-300" />
@ -73,35 +81,35 @@ export function ProductPurchasePanel({ product, children }: { product: ProductDe
{error && <p className="px-5 text-body-medium text-error">{error}</p>} {error && <p className="px-5 text-body-medium text-error">{error}</p>}
{showToast && ( {showToast && (
<div className="fixed inset-x-0 bottom-[135px] z-20 mx-auto flex w-[358px] max-w-[calc(100%-32px)] items-center gap-3 rounded-2xl border border-emerald-border bg-emerald-bg p-4 shadow-[0_4px_12px_0_rgba(16,185,129,0.102)]"> <div className="fixed inset-x-0 bottom-33.75 z-20 mx-auto flex w-89.5 max-w-[calc(100%-32px)] items-center gap-3 rounded-2xl border border-emerald-border bg-emerald-bg p-4 shadow-[0_4px_12px_0_rgba(16,185,129,0.102)]">
<button type="button" onClick={() => setShowToast(false)} aria-label="بستن"> <CheckCircle2 className="h-6 w-6 shrink-0 fill-emerald-500 text-white" />
<X className="h-4 w-4 text-emerald-500" />
</button>
<span className="flex-1 text-right text-[14px] font-bold leading-none text-emerald-500"> <span className="flex-1 text-right text-[14px] font-bold leading-none text-emerald-500">
محصول به سبد خرید اضافه شد محصول به سبد خرید اضافه شد
</span> </span>
<CheckCircle2 className="h-6 w-6 shrink-0 fill-emerald-500 text-white" /> <button type="button" onClick={() => setShowToast(false)} aria-label="بستن">
<X className="h-4 w-4 text-emerald-500" />
</button>
</div> </div>
)} )}
<div className="fixed inset-x-0 bottom-0 z-10 mx-auto flex w-full max-w-[390px] flex-col items-center gap-2 bg-white px-4 pt-4 pb-2 shadow-[0_-4px_15px_0_rgba(0,0,0,0.102)]"> <div className="fixed inset-x-0 bottom-0 z-10 mx-auto flex w-full max-w-97.5 flex-col items-center gap-2 bg-white px-4 pt-4 pb-2 shadow-[0_-4px_15px_0_rgba(0,0,0,0.102)]">
<div className="flex w-full items-center justify-between gap-4"> <div className="flex w-full items-center justify-between gap-4">
<button
type="button"
onClick={handleAddToCart}
disabled={disabled || isPending}
className="flex h-[53px] w-[245px] items-center justify-center rounded-xl bg-primary-action text-body-large font-bold text-white disabled:opacity-40"
>
{isPending ? "در حال افزودن..." : "افزودن به سبد خرید"}
</button>
<div className="flex flex-col items-end gap-0.5"> <div className="flex flex-col items-end gap-0.5">
<span className="text-[12px] leading-none text-gray-600">جمع کل</span> <span className="text-[12px] leading-none text-gray-600">جمع کل</span>
<span className="text-[18px] font-bold leading-none text-foreground"> <span className="text-[18px] font-bold leading-none text-foreground">
{(product.price * quantity).toLocaleString("fa-IR")} تومان {(product.price * quantity).toLocaleString("fa-IR")} تومان
</span> </span>
</div> </div>
<button
type="button"
onClick={handleAddToCart}
disabled={disabled || isPending}
className="flex h-13.25 w-61.25 items-center justify-center rounded-xl bg-primary-action text-body-large font-bold text-white disabled:opacity-40"
>
{isPending ? "در حال افزودن..." : "افزودن به سبد خرید"}
</button>
</div> </div>
<div className="mt-2 h-[5px] w-[134px] rounded-full bg-black" /> <div className="mt-2 h-1.5 w-32 rounded-full bg-black" />
</div> </div>
</> </>
); );

View file

@ -7,9 +7,9 @@ export function HeroBackButton({ href }: { href: string }) {
<Link <Link
href={href} href={href}
aria-label="بازگشت" aria-label="بازگشت"
className="absolute top-4 right-4 flex h-9 w-9 items-center justify-center rounded-full border border-white bg-white/80 backdrop-blur-lg" className="absolute top-4 right-4 flex h-9 w-9 items-center justify-center rounded-full bg-black/35 backdrop-blur-lg"
> >
<ArrowRight className="h-[19.8px] w-[19.8px] text-black" /> <ArrowRight className="h-[19.8px] w-[19.8px] text-white" />
</Link> </Link>
); );
} }