import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Edit, BookOpen, Award, Trophy, Shield, Star, Zap, Target, BadgeCheck, Medal, Flame, LockIcon, Camera, ChevronRight, Download, RefreshCcw } from "lucide-react"; import AvatarUploadDialog from "@/components/generic/AvatarUploadDialog"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import ResponsiveModal from "@/components/generic/ResponsiveModal"; import { useProfile } from "@/contexts/ProfileProvider"; import { useAuth } from "@/contexts/AuthContext"; import { useClientTiers } from "@/contexts/ClientTiersProvider"; import api from "@/utils/api.util"; import { toast } from "sonner"; // ─── Tier badge config ──────────────────────────────────────────────────────── const TIER_BADGES = { free: { src: "/badges/free-access-badge-leaf-flaticon.svg", label: "Free", description: "Active member using the Free Plan.", information: "Free users have access to the platform's core features without a subscription.", }, premium: { src: "/badges/premium-badge-sheriff-flaticon.svg", label: "Premium", description: "Subscriber with access to premium learning content.", information: "Premium members can access all premium courses available on the platform.", }, exclusive: { src: "/badges/exclusive-award-flaticon.svg", label: "Exclusive", description: "Premium member with access to exclusive course content.", information: "This badge represents a member with access to exclusive courses.", }, }; const EARLY_ACCESS_BADGE = { src: "/badges/early-access-badge-percent-flaticon.svg", label: "Early Access", description: "Registered during the Philproperties beta period.", information: "Exclusive to members who registered before Dec 31, 2026.", }; // ─── Achievement icon map (by key) ─────────────────────────────────────────── const ACHIEVEMENT_ICONS = { early_access: Star, premium_first_time: BadgeCheck, exclusive_first_time: Medal, first_course_completed: BookOpen, courses_completed_5: Flame, courses_completed_10: Zap, perfect_quiz_score: Target, profile_completed: Shield, first_referral: Award, }; const getFallbackIcon = (type) => type === "badge" ? BadgeCheck : Trophy; // ─── Certificate landscape card (profile preview) ──────────────────────────── const CertBadgeIcon = ({ className }) => ( ); const LandscapeCertCard = ({ courseTitle, issuedAt, courseUuid }) => { const [downloading, setDownloading] = useState(false); const issuedLabel = issuedAt ? new Date(issuedAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—"; const handleDownload = async () => { setDownloading(true); try { const res = await api.get(`/client/certificates/${courseUuid}`, { responseType: "blob" }); const disposition = res.headers["content-disposition"] ?? ""; const match = disposition.match(/filename="([^"]+)"/); const filename = match ? match[1] : `certificate-${courseUuid}.pdf`; const url = URL.createObjectURL(new Blob([res.data], { type: "application/pdf" })); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } catch { toast.error("Could not download certificate."); } finally { setDownloading(false); } }; return (

Certificate of Completion

{courseTitle}

