glitchgrab 1.59.0 → 1.59.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.
package/dist/index.mjs CHANGED
@@ -8558,6 +8558,69 @@ function getTypeIcon(type, color, size = 24) {
8558
8558
 
8559
8559
  // src/booking-dialog.tsx
8560
8560
  import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
8561
+
8562
+ // src/phone.ts
8563
+ var COUNTRIES = [
8564
+ { iso: "IN", name: "India", dial: "91" },
8565
+ { iso: "AE", name: "UAE", dial: "971" },
8566
+ { iso: "US", name: "US / Canada", dial: "1" },
8567
+ { iso: "GB", name: "UK", dial: "44" },
8568
+ { iso: "SG", name: "Singapore", dial: "65" },
8569
+ { iso: "AU", name: "Australia", dial: "61" },
8570
+ { iso: "SA", name: "Saudi Arabia", dial: "966" },
8571
+ { iso: "QA", name: "Qatar", dial: "974" },
8572
+ { iso: "NP", name: "Nepal", dial: "977" },
8573
+ { iso: "BD", name: "Bangladesh", dial: "880" },
8574
+ { iso: "LK", name: "Sri Lanka", dial: "94" },
8575
+ { iso: "DE", name: "Germany", dial: "49" }
8576
+ ];
8577
+ var ZONE_COUNTRY = {
8578
+ "Asia/Kolkata": "91",
8579
+ "Asia/Calcutta": "91",
8580
+ "Asia/Dubai": "971",
8581
+ "Europe/London": "44",
8582
+ "Asia/Singapore": "65",
8583
+ "Asia/Riyadh": "966",
8584
+ "Asia/Qatar": "974",
8585
+ "Asia/Kathmandu": "977",
8586
+ "Asia/Katmandu": "977",
8587
+ "Asia/Dhaka": "880",
8588
+ "Asia/Colombo": "94",
8589
+ "Europe/Berlin": "49"
8590
+ };
8591
+ function defaultDialForZone(zone) {
8592
+ if (ZONE_COUNTRY[zone]) return ZONE_COUNTRY[zone];
8593
+ if (zone.startsWith("America/")) return "1";
8594
+ if (zone.startsWith("Australia/")) return "61";
8595
+ return "91";
8596
+ }
8597
+ function splitTypedPhone(typed, dial) {
8598
+ const trimmed = typed.trim();
8599
+ const digits = trimmed.replace(/\D/g, "");
8600
+ if (trimmed.startsWith("+")) {
8601
+ const match = [...COUNTRIES].sort((a, b) => b.dial.length - a.dial.length).find((c) => digits.startsWith(c.dial));
8602
+ if (match) return { dial: match.dial, national: digits.slice(match.dial.length) };
8603
+ }
8604
+ if (dial === "91" && digits.length === 12 && digits.startsWith("91")) return { dial, national: digits.slice(2) };
8605
+ if (dial === "91" && digits.length === 11 && digits.startsWith("0")) return { dial, national: digits.slice(1) };
8606
+ return { dial, national: digits };
8607
+ }
8608
+ function phoneProblem(dial, national) {
8609
+ const digits = national.replace(/\D/g, "");
8610
+ if (!digits) return "Enter your mobile number";
8611
+ if (dial === "91") {
8612
+ if (digits.length !== 10) return `An Indian mobile has 10 digits \u2014 this has ${digits.length}`;
8613
+ if (!/^[6-9]/.test(digits)) return "An Indian mobile starts with 6, 7, 8 or 9";
8614
+ return null;
8615
+ }
8616
+ if (digits.length < 6 || digits.length > 12) return "Check the number \u2014 it looks too short or too long";
8617
+ return null;
8618
+ }
8619
+ function toE164(dial, national) {
8620
+ return `+${dial}${national.replace(/\D/g, "")}`;
8621
+ }
8622
+
8623
+ // src/booking-dialog.tsx
8561
8624
  import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8562
8625
  function readQuestions(value) {
8563
8626
  if (!Array.isArray(value)) return [];
@@ -8700,7 +8763,9 @@ function BookingDialog({
8700
8763
  open,
8701
8764
  onClose,
8702
8765
  preview = false,
8703
- availabilityUrl
8766
+ availabilityUrl,
8767
+ intro,
8768
+ onBooked
8704
8769
  }) {
8705
8770
  var _a, _b, _c, _d, _e;
8706
8771
  const wide = useMediaQuery(WIDE_QUERY, true);
@@ -8723,6 +8788,8 @@ function BookingDialog({
8723
8788
  const [name, setName] = useState5("");
8724
8789
  const [email, setEmail] = useState5("");
8725
8790
  const [phone, setPhone] = useState5("");
8791
+ const [dial, setDial] = useState5(() => defaultDialForZone(localZone()));
8792
+ const [phoneTouched, setPhoneTouched] = useState5(false);
8726
8793
  const [note, setNote] = useState5("");
8727
8794
  const [questions, setQuestions] = useState5([]);
8728
8795
  const [answers, setAnswers] = useState5({});
@@ -8833,8 +8900,12 @@ function BookingDialog({
8833
8900
  const view = step !== "slots" ? step : !pageLoaded ? "loading" : questions.length > 0 && !asked ? "questions" : "slots";
8834
8901
  const roomy = step === "slots";
8835
8902
  async function startBooking() {
8836
- var _a2, _b2;
8903
+ var _a2, _b2, _c2;
8837
8904
  if (!selected) return;
8905
+ if (phoneError) {
8906
+ setPhoneTouched(true);
8907
+ return;
8908
+ }
8838
8909
  if (preview) {
8839
8910
  setPreviewEnded(true);
8840
8911
  return;
@@ -8848,7 +8919,7 @@ function BookingDialog({
8848
8919
  body: JSON.stringify({
8849
8920
  name,
8850
8921
  email,
8851
- phone,
8922
+ phone: toE164(dial, phone),
8852
8923
  note,
8853
8924
  answers: Object.fromEntries(given.map((q) => [q.id, q.answer])),
8854
8925
  startsAt: selected.startsAt,
@@ -8867,6 +8938,7 @@ function BookingDialog({
8867
8938
  if (json.data.confirmed) {
8868
8939
  setMeetUrl((_b2 = json.data.meetUrl) != null ? _b2 : null);
8869
8940
  setStep("done");
8941
+ reportBooked(json.data.bookingId, (_c2 = json.data.meetUrl) != null ? _c2 : null);
8870
8942
  return;
8871
8943
  }
8872
8944
  setBookingId(json.data.bookingId);
@@ -8877,8 +8949,16 @@ function BookingDialog({
8877
8949
  setBusy(false);
8878
8950
  }
8879
8951
  }
8952
+ function reportBooked(id, meet) {
8953
+ if (!onBooked || !id || !selected) return;
8954
+ try {
8955
+ onBooked({ bookingId: id, startsAt: selected.startsAt, name, email, phone: toE164(dial, phone), meetUrl: meet });
8956
+ } catch (e) {
8957
+ console.error("[glitchgrab] onBooked threw", e);
8958
+ }
8959
+ }
8880
8960
  async function confirmBooking() {
8881
- var _a2;
8961
+ var _a2, _b2;
8882
8962
  if (!bookingId) return;
8883
8963
  setBusy(true);
8884
8964
  setError(null);
@@ -8895,13 +8975,15 @@ function BookingDialog({
8895
8975
  }
8896
8976
  setMeetUrl(json.data.meetUrl);
8897
8977
  setStep("done");
8978
+ reportBooked(bookingId, (_b2 = json.data.meetUrl) != null ? _b2 : null);
8898
8979
  } catch (e) {
8899
8980
  setError("Couldn't reach the booking service");
8900
8981
  } finally {
8901
8982
  setBusy(false);
8902
8983
  }
8903
8984
  }
8904
- const canSubmitDetails = name.trim() && email.includes("@") && phone.replace(/\D/g, "").length >= 8;
8985
+ const phoneError = phoneProblem(dial, phone);
8986
+ const canSubmitDetails = Boolean(name.trim() && email.includes("@"));
8905
8987
  const selectedEnd = selected ? (_e = selected.endsAt) != null ? _e : new Date(new Date(selected.startsAt).getTime() + page.slotMinutes * 6e4).toISOString() : null;
8906
8988
  const monthDate = new Date(keyToUtc(`${month}-01`));
8907
8989
  const muted = { color: t.muted };
@@ -9048,6 +9130,14 @@ function BookingDialog({
9048
9130
  children: "Preview \u2014 nothing is booked"
9049
9131
  }
9050
9132
  ),
9133
+ intro && /* @__PURE__ */ jsx6(
9134
+ "div",
9135
+ {
9136
+ "data-glitchgrab-booking-intro": true,
9137
+ style: { fontSize: 13, fontWeight: 600, lineHeight: 1.45, paddingRight: 32, color: t.ink, overflowWrap: "anywhere" },
9138
+ children: intro
9139
+ }
9140
+ ),
9051
9141
  /* @__PURE__ */ jsx6("div", { style: { fontSize: 20, fontWeight: 600, lineHeight: 1.25, paddingRight: 32, overflowWrap: "anywhere" }, children: page.title }),
9052
9142
  page.description && /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, lineHeight: 1.5, overflowWrap: "anywhere", ...muted }, children: page.description }),
9053
9143
  /* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4, fontSize: 14 }, children: [
@@ -9490,14 +9580,17 @@ function BookingDialog({
9490
9580
  /* @__PURE__ */ jsx6(Field, { t, label: "Your name", value: name, onChange: setName, autoComplete: "name" }),
9491
9581
  /* @__PURE__ */ jsx6(Field, { t, label: "Email", value: email, onChange: setEmail, type: "email", autoComplete: "email" }),
9492
9582
  /* @__PURE__ */ jsx6(
9493
- Field,
9583
+ PhoneField,
9494
9584
  {
9495
9585
  t,
9496
- label: "Mobile number",
9497
- value: phone,
9498
- onChange: setPhone,
9499
- type: "tel",
9500
- autoComplete: "tel",
9586
+ dial,
9587
+ national: phone,
9588
+ onChange: (nextDial, national) => {
9589
+ setDial(nextDial);
9590
+ setPhone(national);
9591
+ },
9592
+ onBlur: () => setPhoneTouched(true),
9593
+ error: phoneTouched ? phoneError : null,
9501
9594
  hint: requireOtp ? "We send a code here to confirm, then a reminder before the call." : "We send a reminder here before the call."
9502
9595
  }
9503
9596
  ),
@@ -9785,6 +9878,64 @@ function Field({
9785
9878
  hint && /* @__PURE__ */ jsx6("div", { style: { marginTop: 6, fontSize: 12, fontWeight: 400, color: t.muted }, children: hint })
9786
9879
  ] });
9787
9880
  }
9881
+ function PhoneField({
9882
+ t,
9883
+ dial,
9884
+ national,
9885
+ onChange,
9886
+ onBlur,
9887
+ error,
9888
+ hint
9889
+ }) {
9890
+ const box = {
9891
+ boxSizing: "border-box",
9892
+ padding: "9px 12px",
9893
+ background: t.input,
9894
+ color: t.text,
9895
+ border: `1px solid ${error ? t.danger : t.border}`,
9896
+ borderRadius: 8,
9897
+ fontFamily: "inherit",
9898
+ fontSize: 14,
9899
+ outline: "none"
9900
+ };
9901
+ return /* @__PURE__ */ jsxs6("label", { style: { display: "block", fontSize: 13, fontWeight: 500, color: t.text }, children: [
9902
+ "Mobile number",
9903
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8, marginTop: 6 }, children: [
9904
+ /* @__PURE__ */ jsx6(
9905
+ "select",
9906
+ {
9907
+ "aria-label": "Country code",
9908
+ value: dial,
9909
+ onChange: (e) => onChange(e.target.value, national),
9910
+ style: { ...box, flex: "0 0 auto", paddingRight: 8, cursor: "pointer" },
9911
+ children: COUNTRIES.map((c) => /* @__PURE__ */ jsxs6("option", { value: c.dial, children: [
9912
+ c.iso,
9913
+ " +",
9914
+ c.dial
9915
+ ] }, c.iso))
9916
+ }
9917
+ ),
9918
+ /* @__PURE__ */ jsx6(
9919
+ "input",
9920
+ {
9921
+ type: "tel",
9922
+ autoComplete: "tel",
9923
+ inputMode: "numeric",
9924
+ placeholder: dial === "91" ? "98765 43210" : "Mobile number",
9925
+ value: national,
9926
+ "aria-invalid": Boolean(error),
9927
+ onChange: (e) => {
9928
+ const next = splitTypedPhone(e.target.value, dial);
9929
+ onChange(next.dial, next.national);
9930
+ },
9931
+ onBlur,
9932
+ style: { ...box, flex: "1 1 auto", minWidth: 0, width: "100%" }
9933
+ }
9934
+ )
9935
+ ] }),
9936
+ error ? /* @__PURE__ */ jsx6("div", { role: "alert", style: { marginTop: 6, fontSize: 12, fontWeight: 500, color: t.danger }, children: error }) : /* @__PURE__ */ jsx6("div", { style: { marginTop: 6, fontSize: 12, fontWeight: 400, color: t.muted }, children: hint })
9937
+ ] });
9938
+ }
9788
9939
 
9789
9940
  // src/rejection.ts
9790
9941
  var DETAILS_MAX_LENGTH = 1e3;
@@ -10375,8 +10526,18 @@ function GlitchgrabProviderInner({
10375
10526
  const [signedUsers, setSignedUsers] = useState6("OFF");
10376
10527
  const aiBlockedUnsigned = signedUsers === "ENFORCE" && !signature;
10377
10528
  const [booking, setBooking] = useState6("closed");
10378
- const openBookingDialog = useCallback3(() => setBooking("live"), []);
10379
- const openBookingPreview = useCallback3(() => setBooking("preview"), []);
10529
+ const [bookingOptions, setBookingOptions] = useState6({});
10530
+ const openBookingDialog = useCallback3((options) => {
10531
+ setBookingOptions({
10532
+ intro: typeof (options == null ? void 0 : options.intro) === "string" ? options.intro : void 0,
10533
+ onBooked: typeof (options == null ? void 0 : options.onBooked) === "function" ? options.onBooked : void 0
10534
+ });
10535
+ setBooking("live");
10536
+ }, []);
10537
+ const openBookingPreview = useCallback3(() => {
10538
+ setBookingOptions({});
10539
+ setBooking("preview");
10540
+ }, []);
10380
10541
  useEffect6(() => {
10381
10542
  let cancelled = false;
10382
10543
  fetch(`${baseUrl}/api/v1/sdk/project`, {
@@ -10475,6 +10636,8 @@ function GlitchgrabProviderInner({
10475
10636
  baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
10476
10637
  open: booking !== "closed",
10477
10638
  preview: booking === "preview",
10639
+ intro: bookingOptions.intro,
10640
+ onBooked: bookingOptions.onBooked,
10478
10641
  onClose: () => setBooking("closed")
10479
10642
  }
10480
10643
  ),
@@ -10616,12 +10779,13 @@ function BookDemoButton({
10616
10779
  children
10617
10780
  }) {
10618
10781
  const { openBookingDialog } = useGlitchgrab();
10619
- if (children) return /* @__PURE__ */ jsx9(Fragment8, { children: children({ onClick: openBookingDialog }) });
10782
+ const open = () => openBookingDialog();
10783
+ if (children) return /* @__PURE__ */ jsx9(Fragment8, { children: children({ onClick: open }) });
10620
10784
  return /* @__PURE__ */ jsx9(
10621
10785
  "button",
10622
10786
  {
10623
10787
  type: "button",
10624
- onClick: openBookingDialog,
10788
+ onClick: open,
10625
10789
  className,
10626
10790
  style: className ? void 0 : {
10627
10791
  padding: "10px 18px",