@hanzo/iam 0.6.1 → 0.6.2

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/dist/react.d.ts CHANGED
@@ -34,7 +34,7 @@
34
34
  import type { ReactNode } from "react";
35
35
  import { BrowserIamSdk } from "./browser.js";
36
36
  import type { BrowserIamConfig } from "./browser.js";
37
- import type { IamUser, IamOrganization, IamInvitation, IamProject, TokenResponse } from "./types.js";
37
+ import type { IamUser, IamOrganization, IamProject, TokenResponse } from "./types.js";
38
38
  export interface IamProviderProps {
39
39
  /** Browser IAM SDK configuration. */
40
40
  config: BrowserIamConfig;
@@ -123,50 +123,6 @@ export declare function useIamToken(): {
123
123
  isValid: boolean;
124
124
  refresh: () => Promise<string | null>;
125
125
  };
126
- export interface OrgManagementState {
127
- /** Create a new organization. */
128
- createOrg: (org: Partial<IamOrganization>) => Promise<void>;
129
- /** Update an existing organization. */
130
- updateOrg: (org: Partial<IamOrganization>) => Promise<void>;
131
- /** Delete an organization by owner and name. */
132
- deleteOrg: (org: {
133
- owner: string;
134
- name: string;
135
- }) => Promise<void>;
136
- /** Whether a mutation is in progress. */
137
- isLoading: boolean;
138
- }
139
- /**
140
- * Manage organization CRUD operations.
141
- *
142
- * Provides create, update, and delete methods that call the IAM API
143
- * using the current user's access token.
144
- */
145
- export declare function useOrgManagement(): OrgManagementState;
146
- export interface InvitationsState {
147
- /** All invitations for the organization. */
148
- invitations: IamInvitation[];
149
- /** Create a new invitation. */
150
- createInvite: (invitation: Partial<IamInvitation>) => Promise<void>;
151
- /** Send an existing invitation. */
152
- sendInvite: (invitation: {
153
- owner: string;
154
- name: string;
155
- }) => Promise<void>;
156
- /** Verify an invitation code. */
157
- verifyInvite: (code: string) => Promise<IamInvitation | null>;
158
- /** Whether invitations are loading. */
159
- isLoading: boolean;
160
- /** Re-fetch the invitations list. */
161
- refresh: () => Promise<void>;
162
- }
163
- /**
164
- * Manage invitations for an organization.
165
- *
166
- * Fetches the invitation list on mount and provides create, send,
167
- * and verify methods using the current user's access token.
168
- */
169
- export declare function useInvitations(orgName: string): InvitationsState;
170
126
  export { IamContext };
171
127
  export interface OrgProjectSwitcherProps {
172
128
  organizations: Array<{
@@ -205,46 +161,4 @@ export interface OrgProjectSwitcherProps {
205
161
  export declare function OrgProjectSwitcher({ organizations, currentOrgId, switchOrg, projects, currentProjectId, switchProject, onTenantChange, environment, className, alwaysShow, }: OrgProjectSwitcherProps): import("react").DetailedReactHTMLElement<{
206
162
  className: string;
207
163
  }, HTMLElement> | null;
208
- export interface UserOrgMenuProps {
209
- /** Additional CSS class for the outer container. */
210
- className?: string;
211
- /** Called when org changes. Use to sync external state (e.g., tenantStore). */
212
- onOrgChange?: (orgId: string) => void;
213
- /** Called when user clicks logout. */
214
- onLogout?: () => void;
215
- /** Whether to show the "Create Organization" option. Defaults to true. */
216
- showCreateOrg?: boolean;
217
- /** Optional endpoint for org creation (defaults to IAM's /api/add-organization). */
218
- createOrgEndpoint?: string;
219
- }
220
- /**
221
- * Shared user menu + organization switcher for all Hanzo apps.
222
- *
223
- * Shows current user info (name, email, avatar), a dropdown with org list,
224
- * "Create Organization" option, and logout button. Uses only `@hanzo/iam`
225
- * hooks — no external UI library required.
226
- *
227
- * @example
228
- * ```tsx
229
- * import { UserOrgMenu } from '@hanzo/iam/react'
230
- *
231
- * function TopBar() {
232
- * return (
233
- * <nav>
234
- * <UserOrgMenu
235
- * onOrgChange={(orgId) => myStore.setOrg(orgId)}
236
- * onLogout={() => router.push('/login')}
237
- * />
238
- * </nav>
239
- * )
240
- * }
241
- * ```
242
- */
243
- export declare function UserOrgMenu({ className, onOrgChange, onLogout, showCreateOrg, createOrgEndpoint, }: UserOrgMenuProps): import("react").DetailedReactHTMLElement<{
244
- ref: import("react").RefObject<HTMLDivElement | null>;
245
- className: string;
246
- style: {
247
- position: "relative";
248
- };
249
- }, HTMLDivElement> | null;
250
164
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
package/dist/react.js CHANGED
@@ -297,38 +297,27 @@ export function useOrganizations() {
297
297
  let cancelled = false;
298
298
  const fetchOrgs = async () => {
299
299
  setIsLoading(true);
300
- // 1. Parse JWT claims for user's workspace org (immediate, no API call)
301
- // The user's "owner" is the signup org (for auth). Their personal org
302
- // (name == username) is their actual workspace.
300
+ // 1. Parse JWT sub claim for primary org (immediate, no API call)
303
301
  try {
304
- let b64 = accessToken.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
305
- while (b64.length % 4)
306
- b64 += "=";
307
- const payload = JSON.parse(atob(b64));
308
- const userOwner = payload.owner ?? "";
309
- const userName = payload.name ?? "";
310
- const sub = payload.sub ?? "";
311
- const isAdmin = !!payload.isAdmin;
312
- // Personal org is the default workspace
313
- const workspaceOrg = (userName && userName !== userOwner)
314
- ? userName
315
- : userOwner || (sub.includes("/") ? sub.split("/")[0] : "");
316
- if (workspaceOrg && !cancelled) {
317
- const immediateOrgs = [
318
- { owner: "admin", name: workspaceOrg, displayName: workspaceOrg },
319
- ];
320
- // Admin users also see their signup org (they manage it)
321
- if (isAdmin && userOwner && userOwner !== workspaceOrg) {
322
- immediateOrgs.push({ owner: "admin", name: userOwner, displayName: userOwner });
323
- }
324
- setOrganizations(immediateOrgs);
325
- if (!currentOrgId) {
326
- setCurrentOrgId(workspaceOrg);
327
- try {
328
- localStorage.setItem(STORAGE_ORG_KEY, workspaceOrg);
329
- }
330
- catch {
331
- /* ok */
302
+ const payload = JSON.parse(atob(accessToken.split(".")[1]));
303
+ const sub = payload.sub;
304
+ if (sub?.includes("/")) {
305
+ const primaryOrg = sub.split("/")[0];
306
+ if (!cancelled) {
307
+ const syntheticOrg = {
308
+ owner: "admin",
309
+ name: primaryOrg,
310
+ displayName: primaryOrg,
311
+ };
312
+ setOrganizations([syntheticOrg]);
313
+ if (!currentOrgId) {
314
+ setCurrentOrgId(primaryOrg);
315
+ try {
316
+ localStorage.setItem(STORAGE_ORG_KEY, primaryOrg);
317
+ }
318
+ catch {
319
+ /* ok */
320
+ }
332
321
  }
333
322
  }
334
323
  }
@@ -475,137 +464,6 @@ export function useIamToken() {
475
464
  refresh,
476
465
  };
477
466
  }
478
- /**
479
- * Manage organization CRUD operations.
480
- *
481
- * Provides create, update, and delete methods that call the IAM API
482
- * using the current user's access token.
483
- */
484
- export function useOrgManagement() {
485
- const { config, accessToken } = useIam();
486
- const [isLoading, setIsLoading] = useState(false);
487
- const client = useMemo(() => new IamClient({
488
- serverUrl: config.serverUrl,
489
- clientId: config.clientId,
490
- }), [config.serverUrl, config.clientId]);
491
- const createOrg = useCallback(async (org) => {
492
- setIsLoading(true);
493
- try {
494
- await client.createOrganization(org, accessToken ?? undefined);
495
- }
496
- finally {
497
- setIsLoading(false);
498
- }
499
- }, [client, accessToken]);
500
- const updateOrg = useCallback(async (org) => {
501
- setIsLoading(true);
502
- try {
503
- await client.updateOrganization(org, accessToken ?? undefined);
504
- }
505
- finally {
506
- setIsLoading(false);
507
- }
508
- }, [client, accessToken]);
509
- const deleteOrg = useCallback(async (org) => {
510
- setIsLoading(true);
511
- try {
512
- await client.deleteOrganization(org, accessToken ?? undefined);
513
- }
514
- finally {
515
- setIsLoading(false);
516
- }
517
- }, [client, accessToken]);
518
- return { createOrg, updateOrg, deleteOrg, isLoading };
519
- }
520
- /**
521
- * Manage invitations for an organization.
522
- *
523
- * Fetches the invitation list on mount and provides create, send,
524
- * and verify methods using the current user's access token.
525
- */
526
- export function useInvitations(orgName) {
527
- const { config, accessToken, isAuthenticated } = useIam();
528
- const [invitations, setInvitations] = useState([]);
529
- const [isLoading, setIsLoading] = useState(false);
530
- const client = useMemo(() => new IamClient({
531
- serverUrl: config.serverUrl,
532
- clientId: config.clientId,
533
- }), [config.serverUrl, config.clientId]);
534
- const fetchInvitations = useCallback(async () => {
535
- if (!isAuthenticated || !accessToken || !orgName)
536
- return;
537
- setIsLoading(true);
538
- try {
539
- const data = await client.getInvitations(orgName, accessToken);
540
- setInvitations(data);
541
- }
542
- catch {
543
- setInvitations([]);
544
- }
545
- finally {
546
- setIsLoading(false);
547
- }
548
- }, [client, orgName, accessToken, isAuthenticated]);
549
- // Fetch invitations on mount and when orgName changes
550
- useEffect(() => {
551
- if (!isAuthenticated || !accessToken || !orgName) {
552
- setInvitations([]);
553
- return;
554
- }
555
- let cancelled = false;
556
- const load = async () => {
557
- setIsLoading(true);
558
- try {
559
- const data = await client.getInvitations(orgName, accessToken);
560
- if (!cancelled)
561
- setInvitations(data);
562
- }
563
- catch {
564
- if (!cancelled)
565
- setInvitations([]);
566
- }
567
- finally {
568
- if (!cancelled)
569
- setIsLoading(false);
570
- }
571
- };
572
- load();
573
- return () => {
574
- cancelled = true;
575
- };
576
- // eslint-disable-next-line react-hooks/exhaustive-deps
577
- }, [isAuthenticated, accessToken, orgName, config.serverUrl, config.clientId]);
578
- const createInvite = useCallback(async (invitation) => {
579
- setIsLoading(true);
580
- try {
581
- await client.createInvitation(invitation, accessToken ?? undefined);
582
- await fetchInvitations();
583
- }
584
- finally {
585
- setIsLoading(false);
586
- }
587
- }, [client, accessToken, fetchInvitations]);
588
- const sendInvite = useCallback(async (invitation) => {
589
- setIsLoading(true);
590
- try {
591
- await client.sendInvitation(invitation, accessToken ?? undefined);
592
- }
593
- finally {
594
- setIsLoading(false);
595
- }
596
- }, [client, accessToken]);
597
- const verifyInvite = useCallback(async (code) => {
598
- setIsLoading(true);
599
- try {
600
- const resp = await client.verifyInvitation(code, accessToken ?? undefined);
601
- return resp.data ?? null;
602
- }
603
- finally {
604
- setIsLoading(false);
605
- }
606
- }, [client, accessToken]);
607
- return { invitations, createInvite, sendInvite, verifyInvite, isLoading, refresh: fetchInvitations };
608
- }
609
467
  // Re-export context for advanced use
610
468
  export { IamContext };
611
469
  /**
@@ -661,293 +519,4 @@ export function OrgProjectSwitcher({ organizations, currentOrgId, switchOrg, pro
661
519
  ? createElement("span", { className: "rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground" }, environment)
662
520
  : null);
663
521
  }
664
- /**
665
- * Shared user menu + organization switcher for all Hanzo apps.
666
- *
667
- * Shows current user info (name, email, avatar), a dropdown with org list,
668
- * "Create Organization" option, and logout button. Uses only `@hanzo/iam`
669
- * hooks — no external UI library required.
670
- *
671
- * @example
672
- * ```tsx
673
- * import { UserOrgMenu } from '@hanzo/iam/react'
674
- *
675
- * function TopBar() {
676
- * return (
677
- * <nav>
678
- * <UserOrgMenu
679
- * onOrgChange={(orgId) => myStore.setOrg(orgId)}
680
- * onLogout={() => router.push('/login')}
681
- * />
682
- * </nav>
683
- * )
684
- * }
685
- * ```
686
- */
687
- export function UserOrgMenu({ className = "", onOrgChange, onLogout, showCreateOrg = true, createOrgEndpoint, }) {
688
- const { config, isAuthenticated, accessToken, user, logout } = useIam();
689
- const orgState = useOrganizations();
690
- const [open, setOpen] = useState(false);
691
- const [createOpen, setCreateOpen] = useState(false);
692
- const [newOrgName, setNewOrgName] = useState("");
693
- const [newOrgDisplay, setNewOrgDisplay] = useState("");
694
- const [creating, setCreating] = useState(false);
695
- const [error, setError] = useState(null);
696
- const menuRef = useRef(null);
697
- // Close on outside click
698
- useEffect(() => {
699
- const handler = (e) => {
700
- if (menuRef.current && !menuRef.current.contains(e.target)) {
701
- setOpen(false);
702
- }
703
- };
704
- document.addEventListener("mousedown", handler);
705
- return () => document.removeEventListener("mousedown", handler);
706
- }, []);
707
- const handleSwitchOrg = useCallback((orgId) => {
708
- orgState.switchOrg(orgId);
709
- onOrgChange?.(orgId);
710
- setOpen(false);
711
- }, [orgState, onOrgChange]);
712
- const handleLogout = useCallback(() => {
713
- setOpen(false);
714
- if (onLogout) {
715
- onLogout();
716
- }
717
- else {
718
- logout?.();
719
- }
720
- }, [onLogout, logout]);
721
- const handleCreateOrg = useCallback(async () => {
722
- const name = newOrgName.trim();
723
- if (!name)
724
- return;
725
- setCreating(true);
726
- setError(null);
727
- try {
728
- const client = new IamClient({
729
- serverUrl: config.serverUrl,
730
- clientId: config.clientId,
731
- });
732
- if (createOrgEndpoint) {
733
- // Use custom endpoint (e.g., playground's /v1/orgs)
734
- const res = await fetch(createOrgEndpoint, {
735
- method: "POST",
736
- headers: {
737
- "Content-Type": "application/json",
738
- ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
739
- },
740
- body: JSON.stringify({
741
- name,
742
- displayName: newOrgDisplay.trim() || name,
743
- }),
744
- });
745
- if (!res.ok) {
746
- const body = await res.json().catch(() => ({}));
747
- throw new Error(body.error || body.msg || `HTTP ${res.status}`);
748
- }
749
- }
750
- else {
751
- // Use IAM directly
752
- await client.createOrganization({ owner: "admin", name, displayName: newOrgDisplay.trim() || name }, accessToken ?? undefined);
753
- }
754
- // Switch to new org
755
- orgState.switchOrg(name);
756
- onOrgChange?.(name);
757
- setNewOrgName("");
758
- setNewOrgDisplay("");
759
- setCreateOpen(false);
760
- setOpen(false);
761
- // Reload to refresh org list
762
- window.location.reload();
763
- }
764
- catch (e) {
765
- setError(e instanceof Error ? e.message : String(e));
766
- }
767
- finally {
768
- setCreating(false);
769
- }
770
- }, [newOrgName, newOrgDisplay, config, accessToken, createOrgEndpoint, orgState, onOrgChange]);
771
- if (!isAuthenticated)
772
- return null;
773
- const orgs = orgState.organizations ?? [];
774
- const currentLabel = orgs.find((o) => o.name === orgState.currentOrgId)?.displayName ??
775
- orgState.currentOrgId ??
776
- "Select org";
777
- const userName = user?.displayName || user?.name || user?.email || "User";
778
- const userEmail = user?.email || "";
779
- const userAvatar = user?.avatar || "";
780
- // Inline styles (no external CSS dependencies)
781
- const menuStyle = {
782
- position: "absolute",
783
- top: "100%",
784
- right: 0,
785
- marginTop: 4,
786
- minWidth: 240,
787
- borderRadius: 8,
788
- border: "1px solid var(--border, #333)",
789
- background: "var(--popover, #1a1a1a)",
790
- color: "var(--popover-foreground, #fff)",
791
- boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
792
- zIndex: 50,
793
- overflow: "hidden",
794
- };
795
- const itemStyle = {
796
- display: "flex",
797
- alignItems: "center",
798
- gap: 8,
799
- padding: "8px 12px",
800
- fontSize: 13,
801
- cursor: "pointer",
802
- transition: "background 0.1s",
803
- width: "100%",
804
- border: "none",
805
- background: "transparent",
806
- color: "inherit",
807
- textAlign: "left",
808
- };
809
- const activeItemStyle = {
810
- ...itemStyle,
811
- background: "var(--accent, #2a2a2a)",
812
- };
813
- const separatorStyle = {
814
- height: 1,
815
- background: "var(--border, #333)",
816
- margin: "4px 0",
817
- };
818
- const labelStyle = {
819
- padding: "6px 12px",
820
- fontSize: 11,
821
- fontWeight: 600,
822
- textTransform: "uppercase",
823
- letterSpacing: "0.05em",
824
- color: "var(--muted-foreground, #888)",
825
- };
826
- return createElement("div", { ref: menuRef, className: `relative ${className}`, style: { position: "relative" } },
827
- // Trigger button
828
- createElement("button", {
829
- onClick: () => setOpen(!open),
830
- style: {
831
- display: "flex",
832
- alignItems: "center",
833
- gap: 8,
834
- padding: "6px 10px",
835
- borderRadius: 6,
836
- border: "none",
837
- background: "transparent",
838
- cursor: "pointer",
839
- color: "inherit",
840
- fontSize: 13,
841
- fontWeight: 500,
842
- },
843
- "aria-label": "User menu",
844
- }, userAvatar
845
- ? createElement("img", {
846
- src: userAvatar,
847
- alt: userName,
848
- style: { width: 24, height: 24, borderRadius: "50%", objectFit: "cover" },
849
- })
850
- : createElement("div", {
851
- style: {
852
- width: 24,
853
- height: 24,
854
- borderRadius: "50%",
855
- background: "var(--primary, #3b82f6)",
856
- display: "flex",
857
- alignItems: "center",
858
- justifyContent: "center",
859
- fontSize: 11,
860
- fontWeight: 600,
861
- color: "#fff",
862
- },
863
- }, userName.charAt(0).toUpperCase()), createElement("span", { style: { maxWidth: 120, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, currentLabel), createElement("span", { style: { fontSize: 10, opacity: 0.5 } }, open ? "\u25B2" : "\u25BC")),
864
- // Dropdown menu
865
- open &&
866
- createElement("div", { style: menuStyle },
867
- // User info section
868
- createElement("div", { style: { padding: "10px 12px", borderBottom: "1px solid var(--border, #333)" } }, createElement("div", { style: { fontSize: 13, fontWeight: 600 } }, userName), userEmail && createElement("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 2 } }, userEmail)),
869
- // Organization section
870
- createElement("div", { style: labelStyle }, "Organization"), ...orgs.map((org) => createElement("button", {
871
- key: org.name,
872
- onClick: () => handleSwitchOrg(org.name),
873
- style: org.name === orgState.currentOrgId ? activeItemStyle : itemStyle,
874
- onMouseEnter: (e) => { e.target.style.background = "var(--accent, #2a2a2a)"; },
875
- onMouseLeave: (e) => { if (org.name !== orgState.currentOrgId)
876
- e.target.style.background = "transparent"; },
877
- }, org.name === orgState.currentOrgId ? "\u2713 " : " ", org.displayName || org.name)),
878
- // Create org option
879
- showCreateOrg &&
880
- createElement("div", null, createElement("div", { style: separatorStyle }), !createOpen
881
- ? createElement("button", {
882
- onClick: () => setCreateOpen(true),
883
- style: itemStyle,
884
- onMouseEnter: (e) => { e.target.style.background = "var(--accent, #2a2a2a)"; },
885
- onMouseLeave: (e) => { e.target.style.background = "transparent"; },
886
- }, "+ Create Organization")
887
- : createElement("div", { style: { padding: "8px 12px" } }, createElement("input", {
888
- type: "text",
889
- placeholder: "org-name",
890
- value: newOrgName,
891
- onChange: (e) => setNewOrgName(e.target.value),
892
- style: {
893
- width: "100%",
894
- padding: "6px 8px",
895
- fontSize: 12,
896
- borderRadius: 4,
897
- border: "1px solid var(--border, #333)",
898
- background: "var(--background, #111)",
899
- color: "inherit",
900
- marginBottom: 4,
901
- },
902
- disabled: creating,
903
- }), createElement("input", {
904
- type: "text",
905
- placeholder: "Display Name",
906
- value: newOrgDisplay,
907
- onChange: (e) => setNewOrgDisplay(e.target.value),
908
- style: {
909
- width: "100%",
910
- padding: "6px 8px",
911
- fontSize: 12,
912
- borderRadius: 4,
913
- border: "1px solid var(--border, #333)",
914
- background: "var(--background, #111)",
915
- color: "inherit",
916
- marginBottom: 4,
917
- },
918
- disabled: creating,
919
- }), error && createElement("div", { style: { fontSize: 11, color: "#ef4444", marginBottom: 4 } }, error), createElement("div", { style: { display: "flex", gap: 4 } }, createElement("button", {
920
- onClick: handleCreateOrg,
921
- disabled: creating || !newOrgName.trim(),
922
- style: {
923
- flex: 1,
924
- padding: "5px 8px",
925
- fontSize: 12,
926
- borderRadius: 4,
927
- border: "none",
928
- background: "var(--primary, #3b82f6)",
929
- color: "#fff",
930
- cursor: creating ? "wait" : "pointer",
931
- opacity: creating || !newOrgName.trim() ? 0.5 : 1,
932
- },
933
- }, creating ? "Creating..." : "Create"), createElement("button", {
934
- onClick: () => { setCreateOpen(false); setError(null); },
935
- style: {
936
- padding: "5px 8px",
937
- fontSize: 12,
938
- borderRadius: 4,
939
- border: "1px solid var(--border, #333)",
940
- background: "transparent",
941
- color: "inherit",
942
- cursor: "pointer",
943
- },
944
- }, "Cancel")))),
945
- // Logout
946
- createElement("div", { style: separatorStyle }), createElement("button", {
947
- onClick: handleLogout,
948
- style: { ...itemStyle, color: "var(--destructive, #ef4444)" },
949
- onMouseEnter: (e) => { e.target.style.background = "var(--accent, #2a2a2a)"; },
950
- onMouseLeave: (e) => { e.target.style.background = "transparent"; },
951
- }, "Sign out")));
952
- }
953
522
  //# sourceMappingURL=react.js.map