@dubsdotapp/expo 0.5.24 → 0.5.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubsdotapp/expo",
3
- "version": "0.5.24",
3
+ "version": "0.5.26",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/chat/hooks.ts CHANGED
@@ -12,6 +12,7 @@ import type {
12
12
  SentFriendRequest,
13
13
  SendMessageParams,
14
14
  } from './types';
15
+ import type { WhatsNewPost } from '../types';
15
16
 
16
17
  // ── Connection ──
17
18
 
@@ -362,3 +363,64 @@ export function useRespondToFriendRequest(): {
362
363
 
363
364
  return { accept, reject, loading };
364
365
  }
366
+
367
+ // ── What's New ────────────────────────────────────────────────────────────
368
+
369
+ /**
370
+ * Read-only subscription to the calling app's What's New posts.
371
+ *
372
+ * Auto-refreshes via the chat socket when a new `whats_new` notification
373
+ * arrives, so the unread badge / list update without manual refetch.
374
+ */
375
+ export function useWhatsNew(): {
376
+ posts: WhatsNewPost[];
377
+ unreadCount: number;
378
+ loading: boolean;
379
+ refetch: () => Promise<void>;
380
+ markRead: (postIds: number[]) => Promise<void>;
381
+ markAllRead: () => Promise<void>;
382
+ } {
383
+ const { client } = useDubs();
384
+ const { whatsNewPosts, whatsNewUnreadCount, refreshWhatsNew } = useChatContext();
385
+ const [loading, setLoading] = useState(false);
386
+
387
+ const refetch = useCallback(async () => {
388
+ setLoading(true);
389
+ try {
390
+ await refreshWhatsNew();
391
+ } finally {
392
+ setLoading(false);
393
+ }
394
+ }, [refreshWhatsNew]);
395
+
396
+ const markRead = useCallback(
397
+ async (postIds: number[]) => {
398
+ if (postIds.length === 0) return;
399
+ try {
400
+ await client.markWhatsNewRead(postIds);
401
+ await refreshWhatsNew();
402
+ } catch (err) {
403
+ console.error('[Dubs:useWhatsNew] markRead error:', err);
404
+ }
405
+ },
406
+ [client, refreshWhatsNew],
407
+ );
408
+
409
+ const markAllRead = useCallback(async () => {
410
+ try {
411
+ await client.markAllWhatsNewRead();
412
+ await refreshWhatsNew();
413
+ } catch (err) {
414
+ console.error('[Dubs:useWhatsNew] markAllRead error:', err);
415
+ }
416
+ }, [client, refreshWhatsNew]);
417
+
418
+ return {
419
+ posts: whatsNewPosts,
420
+ unreadCount: whatsNewUnreadCount,
421
+ loading,
422
+ refetch,
423
+ markRead,
424
+ markAllRead,
425
+ };
426
+ }
package/src/chat/index.ts CHANGED
@@ -22,6 +22,7 @@ export {
22
22
  useSendFriendRequest,
23
23
  useRespondToFriendRequest,
24
24
  useCancelFriendRequest,
25
+ useWhatsNew,
25
26
  } from './hooks';
26
27
 
27
28
  // Types
@@ -13,6 +13,7 @@ import type {
13
13
  DirectMessage,
14
14
  ChatNotification,
15
15
  } from './types';
16
+ import type { WhatsNewPost } from '../types';
16
17
 
