719 lines
30 KiB
TypeScript
719 lines
30 KiB
TypeScript
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>
|
||
);
|
||
}
|