"use client"; import { Suspense, useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import { useRouter, useSearchParams } from "next/navigation"; import { Award, CalendarDays, MessageSquareHeart, Share2, Sparkles } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { PageHeader } from "@/components/page-header"; import { EventsPanel } from "@/components/events-panel"; import { LeaderboardPanel } from "@/components/leaderboard-panel"; import { CommunityPanel } from "@/components/community-panel"; import { Button } from "@/components/ui/button"; import { UserAvatar, avatarPropsOf } from "@/components/ui/user-avatar"; import { Skeleton, SkeletonAvatar } from "@/components/ui/skeleton"; import { Panel } from "@/components/panel"; import { Badge } from "@/components/ui/badge"; import type { Mentor } from "@/lib/types"; import { http } from "@/lib/api"; import type { ChatMessage, Conversation } from "@/lib/types"; import { cn, formatJalaliStamp } from "@/lib/utils"; import { useAuth } from "@/lib/auth-store"; import { isVerifiedRole, VerifiedBadge } from "@/components/verified-badge"; type Tab = "events" | "chats" | "leagues" | "community"; const TABS: Tab[] = ["events", "chats", "leagues", "community"]; function parseTab(raw: string | null): Tab { if (raw && (TABS as string[]).includes(raw)) { return raw as Tab; } return "events"; } export default function UnwrapPage() { return ( ); } function HubTabs() { const router = useRouter(); const params = useSearchParams(); const [tab, setTab] = useState(() => parseTab(params.get("tab"))); useEffect(() => { const raw = params.get("tab"); const next = parseTab(raw); setTab(next); // Invalid / unknown ?tab=… → clean URL so content isn't blank. if (raw != null && raw !== next) { router.replace(next === "events" ? "/unwrap" : `/unwrap?tab=${next}`, { scroll: false }); } }, [params, router]); const onTab = (value: string) => { const next = parseTab(value); setTab(next); const url = next === "events" ? "/unwrap" : `/unwrap?tab=${next}`; router.replace(url, { scroll: false }); }; return (
جامعه، استمرار و منتورها } description="رویدادهای زنده برای تقویت مهارت، لیگ‌های هفتگی برای ثبات، گفتگو، و شبکه‌های پرگار." className="mb-0" /> رویدادها گفتگو با منتور لیگ‌ها شبکه‌ها
); } function openChat(withId?: number) { window.dispatchEvent(new CustomEvent("pargar:chat", { detail: { open: true, with: withId } })); } function roleWord(role?: string) { if (role === "admin") return "مدیر"; if (role === "mentor") return "منتور"; if (role === "student") return "هنرجو"; return "کاربر"; } function ChatsLauncher() { const me = useAuth((s) => s.user); const staff = me?.role === "admin" || me?.role === "mentor"; const [peoplePage, setPeoplePage] = useState(1); const [alpha, setAlpha] = useState(false); const [convPage, setConvPage] = useState(1); const { data, isLoading } = useQuery({ queryKey: ["mentors", peoplePage], queryFn: () => http.get<{ mentors: Mentor[]; total: number; pageSize: number }>(`/api/mentors?page=${peoplePage}&pageSize=20`), }); const { data: convData, isLoading: convLoading } = useQuery({ queryKey: ["conversations", convPage], queryFn: () => http.get<{ conversations: Conversation[]; total: number; pageSize: number }>( `/api/chats/conversations?page=${convPage}&pageSize=10`, ), }); const mentors = data?.mentors ?? []; const people = useMemo(() => { const list = [...mentors]; if (alpha) list.sort((a, b) => a.name.localeCompare(b.name, "fa")); else list.sort((a, b) => Number(!!b.online) - Number(!!a.online) || a.name.localeCompare(b.name, "fa")); return list; }, [mentors, alpha]); const conversations = convData?.conversations ?? []; const peopleTotal = data?.total ?? mentors.length; const convTotal = convData?.total ?? conversations.length; return (
{/* ── Centered header banner ── */}
یک پنجرهٔ شناور شیشه‌ای در تمام صفحات — با صدا، عکس، ویدیو و پیام صوتی.

گفتگو همیشه در دکمهٔ شناور پایین صفحه در دسترس شماست؛ گفتگوها خصوصی هستند و پاسخ منتورها معمولاً کمتر از یک روز طول می‌کشد.

{/* ── Side-by-side panels ── */}
{/* Conversations panel */}
{convLoading && (
{[0, 1, 2].map((i) => (
))}
)} {conversations.map((c) => ( ))} {!convLoading && conversations.length === 0 && (

هنوز گفتگویی ندارید — از فهرست کنار، یک نفر را انتخاب کنید.

)} {convTotal > (convData?.pageSize ?? 10) && (
{convPage}
)}
{/* Start chat panel */} setAlpha((v) => !v)}> {alpha ? "آنلاین بالا" : "الفبایی"} } >
{isLoading && (
{[0, 1, 2, 3].map((i) => (
))}
)} {people.map((m) => ( openChat(m.id)} className="flex w-full items-center gap-3 rounded-xl border-2 border-border/70 px-3 py-2.5 text-right transition-colors hover:border-primary/50 hover:bg-primary/5" >

{m.name} {isVerifiedRole(m.role) && }

{roleWord(m.role)}

{m.online ? "آنلاین" : "آفلاین"}
))} {!isLoading && mentors.length === 0 && (

{staff ? "کسی برای گفتگو نیست." : "هنوز منتوری در دسترس نیست."}

)} {peopleTotal > (data?.pageSize ?? 20) && (
{peoplePage}
)}
); } function messagePreview(m: ChatMessage) { if (m.body) return m.body; const atts = m.attachments?.length ? m.attachments : m.attachment ? [m.attachment] : []; if (atts.length > 1 && atts.every((a) => a.type === "image")) return `📷 ${atts.length.toLocaleString("fa-IR")} عکس`; if (atts.length > 1 && atts.every((a) => a.type === "file")) return `📎 ${atts.length.toLocaleString("fa-IR")} فایل`; if (atts.length > 1) return `📎 ${atts.length.toLocaleString("fa-IR")} پیوست`; if (atts[0]) { const labels: Record = { image: "📷 عکس", video: "🎬 ویدیو", audio: "🎵 پیام صوتی" }; return labels[atts[0].type] ?? `📎 ${atts[0].name}`; } return ""; }