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 ? ( ) : (

{title}

)} ); }; const LogoutModal = ({ open, onOpenChange, }: { open: boolean; onOpenChange: (open: boolean) => void; }) => { const navigate = useNavigate(); return (

خروج از حساب کاربری

میخواهید از حساب کاربری خود خارج شوید؟

); }; const StoreModal = ({ open, onOpenChange, }: { open: boolean; onOpenChange: (open: boolean) => void; }) => { const navigate = useNavigate(); const handleActionClick = () => { navigate("/store/create"); onOpenChange(false); }; return (

شما در فروشگاه ثبت نشده‌اید

می‌خواهید فروشگاه خود را ایجاد کنید؟

); }; 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 }, ]; };