glitchgrab 1.56.0 → 1.58.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/README.md +65 -1
- package/dist/index.d.mts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +237 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +236 -9
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +87 -3
- package/dist/server.d.ts +87 -3
- package/dist/server.js +66 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +65 -1
- package/dist/server.mjs.map +1 -1
- package/dist/{types-DcDF1mXc.d.mts → types-DfVtKZcp.d.mts} +26 -1
- package/dist/{types-DcDF1mXc.d.ts → types-DfVtKZcp.d.ts} +26 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -288,6 +288,35 @@ curl "https://glitchgrab.dev/api/v1/sdk/feedback?approved=true&minRating=4&limit
|
|
|
288
288
|
|
|
289
289
|
The repo is always derived from the token — there is no `repoId` to pass. Rate limit: 30 submissions per token per hour.
|
|
290
290
|
|
|
291
|
+
## How do I save a lead without filing an issue?
|
|
292
|
+
|
|
293
|
+
A demo form or contact page asks a prospect for their details. Those are a **lead**, not a bug — `submitLead()` stores them in Glitchgrab and never creates a GitHub issue:
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
const { submitLead } = useGlitchgrab();
|
|
297
|
+
|
|
298
|
+
const result = await submitLead({
|
|
299
|
+
name: "Asha Mehta",
|
|
300
|
+
phone: "+91 98765 43210",
|
|
301
|
+
source: "demo", // which form — filter on it later
|
|
302
|
+
email: "asha@firm.in", // optional
|
|
303
|
+
company: "Mehta & Co", // optional
|
|
304
|
+
address: "Pune", // optional
|
|
305
|
+
metadata: { practice: "CA", language: "Hindi" }, // optional, flat values
|
|
306
|
+
});
|
|
307
|
+
// { success: true, id: "…", created: true } — or null on failure. Never throws.
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
One lead per phone number per project: the same visitor submitting twice updates their lead (`created: false`) instead of adding a second row. Leads show under **Calls → Leads** in Glitchgrab, next to any demo the same phone or email went on to book. The owner can switch on a WhatsApp alert for each new lead there.
|
|
311
|
+
|
|
312
|
+
Read them in your own admin with the project's `ggc_` server key — the same key as `listBookings()`:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { listLeads } from "glitchgrab/server";
|
|
316
|
+
|
|
317
|
+
const leads = await listLeads({ source: "demo", since: "2026-09-01" }); // GLITCHGRAB_CALLS_KEY=ggc_…
|
|
318
|
+
```
|
|
319
|
+
|
|
291
320
|
## What keyboard shortcuts are available?
|
|
292
321
|
|
|
293
322
|
Once `GlitchgrabProvider` is mounted, these shortcuts work globally:
|
|
@@ -994,9 +1023,35 @@ if (!saved.ok) console.error(saved.error); // e.g. WhatsApp code already taken
|
|
|
994
1023
|
back instead of trusting what you sent.
|
|
995
1024
|
- **Never throws.** Failures come back as `{ ok: false, error }`, never as defaults.
|
|
996
1025
|
|
|
1026
|
+
### Asking questions before a demo is booked
|
|
1027
|
+
|
|
1028
|
+
Set `questions` and the booking dialog asks them before it shows the calendar;
|
|
1029
|
+
the WhatsApp booking chat asks the same ones. Each is a list of choices the
|
|
1030
|
+
prospect taps — which language they want the demo in, what kind of firm they
|
|
1031
|
+
run — so the right person on your team takes the call.
|
|
1032
|
+
|
|
1033
|
+
```ts
|
|
1034
|
+
await updateBookingSettings({
|
|
1035
|
+
questions: [
|
|
1036
|
+
{ id: "profession", label: "What best describes you?", options: ["CA", "CS", "Tax consultant", "Other"] },
|
|
1037
|
+
{ id: "language", label: "Which language would you like the demo in?", options: ["English", "Hindi", "Marathi"] },
|
|
1038
|
+
],
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
const demos = await listBookings();
|
|
1042
|
+
demos[0].answers; // [{ id: "language", question: "Which language…", answer: "Hindi" }, …]
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
- Up to 5 questions, 2–9 choices each. `required: false` lets a prospect skip one.
|
|
1046
|
+
- Send the whole list to change it; `[]` asks nothing. Left out, it is kept.
|
|
1047
|
+
- Keep a question's `id` when you reword it, so earlier answers still line up.
|
|
1048
|
+
- The answers are also on the Google Calendar event and in the WhatsApp alert.
|
|
1049
|
+
- The dialog needs a `glitchgrab` version newer than 1.57 on the site. An older one
|
|
1050
|
+
still books, without asking.
|
|
1051
|
+
|
|
997
1052
|
```ts
|
|
998
1053
|
getBookingSettings(options?: CallsOptions): Promise<BookingSettingsResult>
|
|
999
|
-
updateBookingSettings(changes:
|
|
1054
|
+
updateBookingSettings(changes: BookingSettingsChanges, options?: CallsOptions): Promise<BookingSettingsResult>
|
|
1000
1055
|
|
|
1001
1056
|
type BookingSettingsResult =
|
|
1002
1057
|
| { ok: true; data: { settings: BookingSettings; saved: boolean; calendarConnected: boolean } }
|
|
@@ -1015,6 +1070,15 @@ interface BookingSettings {
|
|
|
1015
1070
|
horizonDays: number;
|
|
1016
1071
|
noticeMinutes: number;
|
|
1017
1072
|
whatsappCode: string | null;
|
|
1073
|
+
notifyRecipients: { name: string; phone: string }[]; // extra WhatsApp numbers alerted on a booking, max 5
|
|
1074
|
+
questions: BookingQuestion[]; // asked before the calendar, max 5
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
interface BookingQuestion {
|
|
1078
|
+
id: string; // stable name, e.g. "language"; made from the label when left out
|
|
1079
|
+
label: string; // up to 80 characters
|
|
1080
|
+
options: string[]; // 2–9 choices, up to 40 characters each
|
|
1081
|
+
required: boolean; // false lets the prospect skip it
|
|
1018
1082
|
}
|
|
1019
1083
|
```
|
|
1020
1084
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, h as
|
|
3
|
-
export { G as GlitchgrabConfig,
|
|
2
|
+
import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, L as LeadInput, h as LeadResult, i as ReportPayload, j as ReportResult } from './types-DfVtKZcp.mjs';
|
|
3
|
+
export { G as GlitchgrabConfig, k as ReportSeverity, l as ReportType, m as RuntimeInfo } from './types-DfVtKZcp.mjs';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { ReactNode } from 'react';
|
|
6
6
|
|
|
@@ -356,6 +356,13 @@ declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<R
|
|
|
356
356
|
* sendBeacon fallback: the caller needs a real result to show a thank-you.
|
|
357
357
|
*/
|
|
358
358
|
declare function sendFeedback(payload: FeedbackPayload, baseUrl?: string): Promise<FeedbackResult | null>;
|
|
359
|
+
/**
|
|
360
|
+
* POST a lead (#534). Retries transient failures like {@link sendFeedback}; a
|
|
361
|
+
* 4xx other than 429 is a caller mistake and returns null at once.
|
|
362
|
+
*/
|
|
363
|
+
declare function sendLead(token: string, lead: LeadInput & {
|
|
364
|
+
pageUrl?: string;
|
|
365
|
+
}, baseUrl?: string): Promise<LeadResult | null>;
|
|
359
366
|
interface EnhanceContext {
|
|
360
367
|
url?: string;
|
|
361
368
|
visitedPages?: string[];
|
|
@@ -397,4 +404,4 @@ declare function computeSignature(params: {
|
|
|
397
404
|
declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
|
|
398
405
|
declare function clearDedupCache(): void;
|
|
399
406
|
|
|
400
|
-
export { BookDemoButton, Breadcrumb, BreadcrumbType, CaptureErrorOptions, CapturedContext, DeviceInfo, FeedbackButton, FeedbackButtonProps, FeedbackPayload, type FeedbackQuery, FeedbackResult, GLITCHGRAB_SHORTCUT, GLITCHGRAB_SHORTCUT_MAC, GlitchgrabErrorBoundary, GlitchgrabFeedback, GlitchgrabProvider, GlitchgrabProviderProps, type GlitchgrabReport, GlitchgrabSession, ReportButton, ReportButtonProps, ReportPayload, ReportResult, UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, captureError, clearAppContext, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabFeedback, fetchGlitchgrabReports, getAppContext, getBreadcrumbs, getShortcutLabel, initBreadcrumbs, matchesShortcut, sanitizeUrl, sendFeedback, sendReport, setContext, setContexts, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabFeedback, useGlitchgrabReports };
|
|
407
|
+
export { BookDemoButton, Breadcrumb, BreadcrumbType, CaptureErrorOptions, CapturedContext, DeviceInfo, FeedbackButton, FeedbackButtonProps, FeedbackPayload, type FeedbackQuery, FeedbackResult, GLITCHGRAB_SHORTCUT, GLITCHGRAB_SHORTCUT_MAC, GlitchgrabErrorBoundary, GlitchgrabFeedback, GlitchgrabProvider, GlitchgrabProviderProps, type GlitchgrabReport, GlitchgrabSession, LeadInput, LeadResult, ReportButton, ReportButtonProps, ReportPayload, ReportResult, UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, captureError, clearAppContext, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabFeedback, fetchGlitchgrabReports, getAppContext, getBreadcrumbs, getShortcutLabel, initBreadcrumbs, matchesShortcut, sanitizeUrl, sendFeedback, sendLead, sendReport, setContext, setContexts, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabFeedback, useGlitchgrabReports };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, h as
|
|
3
|
-
export { G as GlitchgrabConfig,
|
|
2
|
+
import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, L as LeadInput, h as LeadResult, i as ReportPayload, j as ReportResult } from './types-DfVtKZcp.js';
|
|
3
|
+
export { G as GlitchgrabConfig, k as ReportSeverity, l as ReportType, m as RuntimeInfo } from './types-DfVtKZcp.js';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { ReactNode } from 'react';
|
|
6
6
|
|
|
@@ -356,6 +356,13 @@ declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<R
|
|
|
356
356
|
* sendBeacon fallback: the caller needs a real result to show a thank-you.
|
|
357
357
|
*/
|
|
358
358
|
declare function sendFeedback(payload: FeedbackPayload, baseUrl?: string): Promise<FeedbackResult | null>;
|
|
359
|
+
/**
|
|
360
|
+
* POST a lead (#534). Retries transient failures like {@link sendFeedback}; a
|
|
361
|
+
* 4xx other than 429 is a caller mistake and returns null at once.
|
|
362
|
+
*/
|
|
363
|
+
declare function sendLead(token: string, lead: LeadInput & {
|
|
364
|
+
pageUrl?: string;
|
|
365
|
+
}, baseUrl?: string): Promise<LeadResult | null>;
|
|
359
366
|
interface EnhanceContext {
|
|
360
367
|
url?: string;
|
|
361
368
|
visitedPages?: string[];
|
|
@@ -397,4 +404,4 @@ declare function computeSignature(params: {
|
|
|
397
404
|
declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
|
|
398
405
|
declare function clearDedupCache(): void;
|
|
399
406
|
|
|
400
|
-
export { BookDemoButton, Breadcrumb, BreadcrumbType, CaptureErrorOptions, CapturedContext, DeviceInfo, FeedbackButton, FeedbackButtonProps, FeedbackPayload, type FeedbackQuery, FeedbackResult, GLITCHGRAB_SHORTCUT, GLITCHGRAB_SHORTCUT_MAC, GlitchgrabErrorBoundary, GlitchgrabFeedback, GlitchgrabProvider, GlitchgrabProviderProps, type GlitchgrabReport, GlitchgrabSession, ReportButton, ReportButtonProps, ReportPayload, ReportResult, UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, captureError, clearAppContext, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabFeedback, fetchGlitchgrabReports, getAppContext, getBreadcrumbs, getShortcutLabel, initBreadcrumbs, matchesShortcut, sanitizeUrl, sendFeedback, sendReport, setContext, setContexts, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabFeedback, useGlitchgrabReports };
|
|
407
|
+
export { BookDemoButton, Breadcrumb, BreadcrumbType, CaptureErrorOptions, CapturedContext, DeviceInfo, FeedbackButton, FeedbackButtonProps, FeedbackPayload, type FeedbackQuery, FeedbackResult, GLITCHGRAB_SHORTCUT, GLITCHGRAB_SHORTCUT_MAC, GlitchgrabErrorBoundary, GlitchgrabFeedback, GlitchgrabProvider, GlitchgrabProviderProps, type GlitchgrabReport, GlitchgrabSession, LeadInput, LeadResult, ReportButton, ReportButtonProps, ReportPayload, ReportResult, UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, captureError, clearAppContext, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabFeedback, fetchGlitchgrabReports, getAppContext, getBreadcrumbs, getShortcutLabel, initBreadcrumbs, matchesShortcut, sanitizeUrl, sendFeedback, sendLead, sendReport, setContext, setContexts, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabFeedback, useGlitchgrabReports };
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ __export(index_exports, {
|
|
|
56
56
|
matchesShortcut: () => matchesShortcut,
|
|
57
57
|
sanitizeUrl: () => sanitizeUrl,
|
|
58
58
|
sendFeedback: () => sendFeedback,
|
|
59
|
+
sendLead: () => sendLead,
|
|
59
60
|
sendReport: () => sendReport,
|
|
60
61
|
setContext: () => setContext,
|
|
61
62
|
setContexts: () => setContexts,
|
|
@@ -96,6 +97,7 @@ function toAssistTurnResult(ok, body) {
|
|
|
96
97
|
question: (_d = d.question) != null ? _d : null,
|
|
97
98
|
options: Array.isArray(d.options) ? d.options.filter((o) => typeof o === "string") : [],
|
|
98
99
|
report: (_e = d.report) != null ? _e : null,
|
|
100
|
+
title: typeof d.title === "string" && d.title.trim() ? d.title.trim() : null,
|
|
99
101
|
duplicate: (_f = d.duplicate) != null ? _f : null,
|
|
100
102
|
related: (_g = d.related) != null ? _g : null,
|
|
101
103
|
solved: (_h = d.solved) != null ? _h : null,
|
|
@@ -724,6 +726,36 @@ async function sendFeedback(payload, baseUrl) {
|
|
|
724
726
|
return null;
|
|
725
727
|
}
|
|
726
728
|
}
|
|
729
|
+
async function sendLead(token, lead, baseUrl) {
|
|
730
|
+
var _a, _b;
|
|
731
|
+
try {
|
|
732
|
+
const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/leads`;
|
|
733
|
+
const body = JSON.stringify(lead);
|
|
734
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
735
|
+
try {
|
|
736
|
+
const response = await fetch(url, {
|
|
737
|
+
method: "POST",
|
|
738
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
739
|
+
body
|
|
740
|
+
});
|
|
741
|
+
if (response.ok) {
|
|
742
|
+
const envelope = await response.json();
|
|
743
|
+
return { success: envelope.success, id: (_a = envelope.data) == null ? void 0 : _a.leadId, created: (_b = envelope.data) == null ? void 0 : _b.created };
|
|
744
|
+
}
|
|
745
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
} catch (e) {
|
|
749
|
+
}
|
|
750
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
751
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_BASE_MS * Math.pow(2, attempt)));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return null;
|
|
755
|
+
} catch (e) {
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
727
759
|
function signedFields(identity) {
|
|
728
760
|
if (!(identity == null ? void 0 : identity.signature) || !identity.userId) return {};
|
|
729
761
|
return { userId: identity.userId, signature: identity.signature };
|
|
@@ -1985,6 +2017,8 @@ function AssistSheet({
|
|
|
1985
2017
|
reporterName,
|
|
1986
2018
|
description,
|
|
1987
2019
|
onDescriptionChange,
|
|
2020
|
+
issueTitle,
|
|
2021
|
+
onIssueTitleChange,
|
|
1988
2022
|
severity,
|
|
1989
2023
|
onSeverityChange,
|
|
1990
2024
|
showSeverity,
|
|
@@ -2083,7 +2117,7 @@ function AssistSheet({
|
|
|
2083
2117
|
onDegrade(message, retryable);
|
|
2084
2118
|
}
|
|
2085
2119
|
async function runTurn(history2, autoFile = false) {
|
|
2086
|
-
var _a, _b;
|
|
2120
|
+
var _a, _b, _c;
|
|
2087
2121
|
setBusy(true);
|
|
2088
2122
|
setAutoFileLeft(null);
|
|
2089
2123
|
let result;
|
|
@@ -2140,6 +2174,7 @@ function AssistSheet({
|
|
|
2140
2174
|
}
|
|
2141
2175
|
if (result.report) {
|
|
2142
2176
|
onDescriptionChange(result.report);
|
|
2177
|
+
onIssueTitleChange((_b = result.title) != null ? _b : "");
|
|
2143
2178
|
aiDraftRef.current = result.report;
|
|
2144
2179
|
logEvent("draft_shown");
|
|
2145
2180
|
setMessages([...history2, { role: "assistant", content: result.report, kind: "report" }]);
|
|
@@ -2152,7 +2187,7 @@ function AssistSheet({
|
|
|
2152
2187
|
}
|
|
2153
2188
|
if (result.question) {
|
|
2154
2189
|
setMessages([...history2, { role: "assistant", content: result.question }]);
|
|
2155
|
-
setOptions((
|
|
2190
|
+
setOptions((_c = result.options) != null ? _c : []);
|
|
2156
2191
|
return;
|
|
2157
2192
|
}
|
|
2158
2193
|
onDegrade("The assistant had nothing to add \u2014 write your report below and send it as normal.");
|
|
@@ -2996,6 +3031,40 @@ function AssistSheet({
|
|
|
2996
3031
|
title: true,
|
|
2997
3032
|
footer: "Your details will be added to it instead of opening a new issue."
|
|
2998
3033
|
}),
|
|
3034
|
+
!duplicate && issueTitle && // Wraps rather than scrolls sideways: a title cut off at the
|
|
3035
|
+
// box edge is the very thing #543 is about. One line of text,
|
|
3036
|
+
// so Enter is swallowed.
|
|
3037
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3038
|
+
"textarea",
|
|
3039
|
+
{
|
|
3040
|
+
"aria-label": "Title",
|
|
3041
|
+
value: issueTitle,
|
|
3042
|
+
maxLength: 120,
|
|
3043
|
+
rows: 2,
|
|
3044
|
+
onKeyDown: (e) => {
|
|
3045
|
+
if (e.key === "Enter") e.preventDefault();
|
|
3046
|
+
},
|
|
3047
|
+
onChange: (e) => {
|
|
3048
|
+
stopAutoFile("edited the title");
|
|
3049
|
+
onIssueTitleChange(e.target.value.replace(/[\r\n]+/g, " "));
|
|
3050
|
+
},
|
|
3051
|
+
style: {
|
|
3052
|
+
width: "100%",
|
|
3053
|
+
padding: "8px 10px",
|
|
3054
|
+
lineHeight: 1.45,
|
|
3055
|
+
resize: "none",
|
|
3056
|
+
borderRadius: "8px",
|
|
3057
|
+
border: `1px solid ${t.inputBorder}`,
|
|
3058
|
+
backgroundColor: t.inputBg,
|
|
3059
|
+
color: t.text,
|
|
3060
|
+
fontSize: "13px",
|
|
3061
|
+
fontWeight: 600,
|
|
3062
|
+
fontFamily: "inherit",
|
|
3063
|
+
outline: "none",
|
|
3064
|
+
boxSizing: "border-box"
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
),
|
|
2999
3068
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3000
3069
|
"textarea",
|
|
3001
3070
|
{
|
|
@@ -4637,6 +4706,7 @@ function ReportDialog({
|
|
|
4637
4706
|
const [pickedSimilar, setPickedSimilar] = (0, import_react.useState)(null);
|
|
4638
4707
|
const [addedToIssue, setAddedToIssue] = (0, import_react.useState)(null);
|
|
4639
4708
|
const [aiConversationId, setAiConversationId] = (0, import_react.useState)(null);
|
|
4709
|
+
const [issueTitle, setIssueTitle] = (0, import_react.useState)("");
|
|
4640
4710
|
const [isEnhanced, setIsEnhanced] = (0, import_react.useState)(false);
|
|
4641
4711
|
const [originalDescription, setOriginalDescription] = (0, import_react.useState)(
|
|
4642
4712
|
null
|
|
@@ -5338,6 +5408,7 @@ function ReportDialog({
|
|
|
5338
5408
|
}
|
|
5339
5409
|
if (aiConversationId) {
|
|
5340
5410
|
metadata.aiConversationId = aiConversationId;
|
|
5411
|
+
if (sheetUp && !duplicateOf && issueTitle.trim()) metadata.issueTitle = issueTitle.trim();
|
|
5341
5412
|
}
|
|
5342
5413
|
const result = await report(
|
|
5343
5414
|
// The RATING branch returned above, so anything reaching here is a real
|
|
@@ -5356,6 +5427,7 @@ function ReportDialog({
|
|
|
5356
5427
|
setScreenshots([]);
|
|
5357
5428
|
setAttachments([]);
|
|
5358
5429
|
setAiConversationId(null);
|
|
5430
|
+
setIssueTitle("");
|
|
5359
5431
|
setDuplicateIssueNumber(null);
|
|
5360
5432
|
setDuplicateKind("duplicate");
|
|
5361
5433
|
setSimilarIssues(null);
|
|
@@ -7459,6 +7531,8 @@ function ReportDialog({
|
|
|
7459
7531
|
setDescription(value);
|
|
7460
7532
|
if (validationError) setValidationError(null);
|
|
7461
7533
|
},
|
|
7534
|
+
issueTitle,
|
|
7535
|
+
onIssueTitleChange: setIssueTitle,
|
|
7462
7536
|
severity,
|
|
7463
7537
|
onSeverityChange: (value) => {
|
|
7464
7538
|
setSeverity(value);
|
|
@@ -7624,6 +7698,16 @@ function getTypeIcon(type, color, size = 24) {
|
|
|
7624
7698
|
// src/booking-dialog.tsx
|
|
7625
7699
|
var import_react5 = require("react");
|
|
7626
7700
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
7701
|
+
function readQuestions(value) {
|
|
7702
|
+
if (!Array.isArray(value)) return [];
|
|
7703
|
+
return value.flatMap((row) => {
|
|
7704
|
+
if (!row || typeof row !== "object") return [];
|
|
7705
|
+
const { id, label, options, required } = row;
|
|
7706
|
+
if (typeof id !== "string" || typeof label !== "string" || !Array.isArray(options)) return [];
|
|
7707
|
+
const choices = options.filter((o) => typeof o === "string" && o !== "");
|
|
7708
|
+
return choices.length > 0 ? [{ id, label, options: choices, required: required !== false }] : [];
|
|
7709
|
+
});
|
|
7710
|
+
}
|
|
7627
7711
|
var LIGHT = {
|
|
7628
7712
|
bg: "#ffffff",
|
|
7629
7713
|
pane: "#ffffff",
|
|
@@ -7777,6 +7861,10 @@ function BookingDialog({
|
|
|
7777
7861
|
const [email, setEmail] = (0, import_react5.useState)("");
|
|
7778
7862
|
const [phone, setPhone] = (0, import_react5.useState)("");
|
|
7779
7863
|
const [note, setNote] = (0, import_react5.useState)("");
|
|
7864
|
+
const [questions, setQuestions] = (0, import_react5.useState)([]);
|
|
7865
|
+
const [answers, setAnswers] = (0, import_react5.useState)({});
|
|
7866
|
+
const [pageLoaded, setPageLoaded] = (0, import_react5.useState)(false);
|
|
7867
|
+
const [asked, setAsked] = (0, import_react5.useState)(false);
|
|
7780
7868
|
const [code, setCode] = (0, import_react5.useState)("");
|
|
7781
7869
|
const [bookingId, setBookingId] = (0, import_react5.useState)(null);
|
|
7782
7870
|
const [meetUrl, setMeetUrl] = (0, import_react5.useState)(null);
|
|
@@ -7805,6 +7893,7 @@ function BookingDialog({
|
|
|
7805
7893
|
if (!json.success) {
|
|
7806
7894
|
setError((_a2 = json.error) != null ? _a2 : "Couldn't load available times");
|
|
7807
7895
|
setSlotsByMonth((prev) => ({ ...prev, [target]: [] }));
|
|
7896
|
+
setPageLoaded(true);
|
|
7808
7897
|
return;
|
|
7809
7898
|
}
|
|
7810
7899
|
const data = (_b2 = json.data) != null ? _b2 : {};
|
|
@@ -7817,9 +7906,12 @@ function BookingDialog({
|
|
|
7817
7906
|
slotMinutes: typeof data.slotMinutes === "number" ? data.slotMinutes : 30
|
|
7818
7907
|
});
|
|
7819
7908
|
setWhatsappUrl((_c2 = data.whatsappUrl) != null ? _c2 : null);
|
|
7909
|
+
setQuestions(readQuestions(data.questions));
|
|
7910
|
+
setPageLoaded(true);
|
|
7820
7911
|
} catch (e) {
|
|
7821
7912
|
setError("Couldn't reach the booking service");
|
|
7822
7913
|
setSlotsByMonth((prev) => ({ ...prev, [target]: [] }));
|
|
7914
|
+
setPageLoaded(true);
|
|
7823
7915
|
}
|
|
7824
7916
|
},
|
|
7825
7917
|
[baseUrl, headers]
|
|
@@ -7837,6 +7929,8 @@ function BookingDialog({
|
|
|
7837
7929
|
setBookingId(null);
|
|
7838
7930
|
setError(null);
|
|
7839
7931
|
setAvatarFailed(false);
|
|
7932
|
+
setPageLoaded(false);
|
|
7933
|
+
setAsked(false);
|
|
7840
7934
|
}, [open]);
|
|
7841
7935
|
const monthLoaded = month in slotsByMonth;
|
|
7842
7936
|
(0, import_react5.useEffect)(() => {
|
|
@@ -7865,6 +7959,10 @@ function BookingDialog({
|
|
|
7865
7959
|
const daySlots = day ? (_d = byDay.get(day)) != null ? _d : [] : [];
|
|
7866
7960
|
const atFirstMonth = month <= today.slice(0, 7);
|
|
7867
7961
|
const pastHorizon = monthLoaded && !error && byDay.size === 0 && !atFirstMonth;
|
|
7962
|
+
const given = questions.flatMap((q) => q.options.includes(answers[q.id]) ? [{ ...q, answer: answers[q.id] }] : []);
|
|
7963
|
+
const canContinue = questions.every((q) => !q.required || q.options.includes(answers[q.id]));
|
|
7964
|
+
const view = step !== "slots" ? step : !pageLoaded ? "loading" : questions.length > 0 && !asked ? "questions" : "slots";
|
|
7965
|
+
const roomy = step === "slots";
|
|
7868
7966
|
async function startBooking() {
|
|
7869
7967
|
var _a2;
|
|
7870
7968
|
if (!selected) return;
|
|
@@ -7879,6 +7977,7 @@ function BookingDialog({
|
|
|
7879
7977
|
email,
|
|
7880
7978
|
phone,
|
|
7881
7979
|
note,
|
|
7980
|
+
answers: Object.fromEntries(given.map((q) => [q.id, q.answer])),
|
|
7882
7981
|
startsAt: selected.startsAt,
|
|
7883
7982
|
// The zone the times were shown in, which is the one the visitor chose.
|
|
7884
7983
|
timezone: zone
|
|
@@ -7929,7 +8028,7 @@ function BookingDialog({
|
|
|
7929
8028
|
"div",
|
|
7930
8029
|
{
|
|
7931
8030
|
style: {
|
|
7932
|
-
marginTop: wide ? whatsappUrl &&
|
|
8031
|
+
marginTop: wide ? whatsappUrl && roomy ? 14 : "auto" : 0,
|
|
7933
8032
|
padding: wide ? 0 : "4px 20px 18px",
|
|
7934
8033
|
display: "flex",
|
|
7935
8034
|
justifyContent: wide ? "flex-start" : "center"
|
|
@@ -8070,9 +8169,36 @@ function BookingDialog({
|
|
|
8070
8169
|
selectedEnd && formatTime(selectedEnd, zone, twelve)
|
|
8071
8170
|
] })
|
|
8072
8171
|
] }) }),
|
|
8172
|
+
asked && given.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Row, { icon: "check", t, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
8173
|
+
given.map((q) => q.answer).join(" \xB7 "),
|
|
8174
|
+
view === "slots" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
8175
|
+
" ",
|
|
8176
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
8177
|
+
"button",
|
|
8178
|
+
{
|
|
8179
|
+
type: "button",
|
|
8180
|
+
onClick: () => setAsked(false),
|
|
8181
|
+
className: "gg-bk-link",
|
|
8182
|
+
style: {
|
|
8183
|
+
border: 0,
|
|
8184
|
+
// Room for a thumb without pushing the rows apart.
|
|
8185
|
+
padding: "8px 6px",
|
|
8186
|
+
margin: "-8px 0",
|
|
8187
|
+
background: "transparent",
|
|
8188
|
+
color: t.muted,
|
|
8189
|
+
fontFamily: "inherit",
|
|
8190
|
+
fontSize: 13,
|
|
8191
|
+
textDecoration: "underline",
|
|
8192
|
+
cursor: "pointer"
|
|
8193
|
+
},
|
|
8194
|
+
children: "Change"
|
|
8195
|
+
}
|
|
8196
|
+
)
|
|
8197
|
+
] })
|
|
8198
|
+
] }) }),
|
|
8073
8199
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Row, { icon: "clock", t, children: formatDuration(page.slotMinutes) }),
|
|
8074
8200
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Row, { icon: "video", t, children: "Google Meet" }),
|
|
8075
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Row, { icon: "globe", t, children:
|
|
8201
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Row, { icon: "globe", t, children: roomy ? (
|
|
8076
8202
|
// The visible label is plain text; the native select sits on top of
|
|
8077
8203
|
// it, invisible. A styled select sizes itself to the longest zone
|
|
8078
8204
|
// name in the list, which pushed the chevron to the pane's edge.
|
|
@@ -8092,7 +8218,7 @@ function BookingDialog({
|
|
|
8092
8218
|
] })
|
|
8093
8219
|
) : zone })
|
|
8094
8220
|
] }),
|
|
8095
|
-
whatsappUrl &&
|
|
8221
|
+
whatsappUrl && roomy && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
8096
8222
|
"a",
|
|
8097
8223
|
{
|
|
8098
8224
|
href: whatsappUrl,
|
|
@@ -8363,6 +8489,93 @@ function BookingDialog({
|
|
|
8363
8489
|
]
|
|
8364
8490
|
}
|
|
8365
8491
|
);
|
|
8492
|
+
const loading = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
8493
|
+
"div",
|
|
8494
|
+
{
|
|
8495
|
+
style: {
|
|
8496
|
+
flex: "1 1 auto",
|
|
8497
|
+
minHeight: wide ? void 0 : 200,
|
|
8498
|
+
display: "flex",
|
|
8499
|
+
alignItems: "center",
|
|
8500
|
+
justifyContent: "center",
|
|
8501
|
+
fontSize: 13,
|
|
8502
|
+
...muted
|
|
8503
|
+
},
|
|
8504
|
+
children: "Loading\u2026"
|
|
8505
|
+
}
|
|
8506
|
+
);
|
|
8507
|
+
const ask = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
8508
|
+
"div",
|
|
8509
|
+
{
|
|
8510
|
+
style: {
|
|
8511
|
+
flex: "1 1 auto",
|
|
8512
|
+
minWidth: 0,
|
|
8513
|
+
padding: wide ? "28px 32px" : "20px",
|
|
8514
|
+
boxSizing: "border-box",
|
|
8515
|
+
overflowY: wide ? "auto" : void 0
|
|
8516
|
+
},
|
|
8517
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { maxWidth: 520, display: "flex", flexDirection: "column", gap: 22 }, children: [
|
|
8518
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
|
|
8519
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: 16, fontWeight: 600 }, children: questions.length === 1 ? "One question first" : "A few questions first" }),
|
|
8520
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: 13, lineHeight: 1.5, marginTop: 4, ...muted }, children: "So the right person takes your demo." })
|
|
8521
|
+
] }),
|
|
8522
|
+
questions.map((q) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "group", "aria-label": q.label, children: [
|
|
8523
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { fontSize: 14, fontWeight: 500, overflowWrap: "anywhere" }, children: [
|
|
8524
|
+
q.label,
|
|
8525
|
+
!q.required && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontWeight: 400, ...muted }, children: " (optional)" })
|
|
8526
|
+
] }),
|
|
8527
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 10 }, children: q.options.map((option) => {
|
|
8528
|
+
const on = answers[q.id] === option;
|
|
8529
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
8530
|
+
"button",
|
|
8531
|
+
{
|
|
8532
|
+
type: "button",
|
|
8533
|
+
"aria-pressed": on,
|
|
8534
|
+
className: on ? void 0 : "gg-bk-slot",
|
|
8535
|
+
onClick: () => setAnswers((prev) => {
|
|
8536
|
+
if (on && !q.required) {
|
|
8537
|
+
const rest = { ...prev };
|
|
8538
|
+
delete rest[q.id];
|
|
8539
|
+
return rest;
|
|
8540
|
+
}
|
|
8541
|
+
return { ...prev, [q.id]: option };
|
|
8542
|
+
}),
|
|
8543
|
+
style: {
|
|
8544
|
+
padding: "9px 14px",
|
|
8545
|
+
minHeight: 40,
|
|
8546
|
+
borderRadius: 8,
|
|
8547
|
+
border: `1px solid ${on ? t.ink : t.border}`,
|
|
8548
|
+
background: on ? t.ink : t.pane,
|
|
8549
|
+
color: on ? t.onInk : t.text,
|
|
8550
|
+
fontFamily: "inherit",
|
|
8551
|
+
fontSize: 14,
|
|
8552
|
+
fontWeight: 500,
|
|
8553
|
+
cursor: "pointer",
|
|
8554
|
+
overflowWrap: "anywhere",
|
|
8555
|
+
textAlign: "left"
|
|
8556
|
+
},
|
|
8557
|
+
children: option
|
|
8558
|
+
},
|
|
8559
|
+
option
|
|
8560
|
+
);
|
|
8561
|
+
}) })
|
|
8562
|
+
] }, q.id)),
|
|
8563
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "8px 12px", marginTop: 2 }, children: [
|
|
8564
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
8565
|
+
"button",
|
|
8566
|
+
{
|
|
8567
|
+
type: "button",
|
|
8568
|
+
onClick: () => setAsked(true),
|
|
8569
|
+
disabled: !canContinue,
|
|
8570
|
+
style: { ...primaryStyle(t, !canContinue), whiteSpace: "nowrap" },
|
|
8571
|
+
children: "See available times"
|
|
8572
|
+
}
|
|
8573
|
+
),
|
|
8574
|
+
!canContinue && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: 12, ...muted }, children: "Pick one for each to continue" })
|
|
8575
|
+
] })
|
|
8576
|
+
] })
|
|
8577
|
+
}
|
|
8578
|
+
);
|
|
8366
8579
|
const form = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
8367
8580
|
"div",
|
|
8368
8581
|
{
|
|
@@ -8476,6 +8689,7 @@ function BookingDialog({
|
|
|
8476
8689
|
.gg-bk-day:hover { box-shadow: inset 0 0 0 1.5px ${t.ink}; }
|
|
8477
8690
|
.gg-bk-slot:hover { border-color: ${t.ink} !important; }
|
|
8478
8691
|
.gg-bk-ghost:hover { background: ${t.open} !important; }
|
|
8692
|
+
.gg-bk-link:hover { color: ${t.text} !important; }
|
|
8479
8693
|
.gg-bk-root button:focus-visible, .gg-bk-root a:focus-visible,
|
|
8480
8694
|
.gg-bk-root span:has(> select:focus-visible), .gg-bk-root input:focus-visible, .gg-bk-root textarea:focus-visible {
|
|
8481
8695
|
outline: 2px solid ${t.ink}; outline-offset: 2px;
|
|
@@ -8489,11 +8703,11 @@ function BookingDialog({
|
|
|
8489
8703
|
"aria-label": page.title,
|
|
8490
8704
|
className: "gg-bk-root",
|
|
8491
8705
|
style: {
|
|
8492
|
-
width:
|
|
8706
|
+
width: roomy ? "min(1060px, 100%)" : "min(820px, 100%)",
|
|
8493
8707
|
// A month grid should not jump in height between months; the form
|
|
8494
8708
|
// steps are short and would sit in a mostly empty box.
|
|
8495
|
-
height: wide &&
|
|
8496
|
-
minHeight: wide &&
|
|
8709
|
+
height: wide && roomy ? "min(640px, calc(100vh - 48px))" : void 0,
|
|
8710
|
+
minHeight: wide && !roomy ? 420 : void 0,
|
|
8497
8711
|
margin: wide ? void 0 : "auto 0",
|
|
8498
8712
|
display: "flex",
|
|
8499
8713
|
flexDirection: wide ? "row" : "column",
|
|
@@ -8509,7 +8723,7 @@ function BookingDialog({
|
|
|
8509
8723
|
},
|
|
8510
8724
|
children: [
|
|
8511
8725
|
summary,
|
|
8512
|
-
|
|
8726
|
+
view === "loading" ? loading : view === "questions" ? ask : view === "slots" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
8513
8727
|
calendar,
|
|
8514
8728
|
times
|
|
8515
8729
|
] }) : form,
|
|
@@ -8596,6 +8810,7 @@ function Icon({ name }) {
|
|
|
8596
8810
|
left: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m15 18-6-6 6-6" }),
|
|
8597
8811
|
right: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m9 18 6-6-6-6" }),
|
|
8598
8812
|
down: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 9 6 6 6-6" }),
|
|
8813
|
+
check: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M20 6 9 17l-5-5" }),
|
|
8599
8814
|
clock: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
8600
8815
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "9" }),
|
|
8601
8816
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 7v5l3 2" })
|
|
@@ -9127,6 +9342,17 @@ function GlitchgrabProviderInner({
|
|
|
9127
9342
|
},
|
|
9128
9343
|
[token, baseUrl, session]
|
|
9129
9344
|
);
|
|
9345
|
+
const submitLead = (0, import_react6.useCallback)(
|
|
9346
|
+
async (lead) => {
|
|
9347
|
+
try {
|
|
9348
|
+
const context = captureContext(visitedPagesRef.current);
|
|
9349
|
+
return await sendLead(token, { ...lead, pageUrl: context.url }, baseUrl);
|
|
9350
|
+
} catch (e) {
|
|
9351
|
+
return null;
|
|
9352
|
+
}
|
|
9353
|
+
},
|
|
9354
|
+
[token, baseUrl]
|
|
9355
|
+
);
|
|
9130
9356
|
const openFeedbackDialog = (0, import_react6.useCallback)(() => {
|
|
9131
9357
|
if (typeof window !== "undefined") {
|
|
9132
9358
|
window.dispatchEvent(
|
|
@@ -9304,6 +9530,7 @@ function GlitchgrabProviderInner({
|
|
|
9304
9530
|
setContext: setContext2,
|
|
9305
9531
|
setContexts: setContexts2,
|
|
9306
9532
|
sendFeedback: feedback,
|
|
9533
|
+
submitLead,
|
|
9307
9534
|
openFeedbackDialog,
|
|
9308
9535
|
addBreadcrumb: addBreadcrumb2,
|
|
9309
9536
|
openReportDialog,
|
|
@@ -9769,6 +9996,7 @@ function useGlitchgrabFeedback(query) {
|
|
|
9769
9996
|
matchesShortcut,
|
|
9770
9997
|
sanitizeUrl,
|
|
9771
9998
|
sendFeedback,
|
|
9999
|
+
sendLead,
|
|
9772
10000
|
sendReport,
|
|
9773
10001
|
setContext,
|
|
9774
10002
|
setContexts,
|