@authowl/react 0.22.0 → 0.24.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.
@@ -115,7 +115,7 @@ function readableAccent(brandHex, surfaceHex, dir) {
115
115
  }
116
116
  return dir === 1 ? WHITE : NEAR_BLACK;
117
117
  }
118
- var LIGHT_SURFACE = "#ffffff";
118
+ var LIGHT_SURFACE = "#f4f4f5";
119
119
  var DARK_SURFACE = "#18181b";
120
120
  function deriveBrandRamp(primaryHex) {
121
121
  const solid = normalizeHex(primaryHex) ?? DEFAULT_BRAND_COLOR.toLowerCase();
@@ -151,7 +151,7 @@ function brandRampVars(set) {
151
151
  }
152
152
 
153
153
  // src/hooks.ts
154
- import * as React5 from "react";
154
+ import * as React6 from "react";
155
155
  import {
156
156
  clearInvitationClaim,
157
157
  createMembershipHas,
@@ -159,10 +159,11 @@ import {
159
159
  } from "@authowl/core";
160
160
 
161
161
  // src/provider.tsx
162
- import * as React4 from "react";
162
+ import * as React5 from "react";
163
163
  import {
164
164
  captureInvitationClaim,
165
165
  createAuthOwlClient,
166
+ setActiveLocale,
166
167
  directionFor,
167
168
  getPublicConfig,
168
169
  isLocale,
@@ -367,6 +368,39 @@ function InvitationPrompt() {
367
368
  );
368
369
  }
369
370
 
371
+ // src/last-used-method.ts
372
+ import * as React4 from "react";
373
+ import {
374
+ readLastUsedSignInMethod,
375
+ recordLastUsedSignInMethod,
376
+ rememberPendingSignInMethod,
377
+ settlePendingSignInMethod
378
+ } from "@authowl/core";
379
+ function useLastUsedSignInMethod() {
380
+ const { config } = usePublicConfig();
381
+ const projectId = config?.environmentId ?? null;
382
+ return React4.useMemo(
383
+ () => projectId ? readLastUsedSignInMethod(projectId) : null,
384
+ [projectId]
385
+ );
386
+ }
387
+ function useSignInMethodRecorder() {
388
+ const { config } = usePublicConfig();
389
+ const projectId = config?.environmentId ?? null;
390
+ return React4.useCallback(
391
+ (method, pending) => {
392
+ if (!projectId) return;
393
+ (pending ? rememberPendingSignInMethod : recordLastUsedSignInMethod)(projectId, method);
394
+ },
395
+ [projectId]
396
+ );
397
+ }
398
+ function useConfirmPendingSignInMethod(loaded, signedIn, projectId) {
399
+ React4.useEffect(() => {
400
+ if (loaded && projectId) settlePendingSignInMethod(projectId, signedIn);
401
+ }, [loaded, projectId, signedIn]);
402
+ }
403
+
370
404
  // src/provider.tsx
371
405
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
372
406
  var warnedConfigProjects = /* @__PURE__ */ new Set();
@@ -384,7 +418,13 @@ function warnPublicConfigFailed(resolved, error) {
384
418
  `[AuthOwl] Could not load this project's public config from ${origin} (${reason}). Authentication UI is unavailable until the request succeeds. Likely causes: the API is unreachable, \`apiUrl\` is wrong, or the publishable key points at a missing/mismatched project. Check \`apiUrl\` and \`publishableKey\` on <AuthOwlProvider>. (This warning is dev-only.)`
385
419
  );
386
420
  }
387
- var Context = React4.createContext(null);
421
+ function LastUsedMethodSync() {
422
+ const { config } = usePublicConfig();
423
+ const { isLoaded, isSignedIn } = useUser();
424
+ useConfirmPendingSignInMethod(isLoaded, isSignedIn, config?.environmentId ?? null);
425
+ return null;
426
+ }
427
+ var Context = React5.createContext(null);
388
428
  function detectLocale() {
389
429
  if (typeof document !== "undefined") {
390
430
  const root = document.documentElement;
@@ -409,18 +449,18 @@ function AuthOwlProvider({
409
449
  invitationPrompt = true,
410
450
  children
411
451
  }) {
412
- const fetchRef = React4.useRef(fetch);
452
+ const fetchRef = React5.useRef(fetch);
413
453
  fetchRef.current = fetch;
414
- const resolved = React4.useMemo(
454
+ const resolved = React5.useMemo(
415
455
  () => resolveConfig({ publishableKey, apiUrl, fetch: fetchRef.current }),
416
456
  [publishableKey, apiUrl]
417
457
  );
418
- const client = React4.useMemo(() => createAuthOwlClient(resolved), [resolved]);
419
- const [config, setConfig] = React4.useState(null);
420
- const [configState, setConfigState] = React4.useState("loading");
421
- const [configAttempt, setConfigAttempt] = React4.useState(0);
422
- const retryPublicConfig = React4.useCallback(() => setConfigAttempt((attempt) => attempt + 1), []);
423
- React4.useEffect(() => {
458
+ const client = React5.useMemo(() => createAuthOwlClient(resolved), [resolved]);
459
+ const [config, setConfig] = React5.useState(null);
460
+ const [configState, setConfigState] = React5.useState("loading");
461
+ const [configAttempt, setConfigAttempt] = React5.useState(0);
462
+ const retryPublicConfig = React5.useCallback(() => setConfigAttempt((attempt) => attempt + 1), []);
463
+ React5.useEffect(() => {
424
464
  let active = true;
425
465
  setConfigState("loading");
426
466
  getPublicConfig(resolved).then((c) => {
@@ -438,15 +478,18 @@ function AuthOwlProvider({
438
478
  };
439
479
  }, [resolved, configAttempt]);
440
480
  const merged = resolveAppearance(appearance, config);
441
- const [autoLocale, setAutoLocale] = React4.useState(null);
442
- React4.useEffect(() => {
481
+ const [autoLocale, setAutoLocale] = React5.useState(null);
482
+ React5.useEffect(() => {
443
483
  if (localeProp === "auto") setAutoLocale(detectLocale());
444
484
  }, [localeProp]);
445
485
  const locale = resolveLocale(localeProp, autoLocale, config?.locale);
446
- React4.useEffect(() => {
486
+ React5.useEffect(() => {
487
+ return setActiveLocale(resolved.decoded.projectId, locale);
488
+ }, [locale, resolved.decoded.projectId]);
489
+ React5.useEffect(() => {
447
490
  captureInvitationClaim();
448
491
  }, []);
449
- const ctxValue = React4.useMemo(
492
+ const ctxValue = React5.useMemo(
450
493
  () => ({ client, appearance, config, configState, retryPublicConfig, locale }),
451
494
  [client, appearance, config, configState, retryPublicConfig, locale]
452
495
  );
@@ -460,13 +503,14 @@ function AuthOwlProvider({
460
503
  style: { display: "contents", ...merged.style },
461
504
  children: [
462
505
  children,
506
+ /* @__PURE__ */ jsx4(LastUsedMethodSync, {}),
463
507
  invitationPrompt ? /* @__PURE__ */ jsx4(InvitationPrompt, {}) : null
464
508
  ]
465
509
  }
466
510
  ) });
467
511
  }
468
512
  function useAuthOwlContext() {
469
- const v = React4.useContext(Context);
513
+ const v = React5.useContext(Context);
470
514
  if (!v) {
471
515
  throw new Error("AuthOwl hooks must be used inside <AuthOwlProvider>");
472
516
  }
@@ -490,6 +534,9 @@ function useAuthClient() {
490
534
  function useAccount() {
491
535
  return useAuthClient().account;
492
536
  }
537
+ function usePrivacy() {
538
+ return useAuthClient().privacy;
539
+ }
493
540
  function usePublicConfig() {
494
541
  const { config, configState, retryPublicConfig } = useAuthOwlContext();
495
542
  return {
@@ -501,7 +548,7 @@ function usePublicConfig() {
501
548
  }
502
549
  function useSession() {
503
550
  const client = useAuthClient();
504
- return React5.useSyncExternalStore(
551
+ return React6.useSyncExternalStore(
505
552
  client.sessionStore.subscribe,
506
553
  client.sessionStore.getSnapshot,
507
554
  client.sessionStore.getSnapshot
@@ -531,16 +578,16 @@ function useAuth() {
531
578
  }
532
579
  function useOrganization() {
533
580
  const client = useAuthClient();
534
- const apiRef = React5.useRef(client.organization);
581
+ const apiRef = React6.useRef(client.organization);
535
582
  apiRef.current = client.organization;
536
583
  const { data, isPending } = useSession();
537
584
  const membership = data?.session?.membership ?? null;
538
585
  const activeOrganizationId = data?.session?.activeOrganizationId ?? null;
539
586
  const identity = data?.user?.id ?? null;
540
- const [organization, setOrganization] = React5.useState(null);
541
- const [orgLoaded, setOrgLoaded] = React5.useState(false);
542
- const requestRef = React5.useRef(0);
543
- React5.useEffect(() => {
587
+ const [organization, setOrganization] = React6.useState(null);
588
+ const [orgLoaded, setOrgLoaded] = React6.useState(false);
589
+ const requestRef = React6.useRef(0);
590
+ React6.useEffect(() => {
544
591
  const token = ++requestRef.current;
545
592
  setOrganization(null);
546
593
  setOrgLoaded(false);
@@ -564,7 +611,7 @@ function useOrganization() {
564
611
  requestRef.current += 1;
565
612
  };
566
613
  }, [identity, activeOrganizationId, isPending]);
567
- const bound = React5.useMemo(() => createMembershipHas(membership), [membership]);
614
+ const bound = React6.useMemo(() => createMembershipHas(membership), [membership]);
568
615
  return {
569
616
  organization,
570
617
  membership,
@@ -579,13 +626,13 @@ function useOrganization() {
579
626
  }
580
627
  function useInvitationRecipientHint() {
581
628
  const client = useAuthClient();
582
- const apiRef = React5.useRef(client.organization);
629
+ const apiRef = React6.useRef(client.organization);
583
630
  apiRef.current = client.organization;
584
- const [result, setResult] = React5.useState({
631
+ const [result, setResult] = React6.useState({
585
632
  recipientHint: null,
586
633
  isLoaded: false
587
634
  });
588
- React5.useEffect(() => {
635
+ React6.useEffect(() => {
589
636
  let active = true;
590
637
  const claim = readInvitationClaim();
591
638
  if (!claim) {
@@ -616,18 +663,18 @@ function useInvitationRecipientHint() {
616
663
  }
617
664
  function useOrganizationInvitation() {
618
665
  const client = useAuthClient();
619
- const apiRef = React5.useRef(client.organization);
666
+ const apiRef = React6.useRef(client.organization);
620
667
  apiRef.current = client.organization;
621
668
  const { data, isPending } = useSession();
622
669
  const identity = data?.user?.id ?? null;
623
- const [claim, setClaim] = React5.useState(null);
624
- const [invitation, setInvitation] = React5.useState(null);
625
- const [status, setStatus] = React5.useState("idle");
626
- const requestRef = React5.useRef(0);
627
- React5.useEffect(() => {
670
+ const [claim, setClaim] = React6.useState(null);
671
+ const [invitation, setInvitation] = React6.useState(null);
672
+ const [status, setStatus] = React6.useState("idle");
673
+ const requestRef = React6.useRef(0);
674
+ React6.useEffect(() => {
628
675
  setClaim(readInvitationClaim());
629
676
  }, [identity]);
630
- React5.useEffect(() => {
677
+ React6.useEffect(() => {
631
678
  const token = ++requestRef.current;
632
679
  if (isPending || !claim) return;
633
680
  if (!identity) {
@@ -650,7 +697,7 @@ function useOrganizationInvitation() {
650
697
  requestRef.current += 1;
651
698
  };
652
699
  }, [claim, identity, isPending]);
653
- const accept = React5.useCallback(async () => {
700
+ const accept = React6.useCallback(async () => {
654
701
  const current = readInvitationClaim();
655
702
  if (!current) return false;
656
703
  setStatus("joining");
@@ -672,7 +719,7 @@ function useOrganizationInvitation() {
672
719
  } else setStatus("error");
673
720
  return false;
674
721
  }, []);
675
- const dismiss = React5.useCallback(() => {
722
+ const dismiss = React6.useCallback(() => {
676
723
  clearInvitationClaim();
677
724
  setClaim(null);
678
725
  setInvitation(null);
@@ -733,10 +780,10 @@ function useConsent() {
733
780
  const client = useAuthClient();
734
781
  const { user, isLoaded } = useUser();
735
782
  const userId = user?.id ?? null;
736
- const [status, setStatus] = React5.useState(null);
737
- const [isLoading, setIsLoading] = React5.useState(true);
738
- const reqRef = React5.useRef(0);
739
- const load = React5.useCallback(async () => {
783
+ const [status, setStatus] = React6.useState(null);
784
+ const [isLoading, setIsLoading] = React6.useState(true);
785
+ const reqRef = React6.useRef(0);
786
+ const load = React6.useCallback(async () => {
740
787
  const reqId = ++reqRef.current;
741
788
  setIsLoading(true);
742
789
  let next;
@@ -749,7 +796,7 @@ function useConsent() {
749
796
  setStatus(next);
750
797
  setIsLoading(false);
751
798
  }, [client]);
752
- React5.useEffect(() => {
799
+ React6.useEffect(() => {
753
800
  reqRef.current += 1;
754
801
  setStatus(null);
755
802
  if (!isLoaded) {
@@ -759,7 +806,7 @@ function useConsent() {
759
806
  void load();
760
807
  }, [load, isLoaded, userId]);
761
808
  const version = typeof status?.version === "number" && Number.isFinite(status.version) ? status.version : null;
762
- const accept = React5.useCallback(async () => {
809
+ const accept = React6.useCallback(async () => {
763
810
  if (version == null) return;
764
811
  try {
765
812
  await client.acceptConsent(version);
@@ -782,35 +829,6 @@ function useSignOut() {
782
829
  return { signOut: client.signOut };
783
830
  }
784
831
 
785
- // src/components/use-submit-action.ts
786
- import * as React6 from "react";
787
- function useSubmitAction() {
788
- const [pending, setPending] = React6.useState(false);
789
- const [error, setError] = React6.useState(null);
790
- const toMessage = useServerError();
791
- const run = React6.useCallback(
792
- async (action, { failure, onSuccess, mapError, keepPendingOnSuccess }) => {
793
- setError(null);
794
- setPending(true);
795
- try {
796
- const res = await action();
797
- if (res?.error) {
798
- setError(mapError?.(res.error) ?? toMessage(res.error, failure));
799
- setPending(false);
800
- return;
801
- }
802
- await onSuccess?.(res ?? { data: null, error: null });
803
- if (!keepPendingOnSuccess) setPending(false);
804
- } catch {
805
- setError(failure);
806
- setPending(false);
807
- }
808
- },
809
- [toMessage]
810
- );
811
- return { pending, error, setError, run };
812
- }
813
-
814
832
  // src/components/Spinner.tsx
815
833
  import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
816
834
  function Spinner() {
@@ -839,91 +857,11 @@ function FormError({ children, className, "data-testid": testId }) {
839
857
  );
840
858
  }
841
859
 
842
- // src/components/organization/model.ts
843
- function organizationSlugFromName(name) {
844
- return name.normalize("NFKD").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
845
- }
846
- function organizationRoles(role) {
847
- return role.split(",").map((value) => value.trim()).filter(Boolean);
848
- }
849
- function hasOrganizationRole(member, role) {
850
- return member ? organizationRoles(member.role).includes(role) : false;
851
- }
852
- function canManageOrganization(member) {
853
- return hasOrganizationRole(member, "owner") || hasOrganizationRole(member, "admin");
854
- }
855
- function roleHasStatement(roles, heldRoles, resource, action) {
856
- return roles.some((role) => {
857
- if (!heldRoles.has(role.role) || typeof role.permission !== "object" || role.permission === null) {
858
- return false;
859
- }
860
- const actions = role.permission[resource];
861
- return Array.isArray(actions) && actions.includes(action);
862
- });
863
- }
864
- function teamManagementCapabilities(member, dynamicRoles) {
865
- const heldRoles = new Set(organizationRoles(member.role));
866
- if (heldRoles.has("owner") || heldRoles.has("admin")) {
867
- return {
868
- createTeam: true,
869
- updateTeam: true,
870
- deleteTeam: true,
871
- addTeamMember: true,
872
- removeTeamMember: true
873
- };
874
- }
875
- return {
876
- createTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "create"),
877
- updateTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "update"),
878
- deleteTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "delete"),
879
- addTeamMember: roleHasStatement(dynamicRoles, heldRoles, "member", "update"),
880
- removeTeamMember: roleHasStatement(dynamicRoles, heldRoles, "member", "delete")
881
- };
882
- }
883
-
884
- // src/components/organization/use-organization-roles.ts
885
- import * as React7 from "react";
886
- var BUILTIN_ROLES = ["owner", "admin", "member"];
887
- function useOrganizationRoles(organizationId) {
888
- const api = useAuthClient().organization;
889
- const apiRef = React7.useRef(api);
890
- apiRef.current = api;
891
- const [dynamicRoles, setDynamicRoles] = React7.useState([]);
892
- const requestRef = React7.useRef(0);
893
- React7.useEffect(() => {
894
- const token = ++requestRef.current;
895
- setDynamicRoles([]);
896
- if (!organizationId) return;
897
- void (async () => {
898
- try {
899
- const result = await apiRef.current.listRoles({ organizationId });
900
- if (token !== requestRef.current) return;
901
- setDynamicRoles((result.data ?? []).filter((entry) => entry.role.trim().length > 0));
902
- } catch {
903
- }
904
- })();
905
- return () => {
906
- requestRef.current += 1;
907
- };
908
- }, [organizationId]);
909
- const roles = React7.useMemo(() => {
910
- const seen = /* @__PURE__ */ new Set();
911
- const out = [];
912
- for (const candidate of [...BUILTIN_ROLES, ...dynamicRoles.map((entry) => entry.role)]) {
913
- const key = candidate.trim();
914
- if (!key || seen.has(key)) continue;
915
- seen.add(key);
916
- out.push(key);
917
- }
918
- return out;
919
- }, [dynamicRoles]);
920
- return { roles, dynamicRoles };
921
- }
922
-
923
860
  export {
924
861
  DEFAULT_BRAND_COLOR,
925
862
  useAuthClient,
926
863
  useAccount,
864
+ usePrivacy,
927
865
  usePublicConfig,
928
866
  useSession,
929
867
  useUser,
@@ -947,16 +885,11 @@ export {
947
885
  useServerError,
948
886
  ModalSurface,
949
887
  InvitationPrompt,
888
+ useLastUsedSignInMethod,
889
+ useSignInMethodRecorder,
950
890
  AuthOwlProvider,
951
891
  useAuthOwlContext,
952
- useSubmitAction,
953
892
  Spinner,
954
893
  Busy,
955
- FormError,
956
- organizationSlugFromName,
957
- organizationRoles,
958
- hasOrganizationRole,
959
- canManageOrganization,
960
- teamManagementCapabilities,
961
- useOrganizationRoles
894
+ FormError
962
895
  };
@@ -0,0 +1,46 @@
1
+ import {
2
+ useAuthClient
3
+ } from "./chunk-TOVDZ2ZE.js";
4
+
5
+ // src/components/organization/use-organization-roles.ts
6
+ import * as React from "react";
7
+ var BUILTIN_ROLES = ["owner", "admin", "member"];
8
+ function useOrganizationRoles(organizationId) {
9
+ const api = useAuthClient().organization;
10
+ const apiRef = React.useRef(api);
11
+ apiRef.current = api;
12
+ const [dynamicRoles, setDynamicRoles] = React.useState([]);
13
+ const requestRef = React.useRef(0);
14
+ React.useEffect(() => {
15
+ const token = ++requestRef.current;
16
+ setDynamicRoles([]);
17
+ if (!organizationId) return;
18
+ void (async () => {
19
+ try {
20
+ const result = await apiRef.current.listRoles({ organizationId });
21
+ if (token !== requestRef.current) return;
22
+ setDynamicRoles((result.data ?? []).filter((entry) => entry.role.trim().length > 0));
23
+ } catch {
24
+ }
25
+ })();
26
+ return () => {
27
+ requestRef.current += 1;
28
+ };
29
+ }, [organizationId]);
30
+ const roles = React.useMemo(() => {
31
+ const seen = /* @__PURE__ */ new Set();
32
+ const out = [];
33
+ for (const candidate of [...BUILTIN_ROLES, ...dynamicRoles.map((entry) => entry.role)]) {
34
+ const key = candidate.trim();
35
+ if (!key || seen.has(key)) continue;
36
+ seen.add(key);
37
+ out.push(key);
38
+ }
39
+ return out;
40
+ }, [dynamicRoles]);
41
+ return { roles, dynamicRoles };
42
+ }
43
+
44
+ export {
45
+ useOrganizationRoles
46
+ };