azirid-react 0.6.0 → 0.7.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 CHANGED
@@ -670,6 +670,89 @@ export default function PayphoneCallbackPage() {
670
670
 
671
671
  ---
672
672
 
673
+ ## Multi-tenant
674
+
675
+ Every authenticated user always has an active `tenantId` and `tenantRole` in the JWT. On signup, if no `tenantId` is provided, a personal tenant is automatically created and the user becomes `OWNER`.
676
+
677
+ ### Tenant switching via `useAzirid()`
678
+
679
+ ```tsx
680
+ import { useAzirid } from 'azirid-react'
681
+
682
+ function TenantSwitcher({ tenantId }: { tenantId: string }) {
683
+ const { user, switchTenant } = useAzirid()
684
+
685
+ return (
686
+ <div>
687
+ <p>Active tenant: {user?.tenantId} ({user?.tenantRole})</p>
688
+ <button onClick={() => switchTenant(tenantId)}>Switch</button>
689
+ </div>
690
+ )
691
+ }
692
+ ```
693
+
694
+ `switchTenant(tenantId)` calls the refresh endpoint with the new `tenantId`, updates the access token in memory, and invalidates all queries — no re-login required.
695
+
696
+ ### `useTenants`
697
+
698
+ List all tenants the authenticated user belongs to.
699
+
700
+ ```tsx
701
+ import { useTenants } from 'azirid-react'
702
+
703
+ function TenantList() {
704
+ const { data: tenants, isLoading } = useTenants()
705
+
706
+ if (isLoading) return <Spinner />
707
+
708
+ return (
709
+ <ul>
710
+ {tenants?.map((t) => (
711
+ <li key={t.tenantId}>
712
+ {t.name} — {t.role}
713
+ </li>
714
+ ))}
715
+ </ul>
716
+ )
717
+ }
718
+ ```
719
+
720
+ ### `useTenantMembers`
721
+
722
+ List all members of a specific tenant. The authenticated user must be a member.
723
+
724
+ ```tsx
725
+ import { useTenantMembers } from 'azirid-react'
726
+
727
+ function MemberList({ tenantId }: { tenantId: string }) {
728
+ const { data: members } = useTenantMembers(tenantId)
729
+
730
+ return (
731
+ <ul>
732
+ {members?.map((m) => (
733
+ <li key={m.id}>
734
+ {m.user.email} — {m.role}
735
+ </li>
736
+ ))}
737
+ </ul>
738
+ )
739
+ }
740
+ ```
741
+
742
+ ### `useSwitchTenant`
743
+
744
+ Headless hook for tenant switching (same as `switchTenant` from `useAzirid()`, but usable outside the main context hook).
745
+
746
+ ```tsx
747
+ import { useSwitchTenant } from 'azirid-react'
748
+
749
+ const { switchTenant } = useSwitchTenant()
750
+
751
+ await switchTenant('tenant-id-here')
752
+ ```
753
+
754
+ ---
755
+
673
756
  ## Referrals
674
757
 
675
758
  All referral hooks require `<AziridProvider>` in the tree and an authenticated user.
@@ -1255,7 +1338,7 @@ import type {
1255
1338
 
1256
1339
  | Type | Description |
1257
1340
  | --- | --- |
1258
- | `AuthUser` | Authenticated user object (`id`, `email`, `firstName`, `emailVerified`, `mfaEnabled`, `appId`, `tenantId`, ...) |
1341
+ | `AuthUser` | Authenticated user object (`id`, `email`, `tenantId`, `tenantRole`, `appId`, ...) — `tenantId` and `tenantRole` are always present |
1259
1342
  | `AuthSuccessResponse` | Login/signup response (`accessToken`, `user`) |
1260
1343
  | `AuthState` | Full auth state (`user`, `accessToken`, `isAuthenticated`, `isLoading`, `error`) |
1261
1344
  | `SignupData` | Signup payload (`email`, `password`, `acceptedTosVersion?`, `referralCode?`, ...) |
@@ -1264,6 +1347,14 @@ import type {
1264
1347
  | `AziridProviderProps` | All provider props (see table above) |
1265
1348
  | `AziridContextValue` | Context value returned by `useAzirid()` |
1266
1349
 
1350
+ ### Tenant Types
1351
+
1352
+ | Type | Description |
1353
+ | --- | --- |
1354
+ | `TenantWithRole` | Tenant membership (`tenantId`, `name`, `slug`, `role: 'OWNER' \| 'MEMBER'`, `joinedAt`) |
1355
+ | `TenantMemberInfo` | Tenant member (`id`, `role`, `joinedAt`, `user: { id, email, firstName, lastName }`) |
1356
+ | `CreateTenantData` | Create tenant payload (`name`, `description?`) |
1357
+
1267
1358
  ### Billing Types
1268
1359
 
1269
1360
  | Type | Description |
package/dist/index.cjs CHANGED
@@ -97,7 +97,8 @@ var SUFFIXES = {
97
97
  transferProofs: "billing/transfer-proofs",
98
98
  payphoneConfirm: "billing/payphone/confirm",
99
99
  referralMe: "referrals/me",
100
- referralStats: "referrals/stats"
100
+ referralStats: "referrals/stats",
101
+ userTenants: "tenants"
101
102
  };
102
103
  var BASE_PATHS = {
103
104
  /** Proxy mode (Next.js): requests go to the same origin via route handler */
@@ -161,7 +162,7 @@ function createAccessClient(config, appContext) {
161
162
  return match ? decodeURIComponent(match[1]) : null;
162
163
  }
163
164
  const sessionPaths = [paths.refresh, paths.bootstrap, paths.logout];
164
- async function refreshTokensInternal() {
165
+ async function refreshTokensInternal(opts) {
165
166
  const reqHeaders = {
166
167
  "Content-Type": "application/json",
167
168
  ...config.headers
@@ -175,6 +176,9 @@ function createAccessClient(config, appContext) {
175
176
  if (refreshToken) {
176
177
  bodyPayload["rt"] = refreshToken;
177
178
  }
179
+ if (opts?.tenantId) {
180
+ bodyPayload["tenantId"] = opts.tenantId;
181
+ }
178
182
  const res = await fetch(`${baseUrl}${paths.refresh}`, {
179
183
  method: "POST",
180
184
  headers: reqHeaders,
@@ -195,7 +199,11 @@ function createAccessClient(config, appContext) {
195
199
  if (rt) setRefreshToken(rt);
196
200
  if (xc) setCsrfToken(xc);
197
201
  }
198
- function refreshTokens() {
202
+ function refreshTokens(opts) {
203
+ if (opts?.tenantId) {
204
+ return refreshTokensInternal(opts).finally(() => {
205
+ });
206
+ }
199
207
  if (refreshPromise) return refreshPromise;
200
208
  refreshPromise = refreshTokensInternal().finally(() => {
201
209
  refreshPromise = null;
@@ -303,7 +311,7 @@ function createAccessClient(config, appContext) {
303
311
  getRefreshToken,
304
312
  setCsrfToken,
305
313
  getCsrfToken,
306
- refreshSession: refreshTokens
314
+ refreshSession: (opts) => refreshTokens(opts)
307
315
  };
308
316
  }
309
317
 
@@ -726,6 +734,14 @@ function AziridProviderInner({
726
734
  throw err;
727
735
  }
728
736
  }, [client, queryClient, props, updateAccessToken, clearSession]);
737
+ const switchTenantFn = react.useCallback(
738
+ async (tenantId) => {
739
+ await client.refreshSession({ tenantId });
740
+ updateAccessToken(client.getAccessToken());
741
+ await queryClient.invalidateQueries({ queryKey: ["azirid-access"] });
742
+ },
743
+ [client, queryClient, updateAccessToken]
744
+ );
729
745
  const login = react.useCallback(
730
746
  (data) => loginMutation.mutate(data),
731
747
  [loginMutation]
@@ -748,6 +764,7 @@ function AziridProviderInner({
748
764
  logout,
749
765
  clearError,
750
766
  refresh: refreshFn,
767
+ switchTenant: switchTenantFn,
751
768
  setUser: setUserFn,
752
769
  isLoginPending: loginMutation.isPending,
753
770
  isSignupPending: signupMutation.isPending,
@@ -765,6 +782,7 @@ function AziridProviderInner({
765
782
  logout,
766
783
  clearError,
767
784
  refreshFn,
785
+ switchTenantFn,
768
786
  setUserFn,
769
787
  loginMutation,
770
788
  signupMutation,
@@ -2548,7 +2566,7 @@ function TransferModal2({
2548
2566
  const bankDetails = data.bankDetails;
2549
2567
  const plan = data.plan;
2550
2568
  const intent = data.intent;
2551
- const displayAmount = plan ? formatAmount2(plan.amount, plan.currency) : intent ? formatAmount2(intent.amount * 100, intent.currency) : "";
2569
+ const displayAmount = plan ? formatAmount2(plan.amount, plan.currency) : intent ? formatAmount2(intent.amount, intent.currency) : "";
2552
2570
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: modalStyles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.card, onClick: (e) => e.stopPropagation(), children: [
2553
2571
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: modalStyles.title, children: labels?.bankTransfer ?? "Bank Transfer" }),
2554
2572
  displayAmount && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3214,6 +3232,31 @@ function useTransferProofs() {
3214
3232
  queryFn: () => client.get(client.paths.transferProofs)
3215
3233
  });
3216
3234
  }
3235
+ function useTenants() {
3236
+ const client = useAccessClient();
3237
+ return reactQuery.useQuery({
3238
+ queryKey: ["azirid-access", "tenants"],
3239
+ queryFn: () => client.get(client.paths.userTenants),
3240
+ enabled: !!client.getAccessToken()
3241
+ });
3242
+ }
3243
+ function useTenantMembers(tenantId) {
3244
+ const client = useAccessClient();
3245
+ return reactQuery.useQuery({
3246
+ queryKey: ["azirid-access", "tenants", tenantId, "members"],
3247
+ queryFn: () => client.get(`${client.paths.userTenants}/${tenantId}/members`),
3248
+ enabled: !!client.getAccessToken() && !!tenantId
3249
+ });
3250
+ }
3251
+ function useSwitchTenant() {
3252
+ const client = useAccessClient();
3253
+ const queryClient = reactQuery.useQueryClient();
3254
+ const switchTenant = async (tenantId) => {
3255
+ await client.refreshSession({ tenantId });
3256
+ await queryClient.invalidateQueries({ queryKey: ["azirid-access"] });
3257
+ };
3258
+ return { switchTenant };
3259
+ }
3217
3260
  function zodToFieldErrors(zodError) {
3218
3261
  return zodError.issues.map((issue) => ({
3219
3262
  field: issue.path.join("."),
@@ -3270,7 +3313,7 @@ function usePasswordToggle() {
3270
3313
  }
3271
3314
 
3272
3315
  // src/index.ts
3273
- var SDK_VERSION = "0.6.0";
3316
+ var SDK_VERSION = "0.7.0";
3274
3317
 
3275
3318
  exports.AziridProvider = AziridProvider;
3276
3319
  exports.BASE_PATHS = BASE_PATHS;
@@ -3328,6 +3371,9 @@ exports.useSignup = useSignup;
3328
3371
  exports.useSocialLogin = useSocialLogin;
3329
3372
  exports.useSubmitTransferProof = useSubmitTransferProof;
3330
3373
  exports.useSubscription = useSubscription;
3374
+ exports.useSwitchTenant = useSwitchTenant;
3375
+ exports.useTenantMembers = useTenantMembers;
3376
+ exports.useTenants = useTenants;
3331
3377
  exports.useTransferProofs = useTransferProofs;
3332
3378
  //# sourceMappingURL=index.cjs.map
3333
3379
  //# sourceMappingURL=index.cjs.map