@dubsdotapp/expo 0.5.23 → 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/dist/index.d.mts +55 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +97 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/chat/hooks.ts +62 -0
- package/src/chat/index.ts +1 -0
- package/src/chat/provider.tsx +38 -4
- package/src/client.ts +28 -0
- package/src/index.ts +3 -0
- package/src/types.ts +20 -0
package/package.json
CHANGED
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
package/src/chat/provider.tsx
CHANGED
|
@@ -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
|
|
|
@@ -197,14 +221,21 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
|
|
|
197
221
|
chatSocket.connect({ host, token });
|
|
198
222
|
}
|
|
199
223
|
}
|
|
200
|
-
// Refresh data to catch up on
|
|
224
|
+
// Refresh all key data to catch up on socket events that fired
|
|
225
|
+
// while the app was backgrounded (declines, accepts, cancels,
|
|
226
|
+
// new requests, friend removals, etc.)
|
|
201
227
|
refreshMessages();
|
|
228
|
+
refreshConversations().catch(() => {});
|
|
229
|
+
refreshFriends().catch(() => {});
|
|
230
|
+
refreshPendingRequests().catch(() => {});
|
|
231
|
+
refreshSentFriendRequests().catch(() => {});
|
|
232
|
+
refreshWhatsNew().catch(() => {});
|
|
202
233
|
}
|
|
203
234
|
};
|
|
204
235
|
|
|
205
236
|
const sub = AppState.addEventListener('change', handleAppState);
|
|
206
237
|
return () => sub.remove();
|
|
207
|
-
}, [client, refreshMessages, refreshConversations]);
|
|
238
|
+
}, [client, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);
|
|
208
239
|
|
|
209
240
|
const value = useMemo<ChatContextValue>(
|
|
210
241
|
() => ({
|
|
@@ -218,13 +249,16 @@ export function ChatProvider({ children, autoConnect = true }: ChatProviderProps
|
|
|
218
249
|
friends,
|
|
219
250
|
pendingRequests,
|
|
220
251
|
sentFriendRequests,
|
|
252
|
+
whatsNewPosts,
|
|
253
|
+
whatsNewUnreadCount,
|
|
221
254
|
refreshMessages,
|
|
222
255
|
refreshConversations,
|
|
223
256
|
refreshFriends,
|
|
224
257
|
refreshPendingRequests,
|
|
225
258
|
refreshSentFriendRequests,
|
|
259
|
+
refreshWhatsNew,
|
|
226
260
|
}),
|
|
227
|
-
[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],
|
|
228
262
|
);
|
|
229
263
|
|
|
230
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
|
+
}
|