"use client"; import { motion } from "framer-motion"; import { Check, ChevronLeft, Crown, Eye, EyeOff, Gift, Lock, LogOut, MapPin, Music, Pencil, Search, Star, Upload, Users, Volume2 } from "lucide-react"; import { useRef, useState } from "react"; import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader"; import { RankBadge } from "@/components/online/RankBadge"; import { CoinsPill } from "@/components/online/CoinsPill"; import { XpBar } from "@/components/online/XpBar"; import { Avatar } from "@/components/online/Avatar"; import { useSessionStore } from "@/lib/session-store"; import { useSoundStore } from "@/lib/sound-store"; import { useUIStore } from "@/lib/ui-store"; import { useI18n } from "@/lib/i18n"; import { ACHIEVEMENTS, CARD_BACKS, CARD_FRONTS, CITY_REWARD, TITLES, achievementProgress, ownedAvatarIds, ownedCardBackIds, ownedCardFrontIds, } from "@/lib/online/gamification"; import { IRAN_CITIES, cityById, searchCities, type City } from "@/lib/iran-cities"; import { pushNotification } from "@/lib/notification-store"; import { sound } from "@/lib/sound"; /** Level required before a player can upload a custom profile photo. */ const PHOTO_UPLOAD_MIN_LEVEL = 3; import { AVATARS, Gender, SocialVisibility } from "@/lib/online/types"; import { GENDER_META, SOCIAL_PLATFORMS } from "@/lib/social"; import { backVisualFromDef, cardBackMotif } from "@/lib/cardBack"; import { cn } from "@/lib/cn"; export function ProfileScreen() { const { t, locale } = useI18n(); const profile = useSessionStore((s) => s.profile); const updateProfile = useSessionStore((s) => s.updateProfile); const upgradePlan = useSessionStore((s) => s.upgradePlan); const signOut = useSessionStore((s) => s.signOut); const isAuthed = useSessionStore((s) => s.isAuthed); const go = useUIStore((st) => st.go); const fileRef = useRef(null); const [editing, setEditing] = useState(false); const [name, setName] = useState(profile?.displayName ?? ""); const [tab, setTab] = useState<"basic" | "collection" | "settings">("basic"); if (!profile) return null; const canUpload = profile.level >= PHOTO_UPLOAD_MIN_LEVEL; const s = profile.stats; const winrate = s.games > 0 ? Math.round((s.wins / s.games) * 100) : 0; const titleDef = TITLES.find((x) => x.id === profile.title); const titleName = titleDef ? (locale === "fa" ? titleDef.nameFa : titleDef.nameEn) : null; const ownedFronts = new Set(ownedCardFrontIds(profile)); const ownedBacks = new Set(ownedCardBackIds(profile)); const ownedAvatars = new Set(ownedAvatarIds(profile)); const saveName = async () => { if (name.trim()) await updateProfile({ displayName: name.trim() }); setEditing(false); }; const onUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file || !canUpload) return; const reader = new FileReader(); reader.onload = () => updateProfile({ avatarImage: String(reader.result) }); reader.readAsDataURL(file); }; return ( {/* tabs */}
{( [ ["basic", t("profile.tabBasic")], ["collection", t("profile.tabCollection")], ["settings", t("profile.tabSettings")], ] as const ).map(([key, label]) => ( ))}
{/* ===== Basic: player card + stats/achievements ===== */} {tab === "basic" && (
{/* one-time "set your city" reward */}
{/* player card */}
{profile.level}
{titleName &&
{titleName}
} {editing ? (
setName(e.target.value)} className="rounded-lg bg-navy-900/70 gold-border px-3 py-1.5 text-center text-cream outline-none focus:ring-2 focus:ring-gold-500/40" />
) : ( )}
{profile.plan === "pro" ? ( {t("plan.active")} ) : ( )}
{/* stats + achievements */}
{t("profile.lifeRibbon")}

{t("profile.achievements")} {" "} ({profile.unlocked.length}/{ACHIEVEMENTS.length})

{[...ACHIEVEMENTS] .sort((a, b) => { const ua = profile.unlocked.includes(a.id) ? 1 : 0; const ub = profile.unlocked.includes(b.id) ? 1 : 0; return ub - ua; }) .slice(0, 6) .map((a, idx) => { const prog = achievementProgress(a, s, profile.rating, profile.level); const unlocked = profile.unlocked.includes(a.id) || prog >= a.goal; const pct = Math.min(100, Math.round((prog / a.goal) * 100)); return ( {a.icon}
{locale === "fa" ? a.nameFa : a.nameEn}
{locale === "fa" ? a.descFa : a.descEn}
{!unlocked && a.goal > 1 && (
)}
{unlocked && } ); })}
)} {/* ===== Collection: cosmetics pickers ===== */} {tab === "collection" && (
{/* avatar picker */}

{t("profile.chooseAvatar")}

{AVATARS.filter((a) => ownedAvatars.has(a.id)).map((a) => ( ))}
{/* title picker */}

{t("profile.titleLabel")}

{TITLES.filter((tt) => profile.ownedTitles.includes(tt.id)).map((tt) => ( ))}
{/* card front picker */}

{t("profile.cardFront")}

{CARD_FRONTS.filter((c) => ownedFronts.has(c.id)).map((c) => ( ))}
{/* card back picker */}

{t("profile.cardBack")}

{CARD_BACKS.filter((c) => ownedBacks.has(c.id)).map((c) => ( ))}
)} {/* ===== Settings ===== */} {tab === "settings" && (
{isAuthed && ( )}
)} ); } function Stat({ label, value }: { label: string; value: string | number }) { return (
{value}
{label}
); } const GENDERS: Gender[] = ["male", "female", ""]; const VIS_OPTIONS: { id: SocialVisibility; icon: React.ReactNode; key: string }[] = [ { id: "public", icon: , key: "profile.visPublic" }, { id: "friends", icon: , key: "profile.visFriends" }, { id: "hidden", icon: , key: "profile.visHidden" }, ]; function SocialSettings() { const { t, locale } = useI18n(); const profile = useSessionStore((s) => s.profile); const updateProfile = useSessionStore((s) => s.updateProfile); const [links, setLinks] = useState>(() => ({ instagram: profile?.socials?.instagram ?? "", telegram: profile?.socials?.telegram ?? "", x: profile?.socials?.x ?? "", youtube: profile?.socials?.youtube ?? "", })); const [saved, setSaved] = useState(false); if (!profile) return null; const gender = profile.gender ?? ""; const vis = profile.socialsVisibility ?? "public"; const saveLinks = async () => { const socials = Object.fromEntries( Object.entries(links).map(([k, v]) => [k, v.trim()]).filter(([, v]) => v) ); await updateProfile({ socials }); setSaved(true); setTimeout(() => setSaved(false), 1800); }; return (

{t("profile.social")}

{/* gender */}
{t("profile.gender")}
{GENDERS.map((g) => { const meta = g ? GENDER_META[g] : null; const active = gender === g; return ( ); })}
{/* social links */}
{t("profile.socialLinks")}
{SOCIAL_PLATFORMS.map((p) => (
{p.icon} setLinks((l) => ({ ...l, [p.key]: e.target.value }))} placeholder={p.label} dir="ltr" className="flex-1 rounded-lg bg-navy-900/70 gold-border px-3 py-1.5 text-sm text-cream placeholder:text-cream/30 outline-none focus:ring-2 focus:ring-gold-500/40" />
))}
{/* visibility */}
{t("profile.socialsVisibility")}
{VIS_OPTIONS.map((o) => ( ))}

{t("profile.socialsHint")}

); } /** * One-time gamification box: pick your city from a searchable list and earn a * 500-coin reward (granted server-side the first time a city is set). Once * claimed it collapses to a compact summary with an option to change city. */ function CityRewardCard() { const { t, locale } = useI18n(); const profile = useSessionStore((s) => s.profile); const updateProfile = useSessionStore((s) => s.updateProfile); const claimed = !!profile?.cityRewardClaimed; const currentCity = cityById(profile?.city); const [editing, setEditing] = useState(!claimed); const [query, setQuery] = useState(""); const [pickedId, setPickedId] = useState(profile?.city ?? null); const [saving, setSaving] = useState(false); const results = query.trim() ? searchCities(query, 40) : IRAN_CITIES.slice(0, 40); const picked = cityById(pickedId ?? undefined); const cityName = (c?: City) => (c ? (locale === "fa" ? c.fa : c.en) : ""); const choose = (c: City) => { setPickedId(c.id); setQuery(cityName(c)); }; const save = async () => { if (!pickedId || saving) return; const firstClaim = !claimed; setSaving(true); try { await updateProfile({ city: pickedId }); } finally { setSaving(false); } setEditing(false); if (firstClaim) { sound.play("award"); pushNotification({ kind: "system", titleFa: `+${CITY_REWARD.toLocaleString("fa")} سکه دریافت شد! 🎉`, titleEn: `+${CITY_REWARD} coins earned! 🎉`, bodyFa: "شهر شما ثبت شد.", bodyEn: "Your city has been saved.", icon: "📍", }); } }; // ---- Claimed + collapsed: compact summary ---- if (claimed && !editing) { return (
{cityName(currentCity) || t("city.unknown")}
{t("city.claimed")}
); } // ---- Editing / unclaimed: reward prompt + searchable picker ---- return ( {!claimed && (
{t("city.rewardTitle")}
{t("city.rewardSub").replace("{n}", CITY_REWARD.toLocaleString(locale === "fa" ? "fa" : "en"))}
)} {/* searchable city combobox */}
{ setQuery(e.target.value); setPickedId(null); }} placeholder={t("city.search")} className="w-full rounded-xl bg-navy-900/70 gold-border ltr:pl-9 rtl:pr-9 px-3 py-2.5 text-sm text-cream placeholder:text-cream/30 outline-none focus:ring-2 focus:ring-gold-500/40" />
{/* results — only while there's no committed pick yet */} {!picked && (
{results.length === 0 && (
{t("city.none")}
)} {results.map((c) => ( ))}
)} {/* confirm / claim */} {picked && ( )}
); } function SoundSettings() { const { t } = useI18n(); const { sfx, toggleSfx, music, toggleMusic } = useSoundStore(); return (

{t("settings.audio")}

} label={t("settings.sound")} on={sfx} onClick={toggleSfx} /> } label={t("settings.music")} on={music} onClick={toggleMusic} />
); } function ToggleRow({ icon, label, on, onClick, }: { icon: React.ReactNode; label: string; on: boolean; onClick: () => void; }) { return ( ); }