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:
parent
207d46eefd
commit
4af143bccf
@ -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
|
// Auth pages get a full-width desktop canvas (so they can center their own
|
||||||
// card) but no shopping header/footer.
|
// card) but no shopping header/footer.
|
||||||
const isAuth = p === "/login" || p === "/logout";
|
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];
|
const isThread = !!location.pathname.split("threads")[1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -338,6 +338,7 @@ export const ProductDetailView = memo(function ProductDetailView({
|
|||||||
borderRadius={0}
|
borderRadius={0}
|
||||||
isActive={productActiveStatus}
|
isActive={productActiveStatus}
|
||||||
controlsIsOn={true}
|
controlsIsOn={true}
|
||||||
|
showArrows={true}
|
||||||
onStatusChange={handleProductStatusChange}
|
onStatusChange={handleProductStatusChange}
|
||||||
isChangeStatusDialogOpen={isChangeStatusDialogOpen}
|
isChangeStatusDialogOpen={isChangeStatusDialogOpen}
|
||||||
setIsChangeStatusDialogOpen={setIsChangeStatusDialogOpen}
|
setIsChangeStatusDialogOpen={setIsChangeStatusDialogOpen}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ interface ProductImageCarouselProps {
|
|||||||
isOwner?: boolean;
|
isOwner?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
controlsIsOn?: boolean;
|
controlsIsOn?: boolean;
|
||||||
|
showArrows?: boolean;
|
||||||
onStatusChange?: (newStatus: boolean) => void;
|
onStatusChange?: (newStatus: boolean) => void;
|
||||||
isChangeStatusDialogOpen?: boolean;
|
isChangeStatusDialogOpen?: boolean;
|
||||||
setIsChangeStatusDialogOpen?: (open: boolean) => void;
|
setIsChangeStatusDialogOpen?: (open: boolean) => void;
|
||||||
@ -59,6 +60,7 @@ export const ProductImageCarousel = ({
|
|||||||
height,
|
height,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
controlsIsOn = false,
|
controlsIsOn = false,
|
||||||
|
showArrows = false,
|
||||||
isOwner = false,
|
isOwner = false,
|
||||||
isActive = true,
|
isActive = true,
|
||||||
onStatusChange,
|
onStatusChange,
|
||||||
@ -276,8 +278,8 @@ export const ProductImageCarousel = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Prev / next arrows (desktop) */}
|
{/* Prev / next arrows (desktop, opt-in) */}
|
||||||
{images.length > 1 && (
|
{showArrows && images.length > 1 && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -105,7 +105,9 @@ export default function Store() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col pb-20 static md:relative ">
|
<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="w-full">
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<div className="flex px-4 border rounded-[10px] w-full py-2">
|
<div className="flex px-4 border rounded-[10px] w-full py-2">
|
||||||
|
|||||||
86
app/routes/store.$storeId.tsx
Normal file
86
app/routes/store.$storeId.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user