@dubsdotapp/expo 0.5.17 → 0.5.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2090 -0
- package/dist/index.d.ts +2090 -0
- package/dist/index.js +1916 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1868 -102
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
- package/src/chat/hooks.ts +320 -0
- package/src/chat/index.ts +40 -0
- package/src/chat/provider.tsx +213 -0
- package/src/chat/socket.ts +175 -0
- package/src/chat/types.ts +146 -0
- package/src/client.ts +182 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useEnterJackpot.ts +80 -0
- package/src/hooks/useJackpot.ts +37 -0
- package/src/hooks/useJackpotHistory.ts +34 -0
- package/src/index.ts +55 -0
- package/src/types.ts +69 -0
- package/src/ui/game/JoinGameSheet.tsx +1 -1
- package/src/ui/index.ts +4 -0
- package/src/ui/jackpot/JackpotCard.tsx +417 -0
- package/src/ui/jackpot/JackpotSheet.tsx +683 -0
- package/src/ui/jackpot/JackpotWidget.tsx +74 -0
- package/src/ui/jackpot/index.ts +6 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { io, Socket } from 'socket.io-client';
|
|
2
|
+
import type {
|
|
3
|
+
ChatMessage,
|
|
4
|
+
OnlineUser,
|
|
5
|
+
ChatNotification,
|
|
6
|
+
ChatConnectionStatus,
|
|
7
|
+
DirectMessage,
|
|
8
|
+
SendMessageParams,
|
|
9
|
+
SendDMParams,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export interface ChatSocketConfig {
|
|
13
|
+
/** Server origin, e.g. "https://dubs-server-prod-xxx.herokuapp.com" */
|
|
14
|
+
host: string;
|
|
15
|
+
/** JWT token for authentication */
|
|
16
|
+
token: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ChatSocketListeners {
|
|
20
|
+
onConnectionChange?: (status: ChatConnectionStatus) => void;
|
|
21
|
+
// Global chat
|
|
22
|
+
onNewMessage?: (message: ChatMessage) => void;
|
|
23
|
+
onOnlineUsers?: (users: OnlineUser[]) => void;
|
|
24
|
+
onOnlineCount?: (count: number) => void;
|
|
25
|
+
onTyping?: (data: { userId: number; username: string; walletAddress: string; isTyping: boolean }) => void;
|
|
26
|
+
onReactionAdded?: (data: { messageId: number; userId: number; reaction: string; walletAddress: string }) => void;
|
|
27
|
+
onReactionRemoved?: (data: { messageId: number; userId: number; reaction: string }) => void;
|
|
28
|
+
// Notifications
|
|
29
|
+
onNotification?: (notification: ChatNotification) => void;
|
|
30
|
+
onUnreadCount?: (count: number) => void;
|
|
31
|
+
// DMs
|
|
32
|
+
onDMNewMessage?: (message: DirectMessage) => void;
|
|
33
|
+
onDMMessageSent?: (message: DirectMessage) => void;
|
|
34
|
+
onDMNotification?: (data: { senderId: number; senderUsername: string; senderAvatar: string | null; senderWallet: string; preview: string; createdAt: string }) => void;
|
|
35
|
+
onDMMessagesRead?: (data: { readBy: number; readByWallet: string }) => void;
|
|
36
|
+
// Social
|
|
37
|
+
onFriendRequestAccepted?: (data: { requestId: number; acceptedBy: number; acceptedByUsername: string }) => void;
|
|
38
|
+
onFriendRequestDeclined?: (data: { requestId: number; declinedBy: number }) => void;
|
|
39
|
+
onFriendRemoved?: (data: { removedBy: number }) => void;
|
|
40
|
+
// Errors
|
|
41
|
+
onError?: (error: { message: string }) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Manages the Socket.IO connection to the Dubs chat namespace.
|
|
46
|
+
*/
|
|
47
|
+
export class ChatSocket {
|
|
48
|
+
private socket: Socket | null = null;
|
|
49
|
+
private listeners: ChatSocketListeners = {};
|
|
50
|
+
|
|
51
|
+
/** Set event listeners. Call before or after connect. */
|
|
52
|
+
setListeners(listeners: ChatSocketListeners): void {
|
|
53
|
+
this.listeners = listeners;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Connect to the /chat namespace */
|
|
57
|
+
connect(config: ChatSocketConfig): void {
|
|
58
|
+
if (this.socket?.connected) {
|
|
59
|
+
console.log('[Dubs:ChatSocket] Already connected');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.listeners.onConnectionChange?.('connecting');
|
|
64
|
+
|
|
65
|
+
console.log(`[Dubs:ChatSocket] Connecting to ${config.host}/chat`);
|
|
66
|
+
|
|
67
|
+
this.socket = io(`${config.host}/chat`, {
|
|
68
|
+
auth: { token: config.token },
|
|
69
|
+
transports: ['websocket'],
|
|
70
|
+
reconnection: true,
|
|
71
|
+
reconnectionDelay: 1000,
|
|
72
|
+
reconnectionAttempts: 10,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.socket.on('connect', () => {
|
|
76
|
+
console.log('[Dubs:ChatSocket] Connected');
|
|
77
|
+
this.listeners.onConnectionChange?.('connected');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.socket.on('disconnect', (reason) => {
|
|
81
|
+
console.log('[Dubs:ChatSocket] Disconnected:', reason);
|
|
82
|
+
this.listeners.onConnectionChange?.('disconnected');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this.socket.on('connect_error', (error) => {
|
|
86
|
+
console.error('[Dubs:ChatSocket] Connection error:', error.message);
|
|
87
|
+
this.listeners.onConnectionChange?.('error');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Global chat events
|
|
91
|
+
this.socket.on('new_message', (msg: ChatMessage) => this.listeners.onNewMessage?.(msg));
|
|
92
|
+
this.socket.on('online_users', (users: OnlineUser[]) => this.listeners.onOnlineUsers?.(users));
|
|
93
|
+
this.socket.on('online_count', (count: number) => this.listeners.onOnlineCount?.(count));
|
|
94
|
+
this.socket.on('user_typing', (data: any) => this.listeners.onTyping?.(data));
|
|
95
|
+
this.socket.on('reaction_added', (data: any) => this.listeners.onReactionAdded?.(data));
|
|
96
|
+
this.socket.on('reaction_removed', (data: any) => this.listeners.onReactionRemoved?.(data));
|
|
97
|
+
|
|
98
|
+
// Notifications
|
|
99
|
+
this.socket.on('notification', (n: ChatNotification) => this.listeners.onNotification?.(n));
|
|
100
|
+
this.socket.on('unread_count', (count: number) => this.listeners.onUnreadCount?.(count));
|
|
101
|
+
|
|
102
|
+
// DM events
|
|
103
|
+
this.socket.on('dm:new_message', (msg: DirectMessage) => this.listeners.onDMNewMessage?.(msg));
|
|
104
|
+
this.socket.on('dm:message_sent', (msg: DirectMessage) => this.listeners.onDMMessageSent?.(msg));
|
|
105
|
+
this.socket.on('dm:notification', (data: any) => this.listeners.onDMNotification?.(data));
|
|
106
|
+
this.socket.on('dm:messages_read', (data: any) => this.listeners.onDMMessagesRead?.(data));
|
|
107
|
+
|
|
108
|
+
// Social events
|
|
109
|
+
this.socket.on('friend_request_accepted', (data: any) => this.listeners.onFriendRequestAccepted?.(data));
|
|
110
|
+
this.socket.on('friend_request_declined', (data: any) => this.listeners.onFriendRequestDeclined?.(data));
|
|
111
|
+
this.socket.on('friend_removed', (data: any) => this.listeners.onFriendRemoved?.(data));
|
|
112
|
+
|
|
113
|
+
// Error
|
|
114
|
+
this.socket.on('error', (err: { message: string }) => this.listeners.onError?.(err));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Disconnect from the chat namespace */
|
|
118
|
+
disconnect(): void {
|
|
119
|
+
if (this.socket) {
|
|
120
|
+
console.log('[Dubs:ChatSocket] Disconnecting');
|
|
121
|
+
this.socket.removeAllListeners();
|
|
122
|
+
this.socket.disconnect();
|
|
123
|
+
this.socket = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Whether the socket is currently connected */
|
|
128
|
+
isConnected(): boolean {
|
|
129
|
+
return this.socket?.connected ?? false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Global Chat Actions ──
|
|
133
|
+
|
|
134
|
+
/** Send a message to global chat */
|
|
135
|
+
sendMessage(params: SendMessageParams): void {
|
|
136
|
+
this.socket?.emit('send_message', params);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Emit typing indicator */
|
|
140
|
+
sendTyping(isTyping: boolean): void {
|
|
141
|
+
this.socket?.emit('typing', isTyping);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Add an emoji reaction to a message */
|
|
145
|
+
addReaction(messageId: number, reaction: string): void {
|
|
146
|
+
this.socket?.emit('add_reaction', { messageId, reaction });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Remove an emoji reaction from a message */
|
|
150
|
+
removeReaction(messageId: number, reaction: string): void {
|
|
151
|
+
this.socket?.emit('remove_reaction', { messageId, reaction });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── DM Actions ──
|
|
155
|
+
|
|
156
|
+
/** Join a DM room to receive messages in real-time */
|
|
157
|
+
joinDM(recipientWallet: string): void {
|
|
158
|
+
this.socket?.emit('dm:join', { recipientWallet });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Leave a DM room */
|
|
162
|
+
leaveDM(recipientWallet: string): void {
|
|
163
|
+
this.socket?.emit('dm:leave', { recipientWallet });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Send a direct message via socket */
|
|
167
|
+
sendDM(params: SendDMParams): void {
|
|
168
|
+
this.socket?.emit('dm:send', params);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Mark DMs from a sender as read */
|
|
172
|
+
markDMRead(senderWallet: string): void {
|
|
173
|
+
this.socket?.emit('dm:mark_read', { senderWallet });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// ── Chat Message Types ──
|
|
2
|
+
|
|
3
|
+
export interface ChatMessage {
|
|
4
|
+
id: number;
|
|
5
|
+
userId: number;
|
|
6
|
+
walletAddress: string;
|
|
7
|
+
username: string;
|
|
8
|
+
avatar: string | null;
|
|
9
|
+
message: string;
|
|
10
|
+
replyTo: {
|
|
11
|
+
id: number;
|
|
12
|
+
message: string;
|
|
13
|
+
username: string;
|
|
14
|
+
walletAddress: string;
|
|
15
|
+
} | null;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
edited: boolean;
|
|
18
|
+
editedAt: string | null;
|
|
19
|
+
isWinnerAnnouncement: boolean;
|
|
20
|
+
winAmount?: number;
|
|
21
|
+
roundId?: number;
|
|
22
|
+
gameInvite: Record<string, unknown> | null;
|
|
23
|
+
pnlShare: Record<string, unknown> | null;
|
|
24
|
+
gifUrl: string | null;
|
|
25
|
+
reactions: Record<string, { count: number; reactors: string[] }>;
|
|
26
|
+
mentions: ChatMention[];
|
|
27
|
+
payment: ChatPayment | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ChatMention {
|
|
31
|
+
userId: number;
|
|
32
|
+
username: string;
|
|
33
|
+
walletAddress: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ChatPayment {
|
|
37
|
+
id: number;
|
|
38
|
+
senderUsername: string;
|
|
39
|
+
recipientUsername: string;
|
|
40
|
+
amount: number;
|
|
41
|
+
signature: string;
|
|
42
|
+
status: string;
|
|
43
|
+
errorMessage: string | null;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
confirmedAt: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── DM Types ──
|
|
49
|
+
|
|
50
|
+
export interface DirectMessage {
|
|
51
|
+
id: number;
|
|
52
|
+
senderId: number;
|
|
53
|
+
recipientId?: number;
|
|
54
|
+
senderUsername: string;
|
|
55
|
+
senderAvatar: string | null;
|
|
56
|
+
senderWallet: string;
|
|
57
|
+
message: string;
|
|
58
|
+
read: boolean;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
isOwn: boolean;
|
|
61
|
+
gameInvite: Record<string, unknown> | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface Conversation {
|
|
65
|
+
otherUserId: number;
|
|
66
|
+
otherUsername: string;
|
|
67
|
+
otherAvatar: string | null;
|
|
68
|
+
otherWallet: string;
|
|
69
|
+
lastMessage: string;
|
|
70
|
+
lastMessageAt: string;
|
|
71
|
+
unreadCount: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Social Types ──
|
|
75
|
+
|
|
76
|
+
export interface FriendUser {
|
|
77
|
+
userId: number;
|
|
78
|
+
username: string;
|
|
79
|
+
avatar: string | null;
|
|
80
|
+
walletAddress: string;
|
|
81
|
+
friendsSince?: string;
|
|
82
|
+
isFriend?: boolean;
|
|
83
|
+
isBlocked?: boolean;
|
|
84
|
+
friendRequestSent?: boolean;
|
|
85
|
+
friendRequestReceived?: boolean;
|
|
86
|
+
friendRequestId?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface FriendRequest {
|
|
90
|
+
id: number;
|
|
91
|
+
fromUserId: number;
|
|
92
|
+
fromUsername: string;
|
|
93
|
+
fromAvatar: string | null;
|
|
94
|
+
fromWallet: string;
|
|
95
|
+
createdAt: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Online / Real-time Types ──
|
|
99
|
+
|
|
100
|
+
export interface OnlineUser {
|
|
101
|
+
walletAddress: string;
|
|
102
|
+
username: string;
|
|
103
|
+
avatar: string | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface TypingEvent {
|
|
107
|
+
userId: number;
|
|
108
|
+
username: string;
|
|
109
|
+
walletAddress: string;
|
|
110
|
+
isTyping: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface ChatNotification {
|
|
114
|
+
id: number;
|
|
115
|
+
type: string;
|
|
116
|
+
read: boolean;
|
|
117
|
+
message: string;
|
|
118
|
+
messageId?: number;
|
|
119
|
+
senderUsername: string;
|
|
120
|
+
senderWallet: string;
|
|
121
|
+
senderAvatar: string | null;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
[key: string]: unknown;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Connection State ──
|
|
127
|
+
|
|
128
|
+
export type ChatConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
129
|
+
|
|
130
|
+
// ── Params ──
|
|
131
|
+
|
|
132
|
+
export interface SendMessageParams {
|
|
133
|
+
message: string;
|
|
134
|
+
replyToId?: number;
|
|
135
|
+
mentions?: number[];
|
|
136
|
+
gifUrl?: string;
|
|
137
|
+
gameInvite?: Record<string, unknown>;
|
|
138
|
+
pnlShare?: Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface SendDMParams {
|
|
142
|
+
recipientWallet: string;
|
|
143
|
+
message: string;
|
|
144
|
+
gameInvite?: Record<string, unknown>;
|
|
145
|
+
animation?: string;
|
|
146
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -45,6 +45,13 @@ import type {
|
|
|
45
45
|
StartAttemptResult,
|
|
46
46
|
SubmitScoreResult,
|
|
47
47
|
BuildArcadeEntryResult,
|
|
48
|
+
JackpotRound,
|
|
49
|
+
JackpotLastWinner,
|
|
50
|
+
JackpotEntry,
|
|
51
|
+
JackpotRoundResult,
|
|
52
|
+
JackpotConfig,
|
|
53
|
+
BuildJackpotEnterResult,
|
|
54
|
+
ConfirmJackpotEnterResult,
|
|
48
55
|
} from './types';
|
|
49
56
|
|
|
50
57
|
export interface DubsClientConfig {
|
|
@@ -573,6 +580,181 @@ export class DubsClient {
|
|
|
573
580
|
return res.entry;
|
|
574
581
|
}
|
|
575
582
|
|
|
583
|
+
// ── Jackpot ──
|
|
584
|
+
|
|
585
|
+
/** Get current jackpot round + last winner */
|
|
586
|
+
async getJackpotCurrent(): Promise<{ round: JackpotRound | null; lastWinner: JackpotLastWinner | null }> {
|
|
587
|
+
const res = await this.request<{ success: true; round: JackpotRound | null; lastWinner: JackpotLastWinner | null }>(
|
|
588
|
+
'GET',
|
|
589
|
+
'/jackpot/current',
|
|
590
|
+
);
|
|
591
|
+
return { round: res.round, lastWinner: res.lastWinner };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/** Get current round entries with odds */
|
|
595
|
+
async getJackpotEntries(): Promise<{ roundId: string; entries: JackpotEntry[]; totalEntries: number }> {
|
|
596
|
+
const res = await this.request<{ success: true; roundId: string; entries: JackpotEntry[]; totalEntries: number }>(
|
|
597
|
+
'GET',
|
|
598
|
+
'/jackpot/entries',
|
|
599
|
+
);
|
|
600
|
+
return { roundId: res.roundId, entries: res.entries, totalEntries: res.totalEntries };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Get recent jackpot round results */
|
|
604
|
+
async getJackpotHistory(limit?: number): Promise<JackpotRoundResult[]> {
|
|
605
|
+
const qs = limit ? `?limit=${limit}` : '';
|
|
606
|
+
const res = await this.request<{ success: true; rounds: JackpotRoundResult[] }>(
|
|
607
|
+
'GET',
|
|
608
|
+
`/jackpot/history${qs}`,
|
|
609
|
+
);
|
|
610
|
+
return res.rounds;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** Get jackpot protocol config */
|
|
614
|
+
async getJackpotConfig(): Promise<JackpotConfig> {
|
|
615
|
+
const res = await this.request<{ success: true; config: JackpotConfig }>(
|
|
616
|
+
'GET',
|
|
617
|
+
'/jackpot/config',
|
|
618
|
+
);
|
|
619
|
+
return res.config;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** Build unsigned jackpot enter transaction */
|
|
623
|
+
async buildJackpotEnter(playerAddress: string, amount: number): Promise<BuildJackpotEnterResult> {
|
|
624
|
+
const res = await this.request<{ success: true } & BuildJackpotEnterResult>(
|
|
625
|
+
'POST',
|
|
626
|
+
'/jackpot/build-enter',
|
|
627
|
+
{ playerAddress, amount },
|
|
628
|
+
);
|
|
629
|
+
return { transaction: res.transaction, roundId: res.roundId, amount: res.amount, amountSol: res.amountSol };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** Confirm jackpot entry after wallet signs (creates developer attribution) */
|
|
633
|
+
async confirmJackpotEnter(params: { playerAddress: string; roundId?: string; amount?: number; signature: string }): Promise<ConfirmJackpotEnterResult> {
|
|
634
|
+
const res = await this.request<{ success: true } & ConfirmJackpotEnterResult>(
|
|
635
|
+
'POST',
|
|
636
|
+
'/jackpot/confirm-enter',
|
|
637
|
+
params,
|
|
638
|
+
);
|
|
639
|
+
return { attributed: res.attributed, appId: res.appId, signature: res.signature };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ── Chat ──
|
|
643
|
+
|
|
644
|
+
/** Get recent global chat messages */
|
|
645
|
+
async getChatMessages(params?: { limit?: number; before?: number }): Promise<{ messages: any[]; hasMore: boolean }> {
|
|
646
|
+
const qs = new URLSearchParams();
|
|
647
|
+
if (params?.limit) qs.set('limit', String(params.limit));
|
|
648
|
+
if (params?.before) qs.set('before', String(params.before));
|
|
649
|
+
const query = qs.toString();
|
|
650
|
+
return this.request('GET', `/chat/messages${query ? `?${query}` : ''}`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** Send a message to global chat */
|
|
654
|
+
async sendChatMessage(params: { message: string; replyToId?: number }): Promise<{ message: any }> {
|
|
655
|
+
return this.request('POST', '/chat/message', params);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Edit your own message */
|
|
659
|
+
async editChatMessage(id: number, message: string): Promise<{ message: any }> {
|
|
660
|
+
return this.request('PUT', `/chat/message/${id}`, { message });
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** Delete your own message */
|
|
664
|
+
async deleteChatMessage(id: number): Promise<{ messageId: number }> {
|
|
665
|
+
return this.request('DELETE', `/chat/message/${id}`);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Block a user */
|
|
669
|
+
async blockUser(targetUserId: number): Promise<void> {
|
|
670
|
+
await this.request('POST', `/chat/block/${targetUserId}`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** Unblock a user */
|
|
674
|
+
async unblockUser(targetUserId: number): Promise<void> {
|
|
675
|
+
await this.request('DELETE', `/chat/block/${targetUserId}`);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ── DMs ──
|
|
679
|
+
|
|
680
|
+
/** Get DM conversations inbox */
|
|
681
|
+
async getConversations(limit?: number): Promise<{ conversations: any[] }> {
|
|
682
|
+
const qs = limit ? `?limit=${limit}` : '';
|
|
683
|
+
return this.request('GET', `/dm/conversations${qs}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/** Get conversation history with a specific user */
|
|
687
|
+
async getConversation(walletAddress: string, params?: { limit?: number; beforeId?: number }): Promise<{ messages: any[]; otherUser: any }> {
|
|
688
|
+
const qs = new URLSearchParams();
|
|
689
|
+
if (params?.limit) qs.set('limit', String(params.limit));
|
|
690
|
+
if (params?.beforeId) qs.set('beforeId', String(params.beforeId));
|
|
691
|
+
const query = qs.toString();
|
|
692
|
+
return this.request('GET', `/dm/conversation/${encodeURIComponent(walletAddress)}${query ? `?${query}` : ''}`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Send a direct message */
|
|
696
|
+
async sendDirectMessage(params: { recipientWallet: string; message: string }): Promise<{ message: any }> {
|
|
697
|
+
return this.request('POST', '/dm/send', params);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** Mark all DMs from a user as read */
|
|
701
|
+
async markDMRead(walletAddress: string): Promise<{ markedRead: number }> {
|
|
702
|
+
return this.request('POST', `/dm/read/${encodeURIComponent(walletAddress)}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Get unread DM count */
|
|
706
|
+
async getDMUnreadCount(): Promise<{ unreadCount: number }> {
|
|
707
|
+
return this.request('GET', '/dm/unread');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Delete a DM conversation (soft delete) */
|
|
711
|
+
async deleteConversation(walletAddress: string): Promise<void> {
|
|
712
|
+
await this.request('DELETE', `/dm/conversation/${encodeURIComponent(walletAddress)}`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ── Social ──
|
|
716
|
+
|
|
717
|
+
/** Search users by username */
|
|
718
|
+
async searchUsers(query: string): Promise<{ results: any[] }> {
|
|
719
|
+
const qs = query ? `?q=${encodeURIComponent(query)}` : '';
|
|
720
|
+
return this.request('GET', `/social/search${qs}`);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** Send a friend request */
|
|
724
|
+
async sendFriendRequest(targetUserId: number): Promise<{ requestId: number }> {
|
|
725
|
+
return this.request('POST', `/social/friend-request/${targetUserId}`);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** Get pending friend requests (received) */
|
|
729
|
+
async getPendingFriendRequests(): Promise<{ requests: any[] }> {
|
|
730
|
+
return this.request('GET', '/social/friend-requests');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** Accept a friend request */
|
|
734
|
+
async acceptFriendRequest(requestId: number): Promise<void> {
|
|
735
|
+
await this.request('POST', `/social/request/${requestId}/accept`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Reject a friend request */
|
|
739
|
+
async rejectFriendRequest(requestId: number): Promise<void> {
|
|
740
|
+
await this.request('POST', `/social/request/${requestId}/reject`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** Get friends list */
|
|
744
|
+
async getFriends(): Promise<{ friends: any[] }> {
|
|
745
|
+
return this.request('GET', '/social/friends');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Remove a friend */
|
|
749
|
+
async removeFriend(targetUserId: number): Promise<void> {
|
|
750
|
+
await this.request('DELETE', `/social/friend/${targetUserId}`);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Get blocked users list */
|
|
754
|
+
async getBlockedUsers(): Promise<{ blocked: any[] }> {
|
|
755
|
+
return this.request('GET', '/social/blocked');
|
|
756
|
+
}
|
|
757
|
+
|
|
576
758
|
// ── App Config ──
|
|
577
759
|
|
|
578
760
|
/** Fetch the app's UI customization config (accent color, icon, tagline, environment) */
|
package/src/hooks/index.ts
CHANGED
|
@@ -34,3 +34,9 @@ export { useArcadeCountdown } from './useArcadeCountdown';
|
|
|
34
34
|
export type { ArcadeCountdown } from './useArcadeCountdown';
|
|
35
35
|
export { useArcadeBridge } from './useArcadeBridge';
|
|
36
36
|
export type { UseArcadeBridgeOptions, UseArcadeBridgeResult } from './useArcadeBridge';
|
|
37
|
+
export { useJackpot } from './useJackpot';
|
|
38
|
+
export type { UseJackpotResult } from './useJackpot';
|
|
39
|
+
export { useJackpotHistory } from './useJackpotHistory';
|
|
40
|
+
export type { UseJackpotHistoryResult } from './useJackpotHistory';
|
|
41
|
+
export { useEnterJackpot } from './useEnterJackpot';
|
|
42
|
+
export type { EnterJackpotMutationResult } from './useEnterJackpot';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { useDubs } from '../provider';
|
|
3
|
+
import { signAndSendBase64Transaction } from '../utils/transaction';
|
|
4
|
+
import type { MutationStatus } from '../types';
|
|
5
|
+
|
|
6
|
+
export interface EnterJackpotMutationResult {
|
|
7
|
+
roundId: string;
|
|
8
|
+
amount: string;
|
|
9
|
+
amountSol: number;
|
|
10
|
+
signature: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useEnterJackpot() {
|
|
14
|
+
const { client, wallet, connection } = useDubs();
|
|
15
|
+
const [status, setStatus] = useState<MutationStatus>('idle');
|
|
16
|
+
const [error, setError] = useState<Error | null>(null);
|
|
17
|
+
const [data, setData] = useState<EnterJackpotMutationResult | null>(null);
|
|
18
|
+
|
|
19
|
+
const reset = useCallback(() => {
|
|
20
|
+
setStatus('idle');
|
|
21
|
+
setError(null);
|
|
22
|
+
setData(null);
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const execute = useCallback(async (amountLamports: number): Promise<EnterJackpotMutationResult> => {
|
|
26
|
+
if (!wallet.publicKey) throw new Error('Wallet not connected');
|
|
27
|
+
const walletAddress = wallet.publicKey.toBase58();
|
|
28
|
+
|
|
29
|
+
setStatus('building');
|
|
30
|
+
setError(null);
|
|
31
|
+
setData(null);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// 1. Build unsigned transaction
|
|
35
|
+
console.log('[useEnterJackpot] Step 1: Building transaction...', { walletAddress, amountLamports });
|
|
36
|
+
const buildResult = await client.buildJackpotEnter(walletAddress, amountLamports);
|
|
37
|
+
console.log('[useEnterJackpot] Step 1 done:', { roundId: buildResult.roundId, amount: buildResult.amount });
|
|
38
|
+
|
|
39
|
+
// 2. Sign and send transaction
|
|
40
|
+
setStatus('signing');
|
|
41
|
+
console.log('[useEnterJackpot] Step 2: Signing and sending...');
|
|
42
|
+
const signature = await signAndSendBase64Transaction(
|
|
43
|
+
buildResult.transaction,
|
|
44
|
+
wallet,
|
|
45
|
+
connection,
|
|
46
|
+
);
|
|
47
|
+
console.log('[useEnterJackpot] Step 2 done. Signature:', signature);
|
|
48
|
+
|
|
49
|
+
// 3. Confirm entry with backend (creates developer attribution)
|
|
50
|
+
setStatus('confirming');
|
|
51
|
+
console.log('[useEnterJackpot] Step 3: Confirming with backend...');
|
|
52
|
+
await client.confirmJackpotEnter({
|
|
53
|
+
playerAddress: walletAddress,
|
|
54
|
+
roundId: buildResult.roundId,
|
|
55
|
+
amount: amountLamports,
|
|
56
|
+
signature,
|
|
57
|
+
});
|
|
58
|
+
console.log('[useEnterJackpot] Step 3 done. Entry confirmed + attributed.');
|
|
59
|
+
|
|
60
|
+
const result: EnterJackpotMutationResult = {
|
|
61
|
+
roundId: buildResult.roundId,
|
|
62
|
+
amount: buildResult.amount,
|
|
63
|
+
amountSol: buildResult.amountSol,
|
|
64
|
+
signature,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
setData(result);
|
|
68
|
+
setStatus('success');
|
|
69
|
+
return result;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error('[useEnterJackpot] FAILED:', err);
|
|
72
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
73
|
+
setError(e);
|
|
74
|
+
setStatus('error');
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
}, [client, wallet, connection]);
|
|
78
|
+
|
|
79
|
+
return { execute, status, error, data, reset };
|
|
80
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useDubs } from '../provider';
|
|
3
|
+
import type { JackpotRound, JackpotLastWinner } from '../types';
|
|
4
|
+
|
|
5
|
+
export interface UseJackpotResult {
|
|
6
|
+
round: JackpotRound | null;
|
|
7
|
+
lastWinner: JackpotLastWinner | null;
|
|
8
|
+
loading: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
refetch: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useJackpot(): UseJackpotResult {
|
|
14
|
+
const { client } = useDubs();
|
|
15
|
+
const [round, setRound] = useState<JackpotRound | null>(null);
|
|
16
|
+
const [lastWinner, setLastWinner] = useState<JackpotLastWinner | null>(null);
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
const [error, setError] = useState<Error | null>(null);
|
|
19
|
+
|
|
20
|
+
const fetch = useCallback(async () => {
|
|
21
|
+
setLoading(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
const result = await client.getJackpotCurrent();
|
|
25
|
+
setRound(result.round);
|
|
26
|
+
setLastWinner(result.lastWinner);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
29
|
+
} finally {
|
|
30
|
+
setLoading(false);
|
|
31
|
+
}
|
|
32
|
+
}, [client]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => { fetch(); }, [fetch]);
|
|
35
|
+
|
|
36
|
+
return { round, lastWinner, loading, error, refetch: fetch };
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useDubs } from '../provider';
|
|
3
|
+
import type { JackpotRoundResult } from '../types';
|
|
4
|
+
|
|
5
|
+
export interface UseJackpotHistoryResult {
|
|
6
|
+
rounds: JackpotRoundResult[];
|
|
7
|
+
loading: boolean;
|
|
8
|
+
error: Error | null;
|
|
9
|
+
refetch: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useJackpotHistory(limit?: number): UseJackpotHistoryResult {
|
|
13
|
+
const { client } = useDubs();
|
|
14
|
+
const [rounds, setRounds] = useState<JackpotRoundResult[]>([]);
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [error, setError] = useState<Error | null>(null);
|
|
17
|
+
|
|
18
|
+
const fetch = useCallback(async () => {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
try {
|
|
22
|
+
const result = await client.getJackpotHistory(limit);
|
|
23
|
+
setRounds(result);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
26
|
+
} finally {
|
|
27
|
+
setLoading(false);
|
|
28
|
+
}
|
|
29
|
+
}, [client, limit]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => { fetch(); }, [fetch]);
|
|
32
|
+
|
|
33
|
+
return { rounds, loading, error, refetch: fetch };
|
|
34
|
+
}
|