@luxfi/ui 7.3.2 → 7.4.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.
Files changed (154) hide show
  1. package/dist/alert.cjs +2 -2
  2. package/dist/alert.js +1 -1
  3. package/dist/avatar.cjs +7 -6
  4. package/dist/avatar.js +7 -5
  5. package/dist/badge.cjs +36 -38
  6. package/dist/badge.js +31 -33
  7. package/dist/bank.cjs +13 -13
  8. package/dist/bank.d.cts +31 -24
  9. package/dist/bank.d.ts +31 -24
  10. package/dist/bank.js +1 -1
  11. package/dist/button.cjs +3 -3
  12. package/dist/button.js +1 -1
  13. package/dist/checkbox.cjs +4 -4
  14. package/dist/checkbox.d.cts +3 -2
  15. package/dist/checkbox.d.ts +3 -2
  16. package/dist/checkbox.js +5 -4
  17. package/dist/chrome.cjs +950 -0
  18. package/dist/chrome.d.cts +93 -0
  19. package/dist/chrome.d.ts +93 -0
  20. package/dist/chrome.js +921 -0
  21. package/dist/close-button.cjs +2 -2
  22. package/dist/close-button.js +1 -1
  23. package/dist/dialog.cjs +13 -14
  24. package/dist/dialog.d.cts +58 -5
  25. package/dist/dialog.d.ts +58 -5
  26. package/dist/dialog.js +1 -2
  27. package/dist/drawer.cjs +87 -21
  28. package/dist/drawer.js +85 -19
  29. package/dist/expiration-selector.cjs +3 -2
  30. package/dist/expiration-selector.js +3 -2
  31. package/dist/greeks-display.cjs +6 -6
  32. package/dist/greeks-display.js +6 -6
  33. package/dist/gui.cjs +13 -0
  34. package/dist/gui.d.cts +2 -0
  35. package/dist/gui.d.ts +2 -0
  36. package/dist/gui.js +2 -0
  37. package/dist/heading.cjs +4 -2
  38. package/dist/heading.js +4 -2
  39. package/dist/iam.cjs +206 -0
  40. package/dist/iam.d.cts +65 -0
  41. package/dist/iam.d.ts +65 -0
  42. package/dist/iam.js +201 -0
  43. package/dist/icon-button.cjs +2 -2
  44. package/dist/icon-button.js +1 -1
  45. package/dist/icons.cjs +13 -0
  46. package/dist/icons.d.cts +1 -0
  47. package/dist/icons.d.ts +1 -0
  48. package/dist/icons.js +2 -0
  49. package/dist/identity.cjs +420 -0
  50. package/dist/identity.d.cts +64 -0
  51. package/dist/identity.d.ts +64 -0
  52. package/dist/identity.js +398 -0
  53. package/dist/index.cjs +1699 -860
  54. package/dist/index.d.cts +12 -4
  55. package/dist/index.d.ts +12 -4
  56. package/dist/index.js +1618 -802
  57. package/dist/input.cjs +13 -3
  58. package/dist/input.d.cts +6 -0
  59. package/dist/input.d.ts +6 -0
  60. package/dist/input.js +13 -3
  61. package/dist/lux.config.cjs +77 -0
  62. package/dist/lux.config.d.cts +433 -0
  63. package/dist/lux.config.d.ts +433 -0
  64. package/dist/lux.config.js +69 -0
  65. package/dist/menu.cjs +4 -4
  66. package/dist/menu.js +4 -4
  67. package/dist/next.cjs +99 -0
  68. package/dist/next.d.cts +29 -0
  69. package/dist/next.d.ts +29 -0
  70. package/dist/next.js +96 -0
  71. package/dist/option-chain.cjs +1 -1
  72. package/dist/option-chain.js +1 -1
  73. package/dist/option-position.cjs +2 -1
  74. package/dist/option-position.js +2 -1
  75. package/dist/pnl-diagram.cjs +8 -6
  76. package/dist/pnl-diagram.js +8 -6
  77. package/dist/popover.cjs +8 -9
  78. package/dist/popover.js +1 -2
  79. package/dist/progress.cjs +7 -6
  80. package/dist/progress.js +7 -5
  81. package/dist/provider.cjs +199 -5
  82. package/dist/provider.d.cts +16 -3
  83. package/dist/provider.d.ts +16 -3
  84. package/dist/provider.js +195 -6
  85. package/dist/radio.cjs +9 -7
  86. package/dist/radio.js +10 -7
  87. package/dist/separator.cjs +3 -1
  88. package/dist/separator.js +3 -1
  89. package/dist/slider.cjs +28 -16
  90. package/dist/slider.js +28 -15
  91. package/dist/strategy-builder.cjs +2 -1
  92. package/dist/strategy-builder.js +2 -1
  93. package/dist/switch.cjs +11 -12
  94. package/dist/switch.js +12 -12
  95. package/dist/tag.cjs +38 -41
  96. package/dist/tag.js +31 -34
  97. package/dist/textarea.cjs +13 -3
  98. package/dist/textarea.d.cts +2 -0
  99. package/dist/textarea.d.ts +2 -0
  100. package/dist/textarea.js +13 -3
  101. package/dist/tooltip.cjs +31 -33
  102. package/dist/tooltip.js +25 -27
  103. package/dist/use-narrow.cjs +46 -0
  104. package/dist/use-narrow.d.cts +12 -0
  105. package/dist/use-narrow.d.ts +12 -0
  106. package/dist/use-narrow.js +20 -0
  107. package/dist/vite.cjs +40 -0
  108. package/dist/vite.d.cts +29 -0
  109. package/dist/vite.d.ts +29 -0
  110. package/dist/vite.js +38 -0
  111. package/dist/white-label.cjs +132 -0
  112. package/dist/white-label.d.cts +59 -0
  113. package/dist/white-label.d.ts +59 -0
  114. package/dist/white-label.js +124 -0
  115. package/package.json +95 -17
  116. package/src/avatar.tsx +22 -16
  117. package/src/bank.tsx +2 -2
  118. package/src/button.tsx +1 -1
  119. package/src/checkbox.tsx +15 -10
  120. package/src/chrome.tsx +459 -0
  121. package/src/close-button.tsx +1 -1
  122. package/src/dialog.tsx +1 -1
  123. package/src/drawer.tsx +83 -10
  124. package/src/expiration-selector.tsx +3 -2
  125. package/src/greeks-display.tsx +9 -6
  126. package/src/gui.ts +17 -0
  127. package/src/heading.tsx +12 -8
  128. package/src/iam.ts +170 -0
  129. package/src/icon-button.tsx +1 -1
  130. package/src/icons.ts +18 -0
  131. package/src/identity.tsx +318 -0
  132. package/src/index.ts +50 -1
  133. package/src/input.tsx +24 -4
  134. package/src/lux.config.ts +82 -0
  135. package/src/menu.tsx +4 -4
  136. package/src/next.ts +142 -0
  137. package/src/option-chain.tsx +1 -1
  138. package/src/option-position.tsx +2 -1
  139. package/src/pnl-diagram.tsx +11 -6
  140. package/src/popover.tsx +1 -1
  141. package/src/progress.tsx +15 -8
  142. package/src/provider.tsx +92 -12
  143. package/src/radio.tsx +15 -11
  144. package/src/separator.tsx +8 -3
  145. package/src/slider.tsx +35 -19
  146. package/src/strategy-builder.tsx +3 -2
  147. package/src/switch.tsx +17 -16
  148. package/src/textarea.tsx +18 -4
  149. package/src/tooltip.tsx +4 -23
  150. package/src/use-narrow.ts +40 -0
  151. package/src/vite.ts +78 -0
  152. package/src/white-label.ts +240 -0
  153. package/tokens.css +544 -319
  154. package/src/tokens.css +0 -438