Issued {issuedLabel} Verified
); }; // ─── Component ─────────────────────────────────────────────────────────────── const ProfilePage = () => { const navigate = useNavigate(); const { user } = useAuth(); const { profile, profileLoading, getProfile, achievements, achievementsLoading, getAchievements, fullName, avatarUrl, occupation, uploadAvatar, deleteAvatar, avatarLoading, } = useProfile(); const [avatarDialogOpen, setAvatarDialogOpen] = useState(false); const { myTier, tierLoading, getMyTier } = useClientTiers(); const [badgeOpen, setBadgeOpen] = useState(false); const [selectedBadge, setSelectedBadge] = useState(null); const [inProgressCourses, setInProgressCourses] = useState([]); const [inProgressCoursesLoading, setInProgressCoursesLoading] = useState(false); const [pendingModalOpen, setPendingModalOpen] = useState(false); const [pendingModalCourse, setPendingModalCourse] = useState(null); useEffect(() => { getProfile(); getAchievements(); getMyTier(); (async () => { setInProgressCoursesLoading(true); try { const { data } = await api.get("/client/courses/in-progress"); setInProgressCourses(data.data ?? []); } catch { // silent — empty state handles it } finally { setInProgressCoursesLoading(false); } })(); }, []); // ── Derived ──────────────────────────────────────────────────────────────── const tier = myTier?.tier ?? user?.tier ?? "free"; const tierBadge = TIER_BADGES[tier] ?? TIER_BADGES.free; const displayName = fullName || user?.personal_info?.name?.full_name || user?.email?.split("@")[0] || "—"; const initials = displayName.split(" ").map((w) => w[0]).join("").slice(0, 2).toUpperCase(); const tierRank = { free: 0, premium: 1, exclusive: 2 }; const userRank = tierRank[tier] ?? 0; const certAchievements = achievements.filter((a) => a.key.startsWith("course_completed_")); const hasEarlyAccess = achievements.some((a) => a.key === "early_access"); // Premium badge only shows when currently exclusive (went through premium to get here) const hasPremiumBadge = achievements.some((a) => a.key === "premium_first_time") && userRank >= 2; // Exclusive badge only shows when currently exclusive const hasExclusiveBadge = achievements.some((a) => a.key === "exclusive_first_time") && userRank >= 2; const badgesReady = !tierLoading && !achievementsLoading; // Build badge object with earnedAt from achievements for modal const getBadgeWithDate = (achievementKey, tierKey) => { const achievement = achievements.find((a) => a.key === achievementKey); const earnedAt = achievement?.granted_at ? new Date(achievement.granted_at).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", }) : null; return { ...TIER_BADGES[tierKey], earnedAt }; }; const getEarlyAccessBadge = () => { const achievement = achievements.find((a) => a.key === "early_access"); const earnedAt = achievement?.granted_at ? new Date(achievement.granted_at).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", }) : null; return { ...EARLY_ACCESS_BADGE, earnedAt }; }; const getActiveTierBadgeWithDate = () => { const achievementKey = tier === "premium" ? "premium_first_time" : tier === "exclusive" ? "exclusive_first_time" : null; return achievementKey ? getBadgeWithDate(achievementKey, tier) : tierBadge; }; return (
{/* ── Header card ──────────────────────────────────────────────────── */}
{initials}
{profileLoading ? ( ) : (

{displayName}

)} {/* ── Badges ─────────────────────────────────────────────── */} {badgesReady ? (
{/* Early access — always show if earned */} {hasEarlyAccess && ( {EARLY_ACCESS_BADGE.label} { setSelectedBadge(getEarlyAccessBadge()); setBadgeOpen(true); }} />

{EARLY_ACCESS_BADGE.label}

)} {/* Premium badge — only when currently exclusive */} {hasPremiumBadge && ( Premium { setSelectedBadge(getBadgeWithDate("premium_first_time", "premium")); setBadgeOpen(true); }} />

Premium

)} {/* Exclusive badge — only when currently exclusive */} {hasExclusiveBadge && ( Exclusive { setSelectedBadge(getBadgeWithDate("exclusive_first_time", "exclusive")); setBadgeOpen(true); }} />

Exclusive

)} {/* Current active tier badge — always shown */} {/* For free tier, show free badge. For premium/exclusive already shown above via hasPremiumBadge/hasExclusiveBadge */} {userRank === 0 && ( {tierBadge.label} { setSelectedBadge(tierBadge); setBadgeOpen(true); }} />

{tierBadge.label}

)} {/* For premium tier without achievement yet (edge case) */} {userRank === 1 && !hasPremiumBadge && ( {tierBadge.label} { setSelectedBadge(getActiveTierBadgeWithDate()); setBadgeOpen(true); }} />

{tierBadge.label}

)}
) : ( )}

{user?.email ?? "—"}

{occupation && {occupation}}
{/* ── Badge modal ───────────────────────────────────────────────────── */} setBadgeOpen(false)}>Close } >
{selectedBadge?.src && ( {selectedBadge.label} )}

{selectedBadge?.label}

{selectedBadge?.earnedAt && (

{selectedBadge.earnedAt}

)}

