"use client"; import { AnimatePresence, motion } from "framer-motion"; import { Check, Coins, Lock, Sparkles, X } from "lucide-react"; import { useEffect, useState } from "react"; import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader"; import { Sticker } from "@/components/online/Sticker"; import { CoinsPill } from "@/components/online/CoinsPill"; import { useSessionStore } from "@/lib/session-store"; import { useI18n } from "@/lib/i18n"; import { getService } from "@/lib/online/service"; import { sound } from "@/lib/sound"; import { achievementById, cardBackById } from "@/lib/online/gamification"; import { backVisualFromDef, cardBackMotif } from "@/lib/cardBack"; import { celebrate } from "@/lib/celebration-store"; import { AchievementUnlock, ShopItem } from "@/lib/online/types"; import { cn } from "@/lib/cn"; /** The product artwork, used on the card and (bigger) in the detail sheet. */ function Preview({ item, size }: { item: ShopItem; size: number }) { switch (item.kind) { case "stickerpack": return ; case "cardfront": return ( ); case "cardback": { const back = cardBackById(item.id); return ( {cardBackMotif(back.pattern, back.motif)} ); } case "title": return ( 🏷️ ); default: // avatar, reactionpack, xp → emoji glyph return {item.kind === "xp" ? "⚡" : item.preview}; } } export function ShopScreen() { const { t, locale } = useI18n(); const profile = useSessionStore((s) => s.profile); const setProfile = useSessionStore((s) => s.setProfile); const [items, setItems] = useState([]); const [msg, setMsg] = useState(""); const [detail, setDetail] = useState(null); useEffect(() => { getService().getShopItems().then(setItems); }, []); if (!profile) return null; const owns = (item: ShopItem) => { switch (item.kind) { case "avatar": return profile.ownedAvatars.includes(item.id); case "cardfront": return profile.ownedCardFronts.includes(item.id); case "cardback": return profile.ownedCardBacks.includes(item.id); case "reactionpack": return profile.ownedReactionPacks.includes(item.id); case "title": return profile.ownedTitles.includes(item.id); case "xp": return false; // consumable — always buyable default: return profile.ownedStickerPacks.includes(item.id); } }; // Requirement gate: returns a label while LOCKED, else null. const lockLabel = (item: ShopItem): string | null => { if (item.reqLevel && profile.level < item.reqLevel) return `${t("shop.reqLevel")} ${item.reqLevel}`; if (item.reqRating && profile.rating < item.reqRating) return `${t("shop.reqRating")} ${item.reqRating}`; return null; }; const buy = async (item: ShopItem) => { const before = profile; const res = await getService().buyItem(item.id); if (res.ok && res.profile) { const after = res.profile; setProfile(after); sound.play("purchase"); setDetail(null); // newly-unlocked achievements (e.g. an XP pack crossing a level milestone) const newAch: AchievementUnlock[] = after.unlocked .filter((id) => !before.unlocked.includes(id)) .map((id) => achievementById(id)) .filter((d): d is NonNullable => !!d) .map((d) => ({ id: d.id, nameFa: d.nameFa, nameEn: d.nameEn, icon: d.icon, coinReward: d.coinReward })); if (item.kind === "xp") { celebrate({ variant: "xp", icon: "⚡", title: locale === "fa" ? "امتیاز تجربه" : "Experience", xpGained: item.xp ?? 0, levelBefore: before.level, levelAfter: after.level, achievements: newAch.length ? newAch : undefined, }); } else { celebrate({ variant: "purchase", // avatar/reaction previews are emojis; others fall back to the default glyph icon: item.kind === "avatar" || item.kind === "reactionpack" ? item.preview : undefined, title: locale === "fa" ? item.nameFa : item.nameEn, achievements: newAch.length ? newAch : undefined, }); } } else { setMsg(locale === "fa" ? res.messageFa : res.messageEn); setTimeout(() => setMsg(""), 1800); } }; const sections: { title: string; kind: ShopItem["kind"]; hint?: string }[] = [ { title: t("shop.avatars"), kind: "avatar" }, { title: t("shop.cardfronts"), kind: "cardfront" }, { title: t("shop.cardbacks"), kind: "cardback" }, { title: t("shop.reactions"), kind: "reactionpack" }, { title: t("shop.stickers"), kind: "stickerpack" }, { title: t("shop.titles"), kind: "title", hint: t("shop.titlesHint") }, { title: t("shop.xp"), kind: "xp", hint: t("shop.xpHint") }, ]; return ( } /> {msg && (
{msg}
)} {items.length === 0 && (
{Array.from({ length: 2 }).map((_, s) => (
{Array.from({ length: 3 }).map((_, i) => (
))}
))}
)} {sections.map((sec) => { const list = items.filter((i) => i.kind === sec.kind); if (!list.length) return null; return (
{list.map((item) => ( setDetail(item)} /> ))}
); })} {detail && ( buy(detail)} onClose={() => setDetail(null)} /> )} ); } function Section({ title, hint, children }: { title: string; hint?: string; children: React.ReactNode }) { return (

{title}

{hint &&

{hint}

} {!hint &&
} {children}
); } function ItemCard({ item, owned, reqLabel, onOpen }: { item: ShopItem; owned: boolean; reqLabel: string | null; onOpen: () => void }) { const { locale, t } = useI18n(); const count = item.contents?.length; const luxury = item.price >= 2000; // premium "luxury" tier const locked = !owned && !!reqLabel; return ( {luxury && !owned && ( ✦ {t("shop.luxury")} )} {owned && ( )}
{locale === "fa" ? item.nameFa : item.nameEn} {/* quick detail hint */} {item.kind === "xp" ? `+${item.xp} XP` : count != null ? locale === "fa" ? `${count} مورد` : `${count} items` : " "} {owned ? ( ) : locked ? ( <> {reqLabel} ) : ( <> {item.price.toLocaleString()} )}
); } function DetailSheet({ item, owned, coins, reqLabel, onBuy, onClose, }: { item: ShopItem; owned: boolean; coins: number; reqLabel: string | null; onBuy: () => void; onClose: () => void; }) { const { t, locale } = useI18n(); const name = locale === "fa" ? item.nameFa : item.nameEn; const desc = locale === "fa" ? item.descFa : item.descEn; const locked = !owned && !!reqLabel; const canAfford = coins >= item.price; return ( e.stopPropagation()} className="glass rounded-3xl p-6 w-full max-w-sm text-center relative" >

{name}

{desc &&

{desc}

} {/* what's included */} {item.contents && item.contents.length > 0 && (
{t("shop.includes")} ({item.contents.length})
{item.kind === "stickerpack" ? item.contents.map((s) => (
)) : item.contents.map((e, i) => ( {e} ))}
)} {item.kind === "xp" && (
+{item.xp?.toLocaleString()} XP
)} {/* buy */}
); }