package/dist/iam.js ADDED
@@ -0,0 +1,201 @@
1
+ // src/white-label.ts
2
+ var LUX_BRAND = {
3
+ lux: "#7000FF",
4
+ zoo: "#6C5EFB",
5
+ hanzo: "#EA580C",
6
+ pars: "#1C3879"};
7
+ var PARS_GOLD = "#C8A45C";
8
+ var ORGS = {
9
+ lux: {
10
+ org: "lux",
11
+ name: "Lux",
12
+ domain: "lux.network",
13
+ iamDomain: "lux.id",
14
+ accent: LUX_BRAND.lux,
15
+ accentForeground: "#FFFFFF"
16
+ },
17
+ zoo: {
18
+ org: "zoo",
19
+ name: "Zoo",
20
+ domain: "zoo.ngo",
21
+ // Zoo's IdP is `id.zoo.network`, not a `zoo.id` apex — the one row in this
22
+ // table where the issuer is not `<org>.id`, and the one every hand-rolled
23
+ // per-app branding map got wrong.
24
+ iamDomain: "id.zoo.network",
25
+ accent: LUX_BRAND.zoo,
26
+ accentForeground: "#FFFFFF"
27
+ },
28
+ bootnode: {
29
+ org: "bootnode",
30
+ name: "Bootnode",
31
+ domain: "bootno.de",
32
+ iamDomain: "id.bootno.de",
33
+ accent: LUX_BRAND.lux,
34
+ accentForeground: "#FFFFFF"
35
+ },
36
+ hanzo: {
37
+ org: "hanzo",
38
+ name: "Hanzo",
39
+ domain: "hanzo.ai",
40
+ iamDomain: "hanzo.id",
41
+ accent: LUX_BRAND.hanzo,
42
+ accentForeground: "#FFFFFF"
43
+ },
44
+ pars: {
45
+ org: "pars",
46
+ name: "Pars",
47
+ domain: "pars.id",
48
+ iamDomain: "pars.id",
49
+ accent: LUX_BRAND.pars,
50
+ accentForeground: PARS_GOLD
51
+ }
52
+ };
53
+ var DOMAIN_ORG = [
54
+ ["lux.network", "lux"],
55
+ ["lux.cloud", "lux"],
56
+ ["lux.id", "lux"],
57
+ ["lux.finance", "lux"],
58
+ ["zoo.ngo", "zoo"],
59
+ ["zoo.network", "zoo"],
60
+ ["zoo.cloud", "zoo"],
61
+ ["zoo.id", "zoo"],
62
+ ["hanzo.ai", "hanzo"],
63
+ ["hanzo.cloud", "hanzo"],
64
+ ["hanzo.id", "hanzo"],
65
+ ["pars.id", "pars"],
66
+ ["parsdao.org", "pars"],
67
+ ["bootno.de", "bootnode", "platform"]
68
+ ];
69
+ var DEFAULT_ORG = "lux";
70
+ function normalizeHost(host) {
71
+ const noPort = host.trim().toLowerCase().split(":")[0] ?? "";
72
+ return noPort.replace(/\.$/, "");
73
+ }
74
+ function isUnder(host, domain) {
75
+ return host === domain || host.endsWith(`.${domain}`);
76
+ }
77
+ function matchDomain(host) {
78
+ let best;
79
+ for (const entry of DOMAIN_ORG) {
80
+ if (isUnder(host, entry[0]) && (!best || entry[0].length > best[0].length)) {
81
+ best = entry;
82
+ }
83
+ }
84
+ return best;
85
+ }
86
+ function appSlug(host, domain, apexApp) {
87
+ const sub = host === domain ? "" : host.slice(0, -(domain.length + 1));
88
+ const leftmost = sub.split(".")[0] ?? "";
89
+ if (leftmost && leftmost !== "www") return leftmost;
90
+ if (apexApp) return apexApp;
91
+ const label = domain.split(".")[0] ?? "";
92
+ const tld = domain.split(".").slice(1).join(".");
93
+ return tld.split(".")[0] || label;
94
+ }
95
+ function isLocal(host) {
96
+ return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "0.0.0.0" || host === "[::1]" || host.endsWith(".local");
97
+ }
98
+ function resolveWhiteLabel(host) {
99
+ const h = normalizeHost(host ?? "");
100
+ const match = h ? matchDomain(h) : void 0;
101
+ const org = match ? match[1] : DEFAULT_ORG;
102
+ const identity = ORGS[org];
103
+ const app = match ? appSlug(h, match[0], match[2]) : h && isLocal(h) ? "local" : "app";
104
+ return {
105
+ ...identity,
106
+ host: h,
107
+ app,
108
+ theme: `dark_${org}`,
109
+ issuer: `https://${identity.iamDomain}`,
110
+ clientId: `${org}-${app}`
111
+ };
112
+ }
113
+
114
+ // src/iam.ts
115
+ var IamError = class extends Error {
116
+ constructor(message, status, unauthenticated) {
117
+ super(message);
118
+ this.name = "IamError";
119
+ this.status = status;
120
+ this.unauthenticated = unauthenticated;
121
+ }
122
+ };
123
+ var SIGNED_OUT = /please sign in|authentication required|unauthorized/i;
124
+ function isSignedOut(status, msg) {
125
+ return status === 401 || status === 403 || SIGNED_OUT.test(msg);
126
+ }
127
+ function iamBase(host) {
128
+ const wl = typeof host === "object" && host !== null ? host : resolveWhiteLabel(host);
129
+ return `${wl.issuer}/v1/iam`;
130
+ }
131
+ async function read(call, verb, params) {
132
+ const url = new URL(`${call.base}/${verb}`);
133
+ for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
134
+ let res;
135
+ try {
136
+ res = await fetch(url.toString(), {
137
+ method: "GET",
138
+ // The bearer is the whole credential. IAM does not allow credentialed
139
+ // CORS, so sending cookies would only get the response blocked.
140
+ headers: call.token ? { Authorization: `Bearer ${call.token}` } : {},
141
+ signal: call.signal
142
+ });
143
+ } catch (e) {
144
+ throw new IamError(e instanceof Error ? e.message : "IAM unreachable", 0, false);
145
+ }
146
+ const body = await res.json().catch(() => ({}));
147
+ const msg = typeof body.msg === "string" ? body.msg : "";
148
+ if (!res.ok || body.status === "error") {
149
+ throw new IamError(msg || `IAM ${verb} failed (${res.status})`, res.status, isSignedOut(res.status, msg));
150
+ }
151
+ return body.data;
152
+ }
153
+ var IAM_PAGE_SIZE = 20;
154
+ var iam = {
155
+ /** The signed-in principal, or null when there is no session. */
156
+ async account(call) {
157
+ try {
158
+ const data = await read(call, "get-account", {});
159
+ return data && data.name ? data : null;
160
+ } catch (e) {
161
+ if (e instanceof IamError && e.unauthenticated) return null;
162
+ throw e;
163
+ }
164
+ },
165
+ /**
166
+ * One page of the orgs the principal can see. IAM scopes the read to the
167
+ * caller's authority, so a normal member gets their own org and a super
168
+ * admin gets the tenant list — the page never has to decide.
169
+ */
170
+ async organizations(call, page = 0, query = "", pageSize = IAM_PAGE_SIZE) {
171
+ const params = {
172
+ owner: "admin",
173
+ // IAM orgs live under the reserved `admin` owner
174
+ p: page + 1,
175
+ // the backend page is 1-based
176
+ pageSize,
177
+ sortField: "name",
178
+ sortOrder: "ascending"
179
+ };
180
+ const q = query.trim();
181
+ if (q) {
182
+ params.field = "name";
183
+ params.value = q;
184
+ }
185
+ return await read(call, "get-organizations", params) ?? [];
186
+ },
187
+ /** One organization by name. */
188
+ async organization(call, name) {
189
+ return await read(call, "get-organization", { id: `admin/${name}` }) ?? null;
190
+ },
191
+ /** Members of an organization. */
192
+ async users(call, org) {
193
+ return await read(call, "get-users", { owner: org }) ?? [];
194
+ },
195
+ /** The organization's projects — IAM keys this read by `organization`. */
196
+ async projects(call, org) {
197
+ return await read(call, "get-organization-projects", { organization: org }) ?? [];
198
+ }
199
+ };
200
+
201
+ export { IAM_PAGE_SIZE, IamError, iam, iamBase };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var core = require('@hanzogui/core');
4
+ var gui = require('@hanzo/gui');
5
5
  var React = require('react');
