Files
HokmPlay/src/components/screens/ShopScreen.tsx
T

222 lines
6.6 KiB
TypeScript
Raw Normal View History

"use client";
import { Check, Coins } from "lucide-react";
import { useEffect, useState } from "react";
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
import { Sticker } from "@/components/online/Sticker";
import { useSessionStore } from "@/lib/session-store";
import { useI18n } from "@/lib/i18n";
import { getService } from "@/lib/online/service";
import { sound } from "@/lib/sound";
import { ShopItem } from "@/lib/online/types";
import { cn } from "@/lib/cn";
export function ShopScreen() {
const { t, locale } = useI18n();
const profile = useSessionStore((s) => s.profile);
const setProfile = useSessionStore((s) => s.setProfile);
const [items, setItems] = useState<ShopItem[]>([]);
const [msg, setMsg] = useState("");
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 "xp":
return false; // consumable — always buyable
default:
return profile.ownedStickerPacks.includes(item.id);
}
};
const buy = async (item: ShopItem) => {
const res = await getService().buyItem(item.id);
if (res.ok && res.profile) {
setProfile(res.profile);
sound.play("purchase");
} else {
setMsg(locale === "fa" ? res.messageFa : res.messageEn);
setTimeout(() => setMsg(""), 1800);
}
};
const avatars = items.filter((i) => i.kind === "avatar");
const cardfronts = items.filter((i) => i.kind === "cardfront");
const cardbacks = items.filter((i) => i.kind === "cardback");
const reactions = items.filter((i) => i.kind === "reactionpack");
const stickers = items.filter((i) => i.kind === "stickerpack");
const xp = items.filter((i) => i.kind === "xp");
return (
<ScreenShell>
<ScreenHeader
title={t("shop.title")}
right={
<span className="glass rounded-full px-3 py-1.5 text-xs font-bold text-gold-300 flex items-center gap-1">
<Coins className="size-3.5 text-gold-400" />
{profile.coins.toLocaleString()}
</span>
}
/>
{msg && (
<div className="mb-3 text-center text-rose-300 text-sm glass rounded-xl py-2">{msg}</div>
)}
<Section title={t("shop.avatars")}>
<div className="grid grid-cols-3 gap-3">
{avatars.map((item) => (
<ItemCard key={item.id} item={item} owned={owns(item)} onBuy={() => buy(item)} preview={<span className="text-4xl">{item.preview}</span>} />
))}
</div>
</Section>
<Section title={t("shop.cardfronts")}>
<div className="grid grid-cols-3 gap-3">
{cardfronts.map((item) => (
<ItemCard
key={item.id}
item={item}
owned={owns(item)}
onBuy={() => buy(item)}
preview={
<span
className="w-8 h-11 rounded-md border flex items-center justify-center text-slate-900"
style={{ background: `linear-gradient(160deg, #ffffff, ${item.preview})`, borderColor: "rgba(0,0,0,0.18)" }}
>
</span>
}
/>
))}
</div>
</Section>
<Section title={t("shop.cardbacks")}>
<div className="grid grid-cols-3 gap-3">
{cardbacks.map((item) => (
<ItemCard
key={item.id}
item={item}
owned={owns(item)}
onBuy={() => buy(item)}
preview={
<span
className="w-8 h-11 rounded-md border"
style={{
borderColor: `${item.preview}80`,
background: `repeating-linear-gradient(45deg, ${item.preview}55 0 4px, transparent 4px 8px), #0a142e`,
}}
/>
}
/>
))}
</div>
</Section>
<Section title={t("shop.reactions")}>
<div className="grid grid-cols-3 gap-3">
{reactions.map((item) => (
<ItemCard
key={item.id}
item={item}
owned={owns(item)}
onBuy={() => buy(item)}
preview={<span className="text-4xl">{item.preview}</span>}
/>
))}
</div>
</Section>
<Section title={t("shop.stickers")}>
<div className="grid grid-cols-3 gap-3">
{stickers.map((item) => (
<ItemCard
key={item.id}
item={item}
owned={owns(item)}
onBuy={() => buy(item)}
preview={<Sticker id={item.preview} size={48} />}
/>
))}
</div>
</Section>
<Section title={t("shop.xp")}>
<p className="text-[11px] text-cream/45 -mt-2 mb-3">{t("shop.xpHint")}</p>
<div className="grid grid-cols-3 gap-3">
{xp.map((item) => (
<ItemCard
key={item.id}
item={item}
owned={false}
onBuy={() => buy(item)}
preview={<span className="text-4xl"></span>}
/>
))}
</div>
</Section>
</ScreenShell>
);
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="mb-5">
<h3 className="text-sm font-bold text-cream/80 mb-3">{title}</h3>
{children}
</div>
);
}
function ItemCard({
item,
owned,
onBuy,
preview,
}: {
item: ShopItem;
owned: boolean;
onBuy: () => void;
preview: React.ReactNode;
}) {
const { t } = useI18n();
return (
<div className="glass rounded-2xl p-3 flex flex-col items-center gap-2">
<div className="h-12 flex items-center justify-center">{preview}</div>
<button
disabled={owned}
onClick={onBuy}
className={cn(
"w-full rounded-lg py-1.5 text-xs font-bold flex items-center justify-center gap-1",
owned ? "bg-navy-900/60 text-teal-300" : "btn-gold"
)}
>
{owned ? (
<>
<Check className="size-3.5" />
{t("shop.owned")}
</>
) : (
<>
<Coins className="size-3.5" />
{item.price.toLocaleString()}
</>
)}
</button>
</div>
);
}