2026-06-04 10:11:00 +03:30
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { Check, Coins } from "lucide-react";
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
|
|
|
|
|
import { useSessionStore } from "@/lib/session-store";
|
|
|
|
|
import { useI18n } from "@/lib/i18n";
|
|
|
|
|
import { getService } from "@/lib/online/service";
|
|
|
|
|
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) =>
|
|
|
|
|
item.kind === "avatar"
|
|
|
|
|
? profile.ownedAvatars.includes(item.id)
|
2026-06-04 11:02:25 +03:30
|
|
|
: item.kind === "cardstyle"
|
|
|
|
|
? profile.ownedCardStyles.includes(item.id)
|
|
|
|
|
: profile.ownedReactionPacks.includes(item.id);
|
2026-06-04 10:11:00 +03:30
|
|
|
|
|
|
|
|
const buy = async (item: ShopItem) => {
|
|
|
|
|
const res = await getService().buyItem(item.id);
|
|
|
|
|
if (res.ok && res.profile) {
|
|
|
|
|
setProfile(res.profile);
|
|
|
|
|
} else {
|
|
|
|
|
setMsg(locale === "fa" ? res.messageFa : res.messageEn);
|
|
|
|
|
setTimeout(() => setMsg(""), 1800);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const avatars = items.filter((i) => i.kind === "avatar");
|
2026-06-04 10:49:54 +03:30
|
|
|
const cardstyles = items.filter((i) => i.kind === "cardstyle");
|
2026-06-04 11:02:25 +03:30
|
|
|
const reactions = items.filter((i) => i.kind === "reactionpack");
|
2026-06-04 10:11:00 +03:30
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
2026-06-04 10:49:54 +03:30
|
|
|
<Section title={t("shop.cardstyles")}>
|
2026-06-04 10:11:00 +03:30
|
|
|
<div className="grid grid-cols-3 gap-3">
|
2026-06-04 10:49:54 +03:30
|
|
|
{cardstyles.map((item) => (
|
2026-06-04 10:11:00 +03:30
|
|
|
<ItemCard
|
|
|
|
|
key={item.id}
|
|
|
|
|
item={item}
|
|
|
|
|
owned={owns(item)}
|
|
|
|
|
onBuy={() => buy(item)}
|
|
|
|
|
preview={
|
|
|
|
|
<span
|
2026-06-04 10:49:54 +03:30
|
|
|
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`,
|
|
|
|
|
}}
|
2026-06-04 10:11:00 +03:30
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</Section>
|
2026-06-04 11:02:25 +03:30
|
|
|
|
|
|
|
|
<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>
|
2026-06-04 10:11:00 +03:30
|
|
|
</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>
|
|
|
|
|
);
|
|
|
|
|
}
|