Hamdast1/src/app/components/ProfilePage.tsx
mahmoodsht a535e43e81 ...
2026-05-21 18:13:53 +03:30

719 lines
30 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { LogOut, Edit2, CheckCircle2, Clock, XCircle, TrendingUp, Play, Camera } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { motion } from "motion/react";
import profileIcon from "../../assets/image 5.png";
import coinImage from "figma:asset/f7664d355c12b1003ad460ff44c8f22cfb1bbf5a.png";
import { logout, getUserInfo } from "../../utils/auth";
import { useEffect, useState } from "react";
import { getUserProfile, getCachedProfile, saveUserProfile, getUserProfileData, type UserProfile, type ProfileChallenge, type ProfileCoinTransaction, type ProfilePost } from "../../services/profileService";
import { PostCard } from "./PostCard";
import { AvatarSelectionModal } from "./AvatarSelectionModal";
import { getProfileImageUrl, getFeedImageUrl, getVideoUrl, getAudioUrl, isImageFile, getMagicBagFileUrl, getAvatarUrl, bumpAvatarCacheBust } from "../../services/feedService";
import { ImageWithFallback } from "./figma/ImageWithFallback";
import { usePageTracking } from "../../hooks/usePageTracking";
import { getMissionTypeToTopicId } from "../../utils/topicMapper";
import { useProfile } from "../context/ProfileContext";
const getStatusBadge = (status: string) => {
if (status === "انجام شده") {
return {
icon: <CheckCircle2 className="w-4 h-4" />,
text: "انجام شده",
gradient: "linear-gradient(135deg, rgba(34, 197, 94, 0.9) 0%, rgba(22, 163, 74, 0.9) 100%)",
border: "rgba(34, 197, 94, 0.5)",
shadow: "0 2px 8px rgba(34, 197, 94, 0.4)",
};
} else if (status === "تایید شده") {
return {
icon: <CheckCircle2 className="w-4 h-4" />,
text: "تایید شده",
gradient: "linear-gradient(135deg, rgba(59, 130, 246, 0.9) 0%, rgba(37, 99, 235, 0.9) 100%)",
border: "rgba(59, 130, 246, 0.5)",
shadow: "0 2px 8px rgba(59, 130, 246, 0.4)",
};
} else if (status === "در حال انجام") {
return {
icon: <Play className="w-4 h-4" />,
text: "در حال انجام",
gradient: "linear-gradient(135deg, rgba(255, 193, 7, 0.9) 0%, rgba(255, 160, 0, 0.9) 100%)",
border: "rgba(255, 193, 7, 0.5)",
shadow: "0 2px 8px rgba(255, 193, 7, 0.4)",
};
} else if (status === "رد شده") {
return {
icon: <XCircle className="w-4 h-4" />,
text: "رد شده",
gradient: "linear-gradient(135deg, rgba(239, 68, 68, 0.9) 0%, rgba(220, 38, 38, 0.9) 100%)",
border: "rgba(239, 68, 68, 0.5)",
shadow: "0 2px 8px rgba(239, 68, 68, 0.4)",
};
} else {
return {
icon: <Clock className="w-4 h-4" />,
text: status,
gradient: "linear-gradient(135deg, rgba(156, 163, 175, 0.9) 0%, rgba(107, 114, 128, 0.9) 100%)",
border: "rgba(156, 163, 175, 0.5)",
shadow: "0 2px 8px rgba(156, 163, 175, 0.4)",
};
}
};
export function ProfilePage() {
const navigate = useNavigate();
usePageTracking("پروفایل");
const { refreshProfile } = useProfile();
const [userInfo, setUserInfo] = useState<{ Name: string; Family: string; Username: string } | null>(null);
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const [isLoadingProfile, setIsLoadingProfile] = useState(true);
const [activeTab, setActiveTab] = useState<"challenges" | "coins" | "posts">("challenges");
const [showAvatarModal, setShowAvatarModal] = useState(false);
const [challenges, setChallenges] = useState<ProfileChallenge[]>([]);
const [coinTransactions, setCoinTransactions] = useState<ProfileCoinTransaction[]>([]);
const [posts, setPosts] = useState<ProfilePost[]>([]);
const [isLoadingData, setIsLoadingData] = useState(false);
useEffect(() => {
const info = getUserInfo();
setUserInfo(info);
loadProfile();
}, []);
const loadProfile = async () => {
setIsLoadingProfile(true);
try {
const cachedProfile = getCachedProfile();
if (cachedProfile) {
setUserProfile(cachedProfile);
}
const profile = await getUserProfile();
if (profile) {
setUserProfile(profile);
}
} catch (error) {
// خطاها در سطح سرویس مدیریت می‌شوند
} finally {
setIsLoadingProfile(false);
}
// بارگذاری داده‌های کامل پروفایل
loadProfileData();
};
const loadProfileData = async () => {
setIsLoadingData(true);
try {
const data = await getUserProfileData();
if (data) {
setChallenges(data.challenges);
setCoinTransactions(data.coin_transaction);
setPosts(data.posts);
}
} catch (error) {
console.error("خطا در بارگذاری داده‌های پروفایل:", error);
} finally {
setIsLoadingData(false);
}
};
const handleLogout = async () => {
setIsLoggingOut(true);
try {
await logout();
navigate("/login", { state: { error: "شما با موفقیت از سیستم خارج شدید" } });
} catch (error) {
console.error("خطا در خروج:", error);
navigate("/login", { state: { error: "شما با موفقیت از سیستم خارج شدید" } });
} finally {
setIsLoggingOut(false);
}
};
const toPersianNumber = (num: number | null | undefined): string => {
if (num === null || num === undefined) return "۰";
const persianDigits = "۰۱۲۳۴۵۶۷۸۹";
return String(num).replace(/\d/g, (digit) => persianDigits[parseInt(digit)]);
};
const handleAvatarSelect = async (imageFilename: string) => {
// imageFilename از AvatarSelectionModal نام فایل آپلود شده به سرور است
console.log("handleAvatarSelect called with:", imageFilename);
// ذخیره در پروفایل
if (userProfile) {
// به‌روزرسانی state موقت
setUserProfile({ ...userProfile, image: imageFilename });
// ذخیره در سرور
try {
const username = userProfile.username;
const saveData = {
WorkflowID: userProfile.user_workflowID,
user: {
username: username,
name: userProfile.name,
family: userProfile.family,
education_level: userProfile.education_level,
base: userProfile.base,
image: imageFilename,
},
};
console.log("Saving profile with data:", JSON.stringify(saveData));
const result = await saveUserProfile(saveData);
console.log("Save profile result:", result);
if (result) {
bumpAvatarCacheBust();
}
// بارگذاری مجدد پروفایل از سرور
console.log("Reloading profile from server...");
await refreshProfile();
console.log("Profile reloaded successfully");
} catch (error) {
console.error("Error saving avatar:", error);
alert("خطا در ذخیره تصویر پروفایل");
}
} else {
console.error("No userProfile available");
}
};
const handleDeletePost = (postId: string) => {
// حذف پست از لیست
setPosts((prevPosts) => prevPosts.filter((post) => post.workflow_ID !== postId));
};
const navLikePanelStyle = {
backgroundImage: `
linear-gradient(180deg, #2E1B3D 0%, #23183E 100%),
linear-gradient(120deg, #7c3aed 0%, #f97316 58%, #facc15 100%)
`,
backgroundOrigin: "border-box",
backgroundClip: "padding-box, border-box",
boxShadow:
"0 -7px 20px rgba(7, 0, 18, 0.5), 0 6px 14px rgba(5, 2, 12, 0.26), inset 0 1px 0 rgba(255, 255, 255, 0.2), inset 0 2px 5px rgba(255, 222, 255, 0.09), inset 0 -2px 0 rgba(12, 7, 27, 0.72), inset 0 -8px 14px rgba(8, 4, 18, 0.34), inset 0 0 0 1px rgba(255, 255, 255, 0.045), inset 0 0 0 2px rgba(17, 10, 35, 0.32)",
backdropFilter: "blur(14px)",
WebkitBackdropFilter: "blur(14px)",
} as const;
return (
<div
className="pt-6 pb-2"
dir="rtl"
>
{/* Avatar & Info Section */}
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
className="flex flex-col items-center mb-4"
>
{/* Avatar with Edit Button */}
<div className="relative mb-3">
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", duration: 0.5 }}
className="w-24 h-24 rounded-full p-[2px]"
style={navLikePanelStyle}
>
<div className="w-full h-full rounded-full overflow-hidden flex items-center justify-center" style={navLikePanelStyle}>
{userProfile?.image ? (
<ImageWithFallback
src={getProfileImageUrl(userProfile.image, userProfile.user_stage_id)}
alt="پروفایل"
className="w-full h-full rounded-full object-cover"
fallbackSrc={profileIcon}
style={{
filter: "drop-shadow(0 3px 6px rgba(138, 206, 224, 0.6))",
}}
/>
) : (
<img
src={profileIcon}
alt="پروفایل"
className="w-[84px] h-[84px] object-cover rounded-full"
/>
)}
</div>
</motion.div>
{/* دکمه تغییر عکس */}
<motion.button
whileTap={{ scale: 0.9 }}
whileHover={{ scale: 1.1 }}
onClick={() => setShowAvatarModal(true)}
className="absolute bottom-0 right-0 w-8 h-8 rounded-full flex items-center justify-center"
style={{
background: "linear-gradient(135deg, rgba(255, 193, 7, 0.95) 0%, rgba(255, 152, 0, 0.95) 100%)",
boxShadow: "0 3px 10px rgba(255, 193, 7, 0.6), 0 0 16px rgba(255, 193, 7, 0.4)",
border: "2px solid rgba(255, 255, 255, 0.9)",
}}
>
<Camera className="w-3.5 h-3.5" style={{ color: "#5A3800" }} />
</motion.button>
</div>
{/* Name */}
<h2
className="text-white text-lg font-bold mb-0.5"
style={{
textShadow: "0 3px 8px rgba(138, 206, 224, 0.6), 0 2px 4px rgba(0, 0, 0, 0.8)",
}}
>
{isLoadingProfile
? "در حال بارگذاری..."
: userProfile
? `${userProfile.name} ${userProfile.family}`
: userInfo
? `${userInfo.Name} ${userInfo.Family}`
: "کاربر گرامی"}
</h2>
{/* Education Info */}
{userProfile && (
<div style={{ color: "rgba(138, 206, 224, 0.8)" }} className="text-[10px] mb-3">
{userProfile.education_level} - پایه {userProfile.base}
</div>
)}
{/* Stats */}
<div className="flex gap-2.5 mb-3">
{[
{ label: "چالش‌ها", value: toPersianNumber(challenges.filter(c => c.status === "انجام شده").length) },
{ label: "سکه‌ها", value: toPersianNumber(userProfile?.coin_count) },
{ label: "پست‌ها", value: toPersianNumber(posts.length) },
].map((stat, index) => (
<div
key={index}
className="px-4 py-2 rounded-2xl"
style={{
background: "linear-gradient(135deg, rgba(32, 76, 106, 0.6) 0%, rgba(20, 40, 60, 0.6) 100%)",
border: "1.5px solid rgba(138, 206, 224, 0.3)",
boxShadow: "inset 0 1.5px 6px rgba(0, 0, 0, 0.4), 0 3px 8px rgba(0, 0, 0, 0.3)",
}}
>
<div className="text-white font-bold text-sm mb-0.5">{stat.value}</div>
<div style={{ color: "#8ACEE0" }} className="text-[9px]">
{stat.label}
</div>
</div>
))}
</div>
{/* Action Buttons */}
<div className="flex gap-2 w-full">
{/* Edit Profile Button */}
{userProfile?.user_workflowID ? (
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={() => navigate("/edit-profile")}
className="flex-1 flex items-center justify-center gap-1.5 px-4 py-2 rounded-2xl font-bold text-xs"
style={{
background: "linear-gradient(135deg, rgba(76, 175, 80, 0.9) 0%, rgba(56, 142, 60, 0.9) 100%)",
boxShadow: "0 3px 12px rgba(76, 175, 80, 0.4)",
color: "#FFFFFF",
textShadow: "0 1px 3px rgba(0, 0, 0, 0.5)",
}}
>
<Edit2 size={14} />
<span>ویرایش</span>
</motion.button>
) : !isLoadingProfile ? (
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={() => navigate("/edit-profile")}
className="flex-1 flex items-center justify-center gap-1.5 px-4 py-2 rounded-2xl font-bold text-xs"
style={{
background: "linear-gradient(135deg, rgba(255, 193, 7, 0.9) 0%, rgba(255, 160, 0, 0.9) 100%)",
boxShadow: "0 3px 12px rgba(255, 193, 7, 0.4)",
color: "#FFFFFF",
textShadow: "0 1px 3px rgba(0, 0, 0, 0.5)",
}}
>
<Edit2 size={14} />
<span>تکمیل پروفایل</span>
</motion.button>
) : null}
{/* Logout Button */}
<motion.button
whileHover={{ scale: isLoggingOut ? 1 : 1.03 }}
whileTap={{ scale: isLoggingOut ? 1 : 0.97 }}
onClick={handleLogout}
disabled={isLoggingOut}
className="flex-1 flex items-center justify-center gap-1.5 px-4 py-2 rounded-2xl font-bold text-xs"
style={{
background: isLoggingOut
? "linear-gradient(135deg, rgba(120, 30, 40, 0.7) 0%, rgba(96, 24, 32, 0.7) 100%)"
: "linear-gradient(135deg, rgba(220, 53, 69, 0.9) 0%, rgba(176, 42, 55, 0.9) 100%)",
boxShadow: "0 3px 12px rgba(220, 53, 69, 0.4)",
color: "#FFFFFF",
textShadow: "0 1px 3px rgba(0, 0, 0, 0.5)",
opacity: isLoggingOut ? 0.7 : 1,
cursor: isLoggingOut ? "not-allowed" : "pointer",
}}
>
<LogOut size={14} />
<span>{isLoggingOut ? "خروج..." : "خروج"}</span>
</motion.button>
</div>
</motion.div>
{/* Tab Switcher */}
<div className="mb-3 flex gap-2">
<motion.button
whileTap={{ scale: 0.97 }}
onClick={() => setActiveTab("challenges")}
className="flex-1 py-2 rounded-2xl font-bold text-[11px] flex items-center justify-center gap-1"
style={{
background: activeTab === "challenges"
? "linear-gradient(135deg, rgba(255, 183, 0, 0.95) 0%, rgba(255, 140, 0, 0.95) 100%)"
: "linear-gradient(135deg, rgba(50, 107, 118, 0.6) 0%, rgba(32, 76, 106, 0.6) 100%)",
boxShadow: activeTab === "challenges"
? "0 3px 12px rgba(255, 165, 0, 0.5)"
: "0 2px 6px rgba(0, 0, 0, 0.3)",
border: activeTab === "challenges"
? "1.5px solid rgba(255, 200, 50, 0.5)"
: "1.5px solid rgba(138, 206, 224, 0.3)",
color: activeTab === "challenges" ? "#5A3800" : "#FFFFFF",
textShadow: activeTab === "challenges"
? "0 1px 0 rgba(255, 255, 255, 0.2)"
: "none",
}}
>
سابقه چالشها
</motion.button>
<motion.button
whileTap={{ scale: 0.97 }}
onClick={() => setActiveTab("coins")}
className="flex-1 py-2 rounded-2xl font-bold text-[11px] flex items-center justify-center gap-1"
style={{
background: activeTab === "coins"
? "linear-gradient(135deg, rgba(255, 183, 0, 0.95) 0%, rgba(255, 140, 0, 0.95) 100%)"
: "linear-gradient(135deg, rgba(50, 107, 118, 0.6) 0%, rgba(32, 76, 106, 0.6) 100%)",
boxShadow: activeTab === "coins"
? "0 3px 12px rgba(255, 165, 0, 0.5)"
: "0 2px 6px rgba(0, 0, 0, 0.3)",
border: activeTab === "coins"
? "1.5px solid rgba(255, 200, 50, 0.5)"
: "1.5px solid rgba(138, 206, 224, 0.3)",
color: activeTab === "coins" ? "#5A3800" : "#FFFFFF",
textShadow: activeTab === "coins"
? "0 1px 0 rgba(255, 255, 255, 0.2)"
: "none",
}}
>
سابقه سکهها
</motion.button>
<motion.button
whileTap={{ scale: 0.97 }}
onClick={() => setActiveTab("posts")}
className="flex-1 py-2 rounded-2xl font-bold text-[11px] flex items-center justify-center gap-1"
style={{
background: activeTab === "posts"
? "linear-gradient(135deg, rgba(255, 183, 0, 0.95) 0%, rgba(255, 140, 0, 0.95) 100%)"
: "linear-gradient(135deg, rgba(50, 107, 118, 0.6) 0%, rgba(32, 76, 106, 0.6) 100%)",
boxShadow: activeTab === "posts"
? "0 3px 12px rgba(255, 165, 0, 0.5)"
: "0 2px 6px rgba(0, 0, 0, 0.3)",
border: activeTab === "posts"
? "1.5px solid rgba(255, 200, 50, 0.5)"
: "1.5px solid rgba(138, 206, 224, 0.3)",
color: activeTab === "posts" ? "#5A3800" : "#FFFFFF",
textShadow: activeTab === "posts"
? "0 1px 0 rgba(255, 255, 255, 0.2)"
: "none",
}}
>
پستها
</motion.button>
</div>
{/* Challenges Tab */}
{activeTab === "challenges" && (
<div className="space-y-3">
{isLoadingData ? (
<div className="text-center text-white/60 py-8">در حال بارگذاری...</div>
) : challenges.length === 0 ? (
<div className="text-center text-white/60 py-8">هنوز چالشی ثبت نشده است</div>
) : (
challenges.map((challenge, index) => {
const statusBadge = getStatusBadge(challenge.status);
const coins = parseInt(challenge.coin_count || "0");
const isInProgress = challenge.status === "در حال انجام";
return (
<motion.div
key={challenge.mission_done_id}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.08, duration: 0.3 }}
onClick={() => {
if (isInProgress && challenge.mission_id) {
const topicId = getMissionTypeToTopicId(challenge.mission_type);
navigate(`/chatbot/${topicId}?missionId=${challenge.mission_id}&missionType=${encodeURIComponent(challenge.mission_type)}&continueMode=true`);
}
}}
className={`rounded-2xl p-4 ${isInProgress ? 'cursor-pointer' : ''}`}
style={{
background: isInProgress
? "linear-gradient(135deg, rgba(255, 193, 7, 0.15) 0%, rgba(255, 152, 0, 0.15) 100%)"
: "linear-gradient(135deg, rgba(32, 76, 106, 0.5) 0%, rgba(20, 40, 60, 0.5) 100%)",
border: isInProgress
? "1.5px solid rgba(255, 193, 7, 0.4)"
: "1.5px solid rgba(138, 206, 224, 0.3)",
boxShadow: isInProgress
? "0 4px 16px rgba(255, 193, 7, 0.3)"
: "0 4px 12px rgba(0, 0, 0, 0.3)",
}}
whileHover={isInProgress ? { scale: 1.02, y: -2 } : {}}
whileTap={isInProgress ? { scale: 0.98 } : {}}
>
<div className="flex items-start justify-between mb-2">
<div className="flex items-center gap-2 flex-1">
<h3 className={`font-bold text-sm ${isInProgress ? 'text-yellow-300' : 'text-white'}`}>
{challenge.mission_title}
</h3>
{isInProgress && (
<motion.div
animate={{ x: [0, 4, 0] }}
transition={{ repeat: Infinity, duration: 1.5 }}
>
<Play className="w-3.5 h-3.5 text-yellow-300" />
</motion.div>
)}
</div>
<div
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-white text-[10px] font-bold"
style={{
background: statusBadge.gradient,
border: `1px solid ${statusBadge.border}`,
boxShadow: statusBadge.shadow,
}}
>
{statusBadge.icon}
<span>{statusBadge.text}</span>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-white/60 text-xs">{challenge.datetime1}</span>
{isInProgress && (
<span className="text-white/50 text-[10px] px-2 py-0.5 rounded-full" style={{
background: "rgba(138, 206, 224, 0.2)",
border: "1px solid rgba(138, 206, 224, 0.3)"
}}>
{challenge.mission_type}
</span>
)}
</div>
<div className="flex items-center gap-3">
{(challenge.status === "انجام شده" || challenge.status === "تایید شده") && coins > 0 && (
<div className="flex items-center gap-1.5">
<img src={coinImage} alt="سکه" className="w-4 h-4" />
<span className="text-yellow-400 font-bold text-xs">
+{toPersianNumber(coins)}
</span>
</div>
)}
{isInProgress ? (
<span className="text-yellow-300 text-[10px] font-bold">
برای ادامه کلیک کنید
</span>
) : (
<span className="text-white/50 text-[10px]">
{challenge.mission_type}
</span>
)}
</div>
</div>
</motion.div>
);
})
)}
</div>
)}
{/* Coins Tab */}
{activeTab === "coins" && (
<div className="space-y-3">
{/* Total Coins Summary */}
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="rounded-2xl p-4 mb-4"
style={{
background: "linear-gradient(135deg, rgba(255, 193, 7, 0.2) 0%, rgba(255, 152, 0, 0.2) 100%)",
border: "2px solid rgba(255, 193, 7, 0.4)",
boxShadow: "0 4px 16px rgba(255, 193, 7, 0.3)",
}}
>
<div className="flex items-center justify-between">
<div>
<p className="text-white/70 text-xs mb-1">مجموع سکههای دریافتی</p>
<div className="flex items-center gap-2">
<img src={coinImage} alt="سکه" className="w-8 h-8" />
<span className="text-yellow-300 font-bold text-2xl">
{toPersianNumber(userProfile?.coin_count)}
</span>
</div>
</div>
<TrendingUp className="w-8 h-8 text-yellow-300" />
</div>
</motion.div>
{/* Coin History */}
{isLoadingData ? (
<div className="text-center text-white/60 py-8">در حال بارگذاری...</div>
) : coinTransactions.length === 0 ? (
<div className="text-center text-white/60 py-8">هنوز تراکنشی ثبت نشده است</div>
) : (
coinTransactions.map((item, index) => {
const coins = parseInt(item.coin_count || "0");
const isNegative = coins < 0;
const absCoins = Math.abs(coins);
return (
<motion.div
key={`${item.StageID}-${index}`}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.08, duration: 0.3 }}
className="rounded-2xl p-4"
style={{
background: isNegative
? "linear-gradient(135deg, rgba(76, 29, 29, 0.5) 0%, rgba(60, 20, 20, 0.5) 100%)"
: "linear-gradient(135deg, rgba(32, 76, 106, 0.5) 0%, rgba(20, 40, 60, 0.5) 100%)",
border: isNegative
? "1.5px solid rgba(239, 68, 68, 0.4)"
: "1.5px solid rgba(138, 206, 224, 0.3)",
boxShadow: isNegative
? "0 4px 12px rgba(239, 68, 68, 0.2)"
: "0 4px 12px rgba(0, 0, 0, 0.3)",
}}
>
<div className="flex items-start justify-between mb-2">
<h3 className="text-white font-bold text-sm flex-1">
{item.description}
</h3>
<div className="flex items-center gap-1.5">
<img
src={coinImage}
alt="سکه"
className="w-5 h-5"
style={{
filter: isNegative ? "grayscale(100%) brightness(0.8)" : "none"
}}
/>
<span
className="font-bold text-sm"
style={{
color: isNegative ? "#ef4444" : "#fcd34d"
}}
>
{isNegative ? "-" : "+"}{toPersianNumber(absCoins)}
</span>
</div>
</div>
</motion.div>
);
})
)}
</div>
)}
{/* Posts Tab */}
{activeTab === "posts" && (
<div className="space-y-4">
{isLoadingData ? (
<div className="text-center text-white/60 py-8">در حال بارگذاری...</div>
) : posts.length === 0 ? (
<div className="text-center text-white/60 py-8">هنوز پستی منتشر نشده است</div>
) : (
posts.map((post, index) => {
// تعیین نوع مدیا
let mediaType: 'image' | 'video' | 'audio' = 'image';
let mediaUrl: string | undefined;
let imageUrl: string;
if (post.film) {
mediaType = 'video';
mediaUrl = getVideoUrl(post.StageID);
imageUrl = post.image ? getFeedImageUrl(post.StageID) : '';
} else if (post.audio) {
mediaType = 'audio';
mediaUrl = getAudioUrl(post.StageID);
imageUrl = post.image ? getFeedImageUrl(post.StageID) : '';
} else {
mediaType = 'image';
imageUrl = post.image ? getFeedImageUrl(post.StageID) : '';
}
return (
<div key={post.workflow_ID}>
{/* Simple Topic Label with Line */}
<motion.div
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
className="mb-3 flex items-center gap-3"
dir="rtl"
>
<div className="h-[1px] flex-1" style={{ background: "rgba(138, 206, 224, 0.3)" }} />
<span className="text-xs text-white/60 px-2">
{post.mission_type}
</span>
<div className="h-[1px] flex-1" style={{ background: "rgba(138, 206, 224, 0.3)" }} />
</motion.div>
{/* Post Card */}
<PostCard
id={post.workflow_ID}
authorName={post.full_name}
authorUsername={post.user_id}
authorAvatar={getAvatarUrl(post.person_stage_id)}
image={imageUrl}
title={post.title}
caption={post.description}
likes={post.like_count || 0}
dislikes={post.dislike_count || 0}
comments={post.comment_count || 0}
timestamp=""
topicName={post.mission_type}
mediaType={mediaType}
mediaUrl={mediaUrl}
isOwnPost={true}
workflowID={post.workflow_ID}
missionType={post.mission_type}
initialLikeState={post.like_state || ""}
onDelete={handleDeletePost}
/>
</div>
);
})
)}
</div>
)}
{/* Avatar Selection Modal */}
<AvatarSelectionModal
isOpen={showAvatarModal}
onClose={() => setShowAvatarModal(false)}
onSelectAvatar={handleAvatarSelect}
currentAvatar={userProfile?.image ? getProfileImageUrl(userProfile.image, userProfile.user_stage_id) : undefined}
/>
</div>
);
}