{selectedBadge?.information}

{/* ── Two columns ──────────────────────────────────────────────────── */}
{/* Left column */}
{/* Learning progress */} Learning progress Only you {inProgressCoursesLoading ? (
{[...Array(2)].map((_, i) => (
))}
) : inProgressCourses.length === 0 ? (

No courses in progress.

) : ( inProgressCourses.slice(0, 2).map((course, i) => { const pct = course.lessons_total > 0 ? Math.round((course.lessons_completed / course.lessons_total) * 100) : 0; const quizPending = course.reading_status === 'completed'; return (

{course.title}

{pct}%
{quizPending ? ( { setPendingModalCourse(course); setPendingModalOpen(true); }} > Quiz / Assessment Pending ) : (

{course.lessons_completed} / {course.lessons_total} lessons

)} {i < Math.min(inProgressCourses.length, 2) - 1 && }
); }) )}
{/* Quiz / Assessment Pending detail modal */} { setPendingModalOpen(v); if (!v) setPendingModalCourse(null); }} title={pendingModalCourse?.title ?? 'Pending Items'} description="You've finished reading all lessons. Complete the items below to earn your certificate." hideDrawerClose={false} footer={ } > {pendingModalCourse && (() => { const { pending_quizzes = [], pending_assessment } = pendingModalCourse; const hasQuizzes = pending_quizzes.length > 0; const hasAssessment = !!pending_assessment; if (!hasQuizzes && !hasAssessment) { return (

No pending items found.

); } return (
{hasQuizzes && (

Unit Quizzes

    {pending_quizzes.map((q) => (
  • {q.title || 'Unit Quiz'}

    {q.unit_title}

    Pass: {q.passing_score}%

    {q.attempt_count === 0 ? 'Not attempted' : `${q.attempt_count} attempt${q.attempt_count === 1 ? '' : 's'}`}

    {q.is_required && ( Required )}
  • ))}
)} {hasAssessment && (

Final Assessment

{pending_assessment.title || 'Course Assessment'}

Pass: {pending_assessment.passing_score}%

{pending_assessment.attempt_count === 0 ? 'Not attempted' : `${pending_assessment.attempt_count} attempt${pending_assessment.attempt_count === 1 ? '' : 's'}`}

{pending_assessment.is_required && ( Required )}
)}
); })()}
{/* Account info */} Account info
Member since {profile?.createdAt ? new Date(profile.createdAt).toLocaleDateString("en-US", { month: "long", year: "numeric" }) : "—"}
Plan {tierLoading ? ( ) : ( {tier}{myTier?.expires_at && ` · until ${new Date(myTier.expires_at).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", })}`} )}
{/* Right column */}
{/* Certificates — preview + full list at /certificates */} navigate("/certificates")} >
My Certificates Only you
{achievementsLoading ? ( ) : certAchievements.length > 0 ? ( {certAchievements.length} ) : null}
{!achievementsLoading && certAchievements.length > 0 && ( )}
{/* Achievements — latest 2, full list at /achievements */} navigate("/achievements")} >
Achievements
{achievementsLoading ? ( ) : achievements.length > 0 ? ( {achievements.length} ) : null}
{achievementsLoading ? (
{[...Array(2)].map((_, i) => (
))}
) : achievements.length > 0 ? (() => { const latest = [...achievements] .sort((a, b) => new Date(b.granted_at) - new Date(a.granted_at)) .slice(0, 2); return (
{latest.map((item, i) => { const Icon = ACHIEVEMENT_ICONS[item.key] ?? getFallbackIcon(item.type); return (

{item.label}

{item.description}

{item.granted_at && (

{new Date(item.granted_at).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", })}

)}
{i < latest.length - 1 && }
); })}
); })() : (

Nothing to show yet

Complete more courses to unlock achievements.

)}
setAvatarDialogOpen(false)} currentAvatarUrl={avatarUrl ?? ''} initials={initials} onUpload={uploadAvatar} onDelete={deleteAvatar} loading={avatarLoading} />
); }; export default ProfilePage;