import { MetaFunction, redirect, type LoaderFunctionArgs } from "@remix-run/node";
import { sessionStorage } from "~/sessions.server";
import {
BookmarkMinus,
ChevronLeft,
MapPin,
Wallet,
MessageCircle,
Store,
ShoppingBag,
CircleFadingPlus,
Headset,
Info,
LogOut,
CircleUserRound,
FileQuestion,
Layers,
Pencil,
} from "lucide-react";
import { Link, useNavigate } from "@remix-run/react";
import { useServiceGetProfile } from "~/utils/RequestHandler";
import { useMyStores } from "~/requestHandler/use-seller-hooks";
import { useServiceGetWallet } from "~/requestHandler/use-wallet-hooks";
import { useBadgeCounts } from "~/hooks/useBadgeCounts";
import { Skeleton } from "~/components/ui/skeleton";
import { useState } from "react";
import { Button } from "~/components/ui/button";
import { Dialog, DialogContent, DialogOverlay } from "~/components/ui/dialog";
import { useRootData } from "~/hooks/use-root-data";
import { useServiceGetFollowedSellers } from "~/requestHandler/use-following-hooks";
import { User } from "~/utils/token-manager.server";
import { SunMoonToggle } from "~/components/SunMoonToggle";
import { LoginRequiredDialog } from "~/components/LoginRequiredDialog";
interface MenuItemType {
title: string;
link?: string;
pageName: string;
hasBorder?: boolean;
image: React.ReactNode;
badge?: number | null;
}
// Guard: the account hub requires login (sub-pages are already protected via
// validateTokens; this covers the /profile index itself).
export async function loader({ request }: LoaderFunctionArgs) {
const session = await sessionStorage.getSession(
request.headers.get("cookie")
);
const user = session.get("user");
if (!user?.access_token) {
throw redirect("/login");
}
return null;
}
export default function Profile() {
const { user, theme: initialTheme } = useRootData();
const [storeModalOpen, setStoreModalOpen] = useState(false);
const [loginModalOpen, setLoginModalOpen] = useState(false);
const [logoutModalOpen, setLogoutModalOpen] = useState(false);
const { data: profileData } = useServiceGetProfile();
const { data: myStores } = useMyStores();
const { data: walletData } = useServiceGetWallet();
const { data: badgeCounts } = useBadgeCounts();
const { data: followedSellers, isLoading: isFollowedSellersLoading } =
useServiceGetFollowedSellers();
// Calculate the count of followed sellers
const followedCount = followedSellers?.length || 0;
const walletBalance = walletData?.balance
? new Intl.NumberFormat("fa-IR").format(parseInt(walletData.balance))
: "۰";
// Top quick-actions (also live in the menu, but surfaced as a grid)
const quickActions = [
{
label: "سفارشها",
link: "/profile/orders",
icon: ,
badge: (badgeCounts?.order_updates || null) as number | null,
},
{
label: "نشانها",
link: "/profile/bookmarks",
icon: ,
badge: null,
},
{
label: "دنبالها",
link: "/profile/following",
icon: ,
badge: followedCount || null,
},
{
label: "آدرسها",
link: "/profile/location",
icon: ,
badge: null,
},
];
// The menu list below drops the items surfaced as quick actions / wallet card.
const quickPages = new Set([
"orders",
"bookmarks",
"following",
"location",
"wallet",
]);
// Create dynamic items with the followed count
const menuItems: MenuItemType[] = [
{
title: "سفارشهای من",
link: "/profile/orders",
pageName: "orders",
hasBorder: true,
image: ,
},
{
title: isFollowedSellersLoading
? "دنبال شدهها"
: `دنبال شدهها(${followedCount})`,
link: "/profile/following",
pageName: "following",
image: ,
},
{
title: "پیامها",
link: "/profile/threads",
pageName: "messages",
image: ,
badge: badgeCounts?.chat || null,
},
{
title: "کیف پول",
link: "/profile/wallet",
pageName: "wallet",
image: ,
},
{
title: "آدرس های من",
link: "/profile/location",
pageName: "location",
image: ,
},
{
title: "ورود به فروشگاه من",
link: "",
pageName: "store",
image: ,
},
{
title: "ذخیره شدهها",
link: "/profile/bookmarks",
pageName: "bookmarks",
image: ,
},
{
title: "ستهای من",
link: "/profile/sets",
pageName: "sets",
image: ,
},
{
title: "تماس با پشتیبانی",
pageName: "support",
hasBorder: true,
image: ,
},
{
title: "سوالات متداول",
link: "/profile/faq",
pageName: "faq",
image: ,
},
{
title: "درباره ویترون",
link: "/profile/about",
pageName: "about",
image: ,
},
{
title: "خروج",
pageName: "logout",
link: "/logout",
image: ,
},
];
const navigate = useNavigate();
return (
{/* Desktop: welcome (the sidebar provides the menu) */}
حساب کاربری
یک گزینه را از منوی کناری انتخاب کنید
{/* Mobile: account hub */}
{/* Header: avatar + name + phone + edit */}
{profileData?.username || "کاربر ویترون"}
{profileData?.phoneNumber ? (
{profileData.phoneNumber}
) : null}
{/* Wallet card */}
موجودی کیف پول
{walletBalance}
تومان
افزایش موجودی
{/* Quick actions */}
{quickActions.map((qa) => (
{qa.badge ? (
{qa.badge}
) : null}
{qa.icon}
{qa.label}
))}
حساب من
{/* Theme toggle */}
{initialTheme === "dark" ? "حالت تاریک" : "حالت روشن"}
!quickPages.has(m.pageName))}
onStoreClick={() => {
// Enter a store the user can manage: their own if they own one,
// otherwise the first store they're a staff member of.
const owned = myStores?.find((s) => s.role === "owner");
const target = owned || myStores?.[0];
if (profileData?.sellerUsername) {
navigate(`/store/${profileData.sellerUsername}`);
} else if (target) {
navigate(`/store/${target.username}`);
} else if (user?.access_token) {
setStoreModalOpen(true);
} else {
setLoginModalOpen(true);
}
}}
onLogoutClick={() => {
setLogoutModalOpen(true);
}}
/>
);
}
const MenuItems = ({
items,
onStoreClick,
onLogoutClick,
}: {
items: MenuItemType[];
onStoreClick: () => void;
onLogoutClick: () => void;
}) => {
const { user } = useRootData();
const handleSupportClick = () => {
const supportPhone = import.meta.env.VITE_SUPPORT_PHONE || "";
window.location.href = `tel:${supportPhone}`;
};
return (
{items.map((item: MenuItemType, index: number) => {
// Special handling for "support" item
if (item.pageName === "support") {
return (
);
}
// Special handling for "store" item
if (item.pageName === "store") {
return ;
}
// Special handling for "logout" item
if (item.pageName === "logout") {
if (user?.access_token) {
return ;
}
return null;
}
// Default rendering for other items with Link
return (
);
})}
);
};
// Reusable MenuItem component
const MenuItem = ({
item,
onClick,
}: {
item: MenuItemType;
onClick: () => void;
}) => {
return (
e.key === "Enter" && onClick()}
>
);
};
// Content subcomponent to be used with or without clickable wrapper
const MenuItemContent = ({ item }: { item: MenuItemType }) => {
return (
{item.image}
{item.title}
{item.badge ? (
{item.badge > 99 ? "99+" : item.badge}
) : null}
);
};
// Assign the content component as a property of MenuItem
MenuItem.Content = MenuItemContent;
MenuItemContent.displayName = "MenuItem.Content";
const ProfileTopBar = ({ user }: { user: User }) => {
const { data: dataProfile, isFetching: isFetchingProfile } =
useServiceGetProfile();
return (
{isFetchingProfile ? (
) : user && user.access_token ? (
dataProfile && dataProfile.username ? (
{dataProfile.username}
) : (
)
) : (
)}
);
};
export const CurvedButton = ({
title,
isLoading,
}: {
title: string;
isLoading?: boolean;
}) => {
return (
<>
{isLoading ? (
) : (
)}
>
);
};
const LogoutModal = ({
open,
onOpenChange,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
}) => {
const navigate = useNavigate();
return (
خروج از حساب کاربری
میخواهید از حساب کاربری خود خارج شوید؟
onOpenChange(false)}
>
انصراف
navigate("/logout")}
>
خروج از حساب
);
};
const StoreModal = ({
open,
onOpenChange,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
}) => {
const navigate = useNavigate();
const handleActionClick = () => {
navigate("/store/create");
onOpenChange(false);
};
return (
شما در فروشگاه ثبت نشدهاید
میخواهید فروشگاه خود را ایجاد کنید؟
onOpenChange(false)}
>
انصراف
ایجاد فروشگاه
);
};
export const meta: MetaFunction = () => {
const title = "پروفایل کاربری | ویترون";
const description =
"مدیریت حساب کاربری، مشاهده سفارشها، کیف پول، آدرسها و تنظیمات شخصی در ویترون.";
const imageUrl =
"https://vitrownstatics.s3.ir-thr-at1.arvanstorage.ir/SVG-06.svg?versionId=";
return [
{ title },
{ name: "description", content: description },
{
name: "keywords",
content: "پروفایل کاربری، حساب کاربری، سفارشها، کیف پول، آدرس",
},
{ name: "robots", content: "noindex, follow" },
// Open Graph
{ property: "og:type", content: "website" },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:image", content: imageUrl },
{ property: "og:site_name", content: "ویترون" },
{ property: "og:locale", content: "fa_IR" },
// Twitter Card
{ name: "twitter:card", content: "summary" },
{ name: "twitter:title", content: title },
{ name: "twitter:description", content: description },
];
};