Desktop: account area (shared sidebar) + brands list
- new profile.tsx layout: sticky desktop sidebar (user card + menu) wrapping all /profile/* pages; mobile renders page content only (unchanged) - ProfilePagesHeader hidden at lg (one change covers all account sub-pages + cart); profile index shows a welcome on desktop (menu lives in the sidebar) - sellers: 5-col brand grid with name overlay, contained, mobile header hidden - MyLayout: desktop gate now covers /profile/* and /sellers
This commit is contained in:
parent
96034fe61a
commit
3156fb4204
@ -41,7 +41,8 @@ const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
|
||||
p.startsWith("/collection/") ||
|
||||
p === "/cart" ||
|
||||
p.startsWith("/cart/") ||
|
||||
p === "/profile";
|
||||
p === "/sellers" ||
|
||||
p.startsWith("/profile");
|
||||
// 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";
|
||||
|
||||
@ -20,7 +20,7 @@ const ProfilePagesHeader = memo(function ProfilePagesHeader({
|
||||
}, [navigate]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
|
||||
<div className="lg:hidden flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
|
||||
{!hideBackButton && (
|
||||
<ArrowRight
|
||||
onClick={handleBack}
|
||||
|
||||
@ -125,11 +125,17 @@ export default function Profile() {
|
||||
];
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className="flex flex-col gap-0 lg:max-w-[1000px] lg:mx-auto lg:w-full lg:px-8 lg:py-8">
|
||||
<h1 className="hidden lg:block text-[28px] font-extrabold mb-2">
|
||||
حساب کاربری
|
||||
</h1>
|
||||
<ProfileTopBar user={user} />
|
||||
<div className="flex flex-col gap-0">
|
||||
{/* Desktop: welcome (the sidebar provides the menu) */}
|
||||
<div className="hidden lg:flex flex-col items-center justify-center text-center py-24 border border-WHITE3 rounded-2xl">
|
||||
<CircleUserRound size={64} className="text-GRAY mb-4" />
|
||||
<h2 className="text-[22px] font-extrabold mb-1">حساب کاربری</h2>
|
||||
<p className="text-GRAY">یک گزینه را از منوی کناری انتخاب کنید</p>
|
||||
</div>
|
||||
|
||||
{/* Mobile: account menu */}
|
||||
<div className="flex flex-col gap-0 lg:hidden">
|
||||
<ProfileTopBar user={user} />
|
||||
<div
|
||||
className={`flex flex-row h-[55px] w-full items-center gap-2 justify-between px-[16px] active:bg-hover transition`}
|
||||
>
|
||||
@ -157,6 +163,7 @@ export default function Profile() {
|
||||
setLogoutModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<StoreModal open={storeModalOpen} onOpenChange={setStoreModalOpen} />
|
||||
<LogoutModal open={logoutModalOpen} onOpenChange={setLogoutModalOpen} />
|
||||
<LoginRequiredDialog
|
||||
|
||||
92
app/routes/profile.tsx
Normal file
92
app/routes/profile.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { Link, Outlet, useLocation } from "@remix-run/react";
|
||||
import {
|
||||
ShoppingBag,
|
||||
MessageCircle,
|
||||
CircleFadingPlus,
|
||||
BookmarkMinus,
|
||||
Layers,
|
||||
Wallet,
|
||||
MapPin,
|
||||
Settings,
|
||||
LogOut,
|
||||
CircleUserRound,
|
||||
} from "lucide-react";
|
||||
import { useServiceGetProfile } from "~/utils/RequestHandler";
|
||||
import { useRootData } from "~/hooks/use-root-data";
|
||||
|
||||
const NAV = [
|
||||
{ label: "سفارشها", to: "/profile/orders", icon: ShoppingBag },
|
||||
{ label: "پیامها", to: "/profile/threads", icon: MessageCircle },
|
||||
{ label: "دنبالشدهها", to: "/profile/following", icon: CircleFadingPlus },
|
||||
{ label: "ذخیرهشدهها", to: "/profile/bookmarks", icon: BookmarkMinus },
|
||||
{ label: "ستهای من", to: "/profile/sets", icon: Layers },
|
||||
{ label: "کیف پول", to: "/profile/wallet", icon: Wallet },
|
||||
{ label: "آدرسها", to: "/profile/location", icon: MapPin },
|
||||
{ label: "تنظیمات", to: "/profile/setting", icon: Settings },
|
||||
];
|
||||
|
||||
/**
|
||||
* Layout for the account area. On desktop it renders a sticky sidebar
|
||||
* (user card + menu) next to the page content; on mobile it's just the
|
||||
* page content (each profile page keeps its existing mobile UI).
|
||||
*/
|
||||
export default function ProfileLayout() {
|
||||
const location = useLocation();
|
||||
const { user } = useRootData();
|
||||
const { data: profile } = useServiceGetProfile();
|
||||
|
||||
const isActive = (to: string) =>
|
||||
location.pathname === to || location.pathname.startsWith(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-[280px_1fr] lg:gap-7 lg:items-start">
|
||||
{/* Desktop sidebar */}
|
||||
<aside className="hidden lg:block lg:sticky lg:top-[88px] border border-WHITE3 rounded-2xl overflow-hidden">
|
||||
<div className="p-5 border-b border-WHITE3 text-center">
|
||||
<div className="w-20 h-20 rounded-full bg-WHITE3 grid place-items-center mx-auto mb-3 text-GRAY">
|
||||
<CircleUserRound size={40} />
|
||||
</div>
|
||||
<p className="font-bold text-[16px]">
|
||||
{profile?.username || "کاربر ویترون"}
|
||||
</p>
|
||||
{profile?.phoneNumber ? (
|
||||
<p className="text-GRAY text-[13px] mt-1" dir="ltr">
|
||||
{profile.phoneNumber}
|
||||
</p>
|
||||
) : null}
|
||||
</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.to)
|
||||
? "bg-BLACK text-white"
|
||||
: "text-BLACK2 hover:bg-WHITE2"
|
||||
}`}
|
||||
>
|
||||
<n.icon size={20} />
|
||||
{n.label}
|
||||
</Link>
|
||||
))}
|
||||
{user?.access_token ? (
|
||||
<Link
|
||||
to="/logout"
|
||||
className="flex items-center gap-3 px-3.5 py-3 rounded-xl text-[14.5px] font-semibold text-RED hover:bg-red-50 transition-colors"
|
||||
>
|
||||
<LogOut size={20} />
|
||||
خروج از حساب
|
||||
</Link>
|
||||
) : null}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{/* Page content */}
|
||||
<div className="min-w-0">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -13,7 +13,7 @@ export default function Sellers() {
|
||||
|
||||
// Loading skeleton
|
||||
const SellersSkeleton = () => (
|
||||
<div className="grid grid-cols-2 gap-3 px-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3 lg:gap-4 px-4 lg:px-0">
|
||||
{Array.from({ length: 8 }).map((_, index) => (
|
||||
<Skeleton key={index} className="aspect-square rounded-lg" />
|
||||
))}
|
||||
@ -61,8 +61,8 @@ export default function Sellers() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-WHITE pb-20">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
|
||||
{/* Header (mobile) */}
|
||||
<div className="lg:hidden flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
|
||||
<ArrowRight
|
||||
onClick={handleBack}
|
||||
className="absolute top-5 right-5 cursor-pointer"
|
||||
@ -71,7 +71,10 @@ export default function Sellers() {
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col py-6">
|
||||
<div className="flex flex-col py-6 lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8">
|
||||
<h1 className="hidden lg:block text-[28px] font-extrabold mb-6">
|
||||
فروشگاهها
|
||||
</h1>
|
||||
{status === "pending" ? (
|
||||
<SellersSkeleton />
|
||||
) : status === "error" ? (
|
||||
@ -79,11 +82,11 @@ export default function Sellers() {
|
||||
) : allSellers.length === 0 ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-3 px-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3 lg:gap-4 px-4 lg:px-0">
|
||||
{allSellers.map((seller, index) => (
|
||||
<Link
|
||||
key={seller.id || index}
|
||||
to={`/seller/${seller.sellerId}`}
|
||||
to={`/seller/${seller.sellerUsername || seller.sellerId}`}
|
||||
className="relative aspect-square overflow-hidden rounded-lg group"
|
||||
>
|
||||
{/* Background Image */}
|
||||
@ -98,6 +101,16 @@ export default function Sellers() {
|
||||
<Store size={48} className="text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
{seller.sellerName ? (
|
||||
<>
|
||||
<div className="hidden lg:block absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
|
||||
<div className="hidden lg:flex absolute inset-x-0 bottom-0 p-4 items-end">
|
||||
<span className="text-white font-bold text-[15px] truncate">
|
||||
{seller.sellerName}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user