Files
HokmPlay/src/lib/online-store.ts
T
soroush.asadi 5776036d78 Add friend chat; replace betting wording (شرط) with entry coins
- Friend-to-friend chat outside the game (ChatScreen) with mock replies,
  per-friend history, unread tracking; chat button on each friend row
- OnlineService + mock + online-store extended with chat (list/get/send/markRead)
- Reframe gambling term: "شرط"/"Stake" -> "سکه ورودی"/"Entry coins";
  free entry labeled رایگان/Free

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:21:44 +03:30

173 lines
5.0 KiB
TypeScript

"use client";
import { create } from "zustand";
import { CreateRoomOptions, MatchmakingOptions, getService } from "./online/service";
import {
ChatMessage,
Friend,
FriendRequest,
LeaderboardEntry,
MatchmakingState,
Room,
} from "./online/types";
interface OnlineStore {
friends: Friend[];
requests: FriendRequest[];
room: Room | null;
matchmaking: MatchmakingState;
leaderboard: LeaderboardEntry[];
loadFriends: () => Promise<void>;
addFriend: (q: string) => Promise<{ ok: boolean; messageFa: string; messageEn: string }>;
acceptRequest: (id: string) => Promise<void>;
declineRequest: (id: string) => Promise<void>;
removeFriend: (id: string) => Promise<void>;
createRoom: (opts: CreateRoomOptions) => Promise<void>;
setPartner: (friendId: string | null) => Promise<void>;
inviteToSeat: (seat: 1 | 3, friendId: string) => Promise<void>;
addBot: (seat: 1 | 2 | 3) => Promise<void>;
clearSeat: (seat: 1 | 2 | 3) => Promise<void>;
startRoom: () => Promise<void>;
leaveRoom: () => Promise<void>;
startMatchmaking: (opts: MatchmakingOptions) => Promise<void>;
cancelMatchmaking: () => Promise<void>;
loadLeaderboard: () => Promise<void>;
// chat
activeChatFriend: Friend | null;
chatMessages: ChatMessage[];
openChat: (friend: Friend) => Promise<void>;
sendChat: (text: string) => Promise<void>;
closeChat: () => void;
}
let roomUnsub: (() => void) | null = null;
let mmUnsub: (() => void) | null = null;
let friendUnsub: (() => void) | null = null;
let chatUnsub: (() => void) | null = null;
export const useOnlineStore = create<OnlineStore>((set, get) => ({
friends: [],
requests: [],
room: null,
matchmaking: { phase: "idle", players: [], elapsedMs: 0, ranked: true, stake: 0 },
leaderboard: [],
loadFriends: async () => {
const svc = getService();
const [friends, requests] = await Promise.all([svc.listFriends(), svc.listRequests()]);
set({ friends, requests });
if (!friendUnsub) friendUnsub = svc.onFriends((f) => set({ friends: f }));
},
addFriend: async (q) => {
const res = await getService().addFriend(q);
if (res.ok) await get().loadFriends();
return res;
},
acceptRequest: async (id) => {
await getService().acceptRequest(id);
const requests = await getService().listRequests();
set({ requests });
},
declineRequest: async (id) => {
await getService().declineRequest(id);
set({ requests: get().requests.filter((r) => r.id !== id) });
},
removeFriend: async (id) => {
await getService().removeFriend(id);
},
createRoom: async (opts) => {
const svc = getService();
const room = await svc.createRoom(opts);
set({ room });
if (roomUnsub) roomUnsub();
roomUnsub = svc.onRoom((r) => set({ room: { ...r } }));
},
setPartner: async (friendId) => {
const r = await getService().setPartner(get().room!.id, friendId);
set({ room: { ...r } });
},
inviteToSeat: async (seat, friendId) => {
const r = await getService().inviteToSeat(get().room!.id, seat, friendId);
set({ room: { ...r } });
},
addBot: async (seat) => {
const r = await getService().addBot(get().room!.id, seat);
set({ room: { ...r } });
},
clearSeat: async (seat) => {
const r = await getService().clearSeat(get().room!.id, seat);
set({ room: { ...r } });
},
startRoom: async () => {
const r = await getService().startRoom(get().room!.id);
set({ room: { ...r } });
},
leaveRoom: async () => {
if (get().room) await getService().leaveRoom(get().room!.id);
if (roomUnsub) {
roomUnsub();
roomUnsub = null;
}
set({ room: null });
},
startMatchmaking: async (opts) => {
const svc = getService();
if (mmUnsub) mmUnsub();
mmUnsub = svc.onMatchmaking((s) => set({ matchmaking: s }));
await svc.startMatchmaking(opts);
},
cancelMatchmaking: async () => {
await getService().cancelMatchmaking();
if (mmUnsub) {
mmUnsub();
mmUnsub = null;
}
set({ matchmaking: { phase: "idle", players: [], elapsedMs: 0, ranked: true, stake: 0 } });
},
loadLeaderboard: async () => {
const leaderboard = await getService().getLeaderboard();
set({ leaderboard });
},
activeChatFriend: null,
chatMessages: [],
openChat: async (friend) => {
const svc = getService();
set({ activeChatFriend: friend, chatMessages: await svc.getMessages(friend.id) });
await svc.markRead(friend.id);
if (chatUnsub) chatUnsub();
chatUnsub = svc.onChat((friendId, msgs) => {
const active = get().activeChatFriend;
if (active && active.id === friendId) {
set({ chatMessages: msgs });
svc.markRead(friendId);
}
});
},
sendChat: async (text) => {
const friend = get().activeChatFriend;
if (!friend || !text.trim()) return;
await getService().sendMessage(friend.id, text);
set({ chatMessages: await getService().getMessages(friend.id) });
},
closeChat: () => {
if (chatUnsub) {
chatUnsub();
chatUnsub = null;
}
set({ activeChatFriend: null, chatMessages: [] });
},
}));