@dubsdotapp/expo 0.5.24 → 0.5.25

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.25",
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,6 +181,8 @@ 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
  },
164
187
  onFriendRequestAccepted: () => {
165
188
  refreshFriends();
@@ -177,11 +200,12 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
177
200
  refreshFriends().catch(() => {});
178
201
  refreshPendingRequests().catch(() => {});
179
202
  refreshSentFriendRequests().catch(() => {});
203
+ refreshWhatsNew().catch(() => {});
180
204
 
181
205
  return () => {
182
206
  chatSocket.disconnect();
183
207
  };
184
- }, [client, autoConnect, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests]);
208
+ }, [client, autoConnect, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);
185
209
 
186
210
  // ── Reconnect on app foreground ──
187
211
 
@@ -205,12 +229,13 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
205
229
  refreshFriends().catch(() => {});
206
230
  refreshPendingRequests().catch(() => {});
207
231
  refreshSentFriendRequests().catch(() => {});
232
+ refreshWhatsNew().catch(() => {});
208
233
  }
209
234
  };
210
235
 
211
236
  const sub = AppState.addEventListener('change', handleAppState);
212
237
  return () => sub.remove();
213
- }, [client, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests]);
238
+ }, [client, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);
214
239
 
215
240
  const value = useMemo<ChatContextValue>(
216
241
  () => ({
@@ -224,13 +249,16 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
224
249
  friends,
225
250
  pendingRequests,
226
251
  sentFriendRequests,
252
+ whatsNewPosts,
253
+ whatsNewUnreadCount,
227
254
  refreshMessages,
228
255
  refreshConversations,
229
256
  refreshFriends,
230
257
  refreshPendingRequests,
231
258
  refreshSentFriendRequests,
259
+ refreshWhatsNew,
232
260
  }),
233
- [status, messages, onlineUsers, onlineCount, unreadCount, conversations, friends, pendingRequests, sentFriendRequests, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests],
261
+ [status, messages, onlineUsers, onlineCount, unreadCount, conversations, friends, pendingRequests, sentFriendRequests, whatsNewPosts, whatsNewUnreadCount, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew],
234
262
  );
235
263
 
236
264
  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
+ }