@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,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useMutation,
|
|
3
|
+
useQueryClient,
|
|
4
|
+
UseMutationOptions,
|
|
5
|
+
UseMutationResult,
|
|
6
|
+
} from '@tanstack/react-query';
|
|
7
|
+
import { getApiClient } from '../client';
|
|
8
|
+
import type { AmbassadorResponse, ApplyAmbassadorRequest, UpdateAmbassadorRequest } from '../types';
|
|
9
|
+
import { ambassadorKeys } from '../queries/ambassadors';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// MUTATION HOOKS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Apply to become an ambassador
|
|
17
|
+
*/
|
|
18
|
+
export function useApplyAmbassador(
|
|
19
|
+
options?: Omit<
|
|
20
|
+
UseMutationOptions<AmbassadorResponse, Error, ApplyAmbassadorRequest>,
|
|
21
|
+
'mutationFn'
|
|
22
|
+
>
|
|
23
|
+
): UseMutationResult<AmbassadorResponse, Error, ApplyAmbassadorRequest> {
|
|
24
|
+
const queryClient = useQueryClient();
|
|
25
|
+
|
|
26
|
+
return useMutation({
|
|
27
|
+
mutationFn: async (data: ApplyAmbassadorRequest): Promise<AmbassadorResponse> => {
|
|
28
|
+
const client = getApiClient();
|
|
29
|
+
const response = await client.post('/api/v1/ambassadors/apply', data);
|
|
30
|
+
return response.data;
|
|
31
|
+
},
|
|
32
|
+
onSuccess: (ambassador) => {
|
|
33
|
+
queryClient.setQueryData(ambassadorKeys.me(), ambassador);
|
|
34
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.lists() });
|
|
35
|
+
},
|
|
36
|
+
...options,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Update own ambassador profile
|
|
42
|
+
*/
|
|
43
|
+
export function useUpdateMyAmbassador(
|
|
44
|
+
options?: Omit<
|
|
45
|
+
UseMutationOptions<AmbassadorResponse, Error, Partial<UpdateAmbassadorRequest>>,
|
|
46
|
+
'mutationFn'
|
|
47
|
+
>
|
|
48
|
+
): UseMutationResult<AmbassadorResponse, Error, Partial<UpdateAmbassadorRequest>> {
|
|
49
|
+
const queryClient = useQueryClient();
|
|
50
|
+
|
|
51
|
+
return useMutation({
|
|
52
|
+
mutationFn: async (data: Partial<UpdateAmbassadorRequest>): Promise<AmbassadorResponse> => {
|
|
53
|
+
const client = getApiClient();
|
|
54
|
+
const response = await client.put('/api/v1/ambassadors/me', data);
|
|
55
|
+
return response.data;
|
|
56
|
+
},
|
|
57
|
+
onSuccess: (ambassador) => {
|
|
58
|
+
queryClient.setQueryData(ambassadorKeys.me(), ambassador);
|
|
59
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.lists() });
|
|
60
|
+
},
|
|
61
|
+
...options,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Update ambassador (admin)
|
|
67
|
+
*/
|
|
68
|
+
export function useUpdateAmbassador(
|
|
69
|
+
options?: Omit<
|
|
70
|
+
UseMutationOptions<AmbassadorResponse, Error, { id: string; data: UpdateAmbassadorRequest }>,
|
|
71
|
+
'mutationFn'
|
|
72
|
+
>
|
|
73
|
+
): UseMutationResult<AmbassadorResponse, Error, { id: string; data: UpdateAmbassadorRequest }> {
|
|
74
|
+
const queryClient = useQueryClient();
|
|
75
|
+
|
|
76
|
+
return useMutation({
|
|
77
|
+
mutationFn: async ({ id, data }): Promise<AmbassadorResponse> => {
|
|
78
|
+
const client = getApiClient();
|
|
79
|
+
const response = await client.put(`/api/v1/ambassadors/${id}`, data);
|
|
80
|
+
return response.data;
|
|
81
|
+
},
|
|
82
|
+
onSuccess: (ambassador, { id }) => {
|
|
83
|
+
queryClient.setQueryData(ambassadorKeys.detail(id), ambassador);
|
|
84
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.lists() });
|
|
85
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.pending() });
|
|
86
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.leaderboard() });
|
|
87
|
+
},
|
|
88
|
+
...options,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Approve ambassador application (admin)
|
|
94
|
+
*/
|
|
95
|
+
export function useApproveAmbassador(
|
|
96
|
+
options?: Omit<UseMutationOptions<AmbassadorResponse, Error, string>, 'mutationFn'>
|
|
97
|
+
): UseMutationResult<AmbassadorResponse, Error, string> {
|
|
98
|
+
const queryClient = useQueryClient();
|
|
99
|
+
|
|
100
|
+
return useMutation({
|
|
101
|
+
mutationFn: async (id: string): Promise<AmbassadorResponse> => {
|
|
102
|
+
const client = getApiClient();
|
|
103
|
+
const response = await client.post(`/api/v1/ambassadors/${id}/approve`);
|
|
104
|
+
return response.data;
|
|
105
|
+
},
|
|
106
|
+
onSuccess: (ambassador, id) => {
|
|
107
|
+
queryClient.setQueryData(ambassadorKeys.detail(id), ambassador);
|
|
108
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.lists() });
|
|
109
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.pending() });
|
|
110
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.leaderboard() });
|
|
111
|
+
},
|
|
112
|
+
...options,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reject ambassador application (admin)
|
|
118
|
+
*/
|
|
119
|
+
export function useRejectAmbassador(
|
|
120
|
+
options?: Omit<UseMutationOptions<{ success: boolean }, Error, string>, 'mutationFn'>
|
|
121
|
+
): UseMutationResult<{ success: boolean }, Error, string> {
|
|
122
|
+
const queryClient = useQueryClient();
|
|
123
|
+
|
|
124
|
+
return useMutation({
|
|
125
|
+
mutationFn: async (id: string): Promise<{ success: boolean }> => {
|
|
126
|
+
const client = getApiClient();
|
|
127
|
+
const response = await client.post(`/api/v1/ambassadors/${id}/reject`);
|
|
128
|
+
return response.data;
|
|
129
|
+
},
|
|
130
|
+
onSuccess: (_, id) => {
|
|
131
|
+
queryClient.removeQueries({ queryKey: ambassadorKeys.detail(id) });
|
|
132
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.lists() });
|
|
133
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.pending() });
|
|
134
|
+
},
|
|
135
|
+
...options,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Deactivate ambassador (admin)
|
|
141
|
+
*/
|
|
142
|
+
export function useDeactivateAmbassador(
|
|
143
|
+
options?: Omit<UseMutationOptions<AmbassadorResponse, Error, string>, 'mutationFn'>
|
|
144
|
+
): UseMutationResult<AmbassadorResponse, Error, string> {
|
|
145
|
+
const queryClient = useQueryClient();
|
|
146
|
+
|
|
147
|
+
return useMutation({
|
|
148
|
+
mutationFn: async (id: string): Promise<AmbassadorResponse> => {
|
|
149
|
+
const client = getApiClient();
|
|
150
|
+
const response = await client.post(`/api/v1/ambassadors/${id}/deactivate`);
|
|
151
|
+
return response.data;
|
|
152
|
+
},
|
|
153
|
+
onSuccess: (ambassador, id) => {
|
|
154
|
+
queryClient.setQueryData(ambassadorKeys.detail(id), ambassador);
|
|
155
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.lists() });
|
|
156
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.leaderboard() });
|
|
157
|
+
},
|
|
158
|
+
...options,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Add reward points to ambassador (admin)
|
|
164
|
+
*/
|
|
165
|
+
export function useAddAmbassadorReward(
|
|
166
|
+
options?: Omit<
|
|
167
|
+
UseMutationOptions<{ success: boolean }, Error, { id: string; points: number; reason?: string }>,
|
|
168
|
+
'mutationFn'
|
|
169
|
+
>
|
|
170
|
+
): UseMutationResult<{ success: boolean }, Error, { id: string; points: number; reason?: string }> {
|
|
171
|
+
const queryClient = useQueryClient();
|
|
172
|
+
|
|
173
|
+
return useMutation({
|
|
174
|
+
mutationFn: async ({ id, points, reason }): Promise<{ success: boolean }> => {
|
|
175
|
+
const client = getApiClient();
|
|
176
|
+
const response = await client.post(`/api/v1/ambassadors/${id}/reward`, { points, reason });
|
|
177
|
+
return response.data;
|
|
178
|
+
},
|
|
179
|
+
onSuccess: (_, { id }) => {
|
|
180
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.detail(id) });
|
|
181
|
+
queryClient.invalidateQueries({ queryKey: ambassadorKeys.leaderboard() });
|
|
182
|
+
},
|
|
183
|
+
...options,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Mutation Hooks
|
|
3
|
+
*
|
|
4
|
+
* TanStack Query mutation hooks for authentication-related write operations.
|
|
5
|
+
* These hooks handle user registration, login, token refresh, and Firebase authentication.
|
|
6
|
+
*
|
|
7
|
+
* @module api/mutations/auth
|
|
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 {
|
|
14
|
+
RegisterRequest,
|
|
15
|
+
LoginRequest,
|
|
16
|
+
RefreshTokenRequest,
|
|
17
|
+
FirebaseAuthRequest,
|
|
18
|
+
AuthResponse,
|
|
19
|
+
TokenResponse,
|
|
20
|
+
} from '../types';
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// MUTATION HOOKS
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a new user account
|
|
28
|
+
*
|
|
29
|
+
* @description
|
|
30
|
+
* Creates a new user account with email/phone and password.
|
|
31
|
+
* Returns authentication tokens and user information upon successful registration.
|
|
32
|
+
*
|
|
33
|
+
* @endpoint POST /auth/register
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { useRegister } from '@growsober/sdk';
|
|
38
|
+
*
|
|
39
|
+
* function RegisterForm() {
|
|
40
|
+
* const { mutate: register, isPending, error } = useRegister({
|
|
41
|
+
* onSuccess: (data) => {
|
|
42
|
+
* // Store tokens securely
|
|
43
|
+
* await SecureStore.setItemAsync('accessToken', data.accessToken);
|
|
44
|
+
* await SecureStore.setItemAsync('refreshToken', data.refreshToken);
|
|
45
|
+
* navigation.navigate('Onboarding');
|
|
46
|
+
* },
|
|
47
|
+
* onError: (error) => {
|
|
48
|
+
* Alert.alert('Registration failed', error.message);
|
|
49
|
+
* },
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* const handleSubmit = () => {
|
|
53
|
+
* register({
|
|
54
|
+
* email: 'user@example.com',
|
|
55
|
+
* password: 'SecurePassword123!',
|
|
56
|
+
* name: 'John Doe',
|
|
57
|
+
* });
|
|
58
|
+
* };
|
|
59
|
+
*
|
|
60
|
+
* return <Button onPress={handleSubmit} disabled={isPending} />;
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @param options - TanStack Query mutation options
|
|
65
|
+
* @returns TanStack Query mutation result
|
|
66
|
+
*/
|
|
67
|
+
export function useRegister(
|
|
68
|
+
options?: Omit<UseMutationOptions<AuthResponse, Error, RegisterRequest>, 'mutationFn'>
|
|
69
|
+
): UseMutationResult<AuthResponse, Error, RegisterRequest> {
|
|
70
|
+
const queryClient = useQueryClient();
|
|
71
|
+
|
|
72
|
+
return useMutation({
|
|
73
|
+
mutationFn: async (data: RegisterRequest): Promise<AuthResponse> => {
|
|
74
|
+
const client = getApiClient();
|
|
75
|
+
const response = await client.post<AuthResponse>('/auth/register', data);
|
|
76
|
+
return response.data;
|
|
77
|
+
},
|
|
78
|
+
onSuccess: (data, variables, context) => {
|
|
79
|
+
// Invalidate current user query to trigger refetch with new token
|
|
80
|
+
queryClient.invalidateQueries({ queryKey: userKeys.me() });
|
|
81
|
+
// User's onSuccess is handled by spreading options
|
|
82
|
+
},
|
|
83
|
+
...options,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Login with email/phone and password
|
|
89
|
+
*
|
|
90
|
+
* @description
|
|
91
|
+
* Authenticates an existing user with their credentials.
|
|
92
|
+
* Returns authentication tokens and user information upon successful login.
|
|
93
|
+
*
|
|
94
|
+
* @endpoint POST /auth/login
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* import { useLogin } from '@growsober/sdk';
|
|
99
|
+
*
|
|
100
|
+
* function LoginForm() {
|
|
101
|
+
* const { mutate: login, isPending, error } = useLogin({
|
|
102
|
+
* onSuccess: (data) => {
|
|
103
|
+
* // Store tokens securely
|
|
104
|
+
* await SecureStore.setItemAsync('accessToken', data.accessToken);
|
|
105
|
+
* await SecureStore.setItemAsync('refreshToken', data.refreshToken);
|
|
106
|
+
* navigation.navigate('Home');
|
|
107
|
+
* },
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* const handleSubmit = () => {
|
|
111
|
+
* login({
|
|
112
|
+
* email: 'user@example.com',
|
|
113
|
+
* password: 'SecurePassword123!',
|
|
114
|
+
* });
|
|
115
|
+
* };
|
|
116
|
+
*
|
|
117
|
+
* return (
|
|
118
|
+
* <form onSubmit={handleSubmit}>
|
|
119
|
+
* <input type="email" name="email" />
|
|
120
|
+
* <input type="password" name="password" />
|
|
121
|
+
* <button type="submit" disabled={isPending}>
|
|
122
|
+
* {isPending ? 'Logging in...' : 'Login'}
|
|
123
|
+
* </button>
|
|
124
|
+
* {error && <p className="error">{error.message}</p>}
|
|
125
|
+
* </form>
|
|
126
|
+
* );
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* Login with phone number:
|
|
132
|
+
* ```tsx
|
|
133
|
+
* login({
|
|
134
|
+
* phone: '+1234567890',
|
|
135
|
+
* password: 'SecurePassword123!',
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @param options - TanStack Query mutation options
|
|
140
|
+
* @returns TanStack Query mutation result
|
|
141
|
+
*/
|
|
142
|
+
export function useLogin(
|
|
143
|
+
options?: Omit<UseMutationOptions<AuthResponse, Error, LoginRequest>, 'mutationFn'>
|
|
144
|
+
): UseMutationResult<AuthResponse, Error, LoginRequest> {
|
|
145
|
+
const queryClient = useQueryClient();
|
|
146
|
+
|
|
147
|
+
return useMutation({
|
|
148
|
+
mutationFn: async (data: LoginRequest): Promise<AuthResponse> => {
|
|
149
|
+
const client = getApiClient();
|
|
150
|
+
const response = await client.post<AuthResponse>('/auth/login', data);
|
|
151
|
+
return response.data;
|
|
152
|
+
},
|
|
153
|
+
onSuccess: (data, variables, context) => {
|
|
154
|
+
// Invalidate current user query to trigger refetch with new token
|
|
155
|
+
queryClient.invalidateQueries({ queryKey: userKeys.me() });
|
|
156
|
+
// User's onSuccess is handled by spreading options
|
|
157
|
+
},
|
|
158
|
+
...options,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Refresh access token using refresh token
|
|
164
|
+
*
|
|
165
|
+
* @description
|
|
166
|
+
* Obtains a new access token using a valid refresh token.
|
|
167
|
+
* Should be called when the access token expires.
|
|
168
|
+
*
|
|
169
|
+
* @endpoint POST /auth/refresh
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```tsx
|
|
173
|
+
* import { useRefreshAuthToken } from '@growsober/sdk';
|
|
174
|
+
*
|
|
175
|
+
* function useTokenRefresh() {
|
|
176
|
+
* const { mutateAsync: refreshToken } = useRefreshAuthToken();
|
|
177
|
+
*
|
|
178
|
+
* const handleTokenExpired = async () => {
|
|
179
|
+
* const storedRefreshToken = await SecureStore.getItemAsync('refreshToken');
|
|
180
|
+
*
|
|
181
|
+
* if (!storedRefreshToken) {
|
|
182
|
+
* navigation.navigate('Login');
|
|
183
|
+
* return;
|
|
184
|
+
* }
|
|
185
|
+
*
|
|
186
|
+
* try {
|
|
187
|
+
* const { accessToken, refreshToken: newRefreshToken } = await refreshToken({
|
|
188
|
+
* refreshToken: storedRefreshToken,
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* // Store new tokens
|
|
192
|
+
* await SecureStore.setItemAsync('accessToken', accessToken);
|
|
193
|
+
* await SecureStore.setItemAsync('refreshToken', newRefreshToken);
|
|
194
|
+
* } catch (error) {
|
|
195
|
+
* // Refresh token is invalid or expired
|
|
196
|
+
* navigation.navigate('Login');
|
|
197
|
+
* }
|
|
198
|
+
* };
|
|
199
|
+
*
|
|
200
|
+
* return { handleTokenExpired };
|
|
201
|
+
* }
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* Integrate with SDK configuration:
|
|
206
|
+
* ```tsx
|
|
207
|
+
* import { configureSDK } from '@growsober/sdk';
|
|
208
|
+
*
|
|
209
|
+
* configureSDK({
|
|
210
|
+
* baseURL: 'https://api.growsober.app',
|
|
211
|
+
* getAccessToken: async () => {
|
|
212
|
+
* return await SecureStore.getItemAsync('accessToken');
|
|
213
|
+
* },
|
|
214
|
+
* refreshAccessToken: async () => {
|
|
215
|
+
* const refreshToken = await SecureStore.getItemAsync('refreshToken');
|
|
216
|
+
* const { accessToken, refreshToken: newRefreshToken } = await fetch('/auth/refresh', {
|
|
217
|
+
* method: 'POST',
|
|
218
|
+
* body: JSON.stringify({ refreshToken }),
|
|
219
|
+
* }).then(r => r.json());
|
|
220
|
+
*
|
|
221
|
+
* await SecureStore.setItemAsync('accessToken', accessToken);
|
|
222
|
+
* await SecureStore.setItemAsync('refreshToken', newRefreshToken);
|
|
223
|
+
*
|
|
224
|
+
* return accessToken;
|
|
225
|
+
* },
|
|
226
|
+
* onUnauthorized: () => {
|
|
227
|
+
* navigation.navigate('Login');
|
|
228
|
+
* },
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* @param options - TanStack Query mutation options
|
|
233
|
+
* @returns TanStack Query mutation result
|
|
234
|
+
*/
|
|
235
|
+
export function useRefreshAuthToken(
|
|
236
|
+
options?: Omit<UseMutationOptions<TokenResponse, Error, RefreshTokenRequest>, 'mutationFn'>
|
|
237
|
+
): UseMutationResult<TokenResponse, Error, RefreshTokenRequest> {
|
|
238
|
+
return useMutation({
|
|
239
|
+
mutationFn: async (data: RefreshTokenRequest): Promise<TokenResponse> => {
|
|
240
|
+
const client = getApiClient();
|
|
241
|
+
const response = await client.post<TokenResponse>('/auth/refresh', data);
|
|
242
|
+
return response.data;
|
|
243
|
+
},
|
|
244
|
+
...options,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Authenticate with Firebase ID token
|
|
250
|
+
*
|
|
251
|
+
* @description
|
|
252
|
+
* Authenticates a user using a Firebase ID token.
|
|
253
|
+
* Creates a new user account if one doesn't exist, or logs in an existing user.
|
|
254
|
+
* Returns GrowSober authentication tokens and user information.
|
|
255
|
+
*
|
|
256
|
+
* @endpoint POST /auth/firebase
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```tsx
|
|
260
|
+
* import { useFirebaseAuth } from '@growsober/sdk';
|
|
261
|
+
* import { signInWithPhoneNumber } from 'firebase/auth';
|
|
262
|
+
*
|
|
263
|
+
* function PhoneAuthScreen() {
|
|
264
|
+
* const { mutate: firebaseAuth, isPending } = useFirebaseAuth({
|
|
265
|
+
* onSuccess: (data) => {
|
|
266
|
+
* // Store GrowSober tokens
|
|
267
|
+
* await SecureStore.setItemAsync('accessToken', data.accessToken);
|
|
268
|
+
* await SecureStore.setItemAsync('refreshToken', data.refreshToken);
|
|
269
|
+
*
|
|
270
|
+
* if (data.user.onboardingCompleted) {
|
|
271
|
+
* navigation.navigate('Home');
|
|
272
|
+
* } else {
|
|
273
|
+
* navigation.navigate('Onboarding');
|
|
274
|
+
* }
|
|
275
|
+
* },
|
|
276
|
+
* onError: (error) => {
|
|
277
|
+
* Alert.alert('Authentication failed', error.message);
|
|
278
|
+
* },
|
|
279
|
+
* });
|
|
280
|
+
*
|
|
281
|
+
* const handlePhoneAuth = async (phoneNumber: string) => {
|
|
282
|
+
* try {
|
|
283
|
+
* // Firebase authentication flow
|
|
284
|
+
* const confirmation = await signInWithPhoneNumber(auth, phoneNumber);
|
|
285
|
+
* const code = await promptUserForCode(); // Your UI to get verification code
|
|
286
|
+
* const credential = await confirmation.confirm(code);
|
|
287
|
+
*
|
|
288
|
+
* // Get Firebase ID token
|
|
289
|
+
* const idToken = await credential.user.getIdToken();
|
|
290
|
+
*
|
|
291
|
+
* // Authenticate with GrowSober backend
|
|
292
|
+
* firebaseAuth({ idToken });
|
|
293
|
+
* } catch (error) {
|
|
294
|
+
* console.error('Phone auth error:', error);
|
|
295
|
+
* }
|
|
296
|
+
* };
|
|
297
|
+
*
|
|
298
|
+
* return <PhoneInput onSubmit={handlePhoneAuth} disabled={isPending} />;
|
|
299
|
+
* }
|
|
300
|
+
* ```
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* With Google Sign-In:
|
|
304
|
+
* ```tsx
|
|
305
|
+
* import { GoogleSignin } from '@react-native-google-signin/google-signin';
|
|
306
|
+
*
|
|
307
|
+
* const handleGoogleSignIn = async () => {
|
|
308
|
+
* const { idToken } = await GoogleSignin.signIn();
|
|
309
|
+
* firebaseAuth({ idToken });
|
|
310
|
+
* };
|
|
311
|
+
* ```
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* With Apple Sign-In:
|
|
315
|
+
* ```tsx
|
|
316
|
+
* import * as AppleAuthentication from 'expo-apple-authentication';
|
|
317
|
+
*
|
|
318
|
+
* const handleAppleSignIn = async () => {
|
|
319
|
+
* const credential = await AppleAuthentication.signInAsync({
|
|
320
|
+
* requestedScopes: [
|
|
321
|
+
* AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
322
|
+
* AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
|
323
|
+
* ],
|
|
324
|
+
* });
|
|
325
|
+
* firebaseAuth({ idToken: credential.identityToken });
|
|
326
|
+
* };
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
329
|
+
* @param options - TanStack Query mutation options
|
|
330
|
+
* @returns TanStack Query mutation result
|
|
331
|
+
*/
|
|
332
|
+
export function useFirebaseAuth(
|
|
333
|
+
options?: Omit<UseMutationOptions<AuthResponse, Error, FirebaseAuthRequest>, 'mutationFn'>
|
|
334
|
+
): UseMutationResult<AuthResponse, Error, FirebaseAuthRequest> {
|
|
335
|
+
const queryClient = useQueryClient();
|
|
336
|
+
|
|
337
|
+
return useMutation({
|
|
338
|
+
mutationFn: async (data: FirebaseAuthRequest): Promise<AuthResponse> => {
|
|
339
|
+
const client = getApiClient();
|
|
340
|
+
const response = await client.post<AuthResponse>('/auth/firebase', data);
|
|
341
|
+
return response.data;
|
|
342
|
+
},
|
|
343
|
+
onSuccess: (data, variables, context) => {
|
|
344
|
+
// Invalidate current user query to trigger refetch with new token
|
|
345
|
+
queryClient.invalidateQueries({ queryKey: userKeys.me() });
|
|
346
|
+
// User's onSuccess is handled by spreading options
|
|
347
|
+
},
|
|
348
|
+
...options,
|
|
349
|
+
});
|
|
350
|
+
}
|