@growsober/sdk 1.0.0
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/README.md +276 -0
- package/dist/__tests__/e2e.test.d.ts +7 -0
- package/dist/__tests__/e2e.test.js +472 -0
- package/dist/api/client.d.ts +11 -0
- package/dist/api/client.js +61 -0
- package/dist/api/mutations/admin.d.ts +167 -0
- package/dist/api/mutations/admin.js +326 -0
- package/dist/api/mutations/ambassadors.d.ts +52 -0
- package/dist/api/mutations/ambassadors.js +148 -0
- package/dist/api/mutations/auth.d.ts +267 -0
- package/dist/api/mutations/auth.js +332 -0
- package/dist/api/mutations/bookings.d.ts +59 -0
- package/dist/api/mutations/bookings.js +143 -0
- package/dist/api/mutations/event-chat.d.ts +35 -0
- package/dist/api/mutations/event-chat.js +147 -0
- package/dist/api/mutations/events.d.ts +87 -0
- package/dist/api/mutations/events.js +205 -0
- package/dist/api/mutations/grow90.d.ts +36 -0
- package/dist/api/mutations/grow90.js +132 -0
- package/dist/api/mutations/hubs.d.ts +111 -0
- package/dist/api/mutations/hubs.js +240 -0
- package/dist/api/mutations/index.d.ts +22 -0
- package/dist/api/mutations/index.js +39 -0
- package/dist/api/mutations/jack.d.ts +61 -0
- package/dist/api/mutations/jack.js +104 -0
- package/dist/api/mutations/library.d.ts +67 -0
- package/dist/api/mutations/library.js +168 -0
- package/dist/api/mutations/map.d.ts +153 -0
- package/dist/api/mutations/map.js +181 -0
- package/dist/api/mutations/matching.d.ts +130 -0
- package/dist/api/mutations/matching.js +204 -0
- package/dist/api/mutations/notifications.d.ts +63 -0
- package/dist/api/mutations/notifications.js +106 -0
- package/dist/api/mutations/offers.d.ts +26 -0
- package/dist/api/mutations/offers.js +47 -0
- package/dist/api/mutations/subscriptions.d.ts +127 -0
- package/dist/api/mutations/subscriptions.js +140 -0
- package/dist/api/mutations/support.d.ts +165 -0
- package/dist/api/mutations/support.js +307 -0
- package/dist/api/mutations/users.d.ts +211 -0
- package/dist/api/mutations/users.js +261 -0
- package/dist/api/queries/admin.d.ts +257 -0
- package/dist/api/queries/admin.js +320 -0
- package/dist/api/queries/ambassadors.d.ts +53 -0
- package/dist/api/queries/ambassadors.js +98 -0
- package/dist/api/queries/auth.d.ts +16 -0
- package/dist/api/queries/auth.js +25 -0
- package/dist/api/queries/bookings.d.ts +91 -0
- package/dist/api/queries/bookings.js +102 -0
- package/dist/api/queries/businesses.d.ts +212 -0
- package/dist/api/queries/businesses.js +154 -0
- package/dist/api/queries/event-chat.d.ts +19 -0
- package/dist/api/queries/event-chat.js +75 -0
- package/dist/api/queries/events.d.ts +322 -0
- package/dist/api/queries/events.js +221 -0
- package/dist/api/queries/grow90.d.ts +26 -0
- package/dist/api/queries/grow90.js +85 -0
- package/dist/api/queries/hubs.d.ts +165 -0
- package/dist/api/queries/hubs.js +143 -0
- package/dist/api/queries/index.d.ts +23 -0
- package/dist/api/queries/index.js +40 -0
- package/dist/api/queries/jack.d.ts +63 -0
- package/dist/api/queries/jack.js +92 -0
- package/dist/api/queries/library.d.ts +132 -0
- package/dist/api/queries/library.js +120 -0
- package/dist/api/queries/map.d.ts +216 -0
- package/dist/api/queries/map.js +278 -0
- package/dist/api/queries/matching.d.ts +136 -0
- package/dist/api/queries/matching.js +161 -0
- package/dist/api/queries/notifications.d.ts +78 -0
- package/dist/api/queries/notifications.js +88 -0
- package/dist/api/queries/offers.d.ts +91 -0
- package/dist/api/queries/offers.js +103 -0
- package/dist/api/queries/subscriptions.d.ts +56 -0
- package/dist/api/queries/subscriptions.js +73 -0
- package/dist/api/queries/support.d.ts +106 -0
- package/dist/api/queries/support.js +202 -0
- package/dist/api/queries/users.d.ts +293 -0
- package/dist/api/queries/users.js +370 -0
- package/dist/api/types.d.ts +464 -0
- package/dist/api/types.js +9 -0
- package/dist/hooks/useAuth.d.ts +5 -0
- package/dist/hooks/useAuth.js +39 -0
- package/dist/hooks/useUser.d.ts +43 -0
- package/dist/hooks/useUser.js +44 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +67 -0
- package/package.json +62 -0
- package/src/__tests__/e2e.test.ts +502 -0
- package/src/api/client.ts +71 -0
- package/src/api/mutations/admin.ts +531 -0
- package/src/api/mutations/ambassadors.ts +185 -0
- package/src/api/mutations/auth.ts +350 -0
- package/src/api/mutations/bookings.ts +190 -0
- package/src/api/mutations/event-chat.ts +177 -0
- package/src/api/mutations/events.ts +273 -0
- package/src/api/mutations/grow90.ts +169 -0
- package/src/api/mutations/hubs.ts +385 -0
- package/src/api/mutations/index.ts +23 -0
- package/src/api/mutations/jack.ts +130 -0
- package/src/api/mutations/library.ts +212 -0
- package/src/api/mutations/map.ts +230 -0
- package/src/api/mutations/matching.ts +271 -0
- package/src/api/mutations/notifications.ts +114 -0
- package/src/api/mutations/offers.ts +73 -0
- package/src/api/mutations/subscriptions.ts +162 -0
- package/src/api/mutations/support.ts +390 -0
- package/src/api/mutations/users.ts +271 -0
- package/src/api/queries/admin.ts +480 -0
- package/src/api/queries/ambassadors.ts +139 -0
- package/src/api/queries/auth.ts +24 -0
- package/src/api/queries/bookings.ts +135 -0
- package/src/api/queries/businesses.ts +203 -0
- package/src/api/queries/event-chat.ts +78 -0
- package/src/api/queries/events.ts +272 -0
- package/src/api/queries/grow90.ts +98 -0
- package/src/api/queries/hubs.ts +211 -0
- package/src/api/queries/index.ts +24 -0
- package/src/api/queries/jack.ts +127 -0
- package/src/api/queries/library.ts +166 -0
- package/src/api/queries/map.ts +331 -0
- package/src/api/queries/matching.ts +238 -0
- package/src/api/queries/notifications.ts +103 -0
- package/src/api/queries/offers.ts +136 -0
- package/src/api/queries/subscriptions.ts +91 -0
- package/src/api/queries/support.ts +235 -0
- package/src/api/queries/users.ts +393 -0
- package/src/api/types.ts +596 -0
- package/src/index.ts +57 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useMutation,
|
|
3
|
+
useQueryClient,
|
|
4
|
+
UseMutationOptions,
|
|
5
|
+
UseMutationResult,
|
|
6
|
+
} from '@tanstack/react-query';
|
|
7
|
+
import { getApiClient } from '../client';
|
|
8
|
+
import type {
|
|
9
|
+
DailyCheckInResponse,
|
|
10
|
+
CreateCheckInRequest,
|
|
11
|
+
UpdateCheckInRequest,
|
|
12
|
+
MoodLogResponse,
|
|
13
|
+
CreateMoodLogRequest,
|
|
14
|
+
WinResponse,
|
|
15
|
+
CreateWinRequest,
|
|
16
|
+
HabitResponse,
|
|
17
|
+
CreateHabitRequest,
|
|
18
|
+
UpdateHabitRequest,
|
|
19
|
+
ReflectionResponse,
|
|
20
|
+
CreateReflectionRequest,
|
|
21
|
+
} from '../types';
|
|
22
|
+
import { supportKeys } from '../queries/support';
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// MUTATION HOOKS
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a daily check-in
|
|
30
|
+
*
|
|
31
|
+
* @param options - TanStack Query mutation options
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* const { mutate, isPending } = useCreateCheckIn();
|
|
36
|
+
*
|
|
37
|
+
* mutate({
|
|
38
|
+
* mood: 8,
|
|
39
|
+
* notes: 'Feeling great today!',
|
|
40
|
+
* triggers: ['stress'],
|
|
41
|
+
* gratitude: ['My support group']
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function useCreateCheckIn(
|
|
46
|
+
options?: Omit<
|
|
47
|
+
UseMutationOptions<DailyCheckInResponse, Error, CreateCheckInRequest>,
|
|
48
|
+
'mutationFn'
|
|
49
|
+
>
|
|
50
|
+
): UseMutationResult<DailyCheckInResponse, Error, CreateCheckInRequest> {
|
|
51
|
+
const queryClient = useQueryClient();
|
|
52
|
+
|
|
53
|
+
return useMutation({
|
|
54
|
+
mutationFn: async (data: CreateCheckInRequest): Promise<DailyCheckInResponse> => {
|
|
55
|
+
const client = getApiClient();
|
|
56
|
+
const response = await client.post('/api/v1/support/check-ins', data);
|
|
57
|
+
return response.data;
|
|
58
|
+
},
|
|
59
|
+
onSuccess: (newCheckIn, variables, context) => {
|
|
60
|
+
// Invalidate check-ins list and today's check-in
|
|
61
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.checkIns() });
|
|
62
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.checkInToday() });
|
|
63
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.checkInStreak() });
|
|
64
|
+
|
|
65
|
+
// Call user's onSuccess if provided
|
|
66
|
+
},
|
|
67
|
+
...options,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Update a daily check-in
|
|
73
|
+
*
|
|
74
|
+
* @param options - TanStack Query mutation options
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* const { mutate, isPending } = useUpdateCheckIn();
|
|
79
|
+
*
|
|
80
|
+
* mutate({
|
|
81
|
+
* id: 'checkin-123',
|
|
82
|
+
* data: {
|
|
83
|
+
* mood: 4,
|
|
84
|
+
* notes: 'Updated notes',
|
|
85
|
+
* }
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function useUpdateCheckIn(
|
|
90
|
+
options?: Omit<
|
|
91
|
+
UseMutationOptions<DailyCheckInResponse, Error, { id: string; data: UpdateCheckInRequest }>,
|
|
92
|
+
'mutationFn'
|
|
93
|
+
>
|
|
94
|
+
): UseMutationResult<DailyCheckInResponse, Error, { id: string; data: UpdateCheckInRequest }> {
|
|
95
|
+
const queryClient = useQueryClient();
|
|
96
|
+
|
|
97
|
+
return useMutation({
|
|
98
|
+
mutationFn: async ({ id, data }: { id: string; data: UpdateCheckInRequest }): Promise<DailyCheckInResponse> => {
|
|
99
|
+
const client = getApiClient();
|
|
100
|
+
const response = await client.put(`/api/v1/support/check-ins/${id}`, data);
|
|
101
|
+
return response.data;
|
|
102
|
+
},
|
|
103
|
+
onSuccess: (updatedCheckIn, variables, context) => {
|
|
104
|
+
// Invalidate check-ins list and today's check-in
|
|
105
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.checkIns() });
|
|
106
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.checkInToday() });
|
|
107
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.checkInStreak() });
|
|
108
|
+
},
|
|
109
|
+
...options,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create a mood log
|
|
115
|
+
*
|
|
116
|
+
* @param options - TanStack Query mutation options
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const { mutate, isPending } = useCreateMoodLog();
|
|
121
|
+
*
|
|
122
|
+
* mutate({
|
|
123
|
+
* mood: 7,
|
|
124
|
+
* notes: 'Feeling optimistic',
|
|
125
|
+
* activities: ['exercise', 'meditation']
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function useCreateMoodLog(
|
|
130
|
+
options?: Omit<
|
|
131
|
+
UseMutationOptions<MoodLogResponse, Error, CreateMoodLogRequest>,
|
|
132
|
+
'mutationFn'
|
|
133
|
+
>
|
|
134
|
+
): UseMutationResult<MoodLogResponse, Error, CreateMoodLogRequest> {
|
|
135
|
+
const queryClient = useQueryClient();
|
|
136
|
+
|
|
137
|
+
return useMutation({
|
|
138
|
+
mutationFn: async (data: CreateMoodLogRequest): Promise<MoodLogResponse> => {
|
|
139
|
+
const client = getApiClient();
|
|
140
|
+
const response = await client.post('/api/v1/support/mood', data);
|
|
141
|
+
return response.data;
|
|
142
|
+
},
|
|
143
|
+
onSuccess: (newMoodLog, variables, context) => {
|
|
144
|
+
// Invalidate mood logs list
|
|
145
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.moodLogs() });
|
|
146
|
+
|
|
147
|
+
// Call user's onSuccess if provided
|
|
148
|
+
},
|
|
149
|
+
...options,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create a win
|
|
155
|
+
*
|
|
156
|
+
* @param options - TanStack Query mutation options
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* const { mutate, isPending } = useCreateWin();
|
|
161
|
+
*
|
|
162
|
+
* mutate({
|
|
163
|
+
* title: '30 days sober!',
|
|
164
|
+
* description: 'Hit my first milestone',
|
|
165
|
+
* category: 'sobriety'
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export function useCreateWin(
|
|
170
|
+
options?: Omit<
|
|
171
|
+
UseMutationOptions<WinResponse, Error, CreateWinRequest>,
|
|
172
|
+
'mutationFn'
|
|
173
|
+
>
|
|
174
|
+
): UseMutationResult<WinResponse, Error, CreateWinRequest> {
|
|
175
|
+
const queryClient = useQueryClient();
|
|
176
|
+
|
|
177
|
+
return useMutation({
|
|
178
|
+
mutationFn: async (data: CreateWinRequest): Promise<WinResponse> => {
|
|
179
|
+
const client = getApiClient();
|
|
180
|
+
const response = await client.post('/api/v1/support/wins', data);
|
|
181
|
+
return response.data;
|
|
182
|
+
},
|
|
183
|
+
onSuccess: (newWin, variables, context) => {
|
|
184
|
+
// Invalidate wins lists
|
|
185
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.wins() });
|
|
186
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.winsByCategory() });
|
|
187
|
+
|
|
188
|
+
// Call user's onSuccess if provided
|
|
189
|
+
},
|
|
190
|
+
...options,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Create a habit
|
|
196
|
+
*
|
|
197
|
+
* @param options - TanStack Query mutation options
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```tsx
|
|
201
|
+
* const { mutate, isPending } = useCreateHabit();
|
|
202
|
+
*
|
|
203
|
+
* mutate({
|
|
204
|
+
* name: 'Morning meditation',
|
|
205
|
+
* description: '10 minutes of mindfulness',
|
|
206
|
+
* frequency: 'daily',
|
|
207
|
+
* targetDays: 7
|
|
208
|
+
* });
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
export function useCreateHabit(
|
|
212
|
+
options?: Omit<
|
|
213
|
+
UseMutationOptions<HabitResponse, Error, CreateHabitRequest>,
|
|
214
|
+
'mutationFn'
|
|
215
|
+
>
|
|
216
|
+
): UseMutationResult<HabitResponse, Error, CreateHabitRequest> {
|
|
217
|
+
const queryClient = useQueryClient();
|
|
218
|
+
|
|
219
|
+
return useMutation({
|
|
220
|
+
mutationFn: async (data: CreateHabitRequest): Promise<HabitResponse> => {
|
|
221
|
+
const client = getApiClient();
|
|
222
|
+
const response = await client.post('/api/v1/support/habits', data);
|
|
223
|
+
return response.data;
|
|
224
|
+
},
|
|
225
|
+
onSuccess: (newHabit, variables, context) => {
|
|
226
|
+
// Invalidate habits list
|
|
227
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.habits() });
|
|
228
|
+
|
|
229
|
+
// Call user's onSuccess if provided
|
|
230
|
+
},
|
|
231
|
+
...options,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Update a habit
|
|
237
|
+
*
|
|
238
|
+
* @param options - TanStack Query mutation options
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```tsx
|
|
242
|
+
* const { mutate, isPending } = useUpdateHabit();
|
|
243
|
+
*
|
|
244
|
+
* mutate({
|
|
245
|
+
* id: 'habit-123',
|
|
246
|
+
* data: {
|
|
247
|
+
* name: 'Evening meditation',
|
|
248
|
+
* targetDays: 14
|
|
249
|
+
* }
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export function useUpdateHabit(
|
|
254
|
+
options?: Omit<
|
|
255
|
+
UseMutationOptions<
|
|
256
|
+
HabitResponse,
|
|
257
|
+
Error,
|
|
258
|
+
{ id: string; data: UpdateHabitRequest }
|
|
259
|
+
>,
|
|
260
|
+
'mutationFn'
|
|
261
|
+
>
|
|
262
|
+
): UseMutationResult<HabitResponse, Error, { id: string; data: UpdateHabitRequest }> {
|
|
263
|
+
const queryClient = useQueryClient();
|
|
264
|
+
|
|
265
|
+
return useMutation({
|
|
266
|
+
mutationFn: async ({
|
|
267
|
+
id,
|
|
268
|
+
data,
|
|
269
|
+
}: {
|
|
270
|
+
id: string;
|
|
271
|
+
data: UpdateHabitRequest;
|
|
272
|
+
}): Promise<HabitResponse> => {
|
|
273
|
+
const client = getApiClient();
|
|
274
|
+
const response = await client.put(`/api/v1/support/habits/${id}`, data);
|
|
275
|
+
return response.data;
|
|
276
|
+
},
|
|
277
|
+
onSuccess: (updatedHabit, variables, context) => {
|
|
278
|
+
// Invalidate habits list
|
|
279
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.habits() });
|
|
280
|
+
|
|
281
|
+
// Call user's onSuccess if provided
|
|
282
|
+
},
|
|
283
|
+
...options,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Delete a habit
|
|
289
|
+
*
|
|
290
|
+
* @param options - TanStack Query mutation options
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* const { mutate, isPending } = useDeleteHabit();
|
|
295
|
+
*
|
|
296
|
+
* mutate('habit-123');
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export function useDeleteHabit(
|
|
300
|
+
options?: Omit<UseMutationOptions<void, Error, string>, 'mutationFn'>
|
|
301
|
+
): UseMutationResult<void, Error, string> {
|
|
302
|
+
const queryClient = useQueryClient();
|
|
303
|
+
|
|
304
|
+
return useMutation({
|
|
305
|
+
mutationFn: async (id: string): Promise<void> => {
|
|
306
|
+
const client = getApiClient();
|
|
307
|
+
await client.delete(`/api/v1/support/habits/${id}`);
|
|
308
|
+
},
|
|
309
|
+
onSuccess: (data, habitId, context) => {
|
|
310
|
+
// Invalidate habits list
|
|
311
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.habits() });
|
|
312
|
+
|
|
313
|
+
// Call user's onSuccess if provided
|
|
314
|
+
},
|
|
315
|
+
...options,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Complete a habit for today
|
|
321
|
+
*
|
|
322
|
+
* @param options - TanStack Query mutation options
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```tsx
|
|
326
|
+
* const { mutate, isPending } = useCompleteHabit();
|
|
327
|
+
*
|
|
328
|
+
* mutate('habit-123');
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
export function useCompleteHabit(
|
|
332
|
+
options?: Omit<UseMutationOptions<HabitResponse, Error, string>, 'mutationFn'>
|
|
333
|
+
): UseMutationResult<HabitResponse, Error, string> {
|
|
334
|
+
const queryClient = useQueryClient();
|
|
335
|
+
|
|
336
|
+
return useMutation({
|
|
337
|
+
mutationFn: async (id: string): Promise<HabitResponse> => {
|
|
338
|
+
const client = getApiClient();
|
|
339
|
+
const response = await client.post(`/api/v1/support/habits/${id}/complete`);
|
|
340
|
+
return response.data;
|
|
341
|
+
},
|
|
342
|
+
onSuccess: (completedHabit, habitId, context) => {
|
|
343
|
+
// Invalidate habits list to show updated completion status
|
|
344
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.habits() });
|
|
345
|
+
|
|
346
|
+
// Call user's onSuccess if provided
|
|
347
|
+
},
|
|
348
|
+
...options,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Create a reflection
|
|
354
|
+
*
|
|
355
|
+
* @param options - TanStack Query mutation options
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```tsx
|
|
359
|
+
* const { mutate, isPending } = useCreateReflection();
|
|
360
|
+
*
|
|
361
|
+
* mutate({
|
|
362
|
+
* content: 'Today I learned...',
|
|
363
|
+
* category: 'growth',
|
|
364
|
+
* mood: 8
|
|
365
|
+
* });
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
export function useCreateReflection(
|
|
369
|
+
options?: Omit<
|
|
370
|
+
UseMutationOptions<ReflectionResponse, Error, CreateReflectionRequest>,
|
|
371
|
+
'mutationFn'
|
|
372
|
+
>
|
|
373
|
+
): UseMutationResult<ReflectionResponse, Error, CreateReflectionRequest> {
|
|
374
|
+
const queryClient = useQueryClient();
|
|
375
|
+
|
|
376
|
+
return useMutation({
|
|
377
|
+
mutationFn: async (data: CreateReflectionRequest): Promise<ReflectionResponse> => {
|
|
378
|
+
const client = getApiClient();
|
|
379
|
+
const response = await client.post('/api/v1/support/reflections', data);
|
|
380
|
+
return response.data;
|
|
381
|
+
},
|
|
382
|
+
onSuccess: (newReflection, variables, context) => {
|
|
383
|
+
// Invalidate reflections list
|
|
384
|
+
queryClient.invalidateQueries({ queryKey: supportKeys.reflections() });
|
|
385
|
+
|
|
386
|
+
// Call user's onSuccess if provided
|
|
387
|
+
},
|
|
388
|
+
...options,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users Mutation Hooks
|
|
3
|
+
*
|
|
4
|
+
* TanStack Query mutation hooks for user-related write operations.
|
|
5
|
+
* These hooks handle user profile updates and onboarding completion.
|
|
6
|
+
*
|
|
7
|
+
* @module api/mutations/users
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useMutation, UseMutationOptions, UseMutationResult, useQueryClient } from '@tanstack/react-query';
|
|
11
|
+
import { getApiClient } from '../client';
|
|
12
|
+
import { userKeys } from '../queries/users';
|
|
13
|
+
import type { UpdateUserRequest, UserResponse } from '../types';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// MUTATION HOOKS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Update current user's profile
|
|
21
|
+
*
|
|
22
|
+
* @description
|
|
23
|
+
* Updates the authenticated user's profile information.
|
|
24
|
+
* This can include name, bio, avatar, location, sober date, and other profile fields.
|
|
25
|
+
* Automatically invalidates relevant user queries to refresh the UI.
|
|
26
|
+
*
|
|
27
|
+
* @endpoint PUT /api/v1/users/me
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* import { useUpdateCurrentUser } from '@growsober/sdk';
|
|
32
|
+
*
|
|
33
|
+
* function EditProfileForm() {
|
|
34
|
+
* const { mutate: updateProfile, isPending, error } = useUpdateCurrentUser({
|
|
35
|
+
* onSuccess: (data) => {
|
|
36
|
+
* toast.success('Profile updated successfully');
|
|
37
|
+
* navigation.goBack();
|
|
38
|
+
* },
|
|
39
|
+
* onError: (error) => {
|
|
40
|
+
* toast.error('Failed to update profile: ' + error.message);
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* const handleSubmit = (formData: FormData) => {
|
|
45
|
+
* updateProfile({
|
|
46
|
+
* name: formData.name,
|
|
47
|
+
* bio: formData.bio,
|
|
48
|
+
* avatar: formData.avatar,
|
|
49
|
+
* city: formData.city,
|
|
50
|
+
* soberDate: formData.soberDate,
|
|
51
|
+
* });
|
|
52
|
+
* };
|
|
53
|
+
*
|
|
54
|
+
* return (
|
|
55
|
+
* <form onSubmit={handleSubmit}>
|
|
56
|
+
* <input name="name" />
|
|
57
|
+
* <textarea name="bio" />
|
|
58
|
+
* <button type="submit" disabled={isPending}>
|
|
59
|
+
* {isPending ? 'Saving...' : 'Save Profile'}
|
|
60
|
+
* </button>
|
|
61
|
+
* {error && <p className="error">{error.message}</p>}
|
|
62
|
+
* </form>
|
|
63
|
+
* );
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* Update specific fields:
|
|
69
|
+
* ```tsx
|
|
70
|
+
* function AvatarUploader() {
|
|
71
|
+
* const { mutateAsync: updateProfile } = useUpdateCurrentUser();
|
|
72
|
+
*
|
|
73
|
+
* const handleAvatarChange = async (file: File) => {
|
|
74
|
+
* const uploadedUrl = await uploadImage(file);
|
|
75
|
+
* await updateProfile({ avatar: uploadedUrl });
|
|
76
|
+
* toast.success('Avatar updated!');
|
|
77
|
+
* };
|
|
78
|
+
*
|
|
79
|
+
* return <ImagePicker onSelect={handleAvatarChange} />;
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* Update sober date:
|
|
85
|
+
* ```tsx
|
|
86
|
+
* function SoberDatePicker() {
|
|
87
|
+
* const { mutate: updateProfile } = useUpdateCurrentUser();
|
|
88
|
+
* const { data: user } = useCurrentUser();
|
|
89
|
+
*
|
|
90
|
+
* const handleDateChange = (date: Date) => {
|
|
91
|
+
* updateProfile({
|
|
92
|
+
* soberDate: date.toISOString(),
|
|
93
|
+
* });
|
|
94
|
+
* };
|
|
95
|
+
*
|
|
96
|
+
* return (
|
|
97
|
+
* <DatePicker
|
|
98
|
+
* value={user?.soberDate}
|
|
99
|
+
* onChange={handleDateChange}
|
|
100
|
+
* />
|
|
101
|
+
* );
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @param options - TanStack Query mutation options
|
|
106
|
+
* @returns TanStack Query mutation result
|
|
107
|
+
*/
|
|
108
|
+
export function useUpdateCurrentUser(
|
|
109
|
+
options?: Omit<UseMutationOptions<UserResponse, Error, UpdateUserRequest>, 'mutationFn'>
|
|
110
|
+
): UseMutationResult<UserResponse, Error, UpdateUserRequest> {
|
|
111
|
+
const queryClient = useQueryClient();
|
|
112
|
+
|
|
113
|
+
return useMutation({
|
|
114
|
+
mutationFn: async (data: UpdateUserRequest): Promise<UserResponse> => {
|
|
115
|
+
const client = getApiClient();
|
|
116
|
+
const response = await client.put<UserResponse>('/api/v1/users/me', data);
|
|
117
|
+
return response.data;
|
|
118
|
+
},
|
|
119
|
+
onSuccess: (data, variables, context) => {
|
|
120
|
+
// Invalidate current user query to trigger refetch with updated data
|
|
121
|
+
queryClient.invalidateQueries({ queryKey: userKeys.me() });
|
|
122
|
+
|
|
123
|
+
// Also invalidate the detail query for this user if they've viewed their own profile
|
|
124
|
+
if (data.id) {
|
|
125
|
+
queryClient.invalidateQueries({ queryKey: userKeys.detail(data.id) });
|
|
126
|
+
queryClient.invalidateQueries({ queryKey: userKeys.public(data.id) });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Call user's onSuccess if provided
|
|
130
|
+
// User's onSuccess is handled by spreading options
|
|
131
|
+
},
|
|
132
|
+
...options,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Complete user onboarding
|
|
138
|
+
*
|
|
139
|
+
* @description
|
|
140
|
+
* Marks the user's onboarding process as complete.
|
|
141
|
+
* This is typically called after a user completes all required onboarding steps
|
|
142
|
+
* (e.g., setting up profile, selecting interests, setting sober date).
|
|
143
|
+
* Updates the user's onboardingCompleted flag and unlocks full app access.
|
|
144
|
+
*
|
|
145
|
+
* @endpoint POST /api/v1/users/me/complete-onboarding
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* import { useCompleteOnboarding } from '@growsober/sdk';
|
|
150
|
+
*
|
|
151
|
+
* function OnboardingFinalStep() {
|
|
152
|
+
* const { mutate: completeOnboarding, isPending } = useCompleteOnboarding({
|
|
153
|
+
* onSuccess: () => {
|
|
154
|
+
* toast.success('Welcome to GrowSober!');
|
|
155
|
+
* navigation.navigate('Home');
|
|
156
|
+
* },
|
|
157
|
+
* onError: (error) => {
|
|
158
|
+
* toast.error('Failed to complete onboarding: ' + error.message);
|
|
159
|
+
* },
|
|
160
|
+
* });
|
|
161
|
+
*
|
|
162
|
+
* const handleFinish = () => {
|
|
163
|
+
* completeOnboarding();
|
|
164
|
+
* };
|
|
165
|
+
*
|
|
166
|
+
* return (
|
|
167
|
+
* <div>
|
|
168
|
+
* <h1>You're All Set!</h1>
|
|
169
|
+
* <p>Ready to start your sober journey?</p>
|
|
170
|
+
* <button onClick={handleFinish} disabled={isPending}>
|
|
171
|
+
* {isPending ? 'Setting up...' : 'Get Started'}
|
|
172
|
+
* </button>
|
|
173
|
+
* </div>
|
|
174
|
+
* );
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* Multi-step onboarding with validation:
|
|
180
|
+
* ```tsx
|
|
181
|
+
* function OnboardingFlow() {
|
|
182
|
+
* const [currentStep, setCurrentStep] = useState(1);
|
|
183
|
+
* const { mutateAsync: completeOnboarding } = useCompleteOnboarding();
|
|
184
|
+
* const { data: user } = useCurrentUser();
|
|
185
|
+
*
|
|
186
|
+
* const handleComplete = async () => {
|
|
187
|
+
* // Validate all required fields are filled
|
|
188
|
+
* if (!user?.name || !user?.soberDate || !user?.city) {
|
|
189
|
+
* toast.error('Please complete all required fields');
|
|
190
|
+
* return;
|
|
191
|
+
* }
|
|
192
|
+
*
|
|
193
|
+
* try {
|
|
194
|
+
* await completeOnboarding();
|
|
195
|
+
* // User will be redirected via onSuccess callback
|
|
196
|
+
* } catch (error) {
|
|
197
|
+
* console.error('Onboarding error:', error);
|
|
198
|
+
* }
|
|
199
|
+
* };
|
|
200
|
+
*
|
|
201
|
+
* return (
|
|
202
|
+
* <div>
|
|
203
|
+
* {currentStep === 1 && <ProfileSetup onNext={() => setCurrentStep(2)} />}
|
|
204
|
+
* {currentStep === 2 && <InterestSelection onNext={() => setCurrentStep(3)} />}
|
|
205
|
+
* {currentStep === 3 && <SoberDateSetup onNext={handleComplete} />}
|
|
206
|
+
* </div>
|
|
207
|
+
* );
|
|
208
|
+
* }
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* Check onboarding status:
|
|
213
|
+
* ```tsx
|
|
214
|
+
* function AppNavigator() {
|
|
215
|
+
* const { data: user, isLoading } = useCurrentUser();
|
|
216
|
+
*
|
|
217
|
+
* if (isLoading) return <SplashScreen />;
|
|
218
|
+
*
|
|
219
|
+
* // Redirect to onboarding if not completed
|
|
220
|
+
* if (user && !user.onboardingCompleted) {
|
|
221
|
+
* return <OnboardingFlow />;
|
|
222
|
+
* }
|
|
223
|
+
*
|
|
224
|
+
* return <MainApp />;
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* With analytics tracking:
|
|
230
|
+
* ```tsx
|
|
231
|
+
* const { mutate: completeOnboarding } = useCompleteOnboarding({
|
|
232
|
+
* onSuccess: (data) => {
|
|
233
|
+
* analytics.track('Onboarding Completed', {
|
|
234
|
+
* userId: data.id,
|
|
235
|
+
* completedAt: new Date().toISOString(),
|
|
236
|
+
* profileComplete: !!data.bio && !!data.avatar,
|
|
237
|
+
* });
|
|
238
|
+
* navigation.navigate('Home');
|
|
239
|
+
* },
|
|
240
|
+
* });
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* @param options - TanStack Query mutation options
|
|
244
|
+
* @returns TanStack Query mutation result
|
|
245
|
+
*/
|
|
246
|
+
export function useCompleteOnboarding(
|
|
247
|
+
options?: Omit<UseMutationOptions<UserResponse, Error, void>, 'mutationFn'>
|
|
248
|
+
): UseMutationResult<UserResponse, Error, void> {
|
|
249
|
+
const queryClient = useQueryClient();
|
|
250
|
+
|
|
251
|
+
return useMutation({
|
|
252
|
+
mutationFn: async (): Promise<UserResponse> => {
|
|
253
|
+
const client = getApiClient();
|
|
254
|
+
const response = await client.post<UserResponse>('/api/v1/users/me/complete-onboarding');
|
|
255
|
+
return response.data;
|
|
256
|
+
},
|
|
257
|
+
onSuccess: (data, variables, context) => {
|
|
258
|
+
// Invalidate current user query to trigger refetch with updated onboarding status
|
|
259
|
+
queryClient.invalidateQueries({ queryKey: userKeys.me() });
|
|
260
|
+
|
|
261
|
+
// Also invalidate the detail query for this user
|
|
262
|
+
if (data.id) {
|
|
263
|
+
queryClient.invalidateQueries({ queryKey: userKeys.detail(data.id) });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Call user's onSuccess if provided
|
|
267
|
+
// User's onSuccess is handled by spreading options
|
|
268
|
+
},
|
|
269
|
+
...options,
|
|
270
|
+
});
|
|
271
|
+
}
|