@sevenfold/setto-client 0.21.0 → 0.22.1

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.
@@ -22060,11 +22060,15 @@ function contactTeam() {
22060
22060
  function SettoChat() {
22061
22061
  const { config } = useSetto();
22062
22062
  const { i18n } = useTranslation();
22063
+ const apiUrl = config.apiUrl.replace(/\/+$/, "");
22063
22064
  const [settings, setSettings] = useState(null);
22064
22065
  const [open, setOpen] = useState(false);
22065
22066
  const [messages, setMessages] = useState([]);
22066
22067
  const [draft, setDraft] = useState("");
22067
22068
  const [sending, setSending] = useState(false);
22069
+ const [leadOpen, setLeadOpen] = useState(false);
22070
+ const [leadStatus, setLeadStatus] = useState("idle");
22071
+ const [lead, setLead] = useState({ name: "", email: "", phone: "", interest: "", consent: false });
22068
22072
  const conversationId = useRef(null);
22069
22073
  const visitor = useRef(null);
22070
22074
  const logRef = useRef(null);
@@ -22073,7 +22077,7 @@ function SettoChat() {
22073
22077
  useEffect(() => {
22074
22078
  if (window.location.pathname.startsWith("/setto")) return;
22075
22079
  let cancelled = false;
22076
- fetch(`${config.apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/settings/public`, {
22080
+ fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/settings/public`, {
22077
22081
  headers: { Accept: "application/json" }
22078
22082
  }).then(async (response) => response.ok ? await response.json() : null).then((next) => {
22079
22083
  if (!cancelled && next?.enabled) setSettings(next);
@@ -22082,7 +22086,7 @@ function SettoChat() {
22082
22086
  return () => {
22083
22087
  cancelled = true;
22084
22088
  };
22085
- }, [config.apiUrl, config.siteId]);
22089
+ }, [apiUrl, config.siteId]);
22086
22090
  useEffect(() => {
22087
22091
  if (!open || messages.length > 0 || !settings) return;
22088
22092
  setMessages([{ role: "assistant", content: intro(i18n.language, settings.displayName) }]);
@@ -22101,6 +22105,18 @@ function SettoChat() {
22101
22105
  send: "Send",
22102
22106
  sending: "Svarer …",
22103
22107
  contact: "Kontakt teamet",
22108
+ followUp: "Be om oppfølging",
22109
+ followUpTitle: "La teamet følge opp",
22110
+ followUpDetail: "Legg igjen kontaktinformasjon hvis du vil at teamet skal følge opp denne samtalen.",
22111
+ name: "Navn",
22112
+ email: "E-post",
22113
+ phone: "Telefon",
22114
+ interest: "Hva vil du ha hjelp med?",
22115
+ consent: "Ja, teamet kan bruke opplysningene mine til å følge opp denne henvendelsen.",
22116
+ leadSend: "Send forespørsel",
22117
+ leadSent: "Takk — forespørselen er sendt til teamet.",
22118
+ leadFailed: "Kunne ikke lagre forespørselen. Prøv igjen eller kontakt teamet direkte.",
22119
+ leadConversation: "Still et spørsmål først, så kan vi knytte oppfølgingen til samtalen.",
22104
22120
  failed: "Jeg får ikke svart akkurat nå. Kontakt gjerne teamet direkte."
22105
22121
  } : {
22106
22122
  open: `Chat with ${settings.displayName}`,
@@ -22111,6 +22127,18 @@ function SettoChat() {
22111
22127
  send: "Send",
22112
22128
  sending: "Replying …",
22113
22129
  contact: "Contact the team",
22130
+ followUp: "Ask for follow-up",
22131
+ followUpTitle: "Let the team follow up",
22132
+ followUpDetail: "Leave contact details if you want the team to follow up on this conversation.",
22133
+ name: "Name",
22134
+ email: "Email",
22135
+ phone: "Phone",
22136
+ interest: "What would you like help with?",
22137
+ consent: "Yes, the team may use my details to follow up on this enquiry.",
22138
+ leadSend: "Send request",
22139
+ leadSent: "Thanks — your request has been sent to the team.",
22140
+ leadFailed: "We could not save your request. Please try again or contact the team directly.",
22141
+ leadConversation: "Ask a question first so we can link the follow-up to this conversation.",
22114
22142
  failed: "I cannot reply right now. Please contact the team directly."
22115
22143
  };
22116
22144
  const submit = async (event) => {
@@ -22123,7 +22151,7 @@ function SettoChat() {
22123
22151
  visitor.current ??= visitorId();
22124
22152
  try {
22125
22153
  const response = await fetch(
22126
- `${config.apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/messages`,
22154
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/messages`,
22127
22155
  {
22128
22156
  method: "POST",
22129
22157
  headers: { "Content-Type": "application/json", Accept: "text/plain, application/json" },
@@ -22164,6 +22192,34 @@ function SettoChat() {
22164
22192
  setSending(false);
22165
22193
  }
22166
22194
  };
22195
+ const submitLead = async (event) => {
22196
+ event.preventDefault();
22197
+ if (leadStatus === "sending") return;
22198
+ if (!conversationId.current || !visitor.current) {
22199
+ setLeadStatus("failed");
22200
+ return;
22201
+ }
22202
+ setLeadStatus("sending");
22203
+ try {
22204
+ const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/leads`, {
22205
+ method: "POST",
22206
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
22207
+ body: JSON.stringify({
22208
+ conversationId: conversationId.current,
22209
+ visitorId: visitor.current,
22210
+ consent: lead.consent,
22211
+ name: lead.name.trim() || void 0,
22212
+ email: lead.email.trim() || void 0,
22213
+ phone: lead.phone.trim() || void 0,
22214
+ interest: lead.interest.trim() || void 0
22215
+ })
22216
+ });
22217
+ if (!response.ok) throw new Error(`lead_failed_${response.status}`);
22218
+ setLeadStatus("sent");
22219
+ } catch {
22220
+ setLeadStatus("failed");
22221
+ }
22222
+ };
22167
22223
  return /* @__PURE__ */ jsxs("aside", { className: "setto-chat", "aria-label": labels.title, "data-setto-ui": true, children: [
22168
22224
  open ? /* @__PURE__ */ jsxs("section", { className: "setto-chat__panel", role: "dialog", "aria-modal": "false", "aria-labelledby": `${formId}-title`, children: [
22169
22225
  /* @__PURE__ */ jsxs("header", { className: "setto-chat__header", children: [
@@ -22175,11 +22231,46 @@ function SettoChat() {
22175
22231
  /* @__PURE__ */ jsx("button", { className: "setto-chat__close", type: "button", onClick: () => setOpen(false), "aria-label": labels.close, children: "×" })
22176
22232
  ] }),
22177
22233
  /* @__PURE__ */ jsx("div", { className: "setto-chat__log", ref: logRef, "aria-live": "polite", children: messages.map((item, index) => /* @__PURE__ */ jsx("div", { className: `setto-chat__message setto-chat__message--${item.role}`, children: item.content || /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", children: "···" }) }, `${item.role}-${index}`)) }),
22178
- /* @__PURE__ */ jsx("div", { className: "setto-chat__contact", children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: contactTeam, children: [
22179
- labels.contact,
22180
- " ",
22181
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "↗" })
22182
- ] }) }),
22234
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__contact", children: [
22235
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: contactTeam, children: [
22236
+ labels.contact,
22237
+ " ",
22238
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "↗" })
22239
+ ] }),
22240
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
22241
+ setLeadOpen((value) => !value);
22242
+ setLeadStatus("idle");
22243
+ }, children: labels.followUp })
22244
+ ] }),
22245
+ leadOpen ? /* @__PURE__ */ jsxs("form", { className: "setto-chat__lead", onSubmit: submitLead, children: [
22246
+ /* @__PURE__ */ jsx("strong", { children: labels.followUpTitle }),
22247
+ /* @__PURE__ */ jsx("p", { children: labels.followUpDetail }),
22248
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__lead-grid", children: [
22249
+ /* @__PURE__ */ jsxs("label", { children: [
22250
+ labels.name,
22251
+ /* @__PURE__ */ jsx("input", { value: lead.name, maxLength: 160, onChange: (event) => setLead((current) => ({ ...current, name: event.target.value })) })
22252
+ ] }),
22253
+ /* @__PURE__ */ jsxs("label", { children: [
22254
+ labels.email,
22255
+ /* @__PURE__ */ jsx("input", { type: "email", value: lead.email, maxLength: 320, onChange: (event) => setLead((current) => ({ ...current, email: event.target.value })) })
22256
+ ] }),
22257
+ /* @__PURE__ */ jsxs("label", { children: [
22258
+ labels.phone,
22259
+ /* @__PURE__ */ jsx("input", { type: "tel", value: lead.phone, maxLength: 64, onChange: (event) => setLead((current) => ({ ...current, phone: event.target.value })) })
22260
+ ] }),
22261
+ /* @__PURE__ */ jsxs("label", { children: [
22262
+ labels.interest,
22263
+ /* @__PURE__ */ jsx("input", { value: lead.interest, maxLength: 500, onChange: (event) => setLead((current) => ({ ...current, interest: event.target.value })) })
22264
+ ] })
22265
+ ] }),
22266
+ /* @__PURE__ */ jsxs("label", { className: "setto-chat__consent", children: [
22267
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: lead.consent, onChange: (event) => setLead((current) => ({ ...current, consent: event.target.checked })) }),
22268
+ labels.consent
22269
+ ] }),
22270
+ leadStatus === "sent" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-success", children: labels.leadSent }) : null,
22271
+ leadStatus === "failed" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-error", children: conversationId.current ? labels.leadFailed : labels.leadConversation }) : null,
22272
+ leadStatus !== "sent" ? /* @__PURE__ */ jsx("button", { type: "submit", disabled: leadStatus === "sending" || !lead.consent || !lead.email.trim() && !lead.phone.trim(), children: leadStatus === "sending" ? labels.sending : labels.leadSend }) : null
22273
+ ] }) : null,
22183
22274
  /* @__PURE__ */ jsxs("form", { className: "setto-chat__composer", onSubmit: submit, children: [
22184
22275
  /* @__PURE__ */ jsx("label", { className: "setto-chat__sr", htmlFor: `${formId}-input`, children: labels.placeholder }),
22185
22276
  /* @__PURE__ */ jsx(
@@ -22229,7 +22320,7 @@ function useChatStyles() {
22229
22320
  .setto-chat__launcher { border: 0; min-height: 54px; max-width: min(290px, calc(100vw - 32px)); border-radius: 999px; padding: 8px 18px 8px 9px; display: flex; align-items: center; gap: 10px; background: #251d16; color: #fff9ef; box-shadow: 0 14px 40px rgba(35,29,23,.28); font: 600 14px/1 Archivo, ui-sans-serif, sans-serif; cursor: pointer; transition: transform .18s ease, box-shadow .18s ease; }
22230
22321
  .setto-chat__launcher:hover { transform: translateY(-2px); box-shadow: 0 18px 46px rgba(35,29,23,.36); }
22231
22322
  .setto-chat__launcher-mark { width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center; background: #df6540; color: #fff8eb; font-size: 18px; }
22232
- .setto-chat__panel { width: min(390px, calc(100vw - 32px)); height: min(590px, calc(100dvh - 104px)); margin-bottom: 12px; overflow: hidden; display: grid; grid-template-rows: auto 1fr auto auto; border: 1px solid rgba(37,29,22,.15); border-radius: 20px; background: #fffaf1; box-shadow: 0 22px 70px rgba(35,29,23,.3); }
22323
+ .setto-chat__panel { width: min(390px, calc(100vw - 32px)); height: min(590px, calc(100dvh - 104px)); margin-bottom: 12px; overflow: hidden; display: grid; grid-template-rows: auto 1fr auto auto auto; border: 1px solid rgba(37,29,22,.15); border-radius: 20px; background: #fffaf1; box-shadow: 0 22px 70px rgba(35,29,23,.3); }
22233
22324
  .setto-chat__header { padding: 20px 20px 16px; display: flex; justify-content: space-between; gap: 14px; border-bottom: 1px solid #eadfce; background: radial-gradient(circle at 0 0, #f4d4a9, transparent 52%), #fffaf1; }
22234
22325
  .setto-chat__eyebrow { margin: 0 0 4px; color: #b54628; font-size: 10px; font-weight: 700; letter-spacing: .14em; }
22235
22326
  .setto-chat__header h2 { margin: 0; color: #211a14; font-family: "Instrument Serif", Georgia, serif; font-size: 29px; font-weight: 400; line-height: 1; }
@@ -22241,15 +22332,25 @@ function useChatStyles() {
22241
22332
  .setto-chat__message--visitor { align-self: flex-end; background: #2b221b; color: #fffaf1; border-bottom-right-radius: 3px; }
22242
22333
  .setto-chat__typing { display: inline-block; min-width: 24px; animation: setto-chat-pulse 1.1s ease-in-out infinite; letter-spacing: 2px; }
22243
22334
  @keyframes setto-chat-pulse { 50% { opacity: .3; } }
22244
- .setto-chat__contact { padding: 0 18px 12px; }
22335
+ .setto-chat__contact { padding: 0 18px 12px; display: flex; gap: 14px; flex-wrap: wrap; }
22245
22336
  .setto-chat__contact button { border: 0; padding: 0; background: transparent; color: #b54628; font: 600 12px/1.4 Archivo, ui-sans-serif, sans-serif; cursor: pointer; }
22337
+ .setto-chat__lead { display: grid; gap: 8px; max-height: 255px; overflow: auto; padding: 12px 18px; border-top: 1px solid #eadfce; background: #fff7ed; font-size: 12px; line-height: 1.4; }
22338
+ .setto-chat__lead p { margin: 0; color: #70665a; }
22339
+ .setto-chat__lead-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
22340
+ .setto-chat__lead label { display: grid; gap: 3px; color: #5f5347; font-weight: 600; }
22341
+ .setto-chat__lead input:not([type="checkbox"]) { width: 100%; border: 1px solid #d8ccba; border-radius: 7px; background: #fffdf8; color: #231d17; padding: 7px; font: 13px Archivo, ui-sans-serif, sans-serif; }
22342
+ .setto-chat__lead .setto-chat__consent { grid-template-columns: auto 1fr; align-items: start; font-weight: 400; }
22343
+ .setto-chat__lead button { justify-self: start; min-height: 34px; border: 0; border-radius: 8px; padding: 0 12px; background: #b54628; color: #fffaf1; font: 600 12px Archivo, ui-sans-serif, sans-serif; cursor: pointer; }
22344
+ .setto-chat__lead button:disabled { opacity: .5; cursor: default; }
22345
+ .setto-chat__lead-success { color: #30683d !important; font-weight: 600; }
22346
+ .setto-chat__lead-error { color: #9b2f1e !important; font-weight: 600; }
22246
22347
  .setto-chat__composer { padding: 12px; display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 8px; border-top: 1px solid #eadfce; background: #fffdf8; }
22247
22348
  .setto-chat__composer textarea { width: 100%; max-height: 100px; resize: none; border: 1px solid #d8ccba; border-radius: 10px; background: #fffaf1; color: #231d17; padding: 10px; font: 14px/1.35 Archivo, ui-sans-serif, sans-serif; outline: none; }
22248
22349
  .setto-chat__composer textarea:focus { border-color: #b54628; box-shadow: 0 0 0 3px rgba(181,70,40,.12); }
22249
22350
  .setto-chat__composer button { min-height: 40px; border: 0; border-radius: 9px; padding: 0 13px; background: #b54628; color: #fffaf1; font: 600 13px Archivo, ui-sans-serif, sans-serif; cursor: pointer; }
22250
22351
  .setto-chat__composer button:disabled { opacity: .5; cursor: default; }
22251
22352
  .setto-chat__sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
22252
- @media (max-width: 480px) { .setto-chat { right: 12px; bottom: max(12px, env(safe-area-inset-bottom)); } .setto-chat__panel { width: calc(100vw - 24px); height: min(610px, calc(100dvh - 88px)); } .setto-chat__launcher { min-height: 50px; } }
22353
+ @media (max-width: 480px) { .setto-chat { right: 12px; bottom: max(12px, env(safe-area-inset-bottom)); } .setto-chat__panel { width: calc(100vw - 24px); height: min(610px, calc(100dvh - 88px)); } .setto-chat__launcher { min-height: 50px; } .setto-chat__lead-grid { grid-template-columns: 1fr; } }
22253
22354
  @media (prefers-reduced-motion: reduce) { .setto-chat *, .setto-chat *::before, .setto-chat *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } }
22254
22355
  `;
22255
22356
  document.head.appendChild(style);