6
6
  var clsx = require('clsx');
7
7
  var tailwindMerge = require('tailwind-merge');
@@ -218,7 +218,7 @@ React__namespace.forwardRef(
218
218
  )) });
219
219
  }
220
220
  );
221
- var IconButtonFrame = core.styled(core.View, {
221
+ var IconButtonFrame = gui.styled(gui.View, {
222
222
  name: "LuxIconButton",
223
223
  render: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button" }),
224
224
  role: "button",
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { styled, View } from '@hanzogui/core';
2
+ import { styled, View } from '@hanzo/gui';
3
3
  import * as React from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
package/dist/icons.cjs ADDED
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var lucideReact = require('lucide-react');
5
+
6
+
7
+
8
+ Object.keys(lucideReact).forEach(function (k) {
9
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
10
+ enumerable: true,
11
+ get: function () { return lucideReact[k]; }
12
+ });
13
+ });
@@ -0,0 +1 @@
1
+ export * from 'lucide-react';
@@ -0,0 +1 @@
1
+ export * from 'lucide-react';
package/dist/icons.js ADDED
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export * from 'lucide-react';
@@ -0,0 +1,420 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React2 = require('react');
5
+ require('@hanzo/gui');
6
+ var reactQuery = require('@tanstack/react-query');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
28
+
29
+ // src/white-label.ts
30
+ var LUX_BRAND = {
31
+ lux: "#7000FF",
32
+ zoo: "#6C5EFB",
33
+ hanzo: "#EA580C",
34
+ pars: "#1C3879"};
35
+ var PARS_GOLD = "#C8A45C";
36
+ var ORGS = {
37
+ lux: {
38
+ org: "lux",
39
+ name: "Lux",
40
+ domain: "lux.network",
41
+ iamDomain: "lux.id",
42
+ accent: LUX_BRAND.lux,
43
+ accentForeground: "#FFFFFF"
44
+ },
45
+ zoo: {
46
+ org: "zoo",
47
+ name: "Zoo",
48
+ domain: "zoo.ngo",
49
+ // Zoo's IdP is `id.zoo.network`, not a `zoo.id` apex — the one row in this
50
+ // table where the issuer is not `<org>.id`, and the one every hand-rolled
51
+ // per-app branding map got wrong.
52
+ iamDomain: "id.zoo.network",
53
+ accent: LUX_BRAND.zoo,
54
+ accentForeground: "#FFFFFF"
55
+ },
56
+ bootnode: {
57
+ org: "bootnode",
58
+ name: "Bootnode",
59
+ domain: "bootno.de",
60
+ iamDomain: "id.bootno.de",
61
+ accent: LUX_BRAND.lux,
62
+ accentForeground: "#FFFFFF"
63
+ },
64
+ hanzo: {
65
+ org: "hanzo",
66
+ name: "Hanzo",
67
+ domain: "hanzo.ai",
68
+ iamDomain: "hanzo.id",
69
+ accent: LUX_BRAND.hanzo,
70
+ accentForeground: "#FFFFFF"
71
+ },
72
+ pars: {
73
+ org: "pars",
74
+ name: "Pars",
75
+ domain: "pars.id",
76
+ iamDomain: "pars.id",
77
+ accent: LUX_BRAND.pars,
78
+ accentForeground: PARS_GOLD
79
+ }
80
+ };
81
+ var DOMAIN_ORG = [
82
+ ["lux.network", "lux"],
83
+ ["lux.cloud", "lux"],
84
+ ["lux.id", "lux"],
85
+ ["lux.finance", "lux"],
86
+ ["zoo.ngo", "zoo"],
87
+ ["zoo.network", "zoo"],
88
+ ["zoo.cloud", "zoo"],
89
+ ["zoo.id", "zoo"],
90
+ ["hanzo.ai", "hanzo"],
91
+ ["hanzo.cloud", "hanzo"],
92
+ ["hanzo.id", "hanzo"],
93
+ ["pars.id", "pars"],
94
+ ["parsdao.org", "pars"],
95
+ ["bootno.de", "bootnode", "platform"]
96
+ ];
97
+ var DEFAULT_ORG = "lux";
98
+ function normalizeHost(host) {
99
+ const noPort = host.trim().toLowerCase().split(":")[0] ?? "";
100
+ return noPort.replace(/\.$/, "");
101
+ }
102
+ function isUnder(host, domain) {
103
+ return host === domain || host.endsWith(`.${domain}`);
104
+ }
105
+ function matchDomain(host) {
106
+ let best;
107
+ for (const entry of DOMAIN_ORG) {
108
+ if (isUnder(host, entry[0]) && (!best || entry[0].length > best[0].length)) {
109
+ best = entry;
110
+ }
111
+ }
112
+ return best;
113
+ }
114
+ function appSlug(host, domain, apexApp) {
115
+ const sub = host === domain ? "" : host.slice(0, -(domain.length + 1));
116
+ const leftmost = sub.split(".")[0] ?? "";
117
+ if (leftmost && leftmost !== "www") return leftmost;
118
+ if (apexApp) return apexApp;
119
+ const label = domain.split(".")[0] ?? "";
120
+ const tld = domain.split(".").slice(1).join(".");
121
+ return tld.split(".")[0] || label;
122
+ }
123
+ function isLocal(host) {
124
+ return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "0.0.0.0" || host === "[::1]" || host.endsWith(".local");
125
+ }
126
+ function resolveWhiteLabel(host) {
127
+ const h = normalizeHost(host ?? "");
128
+ const match = h ? matchDomain(h) : void 0;
129
+ const org = match ? match[1] : DEFAULT_ORG;
130
+ const identity = ORGS[org];
131
+ const app = match ? appSlug(h, match[0], match[2]) : h && isLocal(h) ? "local" : "app";
132
+ return {
133
+ ...identity,
134
+ host: h,
135
+ app,
136
+ theme: `dark_${org}`,
137
+ issuer: `https://${identity.iamDomain}`,
138
+ clientId: `${org}-${app}`
139
+ };
140
+ }
141
+
142
+ // src/iam.ts
143
+ var IamError = class extends Error {
144
+ constructor(message, status, unauthenticated) {
145
+ super(message);
146
+ this.name = "IamError";
147
+ this.status = status;
148
+ this.unauthenticated = unauthenticated;
149
+ }
150
+ };
151
+ var SIGNED_OUT = /please sign in|authentication required|unauthorized/i;
152
+ function isSignedOut(status, msg) {
153
+ return status === 401 || status === 403 || SIGNED_OUT.test(msg);
154
+ }
155
+ function iamBase(host) {
156
+ const wl = typeof host === "object" && host !== null ? host : resolveWhiteLabel(host);
157
+ return `${wl.issuer}/v1/iam`;
158
+ }
159
+ async function read(call, verb, params) {
160
+ const url = new URL(`${call.base}/${verb}`);
161
+ for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
162
+ let res;
163
+ try {
164
+ res = await fetch(url.toString(), {
165
+ method: "GET",
166
+ // The bearer is the whole credential. IAM does not allow credentialed
167
+ // CORS, so sending cookies would only get the response blocked.
168
+ headers: call.token ? { Authorization: `Bearer ${call.token}` } : {},
169
+ signal: call.signal
170
+ });
171
+ } catch (e) {
172
+ throw new IamError(e instanceof Error ? e.message : "IAM unreachable", 0, false);
173
+ }
174
+ const body = await res.json().catch(() => ({}));
175
+ const msg = typeof body.msg === "string" ? body.msg : "";
176
+ if (!res.ok || body.status === "error") {
177
+ throw new IamError(msg || `IAM ${verb} failed (${res.status})`, res.status, isSignedOut(res.status, msg));
178
+ }
179
+ return body.data;
180
+ }
181
+ var IAM_PAGE_SIZE = 20;
182
+ var iam = {
183
+ /** The signed-in principal, or null when there is no session. */
184
+ async account(call) {
185
+ try {
186
+ const data = await read(call, "get-account", {});
187
+ return data && data.name ? data : null;
188
+ } catch (e) {
189
+ if (e instanceof IamError && e.unauthenticated) return null;
190
+ throw e;
191
+ }
192
+ },
193
+ /**
194
+ * One page of the orgs the principal can see. IAM scopes the read to the
195
+ * caller's authority, so a normal member gets their own org and a super
196
+ * admin gets the tenant list — the page never has to decide.
197
+ */
198
+ async organizations(call, page = 0, query = "", pageSize = IAM_PAGE_SIZE) {
199
+ const params = {
200
+ owner: "admin",
201
+ // IAM orgs live under the reserved `admin` owner
202
+ p: page + 1,
203
+ // the backend page is 1-based
204
+ pageSize,
205
+ sortField: "name",
206
+ sortOrder: "ascending"
207
+ };
208
+ const q = query.trim();
209
+ if (q) {
210
+ params.field = "name";
211
+ params.value = q;
212
+ }
213
+ return await read(call, "get-organizations", params) ?? [];
214
+ },
215
+ /** One organization by name. */
216
+ async organization(call, name) {
217
+ return await read(call, "get-organization", { id: `admin/${name}` }) ?? null;
218
+ },
219
+ /** Members of an organization. */
220
+ async users(call, org) {
221
+ return await read(call, "get-users", { owner: org }) ?? [];
222
+ },
223
+ /** The organization's projects — IAM keys this read by `organization`. */
224
+ async projects(call, org) {
225
+ return await read(call, "get-organization-projects", { organization: org }) ?? [];
226
+ }
227
+ };
228
+ new reactQuery.QueryClient({
229
+ defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 1, staleTime: 3e4 } }
230
+ });
231
+ var WhiteLabelContext = React2__namespace.default.createContext(resolveWhiteLabel(""));
232
+ var useWhiteLabel = () => React2__namespace.default.useContext(WhiteLabelContext);
233
+ var IAM_TOKEN_KEY = "hanzo_iam_access_token";
234
+ function storedToken() {
235
+ if (typeof window === "undefined") return null;
236
+ for (const store of [window.sessionStorage, window.localStorage]) {
237
+ try {
238
+ const t = store.getItem(IAM_TOKEN_KEY);
239
+ if (t) return t;
240
+ } catch {
241
+ }
242
+ }
243
+ return null;
244
+ }
245
+ function clearStoredTokens() {
246
+ if (typeof window === "undefined") return;
247
+ for (const store of [window.sessionStorage, window.localStorage]) {
248
+ try {
249
+ for (const k of ["access_token", "refresh_token", "id_token", "expires_at"]) {
250
+ store.removeItem(`hanzo_iam_${k}`);
251
+ }
252
+ } catch {
253
+ }
254
+ }
255
+ }
256
+ var defaultAuth = {};
257
+ var scopeKey = (host, kind) => `lux-ui:${kind}:${host}`;
258
+ function readScope(host, kind) {
259
+ if (typeof window === "undefined") return null;
260
+ try {
261
+ return window.localStorage.getItem(scopeKey(host, kind));
262
+ } catch {
263
+ return null;
264
+ }
265
+ }
266
+ function writeScope(host, kind, value) {
267
+ if (typeof window === "undefined") return;
268
+ try {
269
+ window.localStorage.setItem(scopeKey(host, kind), value);
270
+ } catch {
271
+ }
272
+ }
273
+ var IdentityContext = React2__namespace.createContext(null);
274
+ var IdentityProvider = ({
275
+ children,
276
+ auth = defaultAuth,
277
+ loginPath = "/login"
278
+ }) => {
279
+ const whiteLabel = useWhiteLabel();
280
+ const base = React2__namespace.useMemo(() => iamBase(whiteLabel), [whiteLabel]);
281
+ const [status, setStatus] = React2__namespace.useState("loading");
282
+ const [account, setAccount] = React2__namespace.useState(null);
283
+ const [orgs, setOrgs] = React2__namespace.useState([]);
284
+ const [projects, setProjects] = React2__namespace.useState([]);
285
+ const [error, setError] = React2__namespace.useState(null);
286
+ const [orgName, setOrgName] = React2__namespace.useState(null);
287
+ const [projectName, setProjectName] = React2__namespace.useState(null);
288
+ const [epoch, setEpoch] = React2__namespace.useState(0);
289
+ React2__namespace.useEffect(() => {
290
+ const ac = new AbortController();
291
+ let live = true;
292
+ void (async () => {
293
+ const token = await (auth.token ?? storedToken)();
294
+ if (!live) return;
295
+ if (!token) {
296
+ setStatus("anonymous");
297
+ setAccount(null);
298
+ setOrgs([]);
299
+ return;
300
+ }
301
+ const call = { base, token, signal: ac.signal };
302
+ try {
303
+ const me = await iam.account(call);
304
+ if (!live) return;
305
+ if (!me) {
306
+ setStatus("anonymous");
307
+ setAccount(null);
308
+ setOrgs([]);
309
+ return;
310
+ }
311
+ setAccount(me);
312
+ const rows = await iam.organizations(call).catch((e) => {
313
+ if (e instanceof IamError && !e.unauthenticated) return [{ owner: "admin", name: me.owner }];
314
+ throw e;
315
+ });
316
+ if (!live) return;
317
+ setOrgs(rows);
318
+ setStatus("ready");
319
+ setError(null);
320
+ } catch (e) {
321
+ if (!live || ac.signal.aborted) return;
322
+ if (e instanceof IamError && e.unauthenticated) {
323
+ setStatus("anonymous");
324
+ setAccount(null);
325
+ setOrgs([]);
326
+ return;
327
+ }
328
+ setStatus("error");
329
+ setError(e instanceof Error ? e.message : "Could not reach IAM.");
330
+ }
331
+ })();
332
+ return () => {
333
+ live = false;
334
+ ac.abort();
335
+ };
336
+ }, [base, auth, epoch]);
337
+ const org = React2__namespace.useMemo(() => {
338
+ if (orgs.length === 0) return null;
339
+ const saved = orgName ?? readScope(whiteLabel.host, "org");
340
+ return orgs.find((o) => o.name === saved) ?? orgs.find((o) => o.name === account?.owner) ?? orgs[0];
341
+ }, [orgs, orgName, account, whiteLabel.host]);
342
+ React2__namespace.useEffect(() => {
343
+ if (!org) {
344
+ setProjects([]);
345
+ return;
346
+ }
347
+ const ac = new AbortController();
348
+ let live = true;
349
+ void (async () => {
350
+ const token = await (auth.token ?? storedToken)();
351
+ if (!live || !token) return;
352
+ const rows = await iam.projects({ base, token, signal: ac.signal }, org.name).catch(() => []);
353
+ if (live) setProjects(rows);
354
+ })();
355
+ return () => {
356
+ live = false;
357
+ ac.abort();
358
+ };
359
+ }, [base, auth, org, epoch]);
360
+ const project = React2__namespace.useMemo(() => {
361
+ if (projects.length === 0) return null;
362
+ const saved = projectName ?? readScope(whiteLabel.host, "project");
363
+ return projects.find((p) => p.name === saved) ?? projects.find((p) => p.isDefault) ?? projects[0];
364
+ }, [projects, projectName, whiteLabel.host]);
365
+ const value = React2__namespace.useMemo(() => ({
366
+ status,
367
+ account,
368
+ orgs,
369
+ org,
370
+ projects,
371
+ project,
372
+ error,
373
+ whiteLabel,
374
+ setOrg: (name) => {
375
+ writeScope(whiteLabel.host, "org", name);
376
+ setOrgName(name);
377
+ setProjectName(null);
378
+ },
379
+ setProject: (name) => {
380
+ writeScope(whiteLabel.host, "project", name);
381
+ setProjectName(name);
382
+ },
383
+ signIn: () => {
384
+ if (auth.signIn) return auth.signIn();
385
+ if (typeof window !== "undefined") window.location.assign(loginPath);
386
+ },
387
+ signOut: () => {
388
+ if (auth.signOut) return auth.signOut();
389
+ clearStoredTokens();
390
+ if (typeof window !== "undefined") window.location.assign(loginPath);
391
+ },
392
+ reload: () => setEpoch((n) => n + 1)
393
+ }), [status, account, orgs, org, projects, project, error, whiteLabel, auth, loginPath]);
394
+ return /* @__PURE__ */ jsxRuntime.jsx(IdentityContext.Provider, { value, children });
395
+ };
396
+ function useIdentity() {
397
+ const ctx = React2__namespace.useContext(IdentityContext);
398
+ const whiteLabel = useWhiteLabel();
399
+ const fallback = React2__namespace.useMemo(() => ({
400
+ status: "anonymous",
401
+ account: null,
402
+ orgs: [],
403
+ org: null,
404
+ projects: [],
405
+ project: null,
406
+ error: null,
407
+ whiteLabel,
408
+ setOrg: () => void 0,
409
+ setProject: () => void 0,
410
+ signIn: () => {
411
+ if (typeof window !== "undefined") window.location.assign("/login");
412
+ },
413
+ signOut: () => void 0,
414
+ reload: () => void 0
415
+ }), [whiteLabel]);
416
+ return ctx ?? fallback;
417
+ }
418
+
419
+ exports.IdentityProvider = IdentityProvider;
420
+ exports.useIdentity = useIdentity;