@customerhero/react 1.0.1 → 2.0.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/dist/index.cjs +292 -3
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +292 -3
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -100,6 +100,27 @@ function useChat() {
|
|
|
100
100
|
identify: (0, import_react2.useCallback)(
|
|
101
101
|
(payload) => client.identify(payload),
|
|
102
102
|
[client]
|
|
103
|
+
),
|
|
104
|
+
setConsent: (0, import_react2.useCallback)(
|
|
105
|
+
(consent) => client.setConsent(consent),
|
|
106
|
+
[client]
|
|
107
|
+
),
|
|
108
|
+
setTraits: (0, import_react2.useCallback)(
|
|
109
|
+
(traits) => client.setTraits(traits),
|
|
110
|
+
[client]
|
|
111
|
+
),
|
|
112
|
+
submitPreChatForm: (0, import_react2.useCallback)(
|
|
113
|
+
(submission) => client.submitPreChatForm(submission),
|
|
114
|
+
[client]
|
|
115
|
+
),
|
|
116
|
+
cancelPreChatForm: (0, import_react2.useCallback)(() => client.cancelPreChatForm(), [client]),
|
|
117
|
+
fireTrigger: (0, import_react2.useCallback)(
|
|
118
|
+
(triggerId) => client.fireTrigger(triggerId),
|
|
119
|
+
[client]
|
|
120
|
+
),
|
|
121
|
+
consumePendingPrefill: (0, import_react2.useCallback)(
|
|
122
|
+
() => client.consumePendingPrefill(),
|
|
123
|
+
[client]
|
|
103
124
|
)
|
|
104
125
|
};
|
|
105
126
|
}
|
|
@@ -1457,7 +1478,15 @@ var import_js2 = require("@customerhero/js");
|
|
|
1457
1478
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1458
1479
|
var MAX_ATTACHMENTS = 3;
|
|
1459
1480
|
function ChatInput() {
|
|
1460
|
-
const {
|
|
1481
|
+
const {
|
|
1482
|
+
sendMessage,
|
|
1483
|
+
uploadAttachment,
|
|
1484
|
+
isLoading,
|
|
1485
|
+
config,
|
|
1486
|
+
t,
|
|
1487
|
+
consumePendingPrefill,
|
|
1488
|
+
pendingPrefill
|
|
1489
|
+
} = useChat();
|
|
1461
1490
|
const reduced = useReducedMotion();
|
|
1462
1491
|
const [value, setValue] = (0, import_react8.useState)("");
|
|
1463
1492
|
const [attachments, setAttachments] = (0, import_react8.useState)([]);
|
|
@@ -1465,6 +1494,11 @@ function ChatInput() {
|
|
|
1465
1494
|
(0, import_react8.useEffect)(() => {
|
|
1466
1495
|
setCaptureSupported((0, import_js2.canCaptureScreenshot)());
|
|
1467
1496
|
}, []);
|
|
1497
|
+
(0, import_react8.useEffect)(() => {
|
|
1498
|
+
if (pendingPrefill === null) return;
|
|
1499
|
+
const text = consumePendingPrefill();
|
|
1500
|
+
if (text !== null) setValue(text);
|
|
1501
|
+
}, [pendingPrefill, consumePendingPrefill]);
|
|
1468
1502
|
(0, import_react8.useEffect)(() => {
|
|
1469
1503
|
return () => {
|
|
1470
1504
|
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
@@ -1806,8 +1840,263 @@ function ConfigError({ message, title }) {
|
|
|
1806
1840
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
|
|
1807
1841
|
] });
|
|
1808
1842
|
}
|
|
1843
|
+
function fieldKey(field) {
|
|
1844
|
+
if (field.kind === "name" || field.kind === "email" || field.kind === "phone") {
|
|
1845
|
+
return field.kind;
|
|
1846
|
+
}
|
|
1847
|
+
return field.key;
|
|
1848
|
+
}
|
|
1849
|
+
function fieldLabel(field) {
|
|
1850
|
+
if (field.kind === "name") return field.label ?? "Name";
|
|
1851
|
+
if (field.kind === "email") return field.label ?? "Email";
|
|
1852
|
+
if (field.kind === "phone") return field.label ?? "Phone";
|
|
1853
|
+
return field.label;
|
|
1854
|
+
}
|
|
1855
|
+
function validateField(field, value) {
|
|
1856
|
+
const required = "required" in field ? !!field.required : false;
|
|
1857
|
+
if (required && (value === void 0 || value === "" || value === false)) {
|
|
1858
|
+
return "Required";
|
|
1859
|
+
}
|
|
1860
|
+
if (field.kind === "email" && typeof value === "string" && value !== "") {
|
|
1861
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) return "Invalid email";
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function PreChatFormView() {
|
|
1866
|
+
const { preChatForm, submitPreChatForm, cancelPreChatForm, config, t } = useChat();
|
|
1867
|
+
const [values, setValues] = (0, import_react9.useState)({});
|
|
1868
|
+
const [errors, setErrors] = (0, import_react9.useState)({});
|
|
1869
|
+
const [submitting, setSubmitting] = (0, import_react9.useState)(false);
|
|
1870
|
+
if (!preChatForm) return null;
|
|
1871
|
+
function setValue(key, value) {
|
|
1872
|
+
setValues((prev) => ({ ...prev, [key]: value }));
|
|
1873
|
+
setErrors((prev) => ({ ...prev, [key]: null }));
|
|
1874
|
+
}
|
|
1875
|
+
async function handleSubmit(e) {
|
|
1876
|
+
e.preventDefault();
|
|
1877
|
+
if (!preChatForm) return;
|
|
1878
|
+
const nextErrors = {};
|
|
1879
|
+
let hasError = false;
|
|
1880
|
+
for (const f of preChatForm.fields) {
|
|
1881
|
+
const k = fieldKey(f);
|
|
1882
|
+
const err = validateField(f, values[k]);
|
|
1883
|
+
nextErrors[k] = err;
|
|
1884
|
+
if (err) hasError = true;
|
|
1885
|
+
}
|
|
1886
|
+
setErrors(nextErrors);
|
|
1887
|
+
if (hasError) return;
|
|
1888
|
+
const submission = {};
|
|
1889
|
+
const properties = {};
|
|
1890
|
+
for (const f of preChatForm.fields) {
|
|
1891
|
+
const k = fieldKey(f);
|
|
1892
|
+
const v = values[k];
|
|
1893
|
+
if (v === void 0 || v === "") continue;
|
|
1894
|
+
if (f.kind === "name" && typeof v === "string") submission.name = v;
|
|
1895
|
+
else if (f.kind === "email" && typeof v === "string")
|
|
1896
|
+
submission.email = v;
|
|
1897
|
+
else if (f.kind === "phone" && typeof v === "string")
|
|
1898
|
+
submission.phone = v;
|
|
1899
|
+
else if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
1900
|
+
properties[k] = v;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
if (Object.keys(properties).length > 0) submission.properties = properties;
|
|
1904
|
+
setSubmitting(true);
|
|
1905
|
+
try {
|
|
1906
|
+
await submitPreChatForm(submission);
|
|
1907
|
+
} finally {
|
|
1908
|
+
setSubmitting(false);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
const containerStyle = {
|
|
1912
|
+
flex: 1,
|
|
1913
|
+
overflowY: "auto",
|
|
1914
|
+
padding: 20,
|
|
1915
|
+
display: "flex",
|
|
1916
|
+
flexDirection: "column",
|
|
1917
|
+
gap: 12,
|
|
1918
|
+
background: config.backgroundColor,
|
|
1919
|
+
color: config.textColor
|
|
1920
|
+
};
|
|
1921
|
+
const labelStyle = {
|
|
1922
|
+
fontSize: 13,
|
|
1923
|
+
fontWeight: 500,
|
|
1924
|
+
display: "flex",
|
|
1925
|
+
flexDirection: "column",
|
|
1926
|
+
gap: 4
|
|
1927
|
+
};
|
|
1928
|
+
const inputStyle = {
|
|
1929
|
+
width: "100%",
|
|
1930
|
+
border: "1px solid #d4d4d8",
|
|
1931
|
+
borderRadius: 8,
|
|
1932
|
+
padding: "8px 10px",
|
|
1933
|
+
fontSize: 14,
|
|
1934
|
+
background: "white",
|
|
1935
|
+
color: "#111",
|
|
1936
|
+
boxSizing: "border-box"
|
|
1937
|
+
};
|
|
1938
|
+
const errorStyle = { color: "#dc2626", fontSize: 12 };
|
|
1939
|
+
const buttonRowStyle = {
|
|
1940
|
+
display: "flex",
|
|
1941
|
+
gap: 8,
|
|
1942
|
+
marginTop: 8
|
|
1943
|
+
};
|
|
1944
|
+
const submitStyle = {
|
|
1945
|
+
flex: 1,
|
|
1946
|
+
background: config.primaryColor,
|
|
1947
|
+
color: "white",
|
|
1948
|
+
border: "none",
|
|
1949
|
+
borderRadius: 8,
|
|
1950
|
+
padding: "10px 14px",
|
|
1951
|
+
fontSize: 14,
|
|
1952
|
+
fontWeight: 600,
|
|
1953
|
+
cursor: submitting ? "not-allowed" : "pointer",
|
|
1954
|
+
opacity: submitting ? 0.7 : 1
|
|
1955
|
+
};
|
|
1956
|
+
const cancelStyle = {
|
|
1957
|
+
background: "transparent",
|
|
1958
|
+
color: config.textColor,
|
|
1959
|
+
border: "1px solid #d4d4d8",
|
|
1960
|
+
borderRadius: 8,
|
|
1961
|
+
padding: "10px 14px",
|
|
1962
|
+
fontSize: 14,
|
|
1963
|
+
cursor: "pointer"
|
|
1964
|
+
};
|
|
1965
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1966
|
+
"form",
|
|
1967
|
+
{
|
|
1968
|
+
style: containerStyle,
|
|
1969
|
+
onSubmit: handleSubmit,
|
|
1970
|
+
"data-customerhero-prechat-form": true,
|
|
1971
|
+
children: [
|
|
1972
|
+
preChatForm.title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { style: { fontSize: 16, fontWeight: 600, margin: 0 }, children: preChatForm.title }),
|
|
1973
|
+
preChatForm.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 13, margin: 0, opacity: 0.8 }, children: preChatForm.description }),
|
|
1974
|
+
preChatForm.fields.map((field) => {
|
|
1975
|
+
const k = fieldKey(field);
|
|
1976
|
+
const v = values[k];
|
|
1977
|
+
const err = errors[k];
|
|
1978
|
+
const required = "required" in field ? !!field.required : false;
|
|
1979
|
+
const label = fieldLabel(field);
|
|
1980
|
+
if (field.kind === "textarea") {
|
|
1981
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: labelStyle, children: [
|
|
1982
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
|
|
1983
|
+
label,
|
|
1984
|
+
required && " *"
|
|
1985
|
+
] }),
|
|
1986
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1987
|
+
"textarea",
|
|
1988
|
+
{
|
|
1989
|
+
style: { ...inputStyle, minHeight: 80, resize: "vertical" },
|
|
1990
|
+
value: v ?? "",
|
|
1991
|
+
maxLength: field.maxLength,
|
|
1992
|
+
onChange: (e) => setValue(k, e.target.value)
|
|
1993
|
+
}
|
|
1994
|
+
),
|
|
1995
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
|
|
1996
|
+
] }, k);
|
|
1997
|
+
}
|
|
1998
|
+
if (field.kind === "select") {
|
|
1999
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: labelStyle, children: [
|
|
2000
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
|
|
2001
|
+
label,
|
|
2002
|
+
required && " *"
|
|
2003
|
+
] }),
|
|
2004
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2005
|
+
"select",
|
|
2006
|
+
{
|
|
2007
|
+
style: inputStyle,
|
|
2008
|
+
value: v ?? "",
|
|
2009
|
+
onChange: (e) => setValue(k, e.target.value),
|
|
2010
|
+
children: [
|
|
2011
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: "\u2014" }),
|
|
2012
|
+
field.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
|
|
2013
|
+
]
|
|
2014
|
+
}
|
|
2015
|
+
),
|
|
2016
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
|
|
2017
|
+
] }, k);
|
|
2018
|
+
}
|
|
2019
|
+
if (field.kind === "consent") {
|
|
2020
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2021
|
+
"label",
|
|
2022
|
+
{
|
|
2023
|
+
style: {
|
|
2024
|
+
...labelStyle,
|
|
2025
|
+
flexDirection: "row",
|
|
2026
|
+
alignItems: "flex-start",
|
|
2027
|
+
gap: 8
|
|
2028
|
+
},
|
|
2029
|
+
children: [
|
|
2030
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2031
|
+
"input",
|
|
2032
|
+
{
|
|
2033
|
+
type: "checkbox",
|
|
2034
|
+
checked: v === true,
|
|
2035
|
+
onChange: (e) => setValue(k, e.target.checked),
|
|
2036
|
+
style: { marginTop: 3 }
|
|
2037
|
+
}
|
|
2038
|
+
),
|
|
2039
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { fontSize: 13, fontWeight: 400 }, children: [
|
|
2040
|
+
field.label,
|
|
2041
|
+
field.url && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
2042
|
+
" ",
|
|
2043
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2044
|
+
"a",
|
|
2045
|
+
{
|
|
2046
|
+
href: field.url,
|
|
2047
|
+
target: "_blank",
|
|
2048
|
+
rel: "noopener noreferrer",
|
|
2049
|
+
style: { color: config.primaryColor },
|
|
2050
|
+
children: "\u2197"
|
|
2051
|
+
}
|
|
2052
|
+
)
|
|
2053
|
+
] })
|
|
2054
|
+
] }),
|
|
2055
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
|
|
2056
|
+
]
|
|
2057
|
+
},
|
|
2058
|
+
k
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
const inputType = field.kind === "email" ? "email" : field.kind === "phone" ? "tel" : "text";
|
|
2062
|
+
const maxLength = field.kind === "text" ? field.maxLength : void 0;
|
|
2063
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: labelStyle, children: [
|
|
2064
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
|
|
2065
|
+
label,
|
|
2066
|
+
required && " *"
|
|
2067
|
+
] }),
|
|
2068
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2069
|
+
"input",
|
|
2070
|
+
{
|
|
2071
|
+
type: inputType,
|
|
2072
|
+
style: inputStyle,
|
|
2073
|
+
value: v ?? "",
|
|
2074
|
+
maxLength,
|
|
2075
|
+
onChange: (e) => setValue(k, e.target.value)
|
|
2076
|
+
}
|
|
2077
|
+
),
|
|
2078
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
|
|
2079
|
+
] }, k);
|
|
2080
|
+
}),
|
|
2081
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: buttonRowStyle, children: [
|
|
2082
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2083
|
+
"button",
|
|
2084
|
+
{
|
|
2085
|
+
type: "button",
|
|
2086
|
+
onClick: cancelPreChatForm,
|
|
2087
|
+
style: cancelStyle,
|
|
2088
|
+
disabled: submitting,
|
|
2089
|
+
children: t("action_cancel")
|
|
2090
|
+
}
|
|
2091
|
+
),
|
|
2092
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "submit", style: submitStyle, disabled: submitting, children: preChatForm.submitLabel })
|
|
2093
|
+
] })
|
|
2094
|
+
]
|
|
2095
|
+
}
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
1809
2098
|
function ChatWindow() {
|
|
1810
|
-
const { isOpen, config, configError, t, isRtl } = useChat();
|
|
2099
|
+
const { isOpen, config, configError, t, isRtl, preChatFormVisible } = useChat();
|
|
1811
2100
|
const reduced = useReducedMotion();
|
|
1812
2101
|
const [visible, setVisible] = (0, import_react9.useState)(false);
|
|
1813
2102
|
const [shouldRender, setShouldRender] = (0, import_react9.useState)(false);
|
|
@@ -1862,7 +2151,7 @@ function ChatWindow() {
|
|
|
1862
2151
|
};
|
|
1863
2152
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
|
|
1864
2153
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatHeader, {}),
|
|
1865
|
-
configError ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ConfigError, { title: t("unable_to_load"), message: configError }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
2154
|
+
configError ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ConfigError, { title: t("unable_to_load"), message: configError }) : preChatFormVisible ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PreChatFormView, {}) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
1866
2155
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatMessages, {}),
|
|
1867
2156
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatSuggestions, {}),
|
|
1868
2157
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatInput, {})
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { CustomerHeroChatConfig, IdentifyPayload, ActionConfirmationBlock, TranslateFn, ChatState, MessageRating } from '@customerhero/js';
|
|
2
|
+
import { CustomerHeroChatConfig, IdentifyPayload, ActionConfirmationBlock, TranslateFn, ChatState, MessageRating, ConsentSettings, PreChatSubmission } from '@customerhero/js';
|
|
3
3
|
export { ActionConfirmationBlock, IdentifyPayload } from '@customerhero/js';
|
|
4
4
|
|
|
5
5
|
interface ChatWidgetProps extends CustomerHeroChatConfig {
|
|
@@ -38,6 +38,12 @@ interface UseChatReturn extends ChatState {
|
|
|
38
38
|
close: () => void;
|
|
39
39
|
reset: () => void;
|
|
40
40
|
identify: (payload: IdentifyPayload) => void;
|
|
41
|
+
setConsent: (consent: Partial<ConsentSettings>) => void;
|
|
42
|
+
setTraits: (traits: Record<string, string | number | boolean>) => void;
|
|
43
|
+
submitPreChatForm: (submission: PreChatSubmission) => Promise<void>;
|
|
44
|
+
cancelPreChatForm: () => void;
|
|
45
|
+
fireTrigger: (triggerId: string) => void;
|
|
46
|
+
consumePendingPrefill: () => string | null;
|
|
41
47
|
}
|
|
42
48
|
declare function useChat(): UseChatReturn;
|
|
43
49
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { CustomerHeroChatConfig, IdentifyPayload, ActionConfirmationBlock, TranslateFn, ChatState, MessageRating } from '@customerhero/js';
|
|
2
|
+
import { CustomerHeroChatConfig, IdentifyPayload, ActionConfirmationBlock, TranslateFn, ChatState, MessageRating, ConsentSettings, PreChatSubmission } from '@customerhero/js';
|
|
3
3
|
export { ActionConfirmationBlock, IdentifyPayload } from '@customerhero/js';
|
|
4
4
|
|
|
5
5
|
interface ChatWidgetProps extends CustomerHeroChatConfig {
|
|
@@ -38,6 +38,12 @@ interface UseChatReturn extends ChatState {
|
|
|
38
38
|
close: () => void;
|
|
39
39
|
reset: () => void;
|
|
40
40
|
identify: (payload: IdentifyPayload) => void;
|
|
41
|
+
setConsent: (consent: Partial<ConsentSettings>) => void;
|
|
42
|
+
setTraits: (traits: Record<string, string | number | boolean>) => void;
|
|
43
|
+
submitPreChatForm: (submission: PreChatSubmission) => Promise<void>;
|
|
44
|
+
cancelPreChatForm: () => void;
|
|
45
|
+
fireTrigger: (triggerId: string) => void;
|
|
46
|
+
consumePendingPrefill: () => string | null;
|
|
41
47
|
}
|
|
42
48
|
declare function useChat(): UseChatReturn;
|
|
43
49
|
|
package/dist/index.js
CHANGED
|
@@ -79,6 +79,27 @@ function useChat() {
|
|
|
79
79
|
identify: useCallback(
|
|
80
80
|
(payload) => client.identify(payload),
|
|
81
81
|
[client]
|
|
82
|
+
),
|
|
83
|
+
setConsent: useCallback(
|
|
84
|
+
(consent) => client.setConsent(consent),
|
|
85
|
+
[client]
|
|
86
|
+
),
|
|
87
|
+
setTraits: useCallback(
|
|
88
|
+
(traits) => client.setTraits(traits),
|
|
89
|
+
[client]
|
|
90
|
+
),
|
|
91
|
+
submitPreChatForm: useCallback(
|
|
92
|
+
(submission) => client.submitPreChatForm(submission),
|
|
93
|
+
[client]
|
|
94
|
+
),
|
|
95
|
+
cancelPreChatForm: useCallback(() => client.cancelPreChatForm(), [client]),
|
|
96
|
+
fireTrigger: useCallback(
|
|
97
|
+
(triggerId) => client.fireTrigger(triggerId),
|
|
98
|
+
[client]
|
|
99
|
+
),
|
|
100
|
+
consumePendingPrefill: useCallback(
|
|
101
|
+
() => client.consumePendingPrefill(),
|
|
102
|
+
[client]
|
|
82
103
|
)
|
|
83
104
|
};
|
|
84
105
|
}
|
|
@@ -1443,7 +1464,15 @@ import {
|
|
|
1443
1464
|
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1444
1465
|
var MAX_ATTACHMENTS = 3;
|
|
1445
1466
|
function ChatInput() {
|
|
1446
|
-
const {
|
|
1467
|
+
const {
|
|
1468
|
+
sendMessage,
|
|
1469
|
+
uploadAttachment,
|
|
1470
|
+
isLoading,
|
|
1471
|
+
config,
|
|
1472
|
+
t,
|
|
1473
|
+
consumePendingPrefill,
|
|
1474
|
+
pendingPrefill
|
|
1475
|
+
} = useChat();
|
|
1447
1476
|
const reduced = useReducedMotion();
|
|
1448
1477
|
const [value, setValue] = useState6("");
|
|
1449
1478
|
const [attachments, setAttachments] = useState6([]);
|
|
@@ -1451,6 +1480,11 @@ function ChatInput() {
|
|
|
1451
1480
|
useEffect6(() => {
|
|
1452
1481
|
setCaptureSupported(canCaptureScreenshot());
|
|
1453
1482
|
}, []);
|
|
1483
|
+
useEffect6(() => {
|
|
1484
|
+
if (pendingPrefill === null) return;
|
|
1485
|
+
const text = consumePendingPrefill();
|
|
1486
|
+
if (text !== null) setValue(text);
|
|
1487
|
+
}, [pendingPrefill, consumePendingPrefill]);
|
|
1454
1488
|
useEffect6(() => {
|
|
1455
1489
|
return () => {
|
|
1456
1490
|
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
@@ -1792,8 +1826,263 @@ function ConfigError({ message, title }) {
|
|
|
1792
1826
|
/* @__PURE__ */ jsx9("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
|
|
1793
1827
|
] });
|
|
1794
1828
|
}
|
|
1829
|
+
function fieldKey(field) {
|
|
1830
|
+
if (field.kind === "name" || field.kind === "email" || field.kind === "phone") {
|
|
1831
|
+
return field.kind;
|
|
1832
|
+
}
|
|
1833
|
+
return field.key;
|
|
1834
|
+
}
|
|
1835
|
+
function fieldLabel(field) {
|
|
1836
|
+
if (field.kind === "name") return field.label ?? "Name";
|
|
1837
|
+
if (field.kind === "email") return field.label ?? "Email";
|
|
1838
|
+
if (field.kind === "phone") return field.label ?? "Phone";
|
|
1839
|
+
return field.label;
|
|
1840
|
+
}
|
|
1841
|
+
function validateField(field, value) {
|
|
1842
|
+
const required = "required" in field ? !!field.required : false;
|
|
1843
|
+
if (required && (value === void 0 || value === "" || value === false)) {
|
|
1844
|
+
return "Required";
|
|
1845
|
+
}
|
|
1846
|
+
if (field.kind === "email" && typeof value === "string" && value !== "") {
|
|
1847
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) return "Invalid email";
|
|
1848
|
+
}
|
|
1849
|
+
return null;
|
|
1850
|
+
}
|
|
1851
|
+
function PreChatFormView() {
|
|
1852
|
+
const { preChatForm, submitPreChatForm, cancelPreChatForm, config, t } = useChat();
|
|
1853
|
+
const [values, setValues] = useState7({});
|
|
1854
|
+
const [errors, setErrors] = useState7({});
|
|
1855
|
+
const [submitting, setSubmitting] = useState7(false);
|
|
1856
|
+
if (!preChatForm) return null;
|
|
1857
|
+
function setValue(key, value) {
|
|
1858
|
+
setValues((prev) => ({ ...prev, [key]: value }));
|
|
1859
|
+
setErrors((prev) => ({ ...prev, [key]: null }));
|
|
1860
|
+
}
|
|
1861
|
+
async function handleSubmit(e) {
|
|
1862
|
+
e.preventDefault();
|
|
1863
|
+
if (!preChatForm) return;
|
|
1864
|
+
const nextErrors = {};
|
|
1865
|
+
let hasError = false;
|
|
1866
|
+
for (const f of preChatForm.fields) {
|
|
1867
|
+
const k = fieldKey(f);
|
|
1868
|
+
const err = validateField(f, values[k]);
|
|
1869
|
+
nextErrors[k] = err;
|
|
1870
|
+
if (err) hasError = true;
|
|
1871
|
+
}
|
|
1872
|
+
setErrors(nextErrors);
|
|
1873
|
+
if (hasError) return;
|
|
1874
|
+
const submission = {};
|
|
1875
|
+
const properties = {};
|
|
1876
|
+
for (const f of preChatForm.fields) {
|
|
1877
|
+
const k = fieldKey(f);
|
|
1878
|
+
const v = values[k];
|
|
1879
|
+
if (v === void 0 || v === "") continue;
|
|
1880
|
+
if (f.kind === "name" && typeof v === "string") submission.name = v;
|
|
1881
|
+
else if (f.kind === "email" && typeof v === "string")
|
|
1882
|
+
submission.email = v;
|
|
1883
|
+
else if (f.kind === "phone" && typeof v === "string")
|
|
1884
|
+
submission.phone = v;
|
|
1885
|
+
else if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
1886
|
+
properties[k] = v;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (Object.keys(properties).length > 0) submission.properties = properties;
|
|
1890
|
+
setSubmitting(true);
|
|
1891
|
+
try {
|
|
1892
|
+
await submitPreChatForm(submission);
|
|
1893
|
+
} finally {
|
|
1894
|
+
setSubmitting(false);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
const containerStyle = {
|
|
1898
|
+
flex: 1,
|
|
1899
|
+
overflowY: "auto",
|
|
1900
|
+
padding: 20,
|
|
1901
|
+
display: "flex",
|
|
1902
|
+
flexDirection: "column",
|
|
1903
|
+
gap: 12,
|
|
1904
|
+
background: config.backgroundColor,
|
|
1905
|
+
color: config.textColor
|
|
1906
|
+
};
|
|
1907
|
+
const labelStyle = {
|
|
1908
|
+
fontSize: 13,
|
|
1909
|
+
fontWeight: 500,
|
|
1910
|
+
display: "flex",
|
|
1911
|
+
flexDirection: "column",
|
|
1912
|
+
gap: 4
|
|
1913
|
+
};
|
|
1914
|
+
const inputStyle = {
|
|
1915
|
+
width: "100%",
|
|
1916
|
+
border: "1px solid #d4d4d8",
|
|
1917
|
+
borderRadius: 8,
|
|
1918
|
+
padding: "8px 10px",
|
|
1919
|
+
fontSize: 14,
|
|
1920
|
+
background: "white",
|
|
1921
|
+
color: "#111",
|
|
1922
|
+
boxSizing: "border-box"
|
|
1923
|
+
};
|
|
1924
|
+
const errorStyle = { color: "#dc2626", fontSize: 12 };
|
|
1925
|
+
const buttonRowStyle = {
|
|
1926
|
+
display: "flex",
|
|
1927
|
+
gap: 8,
|
|
1928
|
+
marginTop: 8
|
|
1929
|
+
};
|
|
1930
|
+
const submitStyle = {
|
|
1931
|
+
flex: 1,
|
|
1932
|
+
background: config.primaryColor,
|
|
1933
|
+
color: "white",
|
|
1934
|
+
border: "none",
|
|
1935
|
+
borderRadius: 8,
|
|
1936
|
+
padding: "10px 14px",
|
|
1937
|
+
fontSize: 14,
|
|
1938
|
+
fontWeight: 600,
|
|
1939
|
+
cursor: submitting ? "not-allowed" : "pointer",
|
|
1940
|
+
opacity: submitting ? 0.7 : 1
|
|
1941
|
+
};
|
|
1942
|
+
const cancelStyle = {
|
|
1943
|
+
background: "transparent",
|
|
1944
|
+
color: config.textColor,
|
|
1945
|
+
border: "1px solid #d4d4d8",
|
|
1946
|
+
borderRadius: 8,
|
|
1947
|
+
padding: "10px 14px",
|
|
1948
|
+
fontSize: 14,
|
|
1949
|
+
cursor: "pointer"
|
|
1950
|
+
};
|
|
1951
|
+
return /* @__PURE__ */ jsxs6(
|
|
1952
|
+
"form",
|
|
1953
|
+
{
|
|
1954
|
+
style: containerStyle,
|
|
1955
|
+
onSubmit: handleSubmit,
|
|
1956
|
+
"data-customerhero-prechat-form": true,
|
|
1957
|
+
children: [
|
|
1958
|
+
preChatForm.title && /* @__PURE__ */ jsx9("h3", { style: { fontSize: 16, fontWeight: 600, margin: 0 }, children: preChatForm.title }),
|
|
1959
|
+
preChatForm.description && /* @__PURE__ */ jsx9("p", { style: { fontSize: 13, margin: 0, opacity: 0.8 }, children: preChatForm.description }),
|
|
1960
|
+
preChatForm.fields.map((field) => {
|
|
1961
|
+
const k = fieldKey(field);
|
|
1962
|
+
const v = values[k];
|
|
1963
|
+
const err = errors[k];
|
|
1964
|
+
const required = "required" in field ? !!field.required : false;
|
|
1965
|
+
const label = fieldLabel(field);
|
|
1966
|
+
if (field.kind === "textarea") {
|
|
1967
|
+
return /* @__PURE__ */ jsxs6("label", { style: labelStyle, children: [
|
|
1968
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
1969
|
+
label,
|
|
1970
|
+
required && " *"
|
|
1971
|
+
] }),
|
|
1972
|
+
/* @__PURE__ */ jsx9(
|
|
1973
|
+
"textarea",
|
|
1974
|
+
{
|
|
1975
|
+
style: { ...inputStyle, minHeight: 80, resize: "vertical" },
|
|
1976
|
+
value: v ?? "",
|
|
1977
|
+
maxLength: field.maxLength,
|
|
1978
|
+
onChange: (e) => setValue(k, e.target.value)
|
|
1979
|
+
}
|
|
1980
|
+
),
|
|
1981
|
+
err && /* @__PURE__ */ jsx9("span", { style: errorStyle, children: err })
|
|
1982
|
+
] }, k);
|
|
1983
|
+
}
|
|
1984
|
+
if (field.kind === "select") {
|
|
1985
|
+
return /* @__PURE__ */ jsxs6("label", { style: labelStyle, children: [
|
|
1986
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
1987
|
+
label,
|
|
1988
|
+
required && " *"
|
|
1989
|
+
] }),
|
|
1990
|
+
/* @__PURE__ */ jsxs6(
|
|
1991
|
+
"select",
|
|
1992
|
+
{
|
|
1993
|
+
style: inputStyle,
|
|
1994
|
+
value: v ?? "",
|
|
1995
|
+
onChange: (e) => setValue(k, e.target.value),
|
|
1996
|
+
children: [
|
|
1997
|
+
/* @__PURE__ */ jsx9("option", { value: "", children: "\u2014" }),
|
|
1998
|
+
field.options.map((opt) => /* @__PURE__ */ jsx9("option", { value: opt.value, children: opt.label }, opt.value))
|
|
1999
|
+
]
|
|
2000
|
+
}
|
|
2001
|
+
),
|
|
2002
|
+
err && /* @__PURE__ */ jsx9("span", { style: errorStyle, children: err })
|
|
2003
|
+
] }, k);
|
|
2004
|
+
}
|
|
2005
|
+
if (field.kind === "consent") {
|
|
2006
|
+
return /* @__PURE__ */ jsxs6(
|
|
2007
|
+
"label",
|
|
2008
|
+
{
|
|
2009
|
+
style: {
|
|
2010
|
+
...labelStyle,
|
|
2011
|
+
flexDirection: "row",
|
|
2012
|
+
alignItems: "flex-start",
|
|
2013
|
+
gap: 8
|
|
2014
|
+
},
|
|
2015
|
+
children: [
|
|
2016
|
+
/* @__PURE__ */ jsx9(
|
|
2017
|
+
"input",
|
|
2018
|
+
{
|
|
2019
|
+
type: "checkbox",
|
|
2020
|
+
checked: v === true,
|
|
2021
|
+
onChange: (e) => setValue(k, e.target.checked),
|
|
2022
|
+
style: { marginTop: 3 }
|
|
2023
|
+
}
|
|
2024
|
+
),
|
|
2025
|
+
/* @__PURE__ */ jsxs6("span", { style: { fontSize: 13, fontWeight: 400 }, children: [
|
|
2026
|
+
field.label,
|
|
2027
|
+
field.url && /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
2028
|
+
" ",
|
|
2029
|
+
/* @__PURE__ */ jsx9(
|
|
2030
|
+
"a",
|
|
2031
|
+
{
|
|
2032
|
+
href: field.url,
|
|
2033
|
+
target: "_blank",
|
|
2034
|
+
rel: "noopener noreferrer",
|
|
2035
|
+
style: { color: config.primaryColor },
|
|
2036
|
+
children: "\u2197"
|
|
2037
|
+
}
|
|
2038
|
+
)
|
|
2039
|
+
] })
|
|
2040
|
+
] }),
|
|
2041
|
+
err && /* @__PURE__ */ jsx9("span", { style: errorStyle, children: err })
|
|
2042
|
+
]
|
|
2043
|
+
},
|
|
2044
|
+
k
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
const inputType = field.kind === "email" ? "email" : field.kind === "phone" ? "tel" : "text";
|
|
2048
|
+
const maxLength = field.kind === "text" ? field.maxLength : void 0;
|
|
2049
|
+
return /* @__PURE__ */ jsxs6("label", { style: labelStyle, children: [
|
|
2050
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
2051
|
+
label,
|
|
2052
|
+
required && " *"
|
|
2053
|
+
] }),
|
|
2054
|
+
/* @__PURE__ */ jsx9(
|
|
2055
|
+
"input",
|
|
2056
|
+
{
|
|
2057
|
+
type: inputType,
|
|
2058
|
+
style: inputStyle,
|
|
2059
|
+
value: v ?? "",
|
|
2060
|
+
maxLength,
|
|
2061
|
+
onChange: (e) => setValue(k, e.target.value)
|
|
2062
|
+
}
|
|
2063
|
+
),
|
|
2064
|
+
err && /* @__PURE__ */ jsx9("span", { style: errorStyle, children: err })
|
|
2065
|
+
] }, k);
|
|
2066
|
+
}),
|
|
2067
|
+
/* @__PURE__ */ jsxs6("div", { style: buttonRowStyle, children: [
|
|
2068
|
+
/* @__PURE__ */ jsx9(
|
|
2069
|
+
"button",
|
|
2070
|
+
{
|
|
2071
|
+
type: "button",
|
|
2072
|
+
onClick: cancelPreChatForm,
|
|
2073
|
+
style: cancelStyle,
|
|
2074
|
+
disabled: submitting,
|
|
2075
|
+
children: t("action_cancel")
|
|
2076
|
+
}
|
|
2077
|
+
),
|
|
2078
|
+
/* @__PURE__ */ jsx9("button", { type: "submit", style: submitStyle, disabled: submitting, children: preChatForm.submitLabel })
|
|
2079
|
+
] })
|
|
2080
|
+
]
|
|
2081
|
+
}
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
1795
2084
|
function ChatWindow() {
|
|
1796
|
-
const { isOpen, config, configError, t, isRtl } = useChat();
|
|
2085
|
+
const { isOpen, config, configError, t, isRtl, preChatFormVisible } = useChat();
|
|
1797
2086
|
const reduced = useReducedMotion();
|
|
1798
2087
|
const [visible, setVisible] = useState7(false);
|
|
1799
2088
|
const [shouldRender, setShouldRender] = useState7(false);
|
|
@@ -1848,7 +2137,7 @@ function ChatWindow() {
|
|
|
1848
2137
|
};
|
|
1849
2138
|
return /* @__PURE__ */ jsxs6("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
|
|
1850
2139
|
/* @__PURE__ */ jsx9(ChatHeader, {}),
|
|
1851
|
-
configError ? /* @__PURE__ */ jsx9(ConfigError, { title: t("unable_to_load"), message: configError }) : /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
2140
|
+
configError ? /* @__PURE__ */ jsx9(ConfigError, { title: t("unable_to_load"), message: configError }) : preChatFormVisible ? /* @__PURE__ */ jsx9(PreChatFormView, {}) : /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
1852
2141
|
/* @__PURE__ */ jsx9(ChatMessages, {}),
|
|
1853
2142
|
/* @__PURE__ */ jsx9(ChatSuggestions, {}),
|
|
1854
2143
|
/* @__PURE__ */ jsx9(ChatInput, {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@customerhero/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "React components for embedding the CustomerHero chat widget.",
|
|
6
6
|
"keywords": [
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"test": "vitest run"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@customerhero/js": "^
|
|
57
|
+
"@customerhero/js": "^2.0.0",
|
|
58
58
|
"react": ">=18"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@customerhero/js": "^
|
|
61
|
+
"@customerhero/js": "^2.0.0",
|
|
62
62
|
"@testing-library/react": "^16.1.0",
|
|
63
63
|
"@types/react": "^19.0.0",
|
|
64
64
|
"@types/react-dom": "^19.0.0",
|