Desktop: seller dashboard shell + gate carousel arrows to product gallery

- new store.$storeId.tsx layout: sticky admin sidebar (store/products/orders/
  financial/shipping/threads/settings) beside content at lg; mobile unchanged
- MyLayout: store/* gets a full-width desktop canvas (own sidebar), no shopper
  header/footer, mobile store-nav hidden at lg
- store home: hide the redundant mobile store header at lg
- ProductImageCarousel: prev/next arrows now opt-in (showArrows) so they only
  appear on the product gallery, not every masonry card
This commit is contained in:
Arda Samadi 2026-06-17 12:28:59 +03:30
parent 207d46eefd
commit 4af143bccf
5 changed files with 98 additions and 4 deletions

View File

@ -46,7 +46,10 @@ const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
// Auth pages get a full-width desktop canvas (so they can center their own
// card) but no shopping header/footer.
const isAuth = p === "/login" || p === "/logout";
const expandOnDesktop = isDesktopReady || isAuth;
// Seller dashboard: full-width canvas + its own sidebar (store.$storeId.tsx),
// no shopper header/footer.
const isStore = p.startsWith("/store/");
const expandOnDesktop = isDesktopReady || isAuth || isStore;
const isThread = !!location.pathname.split("threads")[1];
return (

View File

@ -338,6 +338,7 @@ export const ProductDetailView = memo(function ProductDetailView({
borderRadius={0}
isActive={productActiveStatus}
controlsIsOn={true}
showArrows={true}
onStatusChange={handleProductStatusChange}
isChangeStatusDialogOpen={isChangeStatusDialogOpen}
setIsChangeStatusDialogOpen={setIsChangeStatusDialogOpen}

View File

@ -46,6 +46,7 @@ interface ProductImageCarouselProps {
isOwner?: boolean;
isActive?: boolean;
controlsIsOn?: boolean;
showArrows?: boolean;
onStatusChange?: (newStatus: boolean) => void;
isChangeStatusDialogOpen?: boolean;
setIsChangeStatusDialogOpen?: (open: boolean) => void;
@ -59,6 +60,7 @@ export const ProductImageCarousel = ({
height,
borderRadius,
controlsIsOn = false,
showArrows = false,
isOwner = false,
isActive = true,
onStatusChange,
@ -276,8 +278,8 @@ export const ProductImageCarousel = ({
</div>
)}
{/* Prev / next arrows (desktop) */}
{images.length > 1 && (
{/* Prev / next arrows (desktop, opt-in) */}
{showArrows && images.length > 1 && (
<>
<button
type="button"

View File

@ -105,7 +105,9 @@ export default function Store() {
return (
<div className="flex flex-col pb-20 static md:relative ">
<SellerDetailHeader sellerData={data} storeId={storeId} />
<div className="lg:hidden">
<SellerDetailHeader sellerData={data} storeId={storeId} />
</div>
<div className="w-full">
<div className="px-4">
<div className="flex px-4 border rounded-[10px] w-full py-2">

View File

@ -0,0 +1,86 @@
import { Link, Outlet, useLocation, useParams } from "@remix-run/react";
import {
LayoutGrid,
Package,
ClipboardList,
Wallet,
Truck,
MessageCircle,
Settings,
ArrowRight,
} from "lucide-react";
import { useSellerData } from "~/requestHandler/use-seller-hooks";
import SellerLogo from "~/components/SellerLogo";
/**
* Desktop layout for the seller dashboard. Renders a sticky admin sidebar
* next to the page content at lg+; on mobile it's just the page content
* (the existing mobile dashboard, with its store-mode bottom nav, is unchanged).
*/
export default function StoreLayout() {
const { storeId } = useParams();
const location = useLocation();
const { data } = useSellerData(storeId);
const base = `/store/${storeId}`;
const NAV = [
{ label: "فروشگاه", to: base, icon: LayoutGrid, exact: true },
{ label: "محصولات", to: `${base}/products`, icon: Package },
{ label: "سفارش‌ها", to: `${base}/orders`, icon: ClipboardList },
{ label: "مالی", to: `${base}/financial-dashboard`, icon: Wallet },
{ label: "روش ارسال", to: `${base}/shipping-method`, icon: Truck },
{ label: "پیام‌ها", to: `${base}/threads`, icon: MessageCircle },
{ label: "تنظیمات", to: `${base}/setting`, icon: Settings },
];
const isActive = (item: { to: string; exact?: boolean }) =>
item.exact
? location.pathname === item.to
: location.pathname.startsWith(item.to);
return (
<div className="lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-8 lg:grid lg:grid-cols-[260px_1fr] lg:gap-7 lg:items-start">
{/* Desktop admin sidebar */}
<aside className="hidden lg:block lg:sticky lg:top-6 border border-WHITE3 rounded-2xl overflow-hidden">
<div className="p-4 border-b border-WHITE3 flex items-center gap-3">
<SellerLogo size="sm" src={data?.storeLogo} alt="store" />
<div className="min-w-0">
<p className="font-bold text-[15px] truncate">
{data?.storeName || "فروشگاه من"}
</p>
<p className="text-GRAY text-[12px]">پنل مدیریت</p>
</div>
</div>
<nav className="p-2.5 flex flex-col gap-0.5">
{NAV.map((n) => (
<Link
key={n.to}
to={n.to}
prefetch="intent"
className={`flex items-center gap-3 px-3.5 py-3 rounded-xl text-[14.5px] font-semibold transition-colors ${
isActive(n)
? "bg-BLACK text-white"
: "text-BLACK2 hover:bg-WHITE2"
}`}
>
<n.icon size={20} />
{n.label}
</Link>
))}
<Link
to="/"
className="flex items-center gap-3 px-3.5 py-3 rounded-xl text-[14.5px] font-semibold text-BLACK2 hover:bg-WHITE2 transition-colors"
>
<ArrowRight size={20} />
بازگشت به فروشگاه
</Link>
</nav>
</aside>
{/* Page content */}
<div className="min-w-0">
<Outlet />
</div>
</div>
);
}