@growsober/sdk 1.0.5 → 1.0.8

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.
Files changed (70) hide show
  1. package/dist/__tests__/e2e.test.d.ts +30 -0
  2. package/dist/__tests__/e2e.test.js +959 -63
  3. package/dist/api/mutations/badges.d.ts +116 -0
  4. package/dist/api/mutations/badges.js +177 -0
  5. package/dist/api/mutations/brands.d.ts +251 -0
  6. package/dist/api/mutations/brands.js +242 -0
  7. package/dist/api/mutations/creators.d.ts +131 -0
  8. package/dist/api/mutations/creators.js +129 -0
  9. package/dist/api/mutations/event-chat.d.ts +2 -2
  10. package/dist/api/mutations/event-chat.js +9 -9
  11. package/dist/api/mutations/index.d.ts +4 -0
  12. package/dist/api/mutations/index.js +5 -1
  13. package/dist/api/mutations/jack.d.ts +29 -0
  14. package/dist/api/mutations/jack.js +41 -1
  15. package/dist/api/mutations/products.d.ts +175 -0
  16. package/dist/api/mutations/products.js +226 -0
  17. package/dist/api/mutations/support.d.ts +20 -1
  18. package/dist/api/mutations/support.js +36 -1
  19. package/dist/api/queries/badges.d.ts +221 -0
  20. package/dist/api/queries/badges.js +290 -0
  21. package/dist/api/queries/bookings.d.ts +1 -1
  22. package/dist/api/queries/brands.d.ts +248 -0
  23. package/dist/api/queries/brands.js +226 -0
  24. package/dist/api/queries/businesses.d.ts +61 -1
  25. package/dist/api/queries/businesses.js +27 -1
  26. package/dist/api/queries/creators.d.ts +332 -0
  27. package/dist/api/queries/creators.js +249 -0
  28. package/dist/api/queries/event-chat.d.ts +1 -1
  29. package/dist/api/queries/event-chat.js +4 -4
  30. package/dist/api/queries/events.d.ts +45 -0
  31. package/dist/api/queries/index.d.ts +5 -0
  32. package/dist/api/queries/index.js +6 -1
  33. package/dist/api/queries/jack.d.ts +80 -0
  34. package/dist/api/queries/jack.js +98 -1
  35. package/dist/api/queries/library.d.ts +8 -0
  36. package/dist/api/queries/products.d.ts +185 -0
  37. package/dist/api/queries/products.js +203 -0
  38. package/dist/api/queries/support.d.ts +46 -1
  39. package/dist/api/queries/support.js +48 -1
  40. package/dist/api/queries/venues.d.ts +304 -0
  41. package/dist/api/queries/venues.js +211 -0
  42. package/dist/api/types.d.ts +245 -0
  43. package/dist/api/types.js +6 -1
  44. package/dist/api/utils/eventGrouping.d.ts +104 -0
  45. package/dist/api/utils/eventGrouping.js +155 -0
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.js +5 -1
  48. package/package.json +5 -2
  49. package/src/__tests__/e2e.test.ts +996 -64
  50. package/src/api/mutations/badges.ts +228 -0
  51. package/src/api/mutations/brands.ts +376 -0
  52. package/src/api/mutations/creators.ts +171 -0
  53. package/src/api/mutations/event-chat.ts +8 -8
  54. package/src/api/mutations/index.ts +4 -0
  55. package/src/api/mutations/jack.ts +50 -1
  56. package/src/api/mutations/products.ts +336 -0
  57. package/src/api/mutations/support.ts +44 -0
  58. package/src/api/queries/badges.ts +385 -0
  59. package/src/api/queries/brands.ts +281 -0
  60. package/src/api/queries/businesses.ts +30 -1
  61. package/src/api/queries/creators.ts +308 -0
  62. package/src/api/queries/event-chat.ts +3 -3
  63. package/src/api/queries/index.ts +5 -0
  64. package/src/api/queries/jack.ts +139 -1
  65. package/src/api/queries/products.ts +312 -0
  66. package/src/api/queries/support.ts +54 -0
  67. package/src/api/queries/venues.ts +271 -0
  68. package/src/api/types.ts +317 -1
  69. package/src/api/utils/eventGrouping.ts +181 -0
  70. package/src/index.ts +6 -0
