@tangle-network/agent-app 0.49.32 → 0.49.34
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/bin/tangle-agent/home-maintenance.py +280 -0
- package/bin/tangle-agent/home.mjs +166 -0
- package/bin/tangle-agent/provision.mjs +168 -0
- package/bin/tangle-agent/runtime.mjs +151 -0
- package/bin/tangle-agent.mjs +26 -0
- package/dist/VaultPane-2SJSP6TS.js +7 -0
- package/dist/app-auth/index.d.ts +14 -2
- package/dist/app-auth/index.js +3 -3
- package/dist/app-auth/index.js.map +1 -1
- package/dist/assistant/index.js +6 -6
- package/dist/chat-routes/index.js +4 -4
- package/dist/chat-store/index.js +2 -2
- package/dist/{chunk-FCUWRMAF.js → chunk-56SVXAIK.js} +14 -9
- package/dist/chunk-56SVXAIK.js.map +1 -0
- package/dist/chunk-6CV66YOB.js +99 -0
- package/dist/chunk-6CV66YOB.js.map +1 -0
- package/dist/{chunk-V4YGTOCS.js → chunk-MIFVOYZ7.js} +5 -5
- package/dist/{chunk-FXWNNHPC.js → chunk-MVIIH2EQ.js} +165 -72
- package/dist/chunk-MVIIH2EQ.js.map +1 -0
- package/dist/{chunk-4PZE7XAM.js → chunk-S3344RBY.js} +4 -4
- package/dist/{chunk-4S36HWE4.js → chunk-WAMJSZYB.js} +1 -1
- package/dist/{chunk-VIL6MHHQ.js → chunk-XTLYE2VO.js} +6 -6
- package/dist/design-canvas-react/index.js +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/hosted-agent/general-skill.d.ts +2 -0
- package/dist/hosted-agent/general.d.ts +53 -0
- package/dist/hosted-agent/index.d.ts +9 -1
- package/dist/hosted-agent/index.js +294 -7
- package/dist/hosted-agent/index.js.map +1 -1
- package/dist/hosted-agent/react/LineBilling.d.ts +3 -0
- package/dist/hosted-agent/react/LineMembers.d.ts +3 -0
- package/dist/hosted-agent/react/LineSetup.d.ts +3 -0
- package/dist/hosted-agent/react/contracts.d.ts +136 -0
- package/dist/hosted-agent/react/index.d.ts +5 -0
- package/dist/hosted-agent/react/index.js +573 -0
- package/dist/hosted-agent/react/index.js.map +1 -0
- package/dist/hosted-agent/react/lines.css +110 -0
- package/dist/hosted-agent/react/styles.d.ts +2 -0
- package/dist/hosted-agent/react/styles.js +1 -0
- package/dist/hosted-agent/react/styles.js.map +1 -0
- package/dist/hosted-agent/workflows.d.ts +17 -0
- package/dist/integrations/index.d.ts +2 -9
- package/dist/integrations/index.js +11 -27
- package/dist/integrations/index.js.map +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/sso.d.ts +74 -7
- package/dist/profile/home.d.ts +23 -0
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +9 -1
- package/dist/profile/index.js.map +1 -1
- package/dist/public-consultation/index.js +4 -4
- package/dist/stories/hosted-agent/fixtures.d.ts +34 -0
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +4 -4
- package/dist/vault/ConfirmDialog.d.ts +1 -1
- package/dist/vault/VaultPane.d.ts +2 -2
- package/dist/vault/contracts.d.ts +8 -0
- package/dist/vault/index.js +1 -1
- package/dist/vault/lazy.d.ts +3 -3
- package/dist/vault/lazy.js +1 -1
- package/dist/vault/lazy.js.map +1 -1
- package/dist/web-react/index.js +22 -22
- package/package.json +50 -17
- package/dist/VaultPane-32HVWZ6A.js +0 -7
- package/dist/chunk-FCUWRMAF.js.map +0 -1
- package/dist/chunk-FXWNNHPC.js.map +0 -1
- /package/dist/{VaultPane-32HVWZ6A.js.map → VaultPane-2SJSP6TS.js.map} +0 -0
- /package/dist/{chunk-V4YGTOCS.js.map → chunk-MIFVOYZ7.js.map} +0 -0
- /package/dist/{chunk-4PZE7XAM.js.map → chunk-S3344RBY.js.map} +0 -0
- /package/dist/{chunk-4S36HWE4.js.map → chunk-WAMJSZYB.js.map} +0 -0
- /package/dist/{chunk-VIL6MHHQ.js.map → chunk-XTLYE2VO.js.map} +0 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Browser-safe line management UI. The host keeps owner credentials server-side. */
|
|
2
|
+
export type { ConnectableLineTransport, LineAnswerTarget, LineBillingProps, LineBillingView, LineBoxMode, LineConnectInput, LineConnectionOption, LineIdentityKind, LineIdentityOption, LineLastTurn, LineMemberRole, LineMembersClient, LineMembersProps, LineSetupClient, LineSetupLine, LineSetupProps, LineSetupSnapshot, } from './contracts';
|
|
3
|
+
export { LineSetup } from './LineSetup';
|
|
4
|
+
export { LineMembers } from './LineMembers';
|
|
5
|
+
export { LineBilling } from './LineBilling';
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
// src/hosted-agent/react/LineSetup.tsx
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var useBrowserLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
|
5
|
+
var TRANSPORT = {
|
|
6
|
+
imessage: "iMessage",
|
|
7
|
+
sms: "SMS",
|
|
8
|
+
whatsapp: "WhatsApp",
|
|
9
|
+
email: "Email"
|
|
10
|
+
};
|
|
11
|
+
var IDENTITY = {
|
|
12
|
+
handle: "Inkbox handle",
|
|
13
|
+
number: "Dedicated number",
|
|
14
|
+
email: "Email line"
|
|
15
|
+
};
|
|
16
|
+
function message(error) {
|
|
17
|
+
return error instanceof Error ? error.message : "The line request failed. Try again.";
|
|
18
|
+
}
|
|
19
|
+
function dateTime(value) {
|
|
20
|
+
const date = new Date(value);
|
|
21
|
+
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
22
|
+
}
|
|
23
|
+
function choicesFor(snapshot) {
|
|
24
|
+
return (snapshot.connections ?? []).flatMap((connection) => connection.identities.map((identity, index) => ({
|
|
25
|
+
key: `${connection.id}:${index}`,
|
|
26
|
+
connection,
|
|
27
|
+
identity
|
|
28
|
+
})));
|
|
29
|
+
}
|
|
30
|
+
function canOfferReconnect(line, choice) {
|
|
31
|
+
if (line.connectionId !== choice.connection.id || line.transport !== choice.identity.transport || line.targetId !== null) return false;
|
|
32
|
+
if (line.providerNumberId === null) return line.transport !== "whatsapp" && !choice.identity.phoneNumberId && !choice.identity.requiresPhoneNumberId;
|
|
33
|
+
return choice.identity.phoneNumberId === line.providerNumberId || !choice.identity.phoneNumberId && choice.identity.requiresPhoneNumberId === true;
|
|
34
|
+
}
|
|
35
|
+
function matchesLineIdentity(line, identity, numberId) {
|
|
36
|
+
if (line.transport !== identity.transport) return false;
|
|
37
|
+
if (line.providerNumberId === null) return line.transport !== "whatsapp" && !identity.phoneNumberId && !identity.requiresPhoneNumberId;
|
|
38
|
+
return numberId === line.providerNumberId;
|
|
39
|
+
}
|
|
40
|
+
function LineSetup({ client, scopeKey, initialTargetId, canManage, onNotice }) {
|
|
41
|
+
const clientRef = useRef(client);
|
|
42
|
+
clientRef.current = client;
|
|
43
|
+
const sequence = useRef(0);
|
|
44
|
+
const incarnation = useRef(0);
|
|
45
|
+
const confirmButton = useRef(null);
|
|
46
|
+
const heading = useRef(null);
|
|
47
|
+
const disconnectTriggers = useRef(/* @__PURE__ */ new Map());
|
|
48
|
+
const restoreFocusTo = useRef(null);
|
|
49
|
+
useBrowserLayoutEffect(() => {
|
|
50
|
+
incarnation.current++;
|
|
51
|
+
return () => {
|
|
52
|
+
incarnation.current++;
|
|
53
|
+
};
|
|
54
|
+
}, [scopeKey, initialTargetId]);
|
|
55
|
+
const [loadedSnapshot, setLoadedSnapshot] = useState(null);
|
|
56
|
+
const snapshot = loadedSnapshot?.scopeKey === scopeKey && loadedSnapshot.initialTargetId === initialTargetId ? loadedSnapshot.value : null;
|
|
57
|
+
const [loading, setLoading] = useState(true);
|
|
58
|
+
const [refreshFailed, setRefreshFailed] = useState(false);
|
|
59
|
+
const [busy, setBusy] = useState(false);
|
|
60
|
+
const [error, setError] = useState(null);
|
|
61
|
+
const [kind, setKind] = useState("handle");
|
|
62
|
+
const [choiceKey, setChoiceKey] = useState("");
|
|
63
|
+
const [targetId, setTargetId] = useState(initialTargetId ?? "");
|
|
64
|
+
const [boxMode, setBoxMode] = useState("per-member");
|
|
65
|
+
const [phoneNumberId, setPhoneNumberId] = useState("");
|
|
66
|
+
const [confirmLineId, setConfirmLineId] = useState(null);
|
|
67
|
+
function stillCurrent(requestIncarnation) {
|
|
68
|
+
return requestIncarnation === incarnation.current;
|
|
69
|
+
}
|
|
70
|
+
async function reload(requestIncarnation) {
|
|
71
|
+
const request = ++sequence.current;
|
|
72
|
+
try {
|
|
73
|
+
const next = await clientRef.current.load();
|
|
74
|
+
if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null;
|
|
75
|
+
setLoadedSnapshot({ scopeKey, initialTargetId, value: next });
|
|
76
|
+
setError(null);
|
|
77
|
+
setRefreshFailed(false);
|
|
78
|
+
setTargetId((current) => next.targets.some((target) => target.id === current) ? current : next.targets.find((target) => target.id === initialTargetId)?.id ?? next.targets[0]?.id ?? "");
|
|
79
|
+
setLoading(false);
|
|
80
|
+
return next;
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null;
|
|
83
|
+
setError(message(cause));
|
|
84
|
+
setRefreshFailed(true);
|
|
85
|
+
setLoading(false);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function retry() {
|
|
90
|
+
if (loading) return;
|
|
91
|
+
setLoading(true);
|
|
92
|
+
void reload(incarnation.current);
|
|
93
|
+
}
|
|
94
|
+
function closeConfirmation(lineId) {
|
|
95
|
+
restoreFocusTo.current = lineId;
|
|
96
|
+
setConfirmLineId(null);
|
|
97
|
+
}
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setLoading(true);
|
|
100
|
+
setLoadedSnapshot(null);
|
|
101
|
+
setError(null);
|
|
102
|
+
setRefreshFailed(false);
|
|
103
|
+
setBusy(false);
|
|
104
|
+
setKind("handle");
|
|
105
|
+
setChoiceKey("");
|
|
106
|
+
setTargetId(initialTargetId ?? "");
|
|
107
|
+
setBoxMode("per-member");
|
|
108
|
+
setPhoneNumberId("");
|
|
109
|
+
setConfirmLineId(null);
|
|
110
|
+
restoreFocusTo.current = null;
|
|
111
|
+
void reload(incarnation.current);
|
|
112
|
+
return () => {
|
|
113
|
+
sequence.current++;
|
|
114
|
+
};
|
|
115
|
+
}, [scopeKey, initialTargetId]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (confirmLineId) confirmButton.current?.focus();
|
|
118
|
+
}, [confirmLineId]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const lineId = restoreFocusTo.current;
|
|
121
|
+
if (confirmLineId !== null || lineId === null) return;
|
|
122
|
+
restoreFocusTo.current = null;
|
|
123
|
+
const trigger = disconnectTriggers.current.get(lineId);
|
|
124
|
+
if (trigger && !trigger.disabled) trigger.focus();
|
|
125
|
+
else heading.current?.focus();
|
|
126
|
+
}, [confirmLineId, snapshot, refreshFailed]);
|
|
127
|
+
const allChoices = snapshot ? choicesFor(snapshot) : [];
|
|
128
|
+
const choices = snapshot ? allChoices.filter((choice) => {
|
|
129
|
+
const occupied2 = snapshot.lines.filter((line2) => line2.status !== "released" && line2.transport === choice.identity.transport);
|
|
130
|
+
const [line] = occupied2;
|
|
131
|
+
return occupied2.length === 0 || occupied2.length === 1 && line !== void 0 && canOfferReconnect(line, choice);
|
|
132
|
+
}) : [];
|
|
133
|
+
const availableKinds = ["handle", "number", "email"].filter((value) => choices.some((choice) => choice.identity.kind === value));
|
|
134
|
+
const selectedKind = availableKinds.includes(kind) ? kind : availableKinds[0];
|
|
135
|
+
const kindChoices = choices.filter((choice) => choice.identity.kind === selectedKind);
|
|
136
|
+
const selected = kindChoices.find((choice) => choice.key === choiceKey) ?? kindChoices[0];
|
|
137
|
+
const selectedTarget = snapshot?.targets.find((target) => target.id === targetId);
|
|
138
|
+
const selectedMode = selectedTarget?.modes.includes(boxMode) ? boxMode : selectedTarget?.modes[0];
|
|
139
|
+
const enteredNumber = selected?.identity.phoneNumberId ?? phoneNumberId.trim();
|
|
140
|
+
const needsNumber = selected?.identity.transport === "whatsapp" || selected?.identity.requiresPhoneNumberId;
|
|
141
|
+
const occupied = selected && snapshot?.lines.filter((line) => line.status !== "released" && line.transport === selected.identity.transport);
|
|
142
|
+
const [occupiedLine] = occupied || [];
|
|
143
|
+
const reconnectChoice = Boolean(selected && occupied?.length === 1 && occupiedLine && canOfferReconnect(occupiedLine, selected));
|
|
144
|
+
const reconnecting = Boolean(reconnectChoice && selected && occupiedLine && matchesLineIdentity(occupiedLine, selected.identity, enteredNumber));
|
|
145
|
+
const blockedByExistingLine = Boolean(occupied?.length && !reconnecting);
|
|
146
|
+
async function connect() {
|
|
147
|
+
if (!selected || !selectedTarget || !selectedMode || needsNumber && !enteredNumber || blockedByExistingLine || busy || loading || refreshFailed) return;
|
|
148
|
+
const requestIncarnation = incarnation.current;
|
|
149
|
+
setBusy(true);
|
|
150
|
+
setError(null);
|
|
151
|
+
try {
|
|
152
|
+
await clientRef.current.connect({
|
|
153
|
+
connectionId: selected.connection.id,
|
|
154
|
+
transport: selected.identity.transport,
|
|
155
|
+
...enteredNumber ? { phoneNumberId: enteredNumber } : {},
|
|
156
|
+
targetId: selectedTarget.id,
|
|
157
|
+
boxMode: selectedMode
|
|
158
|
+
});
|
|
159
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
160
|
+
const updated = await reload(requestIncarnation);
|
|
161
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
162
|
+
if (!updated) {
|
|
163
|
+
onNotice?.({ kind: "error", message: "Line connected, but its current status could not be loaded." });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const line = updated.lines.find((item) => item.status !== "released" && item.connectionId === selected.connection.id && item.transport === selected.identity.transport && item.providerNumberId === (enteredNumber || null));
|
|
167
|
+
onNotice?.({ kind: "success", message: line?.answering ? "The line is answering." : "Line connected. Add a member to start answers." });
|
|
168
|
+
} catch (cause) {
|
|
169
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
170
|
+
const detail = message(cause);
|
|
171
|
+
setError(detail);
|
|
172
|
+
onNotice?.({ kind: "error", message: detail });
|
|
173
|
+
} finally {
|
|
174
|
+
if (stillCurrent(requestIncarnation)) setBusy(false);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function disconnect(lineId) {
|
|
178
|
+
if (busy || loading || refreshFailed) return;
|
|
179
|
+
const requestIncarnation = incarnation.current;
|
|
180
|
+
setBusy(true);
|
|
181
|
+
setError(null);
|
|
182
|
+
try {
|
|
183
|
+
await clientRef.current.disconnect(lineId);
|
|
184
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
185
|
+
const updated = await reload(requestIncarnation);
|
|
186
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
187
|
+
closeConfirmation(lineId);
|
|
188
|
+
onNotice?.(updated ? { kind: "success", message: "The line stopped answering." } : { kind: "error", message: "Line disconnected, but its current status could not be loaded." });
|
|
189
|
+
} catch (cause) {
|
|
190
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
191
|
+
const detail = message(cause);
|
|
192
|
+
setError(detail);
|
|
193
|
+
onNotice?.({ kind: "error", message: detail });
|
|
194
|
+
} finally {
|
|
195
|
+
if (stillCurrent(requestIncarnation)) setBusy(false);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return /* @__PURE__ */ jsxs("section", { className: "tangle-lines", "aria-label": "Agent lines", children: [
|
|
199
|
+
/* @__PURE__ */ jsxs("header", { className: "tangle-lines__heading", children: [
|
|
200
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
201
|
+
/* @__PURE__ */ jsx("h2", { ref: heading, tabIndex: -1, children: "Lines" }),
|
|
202
|
+
snapshot && /* @__PURE__ */ jsxs("p", { children: [
|
|
203
|
+
snapshot.workspaceName,
|
|
204
|
+
" can use one line per transport."
|
|
205
|
+
] })
|
|
206
|
+
] }),
|
|
207
|
+
snapshot && /* @__PURE__ */ jsxs("span", { className: "tangle-lines__count", children: [
|
|
208
|
+
snapshot.lines.filter((line) => line.answering).length,
|
|
209
|
+
" answering"
|
|
210
|
+
] })
|
|
211
|
+
] }),
|
|
212
|
+
error && /* @__PURE__ */ jsxs("div", { className: "tangle-lines__error", role: "alert", children: [
|
|
213
|
+
/* @__PURE__ */ jsx("span", { children: error }),
|
|
214
|
+
(!snapshot || refreshFailed) && /* @__PURE__ */ jsx("button", { type: "button", disabled: loading, onClick: retry, children: "Retry" })
|
|
215
|
+
] }),
|
|
216
|
+
loading && /* @__PURE__ */ jsx("p", { className: "tangle-lines__muted", role: "status", children: "Loading lines\u2026" }),
|
|
217
|
+
snapshot && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
218
|
+
/* @__PURE__ */ jsxs("div", { className: "tangle-lines__list", children: [
|
|
219
|
+
snapshot.lines.filter((line) => line.status !== "released").length === 0 && /* @__PURE__ */ jsx("p", { className: "tangle-lines__empty", children: "No line answers for this workspace yet." }),
|
|
220
|
+
snapshot.lines.filter((line) => line.status !== "released").map((line) => /* @__PURE__ */ jsxs("div", { className: "tangle-lines__row", children: [
|
|
221
|
+
/* @__PURE__ */ jsxs("div", { className: "tangle-lines__row-main", children: [
|
|
222
|
+
/* @__PURE__ */ jsxs("span", { className: "tangle-lines__eyebrow", children: [
|
|
223
|
+
TRANSPORT[line.transport],
|
|
224
|
+
" \xB7 ",
|
|
225
|
+
line.answering ? "Answering" : line.targetId === null ? "Disconnected" : line.status === "active" ? "Awaiting member" : line.status
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsx("strong", { children: line.routerAddress ?? line.address }),
|
|
228
|
+
line.connect && line.targetId !== null && /* @__PURE__ */ jsxs("p", { children: [
|
|
229
|
+
"To reach this agent, text ",
|
|
230
|
+
/* @__PURE__ */ jsx("code", { children: line.connect }),
|
|
231
|
+
"."
|
|
232
|
+
] }),
|
|
233
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
234
|
+
line.targetLabel ?? "No agent or box assigned",
|
|
235
|
+
line.boxMode && ` \xB7 ${line.boxMode === "per-member" ? "one box per member" : "shared box"}`
|
|
236
|
+
] }),
|
|
237
|
+
line.lastTurn.kind === "latest" ? /* @__PURE__ */ jsxs("p", { children: [
|
|
238
|
+
"Last turn: ",
|
|
239
|
+
line.lastTurn.status,
|
|
240
|
+
" \xB7 ",
|
|
241
|
+
dateTime(line.lastTurn.at)
|
|
242
|
+
] }) : /* @__PURE__ */ jsx("p", { children: line.lastTurn.kind === "none" ? "No turns yet." : "Turn history unavailable." })
|
|
243
|
+
] }),
|
|
244
|
+
canManage && line.canDisconnect && line.targetId !== null && line.status === "active" && /* @__PURE__ */ jsx("div", { className: "tangle-lines__row-actions", children: confirmLineId === line.id ? /* @__PURE__ */ jsxs("div", { className: "tangle-lines__confirm", role: "group", "aria-label": `Disconnect ${line.address}?`, children: [
|
|
245
|
+
/* @__PURE__ */ jsx("span", { children: "Disconnect this line?" }),
|
|
246
|
+
/* @__PURE__ */ jsx("button", { ref: confirmButton, type: "button", className: "tangle-lines__danger", disabled: busy || loading || refreshFailed, onClick: () => void disconnect(line.id), children: "Disconnect" }),
|
|
247
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => closeConfirmation(line.id), children: "Keep line" })
|
|
248
|
+
] }) : /* @__PURE__ */ jsx("button", { ref: (node) => {
|
|
249
|
+
if (node) disconnectTriggers.current.set(line.id, node);
|
|
250
|
+
else disconnectTriggers.current.delete(line.id);
|
|
251
|
+
}, type: "button", className: "tangle-lines__quiet", disabled: busy || loading || refreshFailed, onClick: () => setConfirmLineId(line.id), children: "Disconnect line" }) })
|
|
252
|
+
] }, line.id))
|
|
253
|
+
] }),
|
|
254
|
+
canManage && (snapshot.connections === null || choices.length > 0 || allChoices.length === 0) && /* @__PURE__ */ jsxs("div", { className: "tangle-lines__setup", children: [
|
|
255
|
+
/* @__PURE__ */ jsx("h3", { children: "Connect an identity you own" }),
|
|
256
|
+
/* @__PURE__ */ jsx("p", { children: "Choose an identity already connected in Hub. Billing details appear below." }),
|
|
257
|
+
snapshot.connections === null ? /* @__PURE__ */ jsxs("div", { className: "tangle-lines__error", role: "status", children: [
|
|
258
|
+
/* @__PURE__ */ jsx("span", { children: "Hub connections could not be read. Check Hub access and try again." }),
|
|
259
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: loading, onClick: retry, children: "Retry" })
|
|
260
|
+
] }) : availableKinds.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "tangle-lines__empty", children: [
|
|
261
|
+
"Connect an owned handle, number, or mailbox in Hub, then ",
|
|
262
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: loading, onClick: retry, children: "reload" }),
|
|
263
|
+
"."
|
|
264
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
265
|
+
/* @__PURE__ */ jsx("div", { className: "tangle-lines__kinds", role: "group", "aria-label": "Identity type", children: availableKinds.map((value) => /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": selectedKind === value, onClick: () => {
|
|
266
|
+
setKind(value);
|
|
267
|
+
setChoiceKey("");
|
|
268
|
+
setPhoneNumberId("");
|
|
269
|
+
}, children: IDENTITY[value] }, value)) }),
|
|
270
|
+
/* @__PURE__ */ jsxs("div", { className: "tangle-lines__fields", children: [
|
|
271
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
272
|
+
"Owned connection",
|
|
273
|
+
/* @__PURE__ */ jsx("select", { value: selected?.key ?? "", onChange: (event) => {
|
|
274
|
+
setChoiceKey(event.target.value);
|
|
275
|
+
setPhoneNumberId("");
|
|
276
|
+
}, children: kindChoices.map((choice) => /* @__PURE__ */ jsxs("option", { value: choice.key, children: [
|
|
277
|
+
choice.connection.label,
|
|
278
|
+
" \xB7 ",
|
|
279
|
+
choice.identity.label
|
|
280
|
+
] }, choice.key)) })
|
|
281
|
+
] }),
|
|
282
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
283
|
+
"Agent or box",
|
|
284
|
+
/* @__PURE__ */ jsx("select", { value: selectedTarget?.id ?? "", onChange: (event) => setTargetId(event.target.value), children: snapshot.targets.map((target) => /* @__PURE__ */ jsxs("option", { value: target.id, children: [
|
|
285
|
+
target.label,
|
|
286
|
+
" \xB7 ",
|
|
287
|
+
target.kind
|
|
288
|
+
] }, target.id)) })
|
|
289
|
+
] }),
|
|
290
|
+
selectedTarget && selectedTarget.modes.length > 1 && /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
291
|
+
/* @__PURE__ */ jsx("legend", { children: "Conversation space" }),
|
|
292
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
293
|
+
/* @__PURE__ */ jsx("input", { type: "radio", name: `line-box-mode-${scopeKey}`, value: "per-member", checked: selectedMode === "per-member", disabled: !selectedTarget?.modes.includes("per-member"), onChange: () => setBoxMode("per-member") }),
|
|
294
|
+
" One box per member"
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
297
|
+
/* @__PURE__ */ jsx("input", { type: "radio", name: `line-box-mode-${scopeKey}`, value: "shared", checked: selectedMode === "shared", disabled: !selectedTarget?.modes.includes("shared"), onChange: () => setBoxMode("shared") }),
|
|
298
|
+
" Shared box for a team"
|
|
299
|
+
] })
|
|
300
|
+
] }),
|
|
301
|
+
needsNumber && !selected?.identity.phoneNumberId && /* @__PURE__ */ jsxs("label", { children: [
|
|
302
|
+
"Existing provider number ID",
|
|
303
|
+
/* @__PURE__ */ jsx("input", { value: phoneNumberId, onChange: (event) => setPhoneNumberId(event.target.value), autoComplete: "off", placeholder: "Number ID from Hub" })
|
|
304
|
+
] })
|
|
305
|
+
] }),
|
|
306
|
+
reconnectChoice && blockedByExistingLine && /* @__PURE__ */ jsx("p", { className: "tangle-lines__warning", children: "Enter this line's current provider number ID to reconnect." }),
|
|
307
|
+
/* @__PURE__ */ jsx("button", { className: "tangle-lines__primary", type: "button", disabled: busy || loading || refreshFailed || !selected || !selectedTarget || !selectedMode || needsNumber && !enteredNumber || blockedByExistingLine, onClick: () => void connect(), children: busy ? reconnectChoice ? "Reconnecting\u2026" : "Connecting\u2026" : `${reconnectChoice ? "Reconnect" : "Connect"} ${selected ? TRANSPORT[selected.identity.transport] : "line"}` })
|
|
308
|
+
] })
|
|
309
|
+
] })
|
|
310
|
+
] })
|
|
311
|
+
] });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/hosted-agent/react/LineMembers.tsx
|
|
315
|
+
import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
316
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
317
|
+
var useBrowserLayoutEffect2 = typeof document !== "undefined" ? useLayoutEffect2 : useEffect2;
|
|
318
|
+
var STATUS = {
|
|
319
|
+
invited: { label: "Awaiting consent", detail: "The first message from this address confirms consent." },
|
|
320
|
+
active: { label: "Consented", detail: "This member has sent a message to the line." },
|
|
321
|
+
stopped: { label: "Stopped", detail: "The member texted STOP. Only a message from them with START resumes replies." },
|
|
322
|
+
removed: { label: "Removed", detail: "This address no longer has access to the line." }
|
|
323
|
+
};
|
|
324
|
+
function errorMessage(cause) {
|
|
325
|
+
return cause instanceof Error ? cause.message : "The member request failed. Try again.";
|
|
326
|
+
}
|
|
327
|
+
function LineMembers({ lineId, scopeKey, client, roles, canManage, canAdd = true, maxMembers, allowRoleChange = true, allowRemoveLastOwner = false, onNotice }) {
|
|
328
|
+
const clientRef = useRef2(client);
|
|
329
|
+
clientRef.current = client;
|
|
330
|
+
const sequence = useRef2(0);
|
|
331
|
+
const incarnation = useRef2(0);
|
|
332
|
+
const confirmButton = useRef2(null);
|
|
333
|
+
const heading = useRef2(null);
|
|
334
|
+
const removeTriggers = useRef2(/* @__PURE__ */ new Map());
|
|
335
|
+
const restoreFocusTo = useRef2(null);
|
|
336
|
+
useBrowserLayoutEffect2(() => {
|
|
337
|
+
incarnation.current++;
|
|
338
|
+
return () => {
|
|
339
|
+
incarnation.current++;
|
|
340
|
+
};
|
|
341
|
+
}, [lineId, scopeKey]);
|
|
342
|
+
const [memberSnapshot, setMemberSnapshot] = useState2(null);
|
|
343
|
+
const [error, setError] = useState2(null);
|
|
344
|
+
const [refreshFailed, setRefreshFailed] = useState2(false);
|
|
345
|
+
const [address, setAddress] = useState2("");
|
|
346
|
+
const [label, setLabel] = useState2("");
|
|
347
|
+
const [role, setRole] = useState2("member");
|
|
348
|
+
const [busy, setBusy] = useState2(null);
|
|
349
|
+
const [confirmId, setConfirmId] = useState2(null);
|
|
350
|
+
const members = memberSnapshot?.lineId === lineId && memberSnapshot.scopeKey === scopeKey ? memberSnapshot.members : null;
|
|
351
|
+
function stillCurrent(requestIncarnation) {
|
|
352
|
+
return requestIncarnation === incarnation.current;
|
|
353
|
+
}
|
|
354
|
+
async function reload(requestIncarnation) {
|
|
355
|
+
const request = ++sequence.current;
|
|
356
|
+
try {
|
|
357
|
+
const next = await clientRef.current.list(lineId);
|
|
358
|
+
if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false;
|
|
359
|
+
setMemberSnapshot({ lineId, scopeKey, members: next });
|
|
360
|
+
setError(null);
|
|
361
|
+
setRefreshFailed(false);
|
|
362
|
+
return true;
|
|
363
|
+
} catch (cause) {
|
|
364
|
+
if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false;
|
|
365
|
+
setError(errorMessage(cause));
|
|
366
|
+
setRefreshFailed(true);
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function retry() {
|
|
371
|
+
if (busy) return;
|
|
372
|
+
const requestIncarnation = incarnation.current;
|
|
373
|
+
setBusy("refresh");
|
|
374
|
+
try {
|
|
375
|
+
await reload(requestIncarnation);
|
|
376
|
+
} finally {
|
|
377
|
+
if (stillCurrent(requestIncarnation)) setBusy(null);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function closeConfirmation(memberId) {
|
|
381
|
+
restoreFocusTo.current = memberId;
|
|
382
|
+
setConfirmId(null);
|
|
383
|
+
}
|
|
384
|
+
useEffect2(() => {
|
|
385
|
+
setMemberSnapshot(null);
|
|
386
|
+
setError(null);
|
|
387
|
+
setRefreshFailed(false);
|
|
388
|
+
setAddress("");
|
|
389
|
+
setLabel("");
|
|
390
|
+
setRole("member");
|
|
391
|
+
setConfirmId(null);
|
|
392
|
+
restoreFocusTo.current = null;
|
|
393
|
+
setBusy(null);
|
|
394
|
+
void reload(incarnation.current);
|
|
395
|
+
return () => {
|
|
396
|
+
sequence.current++;
|
|
397
|
+
};
|
|
398
|
+
}, [lineId, scopeKey]);
|
|
399
|
+
useEffect2(() => {
|
|
400
|
+
if (confirmId) confirmButton.current?.focus();
|
|
401
|
+
}, [confirmId]);
|
|
402
|
+
useEffect2(() => {
|
|
403
|
+
const memberId = restoreFocusTo.current;
|
|
404
|
+
if (confirmId !== null || memberId === null) return;
|
|
405
|
+
restoreFocusTo.current = null;
|
|
406
|
+
const trigger = removeTriggers.current.get(memberId);
|
|
407
|
+
if (trigger && !trigger.disabled) trigger.focus();
|
|
408
|
+
else heading.current?.focus();
|
|
409
|
+
}, [confirmId, members, refreshFailed]);
|
|
410
|
+
const selectedRole = roles.some((item) => item.value === role) ? role : roles.find((item) => item.value === "member")?.value ?? roles[0]?.value ?? "";
|
|
411
|
+
const activeMembers = members?.filter((member) => member.status !== "removed").length ?? 0;
|
|
412
|
+
const activeOwners = members?.filter((member) => member.role === "owner" && member.status !== "removed").length ?? 0;
|
|
413
|
+
async function mutate(key, action, success, onSuccess) {
|
|
414
|
+
if (busy || refreshFailed) return;
|
|
415
|
+
const requestIncarnation = incarnation.current;
|
|
416
|
+
setBusy(key);
|
|
417
|
+
setError(null);
|
|
418
|
+
try {
|
|
419
|
+
await action();
|
|
420
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
421
|
+
const refreshed = await reload(requestIncarnation);
|
|
422
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
423
|
+
onSuccess?.();
|
|
424
|
+
onNotice?.(refreshed ? { kind: "success", message: success } : { kind: "error", message: "Member updated, but the current member list could not be loaded." });
|
|
425
|
+
} catch (cause) {
|
|
426
|
+
if (!stillCurrent(requestIncarnation)) return;
|
|
427
|
+
const detail = errorMessage(cause);
|
|
428
|
+
setError(detail);
|
|
429
|
+
onNotice?.({ kind: "error", message: detail });
|
|
430
|
+
} finally {
|
|
431
|
+
if (stillCurrent(requestIncarnation)) setBusy(null);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function add() {
|
|
435
|
+
const trimmed = address.trim();
|
|
436
|
+
if (!trimmed || !selectedRole || members === null || maxMembers !== void 0 && activeMembers >= maxMembers) return;
|
|
437
|
+
void mutate("add", () => clientRef.current.add(lineId, { address: trimmed, role: selectedRole, ...label.trim() ? { label: label.trim() } : {} }), "Member invited. Their first message confirms consent.", () => {
|
|
438
|
+
setAddress("");
|
|
439
|
+
setLabel("");
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return /* @__PURE__ */ jsxs2("section", { className: "tangle-lines", "aria-label": "Line members", children: [
|
|
443
|
+
/* @__PURE__ */ jsxs2("header", { className: "tangle-lines__heading", children: [
|
|
444
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
445
|
+
/* @__PURE__ */ jsx2("h2", { ref: heading, tabIndex: -1, children: "Members" }),
|
|
446
|
+
/* @__PURE__ */ jsx2("p", { children: "Each address controls its own consent and STOP state." })
|
|
447
|
+
] }),
|
|
448
|
+
members && /* @__PURE__ */ jsxs2("span", { className: "tangle-lines__count", children: [
|
|
449
|
+
activeMembers,
|
|
450
|
+
" ",
|
|
451
|
+
activeMembers === 1 ? "member" : "members"
|
|
452
|
+
] })
|
|
453
|
+
] }),
|
|
454
|
+
error && /* @__PURE__ */ jsxs2("div", { className: "tangle-lines__error", role: "alert", children: [
|
|
455
|
+
/* @__PURE__ */ jsx2("span", { children: error }),
|
|
456
|
+
(members === null || refreshFailed) && /* @__PURE__ */ jsx2("button", { type: "button", disabled: busy !== null, onClick: () => void retry(), children: "Retry" })
|
|
457
|
+
] }),
|
|
458
|
+
members === null ? !error && /* @__PURE__ */ jsx2("p", { className: "tangle-lines__muted", role: "status", children: "Loading members\u2026" }) : /* @__PURE__ */ jsxs2("div", { className: "tangle-lines__list", children: [
|
|
459
|
+
members.length === 0 && /* @__PURE__ */ jsx2("p", { className: "tangle-lines__empty", children: canAdd ? "No members yet. Invite an address that you own or have permission to add." : "No members yet." }),
|
|
460
|
+
members.map((member) => {
|
|
461
|
+
const lastOwner = member.role === "owner" && activeOwners <= 1;
|
|
462
|
+
const editable = canManage && member.status !== "removed" && (!lastOwner || allowRemoveLastOwner);
|
|
463
|
+
return /* @__PURE__ */ jsxs2("div", { className: "tangle-lines__row", children: [
|
|
464
|
+
/* @__PURE__ */ jsxs2("div", { className: "tangle-lines__row-main", children: [
|
|
465
|
+
/* @__PURE__ */ jsx2("span", { className: `tangle-lines__state tangle-lines__state--${member.status}`, children: STATUS[member.status].label }),
|
|
466
|
+
/* @__PURE__ */ jsx2("strong", { children: member.label || member.address }),
|
|
467
|
+
member.label && /* @__PURE__ */ jsx2("p", { children: member.address }),
|
|
468
|
+
/* @__PURE__ */ jsx2("p", { children: STATUS[member.status].detail })
|
|
469
|
+
] }),
|
|
470
|
+
/* @__PURE__ */ jsx2("div", { className: "tangle-lines__row-actions", children: editable ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
471
|
+
allowRoleChange && !lastOwner ? /* @__PURE__ */ jsxs2("label", { className: "tangle-lines__compact-label", children: [
|
|
472
|
+
"Role",
|
|
473
|
+
/* @__PURE__ */ jsxs2("select", { value: member.role, disabled: busy !== null || refreshFailed, onChange: (event) => void mutate(member.id, () => clientRef.current.update(lineId, member.id, { role: event.target.value }), "Role updated."), children: [
|
|
474
|
+
!roles.some((item) => item.value === member.role) && /* @__PURE__ */ jsx2("option", { value: member.role, children: member.role }),
|
|
475
|
+
roles.map((item) => /* @__PURE__ */ jsxs2("option", { value: item.value, children: [
|
|
476
|
+
item.label,
|
|
477
|
+
item.tools === "chat" ? " \xB7 chat only" : ""
|
|
478
|
+
] }, item.value))
|
|
479
|
+
] })
|
|
480
|
+
] }) : /* @__PURE__ */ jsx2("span", { className: "tangle-lines__role", children: roles.find((item) => item.value === member.role)?.label ?? member.role }),
|
|
481
|
+
confirmId === member.id ? /* @__PURE__ */ jsxs2("div", { className: "tangle-lines__confirm", role: "group", "aria-label": `Remove ${member.label || member.address}?`, children: [
|
|
482
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
483
|
+
"Remove ",
|
|
484
|
+
member.label || member.address,
|
|
485
|
+
"?"
|
|
486
|
+
] }),
|
|
487
|
+
/* @__PURE__ */ jsx2("button", { ref: confirmButton, type: "button", className: "tangle-lines__danger", disabled: busy !== null || refreshFailed, onClick: () => void mutate(member.id, () => clientRef.current.remove(lineId, member.id), "Member removed.", () => closeConfirmation(member.id)), children: "Remove" }),
|
|
488
|
+
/* @__PURE__ */ jsx2("button", { type: "button", disabled: busy !== null, onClick: () => closeConfirmation(member.id), children: "Keep" })
|
|
489
|
+
] }) : /* @__PURE__ */ jsx2("button", { ref: (node) => {
|
|
490
|
+
if (node) removeTriggers.current.set(member.id, node);
|
|
491
|
+
else removeTriggers.current.delete(member.id);
|
|
492
|
+
}, type: "button", className: "tangle-lines__quiet", disabled: busy !== null || refreshFailed, onClick: () => setConfirmId(member.id), children: "Remove" })
|
|
493
|
+
] }) : /* @__PURE__ */ jsx2("span", { className: "tangle-lines__role", children: roles.find((item) => item.value === member.role)?.label ?? member.role }) })
|
|
494
|
+
] }, member.id);
|
|
495
|
+
})
|
|
496
|
+
] }),
|
|
497
|
+
canManage && canAdd && members !== null && (maxMembers === void 0 || activeMembers < maxMembers) && /* @__PURE__ */ jsxs2("div", { className: "tangle-lines__setup", children: [
|
|
498
|
+
/* @__PURE__ */ jsx2("h3", { children: "Invite a member" }),
|
|
499
|
+
/* @__PURE__ */ jsx2("p", { children: "Hub marks the address as invited. The member confirms by messaging the line." }),
|
|
500
|
+
/* @__PURE__ */ jsxs2("div", { className: "tangle-lines__fields", children: [
|
|
501
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
502
|
+
"Phone number or email",
|
|
503
|
+
/* @__PURE__ */ jsx2("input", { value: address, onChange: (event) => setAddress(event.target.value), autoComplete: "off", placeholder: "+15551234567 or name@example.com" })
|
|
504
|
+
] }),
|
|
505
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
506
|
+
"Name (optional)",
|
|
507
|
+
/* @__PURE__ */ jsx2("input", { value: label, onChange: (event) => setLabel(event.target.value), placeholder: "Member name" })
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
510
|
+
"Role",
|
|
511
|
+
/* @__PURE__ */ jsx2("select", { value: selectedRole, disabled: roles.length === 0, onChange: (event) => setRole(event.target.value), children: roles.map((item) => /* @__PURE__ */ jsxs2("option", { value: item.value, children: [
|
|
512
|
+
item.label,
|
|
513
|
+
item.tools === "chat" ? " \xB7 chat only" : ""
|
|
514
|
+
] }, item.value)) })
|
|
515
|
+
] })
|
|
516
|
+
] }),
|
|
517
|
+
/* @__PURE__ */ jsx2("button", { type: "button", className: "tangle-lines__primary", disabled: busy !== null || refreshFailed || !address.trim() || !selectedRole, onClick: add, children: busy === "add" ? "Inviting\u2026" : "Invite member" })
|
|
518
|
+
] })
|
|
519
|
+
] });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/hosted-agent/react/LineBilling.tsx
|
|
523
|
+
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
524
|
+
function resetTime(value) {
|
|
525
|
+
const date = new Date(value);
|
|
526
|
+
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
527
|
+
}
|
|
528
|
+
function LineBilling({ view }) {
|
|
529
|
+
return /* @__PURE__ */ jsxs3("section", { className: "tangle-lines", "aria-label": "Line billing", children: [
|
|
530
|
+
/* @__PURE__ */ jsx3("header", { className: "tangle-lines__heading", children: /* @__PURE__ */ jsxs3("div", { children: [
|
|
531
|
+
/* @__PURE__ */ jsx3("h2", { children: "Billing" }),
|
|
532
|
+
/* @__PURE__ */ jsx3("p", { children: view.lineAddress })
|
|
533
|
+
] }) }),
|
|
534
|
+
/* @__PURE__ */ jsxs3("dl", { className: "tangle-lines__billing", children: [
|
|
535
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
536
|
+
/* @__PURE__ */ jsx3("dt", { children: "Line" }),
|
|
537
|
+
/* @__PURE__ */ jsx3("dd", { children: view.linePayer.kind === "workspace" ? view.linePayer.label : "Payer not verified" }),
|
|
538
|
+
view.linePayer.kind === "workspace" && /* @__PURE__ */ jsxs3("dd", { className: "tangle-lines__billing-detail", children: [
|
|
539
|
+
"Charged to ",
|
|
540
|
+
view.workspaceName,
|
|
541
|
+
"."
|
|
542
|
+
] })
|
|
543
|
+
] }),
|
|
544
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
545
|
+
/* @__PURE__ */ jsx3("dt", { children: "Agent turns" }),
|
|
546
|
+
/* @__PURE__ */ jsx3("dd", { children: view.turnPayer.kind === "unverified" ? "Payer not verified" : view.turnPayer.label }),
|
|
547
|
+
view.turnPayer.kind === "member" && /* @__PURE__ */ jsx3("dd", { className: "tangle-lines__billing-detail", children: "Each member pays for their own admitted turns." })
|
|
548
|
+
] }),
|
|
549
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
550
|
+
/* @__PURE__ */ jsx3("dt", { children: "Allowance" }),
|
|
551
|
+
view.allowance ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
552
|
+
/* @__PURE__ */ jsxs3("dd", { children: [
|
|
553
|
+
view.allowance.turnsPerMemberPerDay,
|
|
554
|
+
" turns per member each day"
|
|
555
|
+
] }),
|
|
556
|
+
view.allowance.used !== void 0 && view.allowance.limit !== void 0 && /* @__PURE__ */ jsxs3("dd", { className: "tangle-lines__billing-detail", children: [
|
|
557
|
+
view.allowance.used,
|
|
558
|
+
" of ",
|
|
559
|
+
view.allowance.limit,
|
|
560
|
+
" turns used",
|
|
561
|
+
view.allowance.resetsAt ? ` \xB7 resets ${resetTime(view.allowance.resetsAt)}` : ""
|
|
562
|
+
] })
|
|
563
|
+
] }) : /* @__PURE__ */ jsx3("dd", { children: "No allowance reported" })
|
|
564
|
+
] })
|
|
565
|
+
] })
|
|
566
|
+
] });
|
|
567
|
+
}
|
|
568
|
+
export {
|
|
569
|
+
LineBilling,
|
|
570
|
+
LineMembers,
|
|
571
|
+
LineSetup
|
|
572
|
+
};
|
|
573
|
+
//# sourceMappingURL=index.js.map
|