17
18
  export interface ChatContextValue {
18
19
  /** The underlying socket manager */
@@ -45,6 +46,12 @@ export interface ChatContextValue {
45
46
  refreshPendingRequests: () => Promise<void>;
46
47
  /** Reload pending friend requests (sent) from REST */
47
48
  refreshSentFriendRequests: () => Promise<void>;
49
+ /** What's New posts for this app */
50
+ whatsNewPosts: WhatsNewPost[];
51
+ /** Number of unread What's New posts */
52
+ whatsNewUnreadCount: number;
53
+ /** Reload What's New posts + unread count from REST */
54
+ refreshWhatsNew: () => Promise<void>;
48
55
  }
49
56
 
50
57
  const ChatContext = createContext<ChatContextValue | null>(null);
@@ -72,6 +79,8 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
72
79
  const [friends, setFriends] = useState<FriendUser[]>([]);
73
80
  const [pendingRequests, setPendingRequests] = useState<FriendRequest[]>([]);
74
81
  const [sentFriendRequests, setSentFriendRequests] = useState<SentFriendRequest[]>([]);
82
+ const [whatsNewPosts, setWhatsNewPosts] = useState<WhatsNewPost[]>([]);
83
+ const [whatsNewUnreadCount, setWhatsNewUnreadCount] = useState(0);
75
84
 
76
85
  // ── REST loaders ──
77
86
 
@@ -121,6 +130,18 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
121
130
  }
122
131
  }, [client]);
123
132
 
133
+ const refreshWhatsNew = useCallback(async () => {
134
+ try {
135
+ const res = await client.getWhatsNewPosts();
136
+ setWhatsNewPosts(res.posts);
137
+ // Count unread from the same payload (avoids a second roundtrip)
138
+ const unread = res.posts.filter((p: WhatsNewPost) => !p.is_read).length;
139
+ setWhatsNewUnreadCount(unread);
140
+ } catch (_) {
141
+ // Server may not yet expose /whats-new endpoints — silent fail
142
+ }
143
+ }, [client]);
144
+
124
145
  // ── Socket setup ──
125
146
 
126
147
  useEffect(() => {
@@ -160,13 +181,25 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
160
181
  refreshSentFriendRequests();
161
182
  }
162
183
  if (n.type === 'friend_request_declined') refreshSentFriendRequests();
184
+ // What's New posts: a developer published a new one for this app
185
+ if (n.type === 'whats_new') refreshWhatsNew();
163
186
  },
187
+ // These events now fan out to BOTH parties (server emits to actor's
188
+ // own room as well), so each handler refreshes every list that could
189
+ // be affected — works whether this client is the actor or the target.
164
190
  onFriendRequestAccepted: () => {
165
191
  refreshFriends();
192
+ refreshPendingRequests();
193
+ refreshSentFriendRequests();
194
+ },
195
+ onFriendRequestDeclined: () => {
196
+ refreshPendingRequests();
197
+ refreshSentFriendRequests();
198
+ },
199
+ onFriendRequestCancelled: () => {
200
+ refreshPendingRequests();
166
201
  refreshSentFriendRequests();
167
202
  },
168
- onFriendRequestDeclined: () => refreshSentFriendRequests(),
169
- onFriendRequestCancelled: () => refreshPendingRequests(),
170
203
  onFriendRemoved: () => refreshFriends(),
171
204
  });
172
205
 
@@ -177,11 +210,12 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
177
210
  refreshFriends().catch(() => {});
178
211
  refreshPendingRequests().catch(() => {});
179
212
  refreshSentFriendRequests().catch(() => {});
213
+ refreshWhatsNew().catch(() => {});
180
214
 
181
215
  return () => {
182
216
  chatSocket.disconnect();
183
217
  };
184
- }, [client, autoConnect, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests]);
218
+ }, [client, autoConnect, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);
185
219
 
186
220
  // ── Reconnect on app foreground ──
187
221
 
@@ -205,12 +239,13 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
205
239
  refreshFriends().catch(() => {});
206
240
  refreshPendingRequests().catch(() => {});
207
241
  refreshSentFriendRequests().catch(() => {});
242
+ refreshWhatsNew().catch(() => {});
208
243
  }
209
244
  };
210
245
 
211
246
  const sub = AppState.addEventListener('change', handleAppState);
212
247
  return () => sub.remove();
213
- }, [client, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests]);
248
+ }, [client, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);
214
249
 
