@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 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 { sendMessage, uploadAttachment, isLoading, config, t } = useChat();
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 { sendMessage, uploadAttachment, isLoading, config, t } = useChat();
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": "1.0.1",
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": "^1.0.1",
57
+ "@customerhero/js": "^2.0.0",
58
58
  "react": ">=18"
59
59
  },
60
60
  "devDependencies": {
61
- "@customerhero/js": "^1.0.1",
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",