1- import { MonkeyMail } from "@monkeytype/schemas/users" ;
1+ import { AllRewards , MonkeyMail } from "@monkeytype/schemas/users" ;
22import { queryCollectionOptions } from "@tanstack/query-db-collection" ;
33import {
44 createCollection ,
5+ createPacedMutations ,
56 eq ,
67 MutationFnParams ,
78 not ,
@@ -13,9 +14,17 @@ import { queryClient } from "../queries";
1314import { baseKey } from "../queries/utils/keys" ;
1415import { isAuthenticated } from "../states/core" ;
1516import { flushDebounceStrategy } from "./utils/flushDebounceStrategy" ;
16- import { showErrorNotification } from "../states/notifications" ;
17+ import {
18+ showErrorNotification ,
19+ showSuccessNotification ,
20+ } from "../states/notifications" ;
21+ import * as BadgeController from "../controllers/badge-controller" ;
22+ import { addBadge , addXp } from "../db" ;
1723
18- export const flushStrategy = flushDebounceStrategy ( { maxWait : 1000 * 60 * 5 } ) ;
24+ const flushStrategy = flushDebounceStrategy ( { maxWait : 1000 * 60 * 5 } ) ;
25+ export function applyPendingInboxActions ( ) : void {
26+ flushStrategy . flush ( ) ;
27+ }
1928
2029const queryKeys = {
2130 root : ( ) => [ ...baseKey ( "inbox" , { isUserSpecific : true } ) ] ,
@@ -24,11 +33,10 @@ const queryKeys = {
2433const [ maxMailboxSize , setMaxMailboxSize ] = createSignal ( 0 ) ;
2534
2635export { maxMailboxSize } ;
27-
2836export type InboxItem = Omit < MonkeyMail , "read" > & {
2937 status : "unclaimed" | "unread" | "read" | "deleted" ;
3038} ;
31- export const inboxCollection = createCollection (
39+ const inboxCollection = createCollection (
3240 queryCollectionOptions ( {
3341 staleTime : 1000 * 60 * 5 ,
3442 queryKey : queryKeys . root ( ) ,
@@ -58,7 +66,81 @@ export const inboxCollection = createCollection(
5866 } ) ,
5967) ;
6068
61- export async function flushPendingChanges ( {
69+ export async function refetchInboxCollection ( ) : Promise < void > {
70+ await inboxCollection . utils . refetch ( ) ;
71+ }
72+
73+ const inboxItemIdsToClaim : string [ ] = [ ] ;
74+ export const mutateInboxItem = createPacedMutations <
75+ Pick < InboxItem , "id" | "status" > ,
76+ InboxItem
77+ > ( {
78+ onMutate : ( { id, status } ) => {
79+ inboxCollection . update ( id , ( old ) => {
80+ if ( old . status === "unclaimed" ) {
81+ inboxItemIdsToClaim . push ( old . id ) ;
82+ }
83+ old . status = status ;
84+ } ) ;
85+ } ,
86+ mutationFn : async ( changes ) => {
87+ await flushPendingChanges ( changes ) ;
88+
89+ const allRewards : AllRewards [ ] = changes . transaction . mutations
90+ . map ( ( it ) => it . modified )
91+ . filter ( ( it ) => inboxItemIdsToClaim . includes ( it . id ) )
92+ . flatMap ( ( it ) => it . rewards ) ;
93+ inboxItemIdsToClaim . length = 0 ;
94+ claimRewards ( allRewards ) ;
95+ } ,
96+ strategy : flushStrategy . strategy ,
97+ } ) ;
98+
99+ function claimRewards ( pendingRewards : AllRewards [ ] ) : void {
100+ if ( pendingRewards . length === 0 ) return ;
101+
102+ let totalXp = 0 ;
103+ const badgeNames : string [ ] = [ ] ;
104+ for ( const reward of pendingRewards ) {
105+ if ( reward . type === "xp" ) {
106+ totalXp += reward . item ;
107+ } else if ( reward . type === "badge" ) {
108+ const badge = BadgeController . getById ( reward . item . id ) ;
109+ if ( badge ) {
110+ badgeNames . push ( badge . name ) ;
111+ addBadge ( reward . item ) ;
112+ }
113+ }
114+ }
115+ if ( totalXp > 0 ) {
116+ addXp ( totalXp ) ;
117+ }
118+
119+ if ( badgeNames . length > 0 ) {
120+ showSuccessNotification (
121+ `New badge${ badgeNames . length > 1 ? "s" : "" } unlocked: ${ badgeNames . join ( ", " ) } ` ,
122+ { durationMs : 5000 , customTitle : "Reward" , customIcon : "gift" } ,
123+ ) ;
124+ }
125+ }
126+
127+ export function claimAllInboxItems ( ) : void {
128+ inboxCollection . forEach ( ( it ) => {
129+ if ( it . status === "unclaimed" ) {
130+ mutateInboxItem ( { id : it . id , status : "read" } ) ;
131+ }
132+ } ) ;
133+ }
134+
135+ export function deleteAllInboxItems ( ) : void {
136+ inboxCollection . forEach ( ( it ) => {
137+ if ( it . status === "unread" || it . status === "read" ) {
138+ mutateInboxItem ( { id : it . id , status : "deleted" } ) ;
139+ }
140+ } ) ;
141+ }
142+
143+ async function flushPendingChanges ( {
62144 transaction,
63145} : MutationFnParams < InboxItem > ) : Promise < unknown > {
64146 const updatedStatus = Object . groupBy (
@@ -84,6 +166,9 @@ export async function flushPendingChanges({
84166 updatedStatus . deleted ?. forEach ( ( deleted ) =>
85167 inboxCollection . utils . writeDelete ( deleted . id ) ,
86168 ) ;
169+ updatedStatus . read ?. forEach ( ( read ) => {
170+ inboxCollection . utils . writeUpdate ( read ) ;
171+ } ) ;
87172 } ) ;
88173
89174 return { refetch : false } ;
0 commit comments