@@ -0,0 +1,171 @@
1
+ import { useMutation, useQueryClient, UseMutationOptions } from '@tanstack/react-query';
2
+ import { getApiClient } from '../client';
3
+ import { creatorKeys } from '../queries/creators';
4
+ import type {
5
+ CreatorResponse,
6
+ CreatorAvailabilityResponse,
7
+ CreateCreatorRequest,
8
+ UpdateCreatorRequest,
9
+ CreateAvailabilityRequest,
10
+ } from '../types';
11
+
12
+ // ============================================================================
13
+ // MUTATION HOOKS
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Create a new creator profile for the current user
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const { mutateAsync: createCreator } = useCreateCreator();
22
+ *
23
+ * await createCreator({
24
+ * slug: 'john-doe',
25
+ * displayName: 'John Doe',
26
+ * bio: 'Wellness facilitator',
27
+ * canFacilitate: true,
28
+ * });
29
+ * ```
30
+ */
31
+ export function useCreateCreator(
32
+ options?: Omit<UseMutationOptions<CreatorResponse, Error, CreateCreatorRequest>, 'mutationFn'>
33
+ ) {
34
+ const queryClient = useQueryClient();
35
+
36
+ return useMutation({
37
+ mutationFn: async (data: CreateCreatorRequest): Promise<CreatorResponse> => {
38
+ const client = getApiClient();
39
+ const response = await client.post('/api/v1/creators', data);
40
+ return response.data;
41
+ },
42
+ onSuccess: () => {
43
+ queryClient.invalidateQueries({ queryKey: creatorKeys.all });
44
+ },
45
+ ...options,
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Update a creator profile
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const { mutateAsync: updateCreator } = useUpdateCreator();
55
+ *
56
+ * await updateCreator({
57
+ * id: 'creator-123',
58
+ * data: { bio: 'Updated bio' },
59
+ * });
60
+ * ```
61
+ */
62
+ export function useUpdateCreator(
63
+ options?: Omit<
64
+ UseMutationOptions<CreatorResponse, Error, { id: string; data: UpdateCreatorRequest }>,
65
+ 'mutationFn'
66
+ >
67
+ ) {
68
+ const queryClient = useQueryClient();
69
+
70
+ return useMutation({
71
+ mutationFn: async ({ id, data }: { id: string; data: UpdateCreatorRequest }): Promise<CreatorResponse> => {
72
+ const client = getApiClient();
73
+ const response = await client.patch(`/api/v1/creators/${id}`, data);
74
+ return response.data;
75
+ },
76
+ onSuccess: (_, variables) => {
77
+ queryClient.invalidateQueries({ queryKey: creatorKeys.detail(variables.id) });
78
+ queryClient.invalidateQueries({ queryKey: creatorKeys.me() });
79
+ },
80
+ ...options,
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Add an availability slot for a creator
86
+ *
87
+ * @example
88
+ * ```tsx
89
+ * const { mutateAsync: addAvailability } = useAddCreatorAvailability();
90
+ *
91
+ * await addAvailability({
92
+ * creatorId: 'creator-123',
93
+ * data: {
94
+ * dayOfWeek: 1, // Monday
95
+ * startTime: '09:00',
96
+ * endTime: '17:00',
97
+ * timezone: 'Europe/London',
98
+ * isRecurring: true,
99
+ * },
100
+ * });
101
+ * ```
102
+ */
103
+ export function useAddCreatorAvailability(
104
+ options?: Omit<
105
+ UseMutationOptions<
106
+ CreatorAvailabilityResponse,
107
+ Error,
108
+ { creatorId: string; data: CreateAvailabilityRequest }
109
+ >,
110
+ 'mutationFn'
111
+ >
112
+ ) {
113
+ const queryClient = useQueryClient();
114
+
115
+ return useMutation({
116
+ mutationFn: async ({
117
+ creatorId,
118
+ data,
119
+ }: {
120
+ creatorId: string;
121
+ data: CreateAvailabilityRequest;
122
+ }): Promise<CreatorAvailabilityResponse> => {
123
+ const client = getApiClient();
124
+ const response = await client.post(`/api/v1/creators/${creatorId}/availability`, data);
125
+ return response.data;
126
+ },
127
+ onSuccess: (_, variables) => {
128
+ queryClient.invalidateQueries({ queryKey: creatorKeys.availability(variables.creatorId) });
129
+ },
130
+ ...options,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Remove an availability slot for a creator
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * const { mutateAsync: removeAvailability } = useRemoveCreatorAvailability();
140
+ *
141
+ * await removeAvailability({
142
+ * creatorId: 'creator-123',
143
+ * availabilityId: 'availability-456',
144
+ * });
145
+ * ```
146
+ */
147
+ export function useRemoveCreatorAvailability(
148
+ options?: Omit<
149
+ UseMutationOptions<void, Error, { creatorId: string; availabilityId: string }>,
150
+ 'mutationFn'
151
+ >
152
+ ) {
153
+ const queryClient = useQueryClient();
154
+
155
+ return useMutation({
156
+ mutationFn: async ({
157
+ creatorId,
158
+ availabilityId,
159
+ }: {
160
+ creatorId: string;
161
+ availabilityId: string;
162
+ }): Promise<void> => {
163
+ const client = getApiClient();
164
+ await client.delete(`/api/v1/creators/${creatorId}/availability/${availabilityId}`);
165
+ },
166
+ onSuccess: (_, variables) => {
167
+ queryClient.invalidateQueries({ queryKey: creatorKeys.availability(variables.creatorId) });
168
+ },
169
+ ...options,
170
+ });
171
+ }
@@ -25,7 +25,7 @@ export function useUpdateChatSettings(eventId: string) {
25
25
  mutationFn: async (data: UpdateChatSettingsRequest) => {
26
26
  const client = getApiClient();
27
27
  const response = await client.put<EventChatResponse>(
28
- `/events/${eventId}/chat/settings`,
28
+ `/api/v1/events/${eventId}/chat/settings`,
29
29
  data
30
30
  );
31
31
  return response.data;
@@ -50,7 +50,7 @@ export function useJoinEventChat(eventId: string) {
50
50
  mutationFn: async () => {
51
51
  const client = getApiClient();
52
52
  const response = await client.post<ChatMemberResponse>(
53
- `/events/${eventId}/chat/join`
53
+ `/api/v1/events/${eventId}/chat/join`
54
54
  );
55
55
  return response.data;
56
56
  },
@@ -70,7 +70,7 @@ export function useLeaveEventChat(eventId: string) {
70
70
  return useMutation({
71
71
  mutationFn: async () => {
72
72
  const client = getApiClient();
73
- await client.post(`/events/${eventId}/chat/leave`);
73
+ await client.post(`/api/v1/events/${eventId}/chat/leave`);
74
74
  },
75
75
  onSuccess: () => {
76
76
  queryClient.invalidateQueries({ queryKey: eventChatKeys.chat(eventId) });
@@ -89,7 +89,7 @@ export function useUpdateMemberSettings(eventId: string) {
89
89
  mutationFn: async (data: UpdateMemberSettingsRequest) => {
90
90
  const client = getApiClient();
91
91
  const response = await client.put<ChatMemberResponse>(
92
- `/events/${eventId}/chat/settings/me`,
92
+ `/api/v1/events/${eventId}/chat/settings/me`,
93
93
  data
94
94
  );
95
95
  return response.data;
@@ -107,7 +107,7 @@ export function useMarkMessagesAsRead(eventId: string) {
107
107
  return useMutation({
108
108
  mutationFn: async () => {
109
109
  const client = getApiClient();
110
- await client.post(`/events/${eventId}/chat/read`);
110
+ await client.post(`/api/v1/events/${eventId}/chat/read`);
111
111
  },
112
112
  });
113
113
  }
@@ -126,7 +126,7 @@ export function useSendEventChatMessage(eventId: string) {
126
126
  mutationFn: async (data: SendChatMessageRequest) => {
127
127
  const client = getApiClient();
128
128
  const response = await client.post<ChatMessageResponse>(
129
- `/events/${eventId}/chat/messages`,
129
+ `/api/v1/events/${eventId}/chat/messages`,
130
130
  data
131
131
  );
132
132
  return response.data;
@@ -148,7 +148,7 @@ export function useUpdateEventChatMessage(eventId: string) {
148
148
  mutationFn: async ({ messageId, content }: { messageId: string } & UpdateChatMessageRequest) => {
149
149
  const client = getApiClient();
150
150
  const response = await client.put<ChatMessageResponse>(
151
- `/events/${eventId}/chat/messages/${messageId}`,
151
+ `/api/v1/events/${eventId}/chat/messages/${messageId}`,
152
152
  { content }
153
153
  );
154
154
  return response.data;
@@ -168,7 +168,7 @@ export function useDeleteEventChatMessage(eventId: string) {
168
168
  return useMutation({
169
169
  mutationFn: async (messageId: string) => {
170
170
  const client = getApiClient();
171
- await client.delete(`/events/${eventId}/chat/messages/${messageId}`);
171
+ await client.delete(`/api/v1/events/${eventId}/chat/messages/${messageId}`);
172
172
  },
173
173
  onSuccess: () => {
174
174
  queryClient.invalidateQueries({ queryKey: eventChatKeys.messages(eventId) });
@@ -22,3 +22,7 @@ export * from './grow90';
22
22
  export * from './matching';
23
23
  export * from './event-chat';
24
24
  export * from './user-pins';
25
+ export * from './badges';
26
+ export * from './creators';
27
+ export * from './brands';
28
+ export * from './products';
@@ -1,6 +1,6 @@
1
1
  import { useMutation, useQueryClient, UseMutationOptions } from '@tanstack/react-query';
2
2
  import { getApiClient } from '../client';
3
- import { jackKeys } from '../queries/jack';
3
+ import { jackKeys, JackEntryType, JackTimelineEntry } from '../queries/jack';
4
4
 
5
5
  // ============================================================================
6
6
  // TYPES
@@ -21,6 +21,12 @@ export interface NewConversationResponse {
21
21
  conversationId: string;
22
22
  }
23
23
 
24
+ export interface AddEntryRequest {
25
+ entryType: JackEntryType;
26
+ content: string;
27
+ metadata?: Record<string, any>;
28
+ }
29
+
24
30
  // ============================================================================
25
31
  // MUTATION HOOKS
26
32
  // ============================================================================
@@ -128,3 +134,46 @@ export function useArchiveJackConversation(
128
134
  ...options,
129
135
  });
130
136
  }
137
+
138
+ /**
139
+ * Add an entry to the Jack timeline (check-in, reflection, mood log, win, journal)
140
+ *
141
+ * @example
142
+ * ```tsx
143
+ * const addEntry = useAddJackEntry();
144
+ *
145
+ * // Add a check-in
146
+ * await addEntry.mutateAsync({
147
+ * entryType: 'CHECK_IN',
148
+ * content: 'Feeling great today! Day 30.',
149
+ * metadata: { moodScore: 4, stayedSober: true },
150
+ * });
151
+ *
152
+ * // Add a win
153
+ * await addEntry.mutateAsync({
154
+ * entryType: 'WIN',
155
+ * content: 'Said no to drinks at dinner!',
156
+ * metadata: { category: 'SOBRIETY' },
157
+ * });
158
+ * ```
159
+ */
160
+ export function useAddJackEntry(
161
+ options?: Omit<UseMutationOptions<JackTimelineEntry, Error, AddEntryRequest>, 'mutationFn'>
162
+ ) {
163
+ const queryClient = useQueryClient();
164
+
165
+ return useMutation({
166
+ mutationFn: async (data: AddEntryRequest): Promise<JackTimelineEntry> => {
167
+ const client = getApiClient();
168
+ const response = await client.post('/api/v1/support/jack/entry', data);
169
+ return response.data?.data || response.data;
170
+ },
171
+ onSuccess: () => {
172
+ // Invalidate timeline and stats queries
173
+ queryClient.invalidateQueries({ queryKey: jackKeys.timeline() });
174
+ queryClient.invalidateQueries({ queryKey: jackKeys.stats() });
175
+ queryClient.invalidateQueries({ queryKey: jackKeys.history() });
176
+ },
177
+ ...options,
178
+ });
179
+ }
@@ -0,0 +1,336 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { getApiClient } from '../client';
3
+ import { productKeys, CreatorProductResponse, ProductBookingResponse, ProductType, DeliveryMethod } from '../queries/products';
4
+ import { creatorKeys } from '../queries/creators';
5
+
6
+ // ============================================================================
7
+ // TYPES
8
+ // ============================================================================
9
+
10
+ export interface CreateProductRequest {
11
+ title: string;
12
+ description?: string;
13
+ imageUrl?: string;
14
+ type?: ProductType;
15
+ price: number;
16
+ currency?: string;
17
+ durationMinutes?: number;
18
+ maxParticipants?: number;
19
+ deliveryMethod?: DeliveryMethod;
20
+ locationDetails?: string;
21
+ slug?: string;
22
+ }
23
+
24
+ export interface UpdateProductRequest {
25
+ title?: string;
26
+ description?: string;
27
+ imageUrl?: string;
28
+ type?: ProductType;
29
+ price?: number;
30
+ currency?: string;
31
+ durationMinutes?: number;
32
+ maxParticipants?: number;
33
+ deliveryMethod?: DeliveryMethod;
34
+ locationDetails?: string;
35
+ isActive?: boolean;
36
+ isFeatured?: boolean;
37
+ }
38
+
39
+ export interface BookProductRequest {
40
+ scheduledAt: string;
41
+ timezone?: string;
42
+ clientNotes?: string;
43
+ }
44
+
45
+ export interface UpdateBookingRequest {
46
+ scheduledAt?: string;
47
+ clientNotes?: string;
48
+ creatorNotes?: string;
49
+ meetingLink?: string;
50
+ }
51
+
52
+ export interface CancelBookingRequest {
53
+ reason?: string;
54
+ }
55
+
56
+ // ============================================================================
57
+ // MUTATION HOOKS - PRODUCTS
58
+ // ============================================================================
59
+
60
+ /**
61
+ * Create a new product for a creator
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const { mutate: createProduct, isLoading } = useCreateProduct();
66
+ *
67
+ * createProduct({
68
+ * creatorId: 'creator-123',
69
+ * data: {
70
+ * title: '1-on-1 Coaching Session',
71
+ * description: 'Personal coaching session',
72
+ * type: 'SESSION_1ON1',
73
+ * price: 5000,
74
+ * durationMinutes: 60,
75
+ * },
76
+ * });
77
+ * ```
78
+ */
79
+ export function useCreateProduct() {
80
+ const queryClient = useQueryClient();
81
+
82
+ return useMutation({
83
+ mutationFn: async ({
84
+ creatorId,
85
+ data,
86
+ }: {
87
+ creatorId: string;
88
+ data: CreateProductRequest;
89
+ }): Promise<CreatorProductResponse> => {
90
+ const client = getApiClient();
91
+ const response = await client.post(`/api/v1/creators/${creatorId}/products`, data);
92
+ return response.data;
93
+ },
94
+ onSuccess: (_, { creatorId }) => {
95
+ queryClient.invalidateQueries({ queryKey: productKeys.byCreator(creatorId) });
96
+ queryClient.invalidateQueries({ queryKey: productKeys.lists() });
97
+ },
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Update an existing product
103
+ *
104
+ * @example
105
+ * ```tsx
106
+ * const { mutate: updateProduct, isLoading } = useUpdateProduct();
107
+ *
108
+ * updateProduct({
109
+ * creatorId: 'creator-123',
110
+ * productId: 'product-456',
111
+ * data: {
112
+ * price: 6000,
113
+ * isActive: true,
114
+ * },
115
+ * });
116
+ * ```
117
+ */
118
+ export function useUpdateProduct() {
119
+ const queryClient = useQueryClient();
120
+
121
+ return useMutation({
122
+ mutationFn: async ({
123
+ creatorId,
124
+ productId,
125
+ data,
126
+ }: {
127
+ creatorId: string;
128
+ productId: string;
129
+ data: UpdateProductRequest;
130
+ }): Promise<CreatorProductResponse> => {
131
+ const client = getApiClient();
132
+ const response = await client.put(
133
+ `/api/v1/creators/${creatorId}/products/${productId}`,
134
+ data
135
+ );
136
+ return response.data;
137
+ },
138
+ onSuccess: (_, { creatorId, productId }) => {
139
+ queryClient.invalidateQueries({ queryKey: productKeys.detail(productId) });
140
+ queryClient.invalidateQueries({ queryKey: productKeys.byCreator(creatorId) });
141
+ queryClient.invalidateQueries({ queryKey: productKeys.lists() });
142
+ },
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Delete a product
148
+ *
149
+ * @example
150
+ * ```tsx
151
+ * const { mutate: deleteProduct, isLoading } = useDeleteProduct();
152
+ *
153
+ * deleteProduct({
154
+ * creatorId: 'creator-123',
155
+ * productId: 'product-456',
156
+ * });
157
+ * ```
158
+ */
159
+ export function useDeleteProduct() {
160
+ const queryClient = useQueryClient();
161
+
162
+ return useMutation({
163
+ mutationFn: async ({
164
+ creatorId,
165
+ productId,
166
+ }: {
167
+ creatorId: string;
168
+ productId: string;
169
+ }): Promise<void> => {
170
+ const client = getApiClient();
171
+ await client.delete(`/api/v1/creators/${creatorId}/products/${productId}`);
172
+ },
173
+ onSuccess: (_, { creatorId, productId }) => {
174
+ queryClient.removeQueries({ queryKey: productKeys.detail(productId) });
175
+ queryClient.invalidateQueries({ queryKey: productKeys.byCreator(creatorId) });
176
+ queryClient.invalidateQueries({ queryKey: productKeys.lists() });
177
+ },
178
+ });
179
+ }
180
+
181
+ // ============================================================================
182
+ // MUTATION HOOKS - BOOKINGS
183
+ // ============================================================================
184
+
185
+ /**
186
+ * Book a product/session
187
+ *
188
+ * @example
189
+ * ```tsx
190
+ * const { mutate: bookProduct, isLoading } = useBookProduct();
191
+ *
192
+ * bookProduct({
193
+ * productId: 'product-123',
194
+ * data: {
195
+ * scheduledAt: '2024-03-15T10:00:00Z',
196
+ * timezone: 'Europe/London',
197
+ * clientNotes: 'Looking forward to the session!',
198
+ * },
199
+ * });
200
+ * ```
201
+ */
202
+ export function useBookProduct() {
203
+ const queryClient = useQueryClient();
204
+
205
+ return useMutation({
206
+ mutationFn: async ({
207
+ productId,
208
+ data,
209
+ }: {
210
+ productId: string;
211
+ data: BookProductRequest;
212
+ }): Promise<ProductBookingResponse> => {
213
+ const client = getApiClient();
214
+ const response = await client.post(`/api/v1/products/${productId}/book`, data);
215
+ return response.data;
216
+ },
217
+ onSuccess: () => {
218
+ queryClient.invalidateQueries({ queryKey: productKeys.myBookings() });
219
+ },
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Update a product booking
225
+ *
226
+ * @example
227
+ * ```tsx
228
+ * const { mutate: updateBooking, isLoading } = useUpdateProductBooking();
229
+ *
230
+ * updateBooking({
231
+ * bookingId: 'booking-123',
232
+ * data: {
233
+ * scheduledAt: '2024-03-16T14:00:00Z',
234
+ * clientNotes: 'Rescheduled due to conflict',
235
+ * },
236
+ * });
237
+ * ```
238
+ */
239
+ export function useUpdateProductBooking() {
240
+ const queryClient = useQueryClient();
241
+
242
+ return useMutation({
243
+ mutationFn: async ({
244
+ bookingId,
245
+ data,
246
+ }: {
247
+ bookingId: string;
248
+ data: UpdateBookingRequest;
249
+ }): Promise<ProductBookingResponse> => {
250
+ const client = getApiClient();
251
+ const response = await client.put(
252
+ `/api/v1/users/me/product-bookings/${bookingId}`,
253
+ data
254
+ );
255
+ return response.data;
256
+ },
257
+ onSuccess: (_, { bookingId }) => {
258
+ queryClient.invalidateQueries({ queryKey: productKeys.bookingDetail(bookingId) });
259
+ queryClient.invalidateQueries({ queryKey: productKeys.myBookings() });
260
+ // Also invalidate creator bookings as they see this
261
+ queryClient.invalidateQueries({ queryKey: productKeys.bookings() });
262
+ },
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Cancel a product booking
268
+ *
269
+ * @example
270
+ * ```tsx
271
+ * const { mutate: cancelBooking, isLoading } = useCancelProductBooking();
272
+ *
273
+ * cancelBooking({
274
+ * bookingId: 'booking-123',
275
+ * data: { reason: 'Schedule conflict' },
276
+ * });
277
+ * ```
278
+ */
279
+ export function useCancelProductBooking() {
280
+ const queryClient = useQueryClient();
281
+
282
+ return useMutation({
283
+ mutationFn: async ({
284
+ bookingId,
285
+ data,
286
+ }: {
287
+ bookingId: string;
288
+ data?: CancelBookingRequest;
289
+ }): Promise<ProductBookingResponse> => {
290
+ const client = getApiClient();
291
+ const response = await client.delete(
292
+ `/api/v1/users/me/product-bookings/${bookingId}`,
293
+ { data }
294
+ );
295
+ return response.data;
296
+ },
297
+ onSuccess: (_, { bookingId }) => {
298
+ queryClient.invalidateQueries({ queryKey: productKeys.bookingDetail(bookingId) });
299
+ queryClient.invalidateQueries({ queryKey: productKeys.myBookings() });
300
+ queryClient.invalidateQueries({ queryKey: productKeys.bookings() });
301
+ },
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Mark a booking as completed (creator only)
307
+ *
308
+ * @example
309
+ * ```tsx
310
+ * const { mutate: completeBooking, isLoading } = useCompleteProductBooking();
311
+ *
312
+ * completeBooking({ bookingId: 'booking-123' });
313
+ * ```
314
+ */
315
+ export function useCompleteProductBooking() {
316
+ const queryClient = useQueryClient();
317
+
318
+ return useMutation({
319
+ mutationFn: async ({
320
+ bookingId,
321
+ }: {
322
+ bookingId: string;
323
+ }): Promise<ProductBookingResponse> => {
324
+ const client = getApiClient();
325
+ const response = await client.post(
326
+ `/api/v1/users/me/product-bookings/${bookingId}/complete`
327
+ );
328
+ return response.data;
329
+ },
330
+ onSuccess: (_, { bookingId }) => {
331
+ queryClient.invalidateQueries({ queryKey: productKeys.bookingDetail(bookingId) });
332
+ queryClient.invalidateQueries({ queryKey: productKeys.myBookings() });
333
+ queryClient.invalidateQueries({ queryKey: productKeys.bookings() });
334
+ },
335
+ });
336
+ }
@@ -18,6 +18,8 @@ import type {
18
18
  UpdateHabitRequest,
19
19
  ReflectionResponse,
20
20
  CreateReflectionRequest,
21
+ CravingLogResponse,
22
+ LogCravingRequest,
21
23
  } from '../types';
22
24
  import { supportKeys } from '../queries/support';
23
25
 
@@ -388,3 +390,45 @@ export function useCreateReflection(
388
390
  ...options,
389
391
  });
390
392
  }
393
+
394
+ /**
395
+ * Log a craving
396
+ *
397
+ * @param options - TanStack Query mutation options
398
+ *
399
+ * @example
400
+ * ```tsx
401
+ * const { mutate, isPending } = useLogCraving();
402
+ *
403
+ * mutate({
404
+ * intensity: 4,
405
+ * trigger: 'Saw a beer ad',
406
+ * triggerType: 'ENVIRONMENT',
407
+ * copingUsed: ['deep_breathing', 'called_friend'],
408
+ * didResist: true
409
+ * });
410
+ * ```
411
+ */
412
+ export function useLogCraving(
413
+ options?: Omit<
414
+ UseMutationOptions<CravingLogResponse, Error, LogCravingRequest>,
415
+ 'mutationFn'
416
+ >
417
+ ): UseMutationResult<CravingLogResponse, Error, LogCravingRequest> {
418
+ const queryClient = useQueryClient();
419
+
420
+ return useMutation({
421
+ mutationFn: async (data: LogCravingRequest): Promise<CravingLogResponse> => {
422
+ const client = getApiClient();
423
+ const response = await client.post('/api/v1/support/cravings', data);
424
+ return response.data;
425
+ },
426
+ onSuccess: (newCraving, variables, context) => {
427
+ // Invalidate cravings list and stats
428
+ queryClient.invalidateQueries({ queryKey: supportKeys.cravings() });
429
+
430
+ // Call user's onSuccess if provided
431
+ },
432
+ ...options,
433
+ });
434
+ }