@uplifted/react 0.22.7 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/dist/AddClientSheet.d.ts +11 -1
- package/dist/AddClientSheet.d.ts.map +1 -1
- package/dist/AddClientSheet.js +26 -15
- package/dist/AddClientSheet.js.map +1 -1
- package/dist/AiSettingsSheet.d.ts +2 -2
- package/dist/AiSettingsSheet.d.ts.map +1 -1
- package/dist/AiSettingsSheet.js +30 -97
- package/dist/AiSettingsSheet.js.map +1 -1
- package/dist/AssistantGrid.d.ts +10 -8
- package/dist/AssistantGrid.d.ts.map +1 -1
- package/dist/AssistantGrid.js +90 -16
- package/dist/AssistantGrid.js.map +1 -1
- package/dist/ChannelBadge.d.ts +1 -1
- package/dist/ChannelBadge.js +1 -1
- package/dist/ClientList.d.ts.map +1 -1
- package/dist/ClientList.js +5 -5
- package/dist/ClientList.js.map +1 -1
- package/dist/Composer.d.ts +2 -0
- package/dist/Composer.d.ts.map +1 -1
- package/dist/Composer.js +5 -6
- package/dist/Composer.js.map +1 -1
- package/dist/ConnectClientSheet.d.ts +26 -0
- package/dist/ConnectClientSheet.d.ts.map +1 -0
- package/dist/ConnectClientSheet.js +67 -0
- package/dist/ConnectClientSheet.js.map +1 -0
- package/dist/ConversationPane.d.ts +3 -1
- package/dist/ConversationPane.d.ts.map +1 -1
- package/dist/ConversationPane.js +30 -7
- package/dist/ConversationPane.js.map +1 -1
- package/dist/HostLinkAction.d.ts +10 -0
- package/dist/HostLinkAction.d.ts.map +1 -0
- package/dist/HostLinkAction.js +14 -0
- package/dist/HostLinkAction.js.map +1 -0
- package/dist/HostPreviewPane.d.ts +4 -2
- package/dist/HostPreviewPane.d.ts.map +1 -1
- package/dist/HostPreviewPane.js +6 -6
- package/dist/HostPreviewPane.js.map +1 -1
- package/dist/ItemActions.d.ts +7 -3
- package/dist/ItemActions.d.ts.map +1 -1
- package/dist/ItemActions.js +4 -3
- package/dist/ItemActions.js.map +1 -1
- package/dist/OfferSheet.d.ts +2 -0
- package/dist/OfferSheet.d.ts.map +1 -1
- package/dist/OfferSheet.js +3 -1
- package/dist/OfferSheet.js.map +1 -1
- package/dist/QuickSend.d.ts +3 -1
- package/dist/QuickSend.d.ts.map +1 -1
- package/dist/QuickSend.js +10 -3
- package/dist/QuickSend.js.map +1 -1
- package/dist/SegmentEditor.d.ts +11 -4
- package/dist/SegmentEditor.d.ts.map +1 -1
- package/dist/SegmentEditor.js +95 -11
- package/dist/SegmentEditor.js.map +1 -1
- package/dist/ThreadFeed.d.ts +7 -1
- package/dist/ThreadFeed.d.ts.map +1 -1
- package/dist/ThreadFeed.js +4 -4
- package/dist/ThreadFeed.js.map +1 -1
- package/dist/UpliftPanel.d.ts +10 -2
- package/dist/UpliftPanel.d.ts.map +1 -1
- package/dist/UpliftPanel.js +120 -20
- package/dist/UpliftPanel.js.map +1 -1
- package/dist/UpliftProvider.d.ts.map +1 -1
- package/dist/UpliftProvider.js +16 -2
- package/dist/UpliftProvider.js.map +1 -1
- package/dist/build-info.json +9 -0
- package/dist/channels.d.ts +3 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +4 -0
- package/dist/channels.js.map +1 -1
- package/dist/draftContext.d.ts +34 -0
- package/dist/draftContext.d.ts.map +1 -0
- package/dist/draftContext.js +48 -0
- package/dist/draftContext.js.map +1 -0
- package/dist/host.d.ts +19 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js.map +1 -1
- package/dist/icons.d.ts +3 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +4 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/phone.d.ts +3 -3
- package/dist/phone.js +3 -3
- package/dist/segments.d.ts +1 -2
- package/dist/segments.d.ts.map +1 -1
- package/dist/segments.js +3 -3
- package/dist/segments.js.map +1 -1
- package/dist/styles.d.ts +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +56 -14
- package/dist/styles.js.map +1 -1
- package/dist/usePreviewDrafts.d.ts +1 -1
- package/dist/usePreviewDrafts.d.ts.map +1 -1
- package/dist/usePreviewDrafts.js +8 -1
- package/dist/usePreviewDrafts.js.map +1 -1
- package/dist/useSend.d.ts +7 -2
- package/dist/useSend.d.ts.map +1 -1
- package/dist/useSend.js +7 -2
- package/dist/useSend.js.map +1 -1
- package/package.json +5 -9
package/dist/SegmentEditor.d.ts
CHANGED
|
@@ -14,12 +14,19 @@ export interface SegmentEditorProps {
|
|
|
14
14
|
disabled?: boolean | undefined;
|
|
15
15
|
/** Nothing can be typed or removed: the box shows fixed text (Uplift's stock intro) and only Send applies. */
|
|
16
16
|
readOnly?: boolean | undefined;
|
|
17
|
+
/** Esc in the editor: the parent's clear. */
|
|
18
|
+
onEscape?: (() => void) | undefined;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Two modes, like native Uplift's editor: a plain textarea for typing, and a contentEditable
|
|
20
|
-
* box that shows an injected draft as
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* box that shows an injected draft as pills the coach can delete one by one. The DOM of the
|
|
23
|
+
* pill box is owned by hand (written on inject, read on change), never by React, so the caret
|
|
24
|
+
* and the browser's own editing stay intact. The parent only ever sees plain text.
|
|
25
|
+
*
|
|
26
|
+
* An injected draft first lands "suggested" (native #633): greyed plain text on a tint with a
|
|
27
|
+
* "Suggested draft" label, so it reads as Uplift's words. Send works as is. The first click,
|
|
28
|
+
* tap or keystroke opens the pills; pills stay grey until the coach clicks or types in one.
|
|
29
|
+
* A locked injection (the stock intro) is never suggested.
|
|
23
30
|
*/
|
|
24
|
-
export declare function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel, disabled, readOnly }: SegmentEditorProps): import("react").JSX.Element;
|
|
31
|
+
export declare function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel, disabled, readOnly, onEscape }: SegmentEditorProps): import("react").JSX.Element;
|
|
25
32
|
//# sourceMappingURL=SegmentEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentEditor.d.ts","sourceRoot":"","sources":["../src/SegmentEditor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SegmentEditor.d.ts","sourceRoot":"","sources":["../src/SegmentEditor.tsx"],"names":[],"mappings":"AAIA,6IAA6I;AAC7I,MAAM,WAAW,SAAS;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE;AAEtF,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,8GAA8G;IAC9G,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,kBAAkB,+BA+LlI"}
|
package/dist/SegmentEditor.js
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { flushSync } from "react-dom";
|
|
3
4
|
import { reconstructMessage, SEG_CLASS, SEG_DELETE_CLASS, toLockedPillHtml, toPillHtml } from "./segments.js";
|
|
4
5
|
/**
|
|
5
6
|
* Two modes, like native Uplift's editor: a plain textarea for typing, and a contentEditable
|
|
6
|
-
* box that shows an injected draft as
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* box that shows an injected draft as pills the coach can delete one by one. The DOM of the
|
|
8
|
+
* pill box is owned by hand (written on inject, read on change), never by React, so the caret
|
|
9
|
+
* and the browser's own editing stay intact. The parent only ever sees plain text.
|
|
10
|
+
*
|
|
11
|
+
* An injected draft first lands "suggested" (native #633): greyed plain text on a tint with a
|
|
12
|
+
* "Suggested draft" label, so it reads as Uplift's words. Send works as is. The first click,
|
|
13
|
+
* tap or keystroke opens the pills; pills stay grey until the coach clicks or types in one.
|
|
14
|
+
* A locked injection (the stock intro) is never suggested.
|
|
9
15
|
*/
|
|
10
|
-
export function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel, disabled, readOnly }) {
|
|
16
|
+
export function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel, disabled, readOnly, onEscape }) {
|
|
11
17
|
const [rich, setRich] = useState(false);
|
|
18
|
+
const [suggested, setSuggested] = useState(false);
|
|
19
|
+
// Set while engage() hands a suggested draft to the pills: it focuses itself, inside the tap.
|
|
20
|
+
const engaging = useRef(false);
|
|
12
21
|
const boxRef = useRef(null);
|
|
13
22
|
const taRef = useRef(null);
|
|
14
23
|
const lastKey = useRef(null);
|
|
15
24
|
const pendingHtml = useRef(null);
|
|
25
|
+
// Bumped on every inject, so a second pick redraws the pills even when already in pill mode.
|
|
26
|
+
const [injected, setInjected] = useState(0);
|
|
16
27
|
// Inject: switch to pills. Done in a layout effect so the box exists before we write to it.
|
|
17
28
|
useEffect(() => {
|
|
18
29
|
if (!inject || !inject.text.trim() || inject.key === lastKey.current)
|
|
@@ -20,15 +31,19 @@ export function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel,
|
|
|
20
31
|
lastKey.current = inject.key;
|
|
21
32
|
pendingHtml.current = inject.locked ? toLockedPillHtml(inject.text, inject.key) : toPillHtml(inject.text, inject.key);
|
|
22
33
|
setRich(true);
|
|
34
|
+
setSuggested(!inject.locked);
|
|
35
|
+
setInjected((n) => n + 1);
|
|
23
36
|
}, [inject]);
|
|
24
37
|
useLayoutEffect(() => {
|
|
25
38
|
if (!rich || pendingHtml.current === null)
|
|
26
39
|
return;
|
|
27
40
|
const el = boxRef.current;
|
|
28
41
|
if (!el)
|
|
29
|
-
return;
|
|
42
|
+
return; // still suggested: the pills are written when the coach engages
|
|
30
43
|
el.innerHTML = pendingHtml.current;
|
|
31
44
|
pendingHtml.current = null;
|
|
45
|
+
if (engaging.current)
|
|
46
|
+
return;
|
|
32
47
|
// Caret at the end, after the last pill, so typing continues the message. Native does the same.
|
|
33
48
|
const t = setTimeout(() => {
|
|
34
49
|
el.focus();
|
|
@@ -43,19 +58,49 @@ export function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel,
|
|
|
43
58
|
el.removeChild(marker);
|
|
44
59
|
}, 80);
|
|
45
60
|
return () => clearTimeout(t);
|
|
46
|
-
}, [rich]);
|
|
61
|
+
}, [rich, injected, suggested]);
|
|
47
62
|
// The parent cleared the text (sent, or switched client): leave pill mode.
|
|
48
63
|
useEffect(() => {
|
|
49
64
|
if (rich && value === "") {
|
|
50
65
|
setRich(false);
|
|
66
|
+
setSuggested(false);
|
|
67
|
+
pendingHtml.current = null;
|
|
51
68
|
if (boxRef.current)
|
|
52
69
|
boxRef.current.innerHTML = "";
|
|
53
70
|
}
|
|
54
71
|
}, [value, rich]);
|
|
72
|
+
// First click, tap, focus or keystroke on a suggested draft: hand it to the pills. Synchronous
|
|
73
|
+
// (flushSync) so focus lands inside the same tap, which is what opens the keyboard on iOS.
|
|
74
|
+
const engage = useCallback(() => {
|
|
75
|
+
if (!suggested || disabled)
|
|
76
|
+
return;
|
|
77
|
+
engaging.current = true;
|
|
78
|
+
flushSync(() => setSuggested(false));
|
|
79
|
+
engaging.current = false;
|
|
80
|
+
const el = boxRef.current;
|
|
81
|
+
if (!el)
|
|
82
|
+
return;
|
|
83
|
+
el.focus();
|
|
84
|
+
const range = document.createRange();
|
|
85
|
+
range.selectNodeContents(el);
|
|
86
|
+
range.collapse(false);
|
|
87
|
+
const sel = window.getSelection();
|
|
88
|
+
sel?.removeAllRanges();
|
|
89
|
+
sel?.addRange(range);
|
|
90
|
+
}, [suggested, disabled]);
|
|
91
|
+
// Mark the pill under the caret as the coach's (white, full-strength text).
|
|
92
|
+
const touchAtCaret = useCallback(() => {
|
|
93
|
+
const node = window.getSelection()?.anchorNode;
|
|
94
|
+
const el = node && (node.nodeType === 1 ? node : node.parentElement);
|
|
95
|
+
const seg = el?.closest(`.${SEG_CLASS}`);
|
|
96
|
+
if (seg && boxRef.current?.contains(seg))
|
|
97
|
+
seg.classList.add("touched");
|
|
98
|
+
}, []);
|
|
55
99
|
const readBack = useCallback(() => {
|
|
56
100
|
const el = boxRef.current;
|
|
57
101
|
if (!el)
|
|
58
102
|
return;
|
|
103
|
+
touchAtCaret();
|
|
59
104
|
const html = el.innerHTML;
|
|
60
105
|
const plain = reconstructMessage(html);
|
|
61
106
|
onChange(plain);
|
|
@@ -64,7 +109,7 @@ export function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel,
|
|
|
64
109
|
setRich(false);
|
|
65
110
|
if (!html.includes(SEG_CLASS) && !plain)
|
|
66
111
|
setRich(false);
|
|
67
|
-
}, [onChange]);
|
|
112
|
+
}, [onChange, touchAtCaret]);
|
|
68
113
|
const removePill = useCallback((id) => {
|
|
69
114
|
const el = boxRef.current?.querySelector(`.${SEG_CLASS}[data-seg="${id}"]`);
|
|
70
115
|
if (!el)
|
|
@@ -78,18 +123,57 @@ export function SegmentEditor({ value, onChange, inject, placeholder, ariaLabel,
|
|
|
78
123
|
e.preventDefault();
|
|
79
124
|
e.stopPropagation();
|
|
80
125
|
removePill(t.getAttribute("data-seg") ?? "");
|
|
126
|
+
return;
|
|
81
127
|
}
|
|
128
|
+
const seg = t.closest(`.${SEG_CLASS}`);
|
|
129
|
+
boxRef.current?.querySelectorAll(`.${SEG_CLASS}.active`).forEach((s) => { if (s !== seg)
|
|
130
|
+
s.classList.remove("active"); });
|
|
131
|
+
if (seg && !seg.classList.contains("u-seg-lock"))
|
|
132
|
+
seg.classList.add("active", "touched");
|
|
82
133
|
}, [removePill]);
|
|
134
|
+
const onBlur = useCallback(() => {
|
|
135
|
+
boxRef.current?.querySelectorAll(`.${SEG_CLASS}.active`).forEach((s) => s.classList.remove("active"));
|
|
136
|
+
}, []);
|
|
83
137
|
const onKeyDown = useCallback((e) => {
|
|
84
138
|
const t = e.target;
|
|
85
139
|
if (t.classList.contains(SEG_DELETE_CLASS) && (e.key === "Enter" || e.key === " ")) {
|
|
86
140
|
e.preventDefault();
|
|
87
141
|
removePill(t.getAttribute("data-seg") ?? "");
|
|
142
|
+
return;
|
|
88
143
|
}
|
|
89
|
-
|
|
144
|
+
if (e.key === "Escape" && onEscape) {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
onEscape();
|
|
147
|
+
}
|
|
148
|
+
}, [removePill, onEscape]);
|
|
149
|
+
if (rich && suggested) {
|
|
150
|
+
return (
|
|
151
|
+
// Distinct keys: both boxes are <div>s in the same slot, and React would otherwise reuse
|
|
152
|
+
// one node, keeping the hand-written pills inside the suggested box (native #634).
|
|
153
|
+
_jsxs("div", { className: "u-editor u-editor-rich u-editor-sugg", role: "textbox", "aria-multiline": "true", "aria-label": ariaLabel, "aria-description": "Suggested draft. Click or start typing to edit.", "aria-disabled": disabled || undefined, tabIndex: disabled ? -1 : 0, "data-testid": "suggested-draft", onFocus: engage, onClick: engage, onKeyDown: (e) => {
|
|
154
|
+
if (e.key === "Escape") {
|
|
155
|
+
if (onEscape) {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
onEscape();
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (e.key === "Tab" || e.metaKey || e.ctrlKey || e.altKey)
|
|
162
|
+
return;
|
|
163
|
+
engage();
|
|
164
|
+
// The first character typed lands at the end of the draft, not nowhere.
|
|
165
|
+
if (e.key.length === 1) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
document.execCommand("insertText", false, e.key);
|
|
168
|
+
}
|
|
169
|
+
}, children: [_jsxs("span", { className: "u-sugg-label", "aria-hidden": "true", children: [_jsx("i", {}), "Suggested draft ", _jsx("em", { children: "\u00B7 click to edit" })] }), _jsx("span", { className: "u-sugg-text", children: value })] }, "suggested"));
|
|
170
|
+
}
|
|
90
171
|
if (rich) {
|
|
91
|
-
return (_jsx("div", { ref: boxRef, className: `u-editor u-editor-rich${readOnly ? " u-editor-locked" : ""}`, role: "textbox", "aria-multiline": "true", "aria-label": ariaLabel, "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, contentEditable: !disabled && !readOnly, suppressContentEditableWarning: true, "data-placeholder": placeholder, onInput: readBack, onClick: onClick, onKeyDown: onKeyDown }));
|
|
172
|
+
return (_jsx("div", { ref: boxRef, className: `u-editor u-editor-rich${readOnly ? " u-editor-locked" : ""}`, role: "textbox", "aria-multiline": "true", "aria-label": ariaLabel, "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, contentEditable: !disabled && !readOnly, suppressContentEditableWarning: true, "data-placeholder": placeholder, onInput: readBack, onClick: onClick, onKeyDown: onKeyDown, onBlur: onBlur }, "rich"));
|
|
92
173
|
}
|
|
93
|
-
return (_jsx("textarea", { ref: taRef, className: "u-editor", "aria-label": ariaLabel, placeholder: placeholder, value: value, disabled: disabled, readOnly: readOnly, onChange: (e) => onChange(e.target.value),
|
|
174
|
+
return (_jsx("textarea", { ref: taRef, className: "u-editor", "aria-label": ariaLabel, placeholder: placeholder, value: value, disabled: disabled, readOnly: readOnly, onChange: (e) => onChange(e.target.value), onKeyDown: (e) => { if (e.key === "Escape" && onEscape) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
onEscape();
|
|
177
|
+
} }, rows: 3 }));
|
|
94
178
|
}
|
|
95
179
|
//# sourceMappingURL=SegmentEditor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentEditor.js","sourceRoot":"","sources":["../src/SegmentEditor.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAuC,MAAM,OAAO,CAAC;AACvH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"SegmentEditor.js","sourceRoot":"","sources":["../src/SegmentEditor.tsx"],"names":[],"mappings":"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"}
|
package/dist/ThreadFeed.d.ts
CHANGED
|
@@ -11,8 +11,14 @@ export interface ThreadFeedProps {
|
|
|
11
11
|
actions?: (m: Message) => ReactNode;
|
|
12
12
|
/** Rendered at the foot of the feed (the closed-window ghost). */
|
|
13
13
|
foot?: ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* On cards for messages the client sent in another conversation (`eventType` "externalMessage"):
|
|
16
|
+
* the way to that conversation, e.g. the host's "Open in Pulse". Rendered under the card only when
|
|
17
|
+
* the card has no action row; with one, pass it to that row instead (ItemActions `extra`).
|
|
18
|
+
*/
|
|
19
|
+
externalMessageAction?: ReactNode;
|
|
14
20
|
}
|
|
15
|
-
export declare function ThreadFeed({ thread, clientName, activitySourceLabel, now, actions, foot }: ThreadFeedProps): import("react").JSX.Element;
|
|
21
|
+
export declare function ThreadFeed({ thread, clientName, activitySourceLabel, now, actions, foot, externalMessageAction }: ThreadFeedProps): import("react").JSX.Element;
|
|
16
22
|
type FeedItem = {
|
|
17
23
|
type: "day";
|
|
18
24
|
key: string;
|
package/dist/ThreadFeed.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadFeed.d.ts","sourceRoot":"","sources":["../src/ThreadFeed.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAK7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,IAAI,CAAC;IACV,wHAAwH;IACxH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,SAAS,CAAC;IACpC,kEAAkE;IAClE,IAAI,CAAC,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ThreadFeed.d.ts","sourceRoot":"","sources":["../src/ThreadFeed.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAK7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,IAAI,CAAC;IACV,wHAAwH;IACxH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,SAAS,CAAC;IACpC,kEAAkE;IAClE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,SAAS,CAAC;CACnC;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,mBAAmB,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,qBAAqB,EAAE,EAAE,eAAe,+BAwCjI;AAED,KAAK,QAAQ,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjG,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAYxD"}
|
package/dist/ThreadFeed.js
CHANGED
|
@@ -5,12 +5,12 @@ import { EventIcon } from "./icons.js";
|
|
|
5
5
|
import { MediaAttachment } from "./MediaAttachment.js";
|
|
6
6
|
import { clockLabel, dayKey, dayLabel } from "./time.js";
|
|
7
7
|
import { useStickToBottom } from "./useStickToBottom.js";
|
|
8
|
-
export function ThreadFeed({ thread, clientName, activitySourceLabel, now, actions, foot }) {
|
|
8
|
+
export function ThreadFeed({ thread, clientName, activitySourceLabel, now, actions, foot, externalMessageAction }) {
|
|
9
9
|
const first = clientName.split(" ")[0] ?? clientName;
|
|
10
10
|
const stick = useStickToBottom(thread.messages[0]?.id);
|
|
11
11
|
return (_jsxs("div", { className: "u-feed", role: "log", "aria-label": `Conversation with ${clientName}`, "aria-busy": thread.status === "loading", ...stick, children: [thread.status === "loading" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "u-skel", style: { width: "40%", alignSelf: "flex-end" } }), _jsx("div", { className: "u-skel", style: { width: "60%" } }), _jsx("div", { className: "u-skel", style: { width: "35%", alignSelf: "center" } })] })), thread.status === "error" && (_jsxs("div", { className: "u-state", role: "alert", children: [_jsx("h3", { children: "Couldn't load this conversation" }), _jsx("p", { children: thread.error?.message }), _jsx("button", { type: "button", className: "u-retry", onClick: thread.reload, children: "Retry" })] })), thread.status === "ready" && thread.messages.length === 0 && (_jsx("div", { className: "u-state", children: _jsxs("p", { children: ["No messages or activity yet. Send ", first, " the first one below."] }) })), thread.status === "ready" && thread.hasMore && (_jsx("button", { type: "button", className: "u-older", onClick: thread.loadOlder, disabled: thread.loadingOlder, children: thread.loadingOlder ? "Loading…" : "Show older" })), thread.status === "ready" && !thread.hasMore && thread.messages.length > 0 && _jsx("span", { className: "u-hist-start", children: "\u2014 beginning of history \u2014" }), thread.status === "ready" && withDays(thread.messages).map((item) => item.type === "day"
|
|
12
12
|
? _jsx("span", { className: "u-day", children: dayLabel(item.at, now) }, item.key)
|
|
13
|
-
: (_jsx(Item, { m: item.message, first: first, source: activitySourceLabel, children: actions ? actions(item.message) : null }, item.message.id))), thread.status === "ready" && foot] }));
|
|
13
|
+
: (_jsx(Item, { m: item.message, first: first, source: activitySourceLabel, externalAction: externalMessageAction, children: actions ? actions(item.message) : null }, item.message.id))), thread.status === "ready" && foot] }));
|
|
14
14
|
}
|
|
15
15
|
/** Insert a day separator wherever the local calendar day changes. */
|
|
16
16
|
export function withDays(messages) {
|
|
@@ -26,11 +26,11 @@ export function withDays(messages) {
|
|
|
26
26
|
}
|
|
27
27
|
return out;
|
|
28
28
|
}
|
|
29
|
-
function Item({ m, first, source, children }) {
|
|
29
|
+
function Item({ m, first, source, children, externalAction }) {
|
|
30
30
|
const time = clockLabel(m.sentAt);
|
|
31
31
|
const [showTemplate, setShowTemplate] = useState(false);
|
|
32
32
|
if (m.kind === "event") {
|
|
33
|
-
return (_jsxs("div", { className: "u-ev", children: [_jsx(EventIcon, { type: m.eventType }), _jsxs("div", { children: [_jsx("b", { children: m.body }), _jsxs("div", { className: "u-meta", children: ["From ", source, time ? ` · ${time}` : ""] }), children] })] }));
|
|
33
|
+
return (_jsxs("div", { className: "u-ev", children: [_jsx(EventIcon, { type: m.eventType }), _jsxs("div", { children: [_jsx("b", { children: m.body }), _jsxs("div", { className: "u-meta", children: ["From ", source, time ? ` · ${time}` : ""] }), m.eventType === "externalMessage" && externalAction && !children && _jsx("div", { className: "u-ev-open", children: externalAction }), children] })] }));
|
|
34
34
|
}
|
|
35
35
|
const out = m.direction !== "inbound";
|
|
36
36
|
const atts = m.attachments ?? [];
|
package/dist/ThreadFeed.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadFeed.js","sourceRoot":"","sources":["../src/ThreadFeed.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ThreadFeed.js","sourceRoot":"","sources":["../src/ThreadFeed.tsx"],"names":[],"mappings":"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"}
|
package/dist/UpliftPanel.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Channel, Client } from "@uplifted/sdk";
|
|
2
|
-
import { type HostActivityLoader, type HostClient } from "./host.js";
|
|
2
|
+
import { type HostActivityLoader, type HostClient, type HostContactLoader } from "./host.js";
|
|
3
3
|
export interface UpliftPanelProps {
|
|
4
4
|
/** Header title. Default "Messages". */
|
|
5
5
|
title?: string;
|
|
@@ -26,6 +26,14 @@ export interface UpliftPanelProps {
|
|
|
26
26
|
hostClients?: ReadonlyArray<HostClient>;
|
|
27
27
|
/** Loads a not-connected host client's recent activity for the preview feed and drafts. */
|
|
28
28
|
hostActivity?: HostActivityLoader;
|
|
29
|
+
/**
|
|
30
|
+
* Reads a host client's phone when the coach connects them. With a phone, and the channel's rules
|
|
31
|
+
* already confirmed (`acknowledgedChannels`), Connect needs no form: the channel follows the number
|
|
32
|
+
* (+1 SMS, anything else WhatsApp). Without it the connect sheet asks for the phone.
|
|
33
|
+
*/
|
|
34
|
+
hostContact?: HostContactLoader;
|
|
35
|
+
/** Open a client's `hostLink` yourself (for example to navigate an embedding parent). Default: a link to a new tab. */
|
|
36
|
+
onOpenHostLink?: (host: HostClient) => void;
|
|
29
37
|
/** The coach's name, so preview drafts can be signed the way the real ones are. */
|
|
30
38
|
coachName?: string;
|
|
31
39
|
/** Fallback roster and open-thread refresh interval while the tab is visible and the stream is down. 0 disables. Default 20000. */
|
|
@@ -36,5 +44,5 @@ export interface UpliftPanelProps {
|
|
|
36
44
|
now?: () => Date;
|
|
37
45
|
className?: string;
|
|
38
46
|
}
|
|
39
|
-
export declare function UpliftPanel({ title, hostName, initialClientId, onCheckout, onSent, onClientAdded, acknowledgedChannels, onChannelAcknowledged, hostClients, hostActivity, coachName, now, className, pollMs, live: liveUpdates }: UpliftPanelProps): import("react").JSX.Element;
|
|
47
|
+
export declare function UpliftPanel({ title, hostName, initialClientId, onCheckout, onSent, onClientAdded, acknowledgedChannels, onChannelAcknowledged, hostClients, hostActivity, hostContact, onOpenHostLink, coachName, now, className, pollMs, live: liveUpdates }: UpliftPanelProps): import("react").JSX.Element;
|
|
40
48
|
//# sourceMappingURL=UpliftPanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UpliftPanel.d.ts","sourceRoot":"","sources":["../src/UpliftPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"UpliftPanel.d.ts","sourceRoot":"","sources":["../src/UpliftPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAS,MAAM,eAAe,CAAC;AAS5D,OAAO,EAAgD,KAAK,kBAAkB,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAiB3I,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,sCAAsC;IACtC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,+FAA+F;IAC/F,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAC;IACjC,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACxC,2FAA2F;IAC3F,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,uHAAuH;IACvH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mIAAmI;IACnI,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmBD,wBAAgB,WAAW,CAAC,EAAE,KAAkB,EAAE,QAAqB,EAAE,eAAsB,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,gBAAgB,+BAoZhT"}
|
package/dist/UpliftPanel.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useInsertionEffect, useMemo, useState, useRef } from "react";
|
|
3
|
-
import { AddClientSheet } from "./AddClientSheet.js";
|
|
3
|
+
import { AddClientSheet, describeAddError } from "./AddClientSheet.js";
|
|
4
|
+
import { ConnectClientSheet } from "./ConnectClientSheet.js";
|
|
5
|
+
import { channelForPhone } from "./channels.js";
|
|
6
|
+
import { coerceToE164, validatePhone } from "./phone.js";
|
|
4
7
|
import { ClientList } from "./ClientList.js";
|
|
5
8
|
import { ConversationPane } from "./ConversationPane.js";
|
|
6
9
|
import { HostPreviewPane } from "./HostPreviewPane.js";
|
|
10
|
+
import { HostLinkAction } from "./HostLinkAction.js";
|
|
7
11
|
import { HOST_SEL, hostOnlyRows, hostSelId, isHostSel } from "./host.js";
|
|
8
|
-
import { OfferSheet } from "./OfferSheet.js";
|
|
12
|
+
import { FREE_CLIENTS, OfferSheet } from "./OfferSheet.js";
|
|
9
13
|
import { ProactiveOptionsSheet } from "./ProactiveOptionsSheet.js";
|
|
10
14
|
import { useProactiveOptions } from "./useProactiveOptions.js";
|
|
11
15
|
import { useInstructions } from "./useInstructions.js";
|
|
@@ -36,7 +40,7 @@ function loadSort() {
|
|
|
36
40
|
catch { /* private mode / SSR */ }
|
|
37
41
|
return "priority";
|
|
38
42
|
}
|
|
39
|
-
export function UpliftPanel({ title = "Messages", hostName = "your app", initialClientId = null, onCheckout, onSent, onClientAdded, acknowledgedChannels, onChannelAcknowledged, hostClients, hostActivity, coachName, now, className, pollMs, live: liveUpdates }) {
|
|
43
|
+
export function UpliftPanel({ title = "Messages", hostName = "your app", initialClientId = null, onCheckout, onSent, onClientAdded, acknowledgedChannels, onChannelAcknowledged, hostClients, hostActivity, hostContact, onOpenHostLink, coachName, now, className, pollMs, live: liveUpdates }) {
|
|
40
44
|
useInsertionEffect(() => { injectStyles(); }, []);
|
|
41
45
|
const list = useClients();
|
|
42
46
|
const { client: api } = useUplift();
|
|
@@ -84,11 +88,31 @@ export function UpliftPanel({ title = "Messages", hostName = "your app", initial
|
|
|
84
88
|
/** Host clients connected during this session, so they leave the not-connected rows at once. */
|
|
85
89
|
const [linkedHere, setLinkedHere] = useState(() => new Set());
|
|
86
90
|
const [connectFor, setConnectFor] = useState(null);
|
|
91
|
+
/** Host clients connected in this session, by Uplift client id, so their header keeps the host link. */
|
|
92
|
+
const [hostByClient, setHostByClient] = useState(() => new Map());
|
|
87
93
|
// What the coach had written to a client they then connected: it waits in the new conversation.
|
|
88
94
|
const [carry, setCarry] = useState(null);
|
|
89
95
|
const [carryFor, setCarryFor] = useState(null);
|
|
96
|
+
// Read after awaits (the phone lookup, the add), where the render's values would be stale.
|
|
97
|
+
const connectForRef = useRef(connectFor);
|
|
98
|
+
connectForRef.current = connectFor;
|
|
99
|
+
const carryRef = useRef(carry);
|
|
100
|
+
carryRef.current = carry;
|
|
101
|
+
const acksRef = useRef(acks);
|
|
102
|
+
acksRef.current = acks;
|
|
103
|
+
/** The connect sheet: the phone the host had (null while loading), and why the last try failed. */
|
|
104
|
+
const [conn, setConn] = useState(null);
|
|
90
105
|
// Why the paywall opened, and the add the coach was making (completed for them once they have paid).
|
|
106
|
+
// `upgradeFirst`: Connect hit the plan wall before the coach entered anything, so after paying the
|
|
107
|
+
// connect form opens for that client.
|
|
91
108
|
const [offer, setOffer] = useState({ reason: "pitch" });
|
|
109
|
+
// The coach's plan, so Connect can show the offer before any form (Yusef, #75: the third client sees the
|
|
110
|
+
// offer first). Unknown until loaded, or if it fails: then Uplift's own refusal on the add opens it.
|
|
111
|
+
const [plan, setPlan] = useState(null);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
api.me().then((c) => setPlan({ subscription: c.subscription, freeLimit: c.freeClientLimit === undefined ? FREE_CLIENTS : c.freeClientLimit }), () => { });
|
|
114
|
+
}, [api]);
|
|
115
|
+
const planActive = () => setPlan((p) => (p ? { ...p, subscription: "active" } : p));
|
|
92
116
|
// The add form reopened prefilled, only when finishing the add after an upgrade failed.
|
|
93
117
|
const [resumeAdd, setResumeAdd] = useState(null);
|
|
94
118
|
const clock = now ? now() : new Date();
|
|
@@ -123,35 +147,100 @@ export function UpliftPanel({ title = "Messages", hostName = "your app", initial
|
|
|
123
147
|
}, [list.status, selected, selectedHost, sorted, hostOnly, selectedId]);
|
|
124
148
|
const open = useCallback((c) => { setSelectedId(c.id); setShowThread(true); }, []);
|
|
125
149
|
const openHost = useCallback((h) => { setSelectedId(hostSelId(h.id)); setShowThread(true); }, []);
|
|
126
|
-
const connect = useCallback((h, draft) => {
|
|
127
|
-
setConnectFor(h);
|
|
128
|
-
setCarry(draft?.trim() ? { hostId: h.id, text: draft } : null);
|
|
129
|
-
setSheet("add");
|
|
130
|
-
}, []);
|
|
131
150
|
const acknowledge = useCallback((ch) => {
|
|
132
151
|
setAcks((s) => new Set(s).add(ch));
|
|
133
152
|
onChannelAcknowledged?.(ch);
|
|
134
153
|
}, [onChannelAcknowledged]);
|
|
135
|
-
const added = useCallback((c, hostId =
|
|
154
|
+
const added = useCallback((c, hostId = connectForRef.current?.id) => {
|
|
155
|
+
const from = connectForRef.current;
|
|
156
|
+
const draft = carryRef.current;
|
|
136
157
|
list.upsert(c);
|
|
137
|
-
if (
|
|
138
|
-
setLinkedHere((s) => new Set(s).add(
|
|
139
|
-
if (
|
|
140
|
-
|
|
158
|
+
if (from)
|
|
159
|
+
setLinkedHere((s) => new Set(s).add(from.id));
|
|
160
|
+
if (hostId)
|
|
161
|
+
setHostByClient((m) => new Map(m).set(c.id, hostId));
|
|
162
|
+
if (draft && draft.hostId === hostId)
|
|
163
|
+
setCarryFor({ clientId: c.id, text: draft.text });
|
|
141
164
|
setCarry(null);
|
|
142
165
|
setConnectFor(null);
|
|
166
|
+
setConn(null);
|
|
143
167
|
setSheet(null);
|
|
144
168
|
setSelectedId(c.id);
|
|
145
169
|
setShowThread(true);
|
|
146
170
|
setLive(`${firstName(c.name)} added to Uplift`);
|
|
147
171
|
onClientAdded?.(c);
|
|
148
|
-
}, [list, onClientAdded
|
|
172
|
+
}, [list, onClientAdded]);
|
|
173
|
+
/** Connects a host client with a known phone. A plan refusal opens the offer; anything else goes back to the sheet. */
|
|
174
|
+
const connectNow = useCallback(async (h, phone, channel) => {
|
|
175
|
+
setConn({ phone, busy: true });
|
|
176
|
+
setSheet("connect");
|
|
177
|
+
const attempt = { name: h.name, phone, channel, hostClientId: h.id };
|
|
178
|
+
const confirm = () => { if (!acksRef.current.has(channel))
|
|
179
|
+
acknowledge(channel); };
|
|
180
|
+
try {
|
|
181
|
+
const c = await api.addClient(attempt);
|
|
182
|
+
confirm();
|
|
183
|
+
added(c, h.id);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
const ue = asUpliftError(err);
|
|
187
|
+
if (ue.code === "subscription_required" || ue.code === "plan_inactive") {
|
|
188
|
+
confirm();
|
|
189
|
+
setConn(null);
|
|
190
|
+
setOffer({ reason: ue.code === "plan_inactive" ? "plan_inactive" : "client_limit", attempt });
|
|
191
|
+
setSheet("offer");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
setConn({ phone, busy: false, error: describeAddError(ue, phone, list.clients) });
|
|
195
|
+
}
|
|
196
|
+
}, [api, added, acknowledge, list.clients]);
|
|
197
|
+
/** Gets the host's phone for them, then connects at once if the channel's rules are confirmed, else asks. */
|
|
198
|
+
const startConnect = useCallback(async (h) => {
|
|
199
|
+
setConn({ phone: null, busy: false });
|
|
200
|
+
setSheet("connect");
|
|
201
|
+
let phone;
|
|
202
|
+
if (hostContact) {
|
|
203
|
+
try {
|
|
204
|
+
const p = coerceToE164((await hostContact(h.id)).phone ?? "");
|
|
205
|
+
if (p && validatePhone(p).valid)
|
|
206
|
+
phone = p;
|
|
207
|
+
}
|
|
208
|
+
catch { /* the sheet asks for it */ }
|
|
209
|
+
}
|
|
210
|
+
if (connectForRef.current?.id !== h.id)
|
|
211
|
+
return; // closed or switched meanwhile
|
|
212
|
+
if (phone && acksRef.current.has(channelForPhone(phone)))
|
|
213
|
+
await connectNow(h, phone, channelForPhone(phone));
|
|
214
|
+
else
|
|
215
|
+
setConn({ phone, busy: false });
|
|
216
|
+
}, [hostContact, connectNow]);
|
|
217
|
+
const connect = useCallback((h, draft) => {
|
|
218
|
+
setConnectFor(h);
|
|
219
|
+
connectForRef.current = h;
|
|
220
|
+
const keep = draft?.trim() ? { hostId: h.id, text: draft } : null;
|
|
221
|
+
setCarry(keep);
|
|
222
|
+
carryRef.current = keep;
|
|
223
|
+
const wall = plan?.subscription === "inactive" ? "plan_inactive"
|
|
224
|
+
: plan?.subscription === "free" && plan.freeLimit !== null && list.clients.length >= plan.freeLimit ? "client_limit" : null;
|
|
225
|
+
if (wall) {
|
|
226
|
+
setOffer({ reason: wall, upgradeFirst: h });
|
|
227
|
+
setSheet("offer");
|
|
228
|
+
}
|
|
229
|
+
else
|
|
230
|
+
void startConnect(h);
|
|
231
|
+
}, [plan, list.clients.length, startConnect]);
|
|
149
232
|
const finishAdd = useCallback(async (attempt) => {
|
|
150
233
|
try {
|
|
151
234
|
added(await api.addClient(attempt.hostClientId ? attempt : { name: attempt.name, phone: attempt.phone, channel: attempt.channel }), attempt.hostClientId ?? undefined);
|
|
152
235
|
}
|
|
153
236
|
catch (err) {
|
|
154
|
-
|
|
237
|
+
const message = asUpliftError(err).message || "Couldn't add this client. Try again.";
|
|
238
|
+
if (attempt.hostClientId && connectForRef.current) {
|
|
239
|
+
setConn({ phone: attempt.phone, busy: false, error: { form: message } });
|
|
240
|
+
setSheet("connect");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
setResumeAdd({ ...attempt, error: message });
|
|
155
244
|
setSheet("add");
|
|
156
245
|
}
|
|
157
246
|
}, [api, added]);
|
|
@@ -201,19 +290,30 @@ export function UpliftPanel({ title = "Messages", hostName = "your app", initial
|
|
|
201
290
|
const paneDrafts = selected ? bulk.results[selected.id] ?? null : null;
|
|
202
291
|
// AI settings + full screen: the right end of the conversation header (and, on narrow panels where
|
|
203
292
|
// the list shows alone, the list toolbar).
|
|
293
|
+
/** The host client behind an Uplift client: linked by the host (`upliftClientId`) or connected in this session. */
|
|
294
|
+
const hostClientFor = (clientId) => {
|
|
295
|
+
const viaSession = hostByClient.get(clientId);
|
|
296
|
+
return hostClients?.find((h) => h.upliftClientId === clientId || h.id === viaSession) ?? null;
|
|
297
|
+
};
|
|
298
|
+
const hostActionFor = (h) => (h?.hostLink
|
|
299
|
+
? _jsx(HostLinkAction, { link: h.hostLink, onOpen: onOpenHostLink ? () => onOpenHostLink(h) : undefined })
|
|
300
|
+
: null);
|
|
204
301
|
const headActions = (_jsxs("span", { className: "u-head-acts", children: [_jsxs("button", { type: "button", className: "u-iconbtn u-iconbtn-pair", "aria-label": "AI settings", title: "AI settings: your rules for drafts, and the \u2728 message options",
|
|
205
302
|
// Like Config AI: with no client connected yet, connecting comes first.
|
|
206
303
|
onClick: () => setSheet(list.clients.length === 0 && hostClients !== undefined ? "add" : "ai"), children: [_jsx("span", { className: "u-ai-spark", children: _jsx(IconSparkles, {}) }), _jsx(IconSliders, {})] }), _jsx("button", { type: "button", className: "u-iconbtn", "aria-pressed": full, "aria-label": full ? "Exit full screen" : "Full screen", title: full ? "Exit full screen (Esc)" : "Full screen", onClick: () => setFull((f) => !f), children: full ? _jsx(IconMinimize, {}) : _jsx(IconMaximize, {}) })] }));
|
|
207
304
|
return (_jsxs("section", { className: `uplift${full ? " u-full" : ""}${className ? ` ${className}` : ""}`, "aria-label": "Uplift messages", children: [_jsx("h2", { className: "u-sr", children: title }), bulk.blocked
|
|
208
305
|
? (_jsxs("div", { className: "u-banner", role: "status", children: [_jsx("span", { children: "Your Uplift plan isn't active, so Bulk generate is paused." }), _jsx("span", { className: "u-sp" }), _jsx("button", { type: "button", className: "u-linkbtn", onClick: () => { setOffer({ reason: "plan_inactive" }); setSheet("offer"); }, children: "Reactivate" })] }))
|
|
209
306
|
: null, _jsxs("div", { ref: split, className: `u-split${showThread ? " u-show-thread" : ""}`, children: [_jsx(ClientList, { status: list.status, error: list.error, rows: rows, totalCount: clients.length + hostOnly.length, search: search, onSearch: setSearch, sort: sort, onSort: setSort, selectedId: selectedId, onOpen: open, onRetry: list.reload, onAddClient: () => setSheet("add"), onBulk: () => setSheet("bulk"), headActions: headActions, bulkDisabled: bulk.running || clients.length + hostOnly.length === 0, hostManaged: hostClients !== undefined, drafts: bulk.results, bulkStatus: bulk.status, onQuickSent: quickSent, hostName: hostName, onOpenHost: openHost, onConnectHost: connect, now: clock }), selectedHost
|
|
210
|
-
? (_jsx(HostPreviewPane, { host: selectedHost, hostName: hostName, coachName: coachName, loadActivity: hostActivity, now: clock, onBack: () => setShowThread(false), onConnect: connect, purposes: proactive.purposes, onCustomize: () => setSheet("proactive"), instructions: instructions, connectedCount: list.clients.length, onPlanInactive: () => { setOffer({ reason: "plan_inactive" }); setSheet("offer"); }, cachedDrafts: bulk.results[hostSelId(selectedHost.id)] ?? null, headActions: headActions }, HOST_SEL + selectedHost.id))
|
|
307
|
+
? (_jsx(HostPreviewPane, { host: selectedHost, hostName: hostName, coachName: coachName, loadActivity: hostActivity, now: clock, onBack: () => setShowThread(false), onConnect: connect, purposes: proactive.purposes, onCustomize: () => setSheet("proactive"), instructions: instructions, connectedCount: list.clients.length, onPlanInactive: () => { setOffer({ reason: "plan_inactive" }); setSheet("offer"); }, cachedDrafts: bulk.results[hostSelId(selectedHost.id)] ?? null, headActions: headActions, hostAction: hostActionFor(selectedHost) }, HOST_SEL + selectedHost.id))
|
|
211
308
|
: selected
|
|
212
|
-
? (_jsx(ConversationPane, { client: selected, activitySourceLabel: hostName, now: clock, onBack: () => setShowThread(false), announce: setLive, onSent: sent, onNeedsPlan: (reason) => { setOffer({ reason: reason ?? "client_limit" }); setSheet("offer"); }, initialText: carryFor?.clientId === selected.id ? carryFor.text : undefined, cachedDrafts: paneDrafts, purposes: proactive.purposes, onCustomize: () => setSheet("proactive"), onRead: read, instructions: instructions, refreshKey: poll + threadPing, headActions: headActions }, selected.id))
|
|
213
|
-
: _jsx("div", { className: "u-pane", children: _jsx("div", { className: "u-pane-head u-pane-head-empty", children: headActions }) })] }), sheet === "add" && (_jsx(AddClientSheet, { onClose: () => { setSheet(null); setConnectFor(null); setCarry(null); setResumeAdd(null); }, onAdded: (c) => { setResumeAdd(null); added(c); }, onNeedsPlan: (ctx) => { setOffer(ctx); setSheet("offer"); }, acknowledged: acks, onAcknowledge: acknowledge, hostClients: hostClients?.filter((h) => !h.upliftClientId && !linkedHere.has(h.id)), existing: list.clients, hostName: hostName, prefill: resumeAdd ?? (connectFor ? { name: connectFor.name, hostClientId: connectFor.id } : undefined) })), sheet === "bulk" && (_jsx(BulkGenerateSheet, { candidates: sortInbox(clients, hostOnly, sort).map((r) => (r.kind === "client"
|
|
309
|
+
? (_jsx(ConversationPane, { client: selected, activitySourceLabel: hostName, now: clock, onBack: () => setShowThread(false), announce: setLive, onSent: sent, onNeedsPlan: (reason) => { setOffer({ reason: reason ?? "client_limit" }); setSheet("offer"); }, initialText: carryFor?.clientId === selected.id ? carryFor.text : undefined, cachedDrafts: paneDrafts, purposes: proactive.purposes, onCustomize: () => setSheet("proactive"), onRead: read, instructions: instructions, refreshKey: poll + threadPing, headActions: headActions, hostAction: hostActionFor(hostClientFor(selected.id)) }, selected.id))
|
|
310
|
+
: _jsx("div", { className: "u-pane", children: _jsx("div", { className: "u-pane-head u-pane-head-empty", children: headActions }) })] }), sheet === "add" && (_jsx(AddClientSheet, { onClose: () => { setSheet(null); setConnectFor(null); setCarry(null); setResumeAdd(null); }, onAdded: (c) => { setResumeAdd(null); added(c); }, onNeedsPlan: (ctx) => { setOffer(ctx); setSheet("offer"); }, acknowledged: acks, onAcknowledge: acknowledge, hostClients: hostClients?.filter((h) => !h.upliftClientId && !linkedHere.has(h.id)), existing: list.clients, hostName: hostName, prefill: resumeAdd ?? (connectFor ? { name: connectFor.name, hostClientId: connectFor.id } : undefined) })), sheet === "connect" && connectFor && conn && (_jsx(ConnectClientSheet, { name: connectFor.name, phone: conn.phone, error: conn.error, acknowledged: acks, busy: conn.busy, onConnect: (phone, channel) => { void connectNow(connectFor, phone, channel); }, onClose: () => { setSheet(null); setConnectFor(null); setCarry(null); setConn(null); } }, connectFor.id)), sheet === "bulk" && (_jsx(BulkGenerateSheet, { candidates: sortInbox(clients, hostOnly, sort).map((r) => (r.kind === "client"
|
|
214
311
|
? { target: { kind: "client", id: r.client.id }, name: r.client.name, lastActiveAt: r.client.lastActivity?.at ?? r.client.lastContactAt, connected: true }
|
|
215
|
-
: { target: { kind: "host", id: r.host.id, name: r.host.name }, name: r.host.name, lastActiveAt: r.host.lastActiveAt ?? null, connected: false })), now: clock, onClose: () => setSheet(null), onGenerate: bulk.start, onEditInstructions: instructions.data?.canEdit ? () => setSheet("ai") : undefined })), sheet === "proactive" && _jsx(ProactiveOptionsSheet, { state: proactive, onClose: () => setSheet(null) }), sheet === "ai" && _jsx(AiSettingsSheet, { instructions: instructions, proactive: proactive, hostName: hostName, onClose: () => setSheet(null), onUpgrade: () => { setOffer({ reason: "feature" }); setSheet("offer"); } }), sheet === "offer" && (_jsx(OfferSheet, { onClose: () => { setSheet(null); setConnectFor(null); }, onCheckout: onCheckout, hostName: hostName, reason: offer.reason, connecting: offer.attempt?.name, connected: list.clients.map((c) => ({ id: c.id, name: c.name })),
|
|
312
|
+
: { target: { kind: "host", id: r.host.id, name: r.host.name }, name: r.host.name, lastActiveAt: r.host.lastActiveAt ?? null, connected: false })), now: clock, onClose: () => setSheet(null), onGenerate: bulk.start, onEditInstructions: instructions.data?.canEdit ? () => setSheet("ai") : undefined })), sheet === "proactive" && _jsx(ProactiveOptionsSheet, { state: proactive, onClose: () => setSheet(null) }), sheet === "ai" && _jsx(AiSettingsSheet, { instructions: instructions, proactive: proactive, hostName: hostName, onClose: () => setSheet(null), onUpgrade: () => { setOffer({ reason: "feature" }); setSheet("offer"); } }), sheet === "offer" && (_jsx(OfferSheet, { onClose: () => { setSheet(null); setConnectFor(null); setCarry(null); }, onCheckout: onCheckout, hostName: hostName, reason: offer.reason, freeClients: plan?.freeLimit ?? undefined, connecting: offer.attempt?.name ?? offer.upgradeFirst?.name, connected: list.clients.map((c) => ({ id: c.id, name: c.name })),
|
|
216
313
|
// Paid: finish the add the coach was making. Only if that fails does the form come back, prefilled.
|
|
217
|
-
|
|
314
|
+
// Upgraded before entering anything: the connect form opens for that client.
|
|
315
|
+
onUpgraded: offer.attempt ? () => { planActive(); void finishAdd(offer.attempt); }
|
|
316
|
+
: offer.upgradeFirst ? () => { planActive(); void startConnect(offer.upgradeFirst); }
|
|
317
|
+
: undefined })), _jsx("div", { className: "u-sr", "aria-live": "polite", children: live })] }));
|
|
218
318
|
}
|
|
219
319
|
//# sourceMappingURL=UpliftPanel.js.map
|