@hanzo/rooms 0.1.50 → 0.1.52
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/Me.js +24 -6
- package/dist/Orgs.js +17 -6
- package/dist/lib/api.d.ts +9 -0
- package/dist/lib/api.js +16 -1
- package/dist/lib/host.d.ts +7 -0
- package/dist/lib/host.js +14 -0
- package/package.json +1 -1
package/dist/Me.js
CHANGED
|
@@ -24,7 +24,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
24
24
|
// `POST /v1/account/orgs` the no-organization gate uses (components/workspace/
|
|
25
25
|
// Orgs.tsx); the next load mints a token that carries it.
|
|
26
26
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
27
|
-
import { Popover, SizableText, View, XStack, YStack } from '@hanzo/gui';
|
|
27
|
+
import { Popover, SizableText, usePopperContextSlow, View, XStack, YStack } from '@hanzo/gui';
|
|
28
28
|
import { Building2, ChevronsUpDown, CreditCard, ExternalLink, Gauge, KeyRound, LifeBuoy, LogOut, Plus, Settings, Sparkles, Users, X } from 'lucide-react';
|
|
29
29
|
import { label, Meter, path, said as spoken, useStanding, useWho } from '@hanzo/build';
|
|
30
30
|
import { useOrganizations } from '@hanzo/iam/react';
|
|
@@ -132,6 +132,24 @@ function rove(e, close) {
|
|
|
132
132
|
e.preventDefault();
|
|
133
133
|
rows[to]?.focus();
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The menu grows once the plan is read (`Meter`), after it was placed, and the
|
|
137
|
+
* popover follows its anchor, not its own size. So it is placed again whenever
|
|
138
|
+
* its content changes size, and never hangs over the row it opens from.
|
|
139
|
+
*/
|
|
140
|
+
function Refit({ children }) {
|
|
141
|
+
const { update } = usePopperContextSlow();
|
|
142
|
+
const box = useRef(null);
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const node = box.current;
|
|
145
|
+
if (!node || typeof ResizeObserver === 'undefined')
|
|
146
|
+
return;
|
|
147
|
+
const watch = new ResizeObserver(() => update());
|
|
148
|
+
watch.observe(node);
|
|
149
|
+
return () => watch.disconnect();
|
|
150
|
+
}, [update]);
|
|
151
|
+
return (_jsx(View, { render: _jsx("div", { ref: box }), self: "stretch", minW: 0, children: children }));
|
|
152
|
+
}
|
|
135
153
|
export function Me({ host, narrow = false, onOrg, }) {
|
|
136
154
|
const menu = useWho();
|
|
137
155
|
const [creating, setCreating] = useState(false);
|
|
@@ -168,11 +186,11 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
168
186
|
return null;
|
|
169
187
|
return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange: menu.onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: "100%", children: _jsxs(XStack, { ref: (el) => {
|
|
170
188
|
row.current = el instanceof HTMLElement ? el : null;
|
|
171
|
-
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Meter, { read: read, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(Refit, { children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Meter, { read: read, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
|
|
190
|
+
menu.onOpenChange(false);
|
|
191
|
+
if (o.name !== current)
|
|
192
|
+
onOrg(o.name);
|
|
193
|
+
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(site(ORG_SETTINGS))) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), label: "Billing", onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Item, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), label: "API keys", onPress: () => (menu.onOpenChange(false), window.location.assign(KEYS)) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "View all plans", onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(Rule, {}), _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) })] })) : null] }) }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
|
|
176
194
|
}
|
|
177
195
|
/**
|
|
178
196
|
* The sheet that creates an organization: a name, then `POST /v1/account/orgs`,
|
package/dist/Orgs.js
CHANGED
|
@@ -175,12 +175,13 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
|
|
|
175
175
|
if (!hydrated)
|
|
176
176
|
return front && Landing ? _jsx(Landing, {}) : _jsx(Veil, {});
|
|
177
177
|
// Past hydration the room keeps its place in the tree in every branch that
|
|
178
|
-
// draws it — the veil
|
|
178
|
+
// draws it — `Held`, the veil AFTER it — so a token refresh mid-session never
|
|
179
179
|
// remounts what the reader had open. The veil is unconditional now: it was
|
|
180
180
|
// hung only when a token was already in hand, which left a visitor arriving
|
|
181
|
-
// without one looking at the room while the SDK decided.
|
|
181
|
+
// without one looking at the room while the SDK decided. Under it the room is
|
|
182
|
+
// inert: it takes no key or press until the gate has decided.
|
|
182
183
|
if (isLoading)
|
|
183
|
-
return front && Landing && !hasSession() && !arriving ? _jsx(Landing, {}) : _jsxs(_Fragment, { children: [children, _jsx(Veil, {})] });
|
|
184
|
+
return front && Landing && !hasSession() && !arriving ? _jsx(Landing, {}) : _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
|
|
184
185
|
if (arriving && !isAuthenticated)
|
|
185
186
|
return _jsx(SignIn, { to: LOGIN });
|
|
186
187
|
if (!isAuthenticated)
|
|
@@ -202,11 +203,11 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
|
|
|
202
203
|
// exception rather than the fallback. The one exception to the plan is a
|
|
203
204
|
// personal organization on Free, off hanzo.team, and its kind is read first.
|
|
204
205
|
if (!tier && !wrong)
|
|
205
|
-
return _jsxs(_Fragment, { children: [children, _jsx(Veil, {})] });
|
|
206
|
+
return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
|
|
206
207
|
if (free && personal === null)
|
|
207
|
-
return _jsxs(_Fragment, { children: [children, _jsx(Veil, {})] });
|
|
208
|
+
return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
|
|
208
209
|
if (open)
|
|
209
|
-
return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: children });
|
|
210
|
+
return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: _jsx(Held, { veiled: false, children: children }) });
|
|
210
211
|
const swap = () => setPicked(false);
|
|
211
212
|
if (door) {
|
|
212
213
|
// A billing OUTAGE is ours; the plan is still the condition.
|
|
@@ -293,6 +294,16 @@ function Heading({ title, lede, lead }) {
|
|
|
293
294
|
function Veil() {
|
|
294
295
|
return _jsx(View, { "aria-hidden": true, position: "fixed", inset: 0, z: SHEET, bg: "var(--background)" });
|
|
295
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* The room, in the one place every branch that draws it puts it. Under the veil
|
|
299
|
+
* it is inert and hidden from assistive technology, so what the veil covers
|
|
300
|
+
* takes no focus, key or press: Dev's composer focuses itself as it mounts, and
|
|
301
|
+
* a team on Free could otherwise type and send before its ask was drawn. It
|
|
302
|
+
* lays out as its children do.
|
|
303
|
+
*/
|
|
304
|
+
function Held({ veiled, children }) {
|
|
305
|
+
return (_jsx(View, { render: _jsx("div", { "data-slot": "held", inert: veiled, "aria-hidden": veiled || undefined }), display: "contents", children: children }));
|
|
306
|
+
}
|
|
296
307
|
/** An open organization, sent on. */
|
|
297
308
|
function Forward({ to }) {
|
|
298
309
|
const { router } = useRooms();
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
* production address was frozen into the bundle before a browser could say
|
|
20
20
|
* where it was. Called at request time it answers for the page that is open.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* The devnet's gateway and identity. A page on a devnet host (`devnet()` in
|
|
24
|
+
* host.ts) talks to these and to nothing of production's: the export is the
|
|
25
|
+
* same bytes on hanzo.ai and on app-dev.hanzo.ai, so the host it is open on is
|
|
26
|
+
* the only thing that tells the two apart, and it outranks any address the
|
|
27
|
+
* build stated with `configure()`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const API_DEV = "https://api-dev.hanzo.ai";
|
|
30
|
+
export declare const IAM_DEV = "https://id-dev.hanzo.ai";
|
|
22
31
|
/** The gateway's origin, with no trailing slash: callers write `${api()}/v1/…`. */
|
|
23
32
|
export declare function api(): string;
|
|
24
33
|
/**
|
package/dist/lib/api.js
CHANGED
|
@@ -19,10 +19,23 @@
|
|
|
19
19
|
* production address was frozen into the bundle before a browser could say
|
|
20
20
|
* where it was. Called at request time it answers for the page that is open.
|
|
21
21
|
*/
|
|
22
|
-
import { guest, team } from './host.js';
|
|
22
|
+
import { devnet, guest, team } from './host.js';
|
|
23
23
|
import { where } from '../where.js';
|
|
24
|
+
/**
|
|
25
|
+
* The devnet's gateway and identity. A page on a devnet host (`devnet()` in
|
|
26
|
+
* host.ts) talks to these and to nothing of production's: the export is the
|
|
27
|
+
* same bytes on hanzo.ai and on app-dev.hanzo.ai, so the host it is open on is
|
|
28
|
+
* the only thing that tells the two apart, and it outranks any address the
|
|
29
|
+
* build stated with `configure()`.
|
|
30
|
+
*/
|
|
31
|
+
export const API_DEV = 'https://api-dev.hanzo.ai';
|
|
32
|
+
export const IAM_DEV = 'https://id-dev.hanzo.ai';
|
|
33
|
+
/** Whether the page in front of us is on a devnet host. */
|
|
34
|
+
const onDevnet = () => typeof window !== 'undefined' && devnet(window.location.hostname);
|
|
24
35
|
/** The gateway's origin, with no trailing slash: callers write `${api()}/v1/…`. */
|
|
25
36
|
export function api() {
|
|
37
|
+
if (onDevnet())
|
|
38
|
+
return API_DEV;
|
|
26
39
|
const set = where().api;
|
|
27
40
|
if (set)
|
|
28
41
|
return set.replace(/\/+$/, '');
|
|
@@ -52,6 +65,8 @@ export function api() {
|
|
|
52
65
|
* the day it is changed.
|
|
53
66
|
*/
|
|
54
67
|
export function iam() {
|
|
68
|
+
if (onDevnet())
|
|
69
|
+
return IAM_DEV;
|
|
55
70
|
return (where().iam || 'https://hanzo.id').replace(/\/+$/, '');
|
|
56
71
|
}
|
|
57
72
|
/**
|
package/dist/lib/host.d.ts
CHANGED
|
@@ -128,3 +128,10 @@ export declare const WEB = "https://hanzo.ai/chat";
|
|
|
128
128
|
* on hanzo.ai.
|
|
129
129
|
*/
|
|
130
130
|
export declare const guest: (hostname: string) => boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Whether a host is the devnet's: its production twin with `-dev` on the one
|
|
133
|
+
* label under hanzo.ai (app-dev.hanzo.ai, api-dev.hanzo.ai, id-dev.hanzo.ai).
|
|
134
|
+
* A deeper name (a.b-dev.hanzo.ai) is not one, and neither is a name that only
|
|
135
|
+
* contains the suffix (app-dev.hanzo.ai.example.com).
|
|
136
|
+
*/
|
|
137
|
+
export declare function devnet(hostname: string): boolean;
|
package/dist/lib/host.js
CHANGED
|
@@ -197,3 +197,17 @@ export const WEB = `${SIGNIN}${CHAT}`;
|
|
|
197
197
|
* on hanzo.ai.
|
|
198
198
|
*/
|
|
199
199
|
export const guest = (hostname) => hostname === 'hanzo.app' || hostname.endsWith('.hanzo.app') || hostname === 'hanzo.chat';
|
|
200
|
+
/** The devnet's hosts are one label under hanzo.ai ending in `-dev`: app-dev, api-dev, id-dev. */
|
|
201
|
+
const DEVNET = '-dev.hanzo.ai';
|
|
202
|
+
/**
|
|
203
|
+
* Whether a host is the devnet's: its production twin with `-dev` on the one
|
|
204
|
+
* label under hanzo.ai (app-dev.hanzo.ai, api-dev.hanzo.ai, id-dev.hanzo.ai).
|
|
205
|
+
* A deeper name (a.b-dev.hanzo.ai) is not one, and neither is a name that only
|
|
206
|
+
* contains the suffix (app-dev.hanzo.ai.example.com).
|
|
207
|
+
*/
|
|
208
|
+
export function devnet(hostname) {
|
|
209
|
+
if (!hostname.endsWith(DEVNET))
|
|
210
|
+
return false;
|
|
211
|
+
const label = hostname.slice(0, -'.hanzo.ai'.length);
|
|
212
|
+
return label.length > '-dev'.length && !label.includes('.');
|
|
213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|