@safercity/sdk-react 0.1.3 → 0.2.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 +53 -8
- package/dist/index.cjs +136 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -31
- package/dist/index.d.ts +226 -31
- package/dist/index.js +126 -25
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
React hooks and components for SaferCity SDK with TanStack Query integration.
|
|
4
4
|
|
|
5
|
+
## What's New in v0.2.0
|
|
6
|
+
|
|
7
|
+
- **Panic Information Hooks** - New hooks for managing user panic profiles and emergency contacts.
|
|
8
|
+
- **User Scoping** - `SaferCityProvider` now supports `userId` for automatic request scoping.
|
|
9
|
+
- **Enhanced Crime Hooks** - Added `useBanner` and `useCrimeCategoriesWithTypes`.
|
|
10
|
+
- **Security Hardening** - Removed admin-only hooks (`useUsers`, `useDeleteUser`, `useSubscriptionStats`).
|
|
11
|
+
- **Path Alignment** - Hooks now use the latest singular API paths.
|
|
12
|
+
|
|
5
13
|
## What's New in v0.1.3
|
|
6
14
|
|
|
7
15
|
- **Security hardening** - Removed `usePanics` and `useSubscriptionStats` hooks (client-side listing/stats are now restricted)
|
|
@@ -49,6 +57,7 @@ function App() {
|
|
|
49
57
|
mode="direct"
|
|
50
58
|
baseUrl="https://api.safercity.com"
|
|
51
59
|
tenantId="tenant-123"
|
|
60
|
+
userId="user-123" // optional, for auto-scoping
|
|
52
61
|
getAccessToken={() => session?.accessToken}
|
|
53
62
|
>
|
|
54
63
|
<YourApp />
|
|
@@ -57,6 +66,27 @@ function App() {
|
|
|
57
66
|
}
|
|
58
67
|
```
|
|
59
68
|
|
|
69
|
+
### Cookie Mode
|
|
70
|
+
|
|
71
|
+
Browser with `credentials: include`. For first-party web apps using session cookies.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { SaferCityProvider } from '@safercity/sdk-react';
|
|
75
|
+
|
|
76
|
+
function App() {
|
|
77
|
+
return (
|
|
78
|
+
<SaferCityProvider
|
|
79
|
+
mode="cookie"
|
|
80
|
+
baseUrl="https://api.safercity.com"
|
|
81
|
+
userId="user-123" // optional
|
|
82
|
+
>
|
|
83
|
+
<YourApp />
|
|
84
|
+
</SaferCityProvider>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
|
|
60
90
|
The provider automatically refreshes the token every 30 seconds by calling `getAccessToken`.
|
|
61
91
|
|
|
62
92
|
### Cookie Mode
|
|
@@ -137,16 +167,16 @@ interface SessionState {
|
|
|
137
167
|
## Using Hooks
|
|
138
168
|
|
|
139
169
|
```tsx
|
|
140
|
-
import {
|
|
170
|
+
import { useUser, useCreatePanic } from '@safercity/sdk-react';
|
|
141
171
|
|
|
142
|
-
function Dashboard() {
|
|
143
|
-
const { data:
|
|
172
|
+
function Dashboard({ userId }: { userId: string }) {
|
|
173
|
+
const { data: user, isLoading } = useUser(userId);
|
|
144
174
|
|
|
145
175
|
if (isLoading) return <div>Loading...</div>;
|
|
146
176
|
|
|
147
177
|
return (
|
|
148
178
|
<div>
|
|
149
|
-
<h1>
|
|
179
|
+
<h1>Welcome, {user?.data.firstName}</h1>
|
|
150
180
|
</div>
|
|
151
181
|
);
|
|
152
182
|
}
|
|
@@ -184,31 +214,46 @@ function PanicButton({ userId }: { userId: string }) {
|
|
|
184
214
|
- `useWhoAmI()` - Current auth context
|
|
185
215
|
|
|
186
216
|
### Users
|
|
187
|
-
- `
|
|
188
|
-
- `useUser(userId)` - Get user by ID
|
|
217
|
+
- `useUser(userId?)` - Get user by ID (defaults to client's `userId`)
|
|
189
218
|
- `useCreateUser()` - Create user mutation
|
|
190
219
|
- `useUpdateUser()` - Update user mutation
|
|
191
|
-
- `useDeleteUser()` - Delete user mutation
|
|
192
220
|
|
|
193
221
|
### Panics
|
|
194
222
|
- `usePanic(panicId, query?)` - Get panic by ID
|
|
195
223
|
- `useCreatePanic()` - Create panic mutation
|
|
196
224
|
- `useUpdatePanicLocation()` - Update location mutation
|
|
197
225
|
- `useCancelPanic()` - Cancel panic mutation
|
|
226
|
+
- `usePanicTypes(userId?)` - Get available panic types for a user
|
|
198
227
|
- `usePanicStream(panicId, options?)` - Stream panic updates
|
|
199
228
|
|
|
229
|
+
### Panic Information
|
|
230
|
+
- `usePanicInformation(id)` - Get profile by ID
|
|
231
|
+
- `usePanicInformationByUser(userId?)` - Get profile by user ID
|
|
232
|
+
- `usePanicEligibility(userId?)` - Check user eligibility
|
|
233
|
+
- `useCreatePanicInformation()` - Create profile mutation
|
|
234
|
+
- `useUpdatePanicInformation()` - Update profile mutation
|
|
235
|
+
- `useDeletePanicInformation()` - Delete profile mutation
|
|
236
|
+
|
|
200
237
|
### Subscriptions
|
|
201
238
|
- `useSubscriptionTypes()` - List subscription types
|
|
202
|
-
- `useSubscriptions(filters?)` - List subscriptions
|
|
239
|
+
- `useSubscriptions(filters?)` - List subscriptions for a user
|
|
203
240
|
- `useCreateSubscription()` - Create subscription mutation
|
|
241
|
+
- `useSubscribeUser()` - Subscribe user mutation
|
|
242
|
+
|
|
243
|
+
### Notifications
|
|
244
|
+
- `useBulkTriggerNotifications()` - Bulk trigger notifications mutation
|
|
204
245
|
|
|
205
246
|
### Location Safety
|
|
206
247
|
- `useLocationSafety(lat, lng, radius?)` - Check location safety
|
|
207
248
|
|
|
249
|
+
### Banner
|
|
250
|
+
- `useBanner(body)` - Get crime banner data for a location
|
|
251
|
+
|
|
208
252
|
### Crimes
|
|
209
253
|
- `useCrimes(filters?)` - List crimes
|
|
210
254
|
- `useCrimeCategories()` - Get crime categories
|
|
211
255
|
- `useCrimeTypes()` - Get crime types
|
|
256
|
+
- `useCrimeCategoriesWithTypes()` - Get categories with nested types
|
|
212
257
|
|
|
213
258
|
## Streaming Hook
|
|
214
259
|
|
package/dist/index.cjs
CHANGED
|
@@ -49,13 +49,15 @@ function SaferCityProvider(props) {
|
|
|
49
49
|
if (isDirectMode(props)) {
|
|
50
50
|
return sdk.createSaferCityClient({
|
|
51
51
|
baseUrl: props.baseUrl,
|
|
52
|
-
tenantId: props.tenantId
|
|
52
|
+
tenantId: props.tenantId,
|
|
53
|
+
userId: props.userId
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
if (isCookieMode(props)) {
|
|
56
57
|
return sdk.createSaferCityClient({
|
|
57
58
|
baseUrl: props.baseUrl,
|
|
58
59
|
tenantId: props.tenantId,
|
|
60
|
+
userId: props.userId,
|
|
59
61
|
headers: {
|
|
60
62
|
// Note: cookies are sent automatically with fetch credentials: 'include'
|
|
61
63
|
}
|
|
@@ -92,6 +94,7 @@ function SaferCityProvider(props) {
|
|
|
92
94
|
if (!isMounted) return;
|
|
93
95
|
if (response.ok) {
|
|
94
96
|
const data = await response.json();
|
|
97
|
+
client.setUserId(data.userId);
|
|
95
98
|
setSession({
|
|
96
99
|
isAuthenticated: data.authenticated,
|
|
97
100
|
isLoading: false,
|
|
@@ -118,7 +121,7 @@ function SaferCityProvider(props) {
|
|
|
118
121
|
return () => {
|
|
119
122
|
isMounted = false;
|
|
120
123
|
};
|
|
121
|
-
}, [mode, props]);
|
|
124
|
+
}, [mode, props, client]);
|
|
122
125
|
const createSession = react.useCallback(async (token, tenantId) => {
|
|
123
126
|
if (mode !== "cookie" || !isCookieMode(props)) {
|
|
124
127
|
throw new Error("createSession is only available in cookie mode");
|
|
@@ -253,12 +256,17 @@ var saferCityKeys = {
|
|
|
253
256
|
authWhoami: () => [...saferCityKeys.auth(), "whoami"],
|
|
254
257
|
// Users
|
|
255
258
|
users: () => [...saferCityKeys.all, "users"],
|
|
256
|
-
usersList: (filters) => [...saferCityKeys.users(), "list", filters],
|
|
257
259
|
usersDetail: (userId) => [...saferCityKeys.users(), "detail", userId],
|
|
258
260
|
// Panics
|
|
259
261
|
panics: () => [...saferCityKeys.all, "panics"],
|
|
260
262
|
panicsList: (filters) => [...saferCityKeys.panics(), "list", filters],
|
|
261
263
|
panicsDetail: (panicId) => [...saferCityKeys.panics(), "detail", panicId],
|
|
264
|
+
panicTypes: (userId) => [...saferCityKeys.panics(), "types", userId],
|
|
265
|
+
// Panic Information
|
|
266
|
+
panicInformation: () => [...saferCityKeys.all, "panicInformation"],
|
|
267
|
+
panicInformationDetail: (id) => [...saferCityKeys.panicInformation(), "detail", id],
|
|
268
|
+
panicInformationByUser: (userId) => [...saferCityKeys.panicInformation(), "byUser", userId],
|
|
269
|
+
panicEligibility: (userId) => [...saferCityKeys.panicInformation(), "eligibility", userId],
|
|
262
270
|
// Subscriptions
|
|
263
271
|
subscriptions: () => [...saferCityKeys.all, "subscriptions"],
|
|
264
272
|
subscriptionsList: (filters) => [...saferCityKeys.subscriptions(), "list", filters],
|
|
@@ -266,11 +274,14 @@ var saferCityKeys = {
|
|
|
266
274
|
subscriptionsStats: () => [...saferCityKeys.subscriptions(), "stats"],
|
|
267
275
|
// Location Safety
|
|
268
276
|
locationSafety: (lat, lng) => [...saferCityKeys.all, "location-safety", lat, lng],
|
|
277
|
+
// Banner
|
|
278
|
+
banner: (body) => [...saferCityKeys.all, "banner", body],
|
|
269
279
|
// Crimes
|
|
270
280
|
crimes: () => [...saferCityKeys.all, "crimes"],
|
|
271
281
|
crimesList: (filters) => [...saferCityKeys.crimes(), "list", filters],
|
|
272
282
|
crimesCategories: () => [...saferCityKeys.crimes(), "categories"],
|
|
273
|
-
crimesTypes: () => [...saferCityKeys.crimes(), "types"]
|
|
283
|
+
crimesTypes: () => [...saferCityKeys.crimes(), "types"],
|
|
284
|
+
crimeCategoriesWithTypes: () => [...saferCityKeys.crimes(), "categoriesWithTypes"]
|
|
274
285
|
};
|
|
275
286
|
function useHealthCheck(options) {
|
|
276
287
|
const client = useSaferCityClient();
|
|
@@ -288,14 +299,6 @@ function useWhoAmI(options) {
|
|
|
288
299
|
...options
|
|
289
300
|
});
|
|
290
301
|
}
|
|
291
|
-
function useUsers(filters, options) {
|
|
292
|
-
const client = useSaferCityClient();
|
|
293
|
-
return reactQuery.useQuery({
|
|
294
|
-
queryKey: saferCityKeys.usersList(filters),
|
|
295
|
-
queryFn: () => client.users.list(filters),
|
|
296
|
-
...options
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
302
|
function useUser(userId, options) {
|
|
300
303
|
const client = useSaferCityClient();
|
|
301
304
|
return reactQuery.useQuery({
|
|
@@ -323,18 +326,6 @@ function useUpdateUser(options) {
|
|
|
323
326
|
mutationFn: ({ userId, data }) => client.users.update(userId, data),
|
|
324
327
|
onSuccess: (_, { userId }) => {
|
|
325
328
|
queryClient.invalidateQueries({ queryKey: saferCityKeys.usersDetail(userId) });
|
|
326
|
-
queryClient.invalidateQueries({ queryKey: saferCityKeys.usersList() });
|
|
327
|
-
},
|
|
328
|
-
...options
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function useDeleteUser(options) {
|
|
332
|
-
const client = useSaferCityClient();
|
|
333
|
-
const queryClient = reactQuery.useQueryClient();
|
|
334
|
-
return reactQuery.useMutation({
|
|
335
|
-
mutationFn: (userId) => client.users.delete(userId),
|
|
336
|
-
onSuccess: () => {
|
|
337
|
-
queryClient.invalidateQueries({ queryKey: saferCityKeys.users() });
|
|
338
329
|
},
|
|
339
330
|
...options
|
|
340
331
|
});
|
|
@@ -382,6 +373,77 @@ function useCancelPanic(options) {
|
|
|
382
373
|
...options
|
|
383
374
|
});
|
|
384
375
|
}
|
|
376
|
+
function usePanicTypes(userId, options) {
|
|
377
|
+
const client = useSaferCityClient();
|
|
378
|
+
return reactQuery.useQuery({
|
|
379
|
+
queryKey: saferCityKeys.panicTypes(userId),
|
|
380
|
+
queryFn: () => client.panics.types(userId),
|
|
381
|
+
enabled: !!userId,
|
|
382
|
+
staleTime: 1e3 * 60 * 10,
|
|
383
|
+
// 10 minutes - types don't change often
|
|
384
|
+
...options
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
function usePanicInformation(id, options) {
|
|
388
|
+
const client = useSaferCityClient();
|
|
389
|
+
return reactQuery.useQuery({
|
|
390
|
+
queryKey: saferCityKeys.panicInformationDetail(id),
|
|
391
|
+
queryFn: () => client.panicInformation.get(id),
|
|
392
|
+
enabled: !!id,
|
|
393
|
+
...options
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function usePanicInformationByUser(userId, options) {
|
|
397
|
+
const client = useSaferCityClient();
|
|
398
|
+
return reactQuery.useQuery({
|
|
399
|
+
queryKey: saferCityKeys.panicInformationByUser(userId),
|
|
400
|
+
queryFn: () => client.panicInformation.getByUser(userId),
|
|
401
|
+
enabled: !!userId,
|
|
402
|
+
...options
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function usePanicEligibility(userId, options) {
|
|
406
|
+
const client = useSaferCityClient();
|
|
407
|
+
return reactQuery.useQuery({
|
|
408
|
+
queryKey: saferCityKeys.panicEligibility(userId),
|
|
409
|
+
queryFn: () => client.panicInformation.validateEligibility(userId),
|
|
410
|
+
enabled: !!userId,
|
|
411
|
+
...options
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function useCreatePanicInformation(options) {
|
|
415
|
+
const client = useSaferCityClient();
|
|
416
|
+
const queryClient = reactQuery.useQueryClient();
|
|
417
|
+
return reactQuery.useMutation({
|
|
418
|
+
mutationFn: (data) => client.panicInformation.create(data),
|
|
419
|
+
onSuccess: () => {
|
|
420
|
+
queryClient.invalidateQueries({ queryKey: saferCityKeys.panicInformation() });
|
|
421
|
+
},
|
|
422
|
+
...options
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function useUpdatePanicInformation(options) {
|
|
426
|
+
const client = useSaferCityClient();
|
|
427
|
+
const queryClient = reactQuery.useQueryClient();
|
|
428
|
+
return reactQuery.useMutation({
|
|
429
|
+
mutationFn: ({ id, data }) => client.panicInformation.update(id, data),
|
|
430
|
+
onSuccess: () => {
|
|
431
|
+
queryClient.invalidateQueries({ queryKey: saferCityKeys.panicInformation() });
|
|
432
|
+
},
|
|
433
|
+
...options
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function useDeletePanicInformation(options) {
|
|
437
|
+
const client = useSaferCityClient();
|
|
438
|
+
const queryClient = reactQuery.useQueryClient();
|
|
439
|
+
return reactQuery.useMutation({
|
|
440
|
+
mutationFn: (id) => client.panicInformation.delete(id),
|
|
441
|
+
onSuccess: () => {
|
|
442
|
+
queryClient.invalidateQueries({ queryKey: saferCityKeys.panicInformation() });
|
|
443
|
+
},
|
|
444
|
+
...options
|
|
445
|
+
});
|
|
446
|
+
}
|
|
385
447
|
function useSubscriptionTypes(options) {
|
|
386
448
|
const client = useSaferCityClient();
|
|
387
449
|
return reactQuery.useQuery({
|
|
@@ -411,6 +473,24 @@ function useCreateSubscription(options) {
|
|
|
411
473
|
...options
|
|
412
474
|
});
|
|
413
475
|
}
|
|
476
|
+
function useSubscribeUser(options) {
|
|
477
|
+
const client = useSaferCityClient();
|
|
478
|
+
const queryClient = reactQuery.useQueryClient();
|
|
479
|
+
return reactQuery.useMutation({
|
|
480
|
+
mutationFn: (body) => client.subscriptions.subscribeUser(body),
|
|
481
|
+
onSuccess: () => {
|
|
482
|
+
queryClient.invalidateQueries({ queryKey: saferCityKeys.subscriptions() });
|
|
483
|
+
},
|
|
484
|
+
...options
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function useBulkTriggerNotifications(options) {
|
|
488
|
+
const client = useSaferCityClient();
|
|
489
|
+
return reactQuery.useMutation({
|
|
490
|
+
mutationFn: (body) => client.notifications.bulkTrigger(body),
|
|
491
|
+
...options
|
|
492
|
+
});
|
|
493
|
+
}
|
|
414
494
|
function useLocationSafety(latitude, longitude, radius, options) {
|
|
415
495
|
const client = useSaferCityClient();
|
|
416
496
|
return reactQuery.useQuery({
|
|
@@ -422,6 +502,17 @@ function useLocationSafety(latitude, longitude, radius, options) {
|
|
|
422
502
|
...options
|
|
423
503
|
});
|
|
424
504
|
}
|
|
505
|
+
function useBanner(body, options) {
|
|
506
|
+
const client = useSaferCityClient();
|
|
507
|
+
return reactQuery.useQuery({
|
|
508
|
+
queryKey: saferCityKeys.banner(body),
|
|
509
|
+
queryFn: () => client.banner.get(body),
|
|
510
|
+
enabled: body.latitude !== void 0 && body.longitude !== void 0,
|
|
511
|
+
staleTime: 1e3 * 60 * 5,
|
|
512
|
+
// 5 minutes - location-based, changes moderately
|
|
513
|
+
...options
|
|
514
|
+
});
|
|
515
|
+
}
|
|
425
516
|
function useCrimes(filters, options) {
|
|
426
517
|
const client = useSaferCityClient();
|
|
427
518
|
return reactQuery.useQuery({
|
|
@@ -450,6 +541,16 @@ function useCrimeTypes(options) {
|
|
|
450
541
|
...options
|
|
451
542
|
});
|
|
452
543
|
}
|
|
544
|
+
function useCrimeCategoriesWithTypes(options) {
|
|
545
|
+
const client = useSaferCityClient();
|
|
546
|
+
return reactQuery.useQuery({
|
|
547
|
+
queryKey: saferCityKeys.crimeCategoriesWithTypes(),
|
|
548
|
+
queryFn: () => client.crimes.categoriesWithTypes(),
|
|
549
|
+
staleTime: 1e3 * 60 * 30,
|
|
550
|
+
// 30 minutes - categories rarely change
|
|
551
|
+
...options
|
|
552
|
+
});
|
|
553
|
+
}
|
|
453
554
|
function usePanicStream(panicId, options = {}) {
|
|
454
555
|
const client = useSaferCityClient();
|
|
455
556
|
const {
|
|
@@ -647,29 +748,38 @@ function useStream(createStream, options = {}) {
|
|
|
647
748
|
exports.SaferCityProvider = SaferCityProvider;
|
|
648
749
|
exports.saferCityKeys = saferCityKeys;
|
|
649
750
|
exports.useAuthMode = useAuthMode;
|
|
751
|
+
exports.useBanner = useBanner;
|
|
752
|
+
exports.useBulkTriggerNotifications = useBulkTriggerNotifications;
|
|
650
753
|
exports.useCancelPanic = useCancelPanic;
|
|
651
754
|
exports.useCreatePanic = useCreatePanic;
|
|
755
|
+
exports.useCreatePanicInformation = useCreatePanicInformation;
|
|
652
756
|
exports.useCreateSubscription = useCreateSubscription;
|
|
653
757
|
exports.useCreateUser = useCreateUser;
|
|
654
758
|
exports.useCrimeCategories = useCrimeCategories;
|
|
759
|
+
exports.useCrimeCategoriesWithTypes = useCrimeCategoriesWithTypes;
|
|
655
760
|
exports.useCrimeTypes = useCrimeTypes;
|
|
656
761
|
exports.useCrimes = useCrimes;
|
|
657
|
-
exports.
|
|
762
|
+
exports.useDeletePanicInformation = useDeletePanicInformation;
|
|
658
763
|
exports.useHealthCheck = useHealthCheck;
|
|
659
764
|
exports.useLocationSafety = useLocationSafety;
|
|
660
765
|
exports.usePanic = usePanic;
|
|
766
|
+
exports.usePanicEligibility = usePanicEligibility;
|
|
767
|
+
exports.usePanicInformation = usePanicInformation;
|
|
768
|
+
exports.usePanicInformationByUser = usePanicInformationByUser;
|
|
661
769
|
exports.usePanicStream = usePanicStream;
|
|
770
|
+
exports.usePanicTypes = usePanicTypes;
|
|
662
771
|
exports.useSaferCity = useSaferCity;
|
|
663
772
|
exports.useSaferCityClient = useSaferCityClient;
|
|
664
773
|
exports.useSession = useSession;
|
|
665
774
|
exports.useSessionManager = useSessionManager;
|
|
666
775
|
exports.useStream = useStream;
|
|
776
|
+
exports.useSubscribeUser = useSubscribeUser;
|
|
667
777
|
exports.useSubscriptionTypes = useSubscriptionTypes;
|
|
668
778
|
exports.useSubscriptions = useSubscriptions;
|
|
779
|
+
exports.useUpdatePanicInformation = useUpdatePanicInformation;
|
|
669
780
|
exports.useUpdatePanicLocation = useUpdatePanicLocation;
|
|
670
781
|
exports.useUpdateUser = useUpdateUser;
|
|
671
782
|
exports.useUser = useUser;
|
|
672
|
-
exports.useUsers = useUsers;
|
|
673
783
|
exports.useWhoAmI = useWhoAmI;
|
|
674
784
|
Object.keys(sdk).forEach(function (k) {
|
|
675
785
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|