215
250
  const value = useMemo<ChatContextValue>(
216
251
  () => ({
@@ -224,13 +259,16 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
224
259
  friends,
225
260
  pendingRequests,
226
261
  sentFriendRequests,
262
+ whatsNewPosts,
263
+ whatsNewUnreadCount,
227
264
  refreshMessages,
228
265
  refreshConversations,
229
266
  refreshFriends,
230
267
  refreshPendingRequests,
231
268
  refreshSentFriendRequests,
269
+ refreshWhatsNew,
232
270
  }),
233
- [status, messages, onlineUsers, onlineCount, unreadCount, conversations, friends, pendingRequests, sentFriendRequests, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests],
271
+ [status, messages, onlineUsers, onlineCount, unreadCount, conversations, friends, pendingRequests, sentFriendRequests, whatsNewPosts, whatsNewUnreadCount, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew],
234
272
  );
235
273
 
236
274
  return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
package/src/client.ts CHANGED
@@ -35,6 +35,7 @@ import type {
35
35
  CheckUsernameResult,
36
36
  LiveScore,
37
37
  UiConfig,
38
+ WhatsNewPost,
38
39
  UFCEvent,
39
40
  UFCFighterDetail,
40
41
  ArcadePool,
@@ -765,6 +766,33 @@ export class DubsClient {
765
766
  return this.request('GET', '/social/blocked');
766
767
  }
767
768
 
769
+ // ── What's New ──
770
+
771
+ /** List published What's New posts for this app, with is_read for the current user. */
772
+ async getWhatsNewPosts(): Promise<{ posts: WhatsNewPost[] }> {
773
+ return this.request('GET', '/whats-new/posts');
774
+ }
775
+
776
+ /** Fetch a single What's New post by id. */
777
+ async getWhatsNewPost(postId: number): Promise<{ post: WhatsNewPost }> {
778
+ return this.request('GET', `/whats-new/posts/${postId}`);
779
+ }
780
+
781
+ /** Number of unread What's New posts for the current user (this app). */
782
+ async getWhatsNewUnreadCount(): Promise<{ count: number }> {
783
+ return this.request('GET', '/whats-new/unread-count');
784
+ }
785
+
786
+ /** Mark specific posts as read. */
787
+ async markWhatsNewRead(postIds: number[]): Promise<void> {
788
+ await this.request('POST', '/whats-new/mark-read', { postIds });
789
+ }
790
+
791
+ /** Mark every unread post as read. */
792
+ async markAllWhatsNewRead(): Promise<{ updated: number }> {
793
+ return this.request('POST', '/whats-new/mark-all-read');
794
+ }
795
+
768
796
  // ── App Config ──
769
797
 
770
798
  /** Fetch the app's UI customization config (accent color, icon, tagline, environment) */
package/src/index.ts CHANGED
@@ -62,6 +62,8 @@ export type {
62
62
  LiveScore,
63
63
  LiveScoreCompetitor,
64
64
  UiConfig,
65
+ WhatsNewPost,
66
+ WhatsNewCategory,
65
67
  UFCFighter,
66
68
  UFCFighterDetail,
67
69
  UFCData,
@@ -191,6 +193,7 @@ export {
191
193
  useSendFriendRequest,
192
194
  useRespondToFriendRequest,
193
195
  useCancelFriendRequest,
196
+ useWhatsNew,
194
197
  } from './chat';
195
198
  export type {
196
199
  ChatProviderProps,
package/src/types.ts CHANGED
@@ -660,3 +660,23 @@ export interface UiConfig {
660
660
  ios: boolean;
661
661
  };
662
662
  }
663
+
664
+ // ── What's New (per-app feature announcements) ──
665
+
666
+ export type WhatsNewCategory = 'feature' | 'improvement' | 'bugfix' | 'announcement';
667
+
668
+ export interface WhatsNewPost {
669
+ id: number;
670
+ title: string;
671
+ content: string;
672
+ gif_url: string | null;
673
+ category: WhatsNewCategory;
674
+ version: string | null;
675
+ is_pinned: boolean;
676
+ is_published: boolean;
677
+ /** Present when the post is fetched in an authenticated context. */
678
+ is_read?: boolean;
679
+ created_by: string;
680
+ created_at: string;
681
+ updated_at: string;
682
+ }