@sunboard/react 0.1.3 → 0.3.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.d.mts +70 -31
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +359 -224
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +773 -560
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -18,143 +18,15 @@ async function navigateWithFallback(url, navigate) {
|
|
|
18
18
|
//#region src/hooks/sunboard-context.ts
|
|
19
19
|
const SunboardContext = createContext(null);
|
|
20
20
|
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/hooks/useSunboardPreviewSpec.ts
|
|
23
|
-
function useSunboardPreviewSpec() {
|
|
24
|
-
const [state, setState] = useState(() => ({
|
|
25
|
-
error: null,
|
|
26
|
-
loading: getPreviewSpecUrl() !== null,
|
|
27
|
-
source: "none",
|
|
28
|
-
spec: null
|
|
29
|
-
}));
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
const previewUrl = getPreviewSpecUrl();
|
|
32
|
-
if (!previewUrl) {
|
|
33
|
-
setState({
|
|
34
|
-
error: null,
|
|
35
|
-
loading: false,
|
|
36
|
-
source: "none",
|
|
37
|
-
spec: null
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const controller = new AbortController();
|
|
42
|
-
let cancelled = false;
|
|
43
|
-
let eventSource = null;
|
|
44
|
-
const loadSpec = async (signal) => {
|
|
45
|
-
const response = await fetch(previewUrl.toString(), { signal });
|
|
46
|
-
if (!response.ok) throw new Error(`Sunboard preview failed with ${response.status}.`);
|
|
47
|
-
const json = await response.json();
|
|
48
|
-
if (!isSunboardSpecLike(json)) throw new Error("Sunboard preview returned an invalid spec payload.");
|
|
49
|
-
return json;
|
|
50
|
-
};
|
|
51
|
-
setState({
|
|
52
|
-
error: null,
|
|
53
|
-
loading: true,
|
|
54
|
-
source: "none",
|
|
55
|
-
spec: null
|
|
56
|
-
});
|
|
57
|
-
loadSpec(controller.signal).then((previewSpec) => {
|
|
58
|
-
if (cancelled) return;
|
|
59
|
-
setState({
|
|
60
|
-
error: null,
|
|
61
|
-
loading: false,
|
|
62
|
-
source: "preview",
|
|
63
|
-
spec: previewSpec
|
|
64
|
-
});
|
|
65
|
-
const eventsUrl = buildEventsUrl(previewUrl);
|
|
66
|
-
if (eventsUrl && typeof EventSource !== "undefined") {
|
|
67
|
-
eventSource = new EventSource(eventsUrl);
|
|
68
|
-
let firstMessage = true;
|
|
69
|
-
eventSource.onmessage = () => {
|
|
70
|
-
if (firstMessage) {
|
|
71
|
-
firstMessage = false;
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
reloadSpec(previewUrl);
|
|
75
|
-
};
|
|
76
|
-
eventSource.onerror = () => {};
|
|
77
|
-
}
|
|
78
|
-
}).catch((error) => {
|
|
79
|
-
if (controller.signal.aborted || cancelled) return;
|
|
80
|
-
setState({
|
|
81
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
82
|
-
loading: false,
|
|
83
|
-
source: "none",
|
|
84
|
-
spec: null
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
const reloadSpec = async (url) => {
|
|
88
|
-
try {
|
|
89
|
-
const response = await fetch(url.toString());
|
|
90
|
-
if (!response.ok) throw new Error(`Sunboard preview failed with ${response.status}.`);
|
|
91
|
-
const json = await response.json();
|
|
92
|
-
if (!isSunboardSpecLike(json)) throw new Error("Sunboard preview returned an invalid spec payload.");
|
|
93
|
-
if (cancelled) return;
|
|
94
|
-
setState({
|
|
95
|
-
error: null,
|
|
96
|
-
loading: false,
|
|
97
|
-
source: "preview",
|
|
98
|
-
spec: json
|
|
99
|
-
});
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (cancelled) return;
|
|
102
|
-
setState((current) => ({
|
|
103
|
-
...current,
|
|
104
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
return () => {
|
|
109
|
-
cancelled = true;
|
|
110
|
-
controller.abort();
|
|
111
|
-
eventSource?.close();
|
|
112
|
-
};
|
|
113
|
-
}, []);
|
|
114
|
-
return state;
|
|
115
|
-
}
|
|
116
|
-
function buildEventsUrl(specUrl) {
|
|
117
|
-
try {
|
|
118
|
-
const eventsUrl = new URL(specUrl.toString());
|
|
119
|
-
eventsUrl.pathname = eventsUrl.pathname.replace(/\/spec\.json$/, "/events");
|
|
120
|
-
if (eventsUrl.pathname === specUrl.pathname) return null;
|
|
121
|
-
return eventsUrl.toString();
|
|
122
|
-
} catch {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function getPreviewSpecUrl() {
|
|
127
|
-
if (typeof window === "undefined") return null;
|
|
128
|
-
const pageUrl = new URL(window.location.href);
|
|
129
|
-
if (pageUrl.searchParams.get("sunboard_preview") !== "1") return null;
|
|
130
|
-
const previewUrlParam = pageUrl.searchParams.get("sunboard_preview_url");
|
|
131
|
-
if (!previewUrlParam) return null;
|
|
132
|
-
try {
|
|
133
|
-
const previewUrl = new URL(previewUrlParam);
|
|
134
|
-
return isAllowedLocalPreviewUrl(previewUrl) ? previewUrl : null;
|
|
135
|
-
} catch {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function isAllowedLocalPreviewUrl(url) {
|
|
140
|
-
return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
141
|
-
}
|
|
142
|
-
function isSunboardSpecLike(value) {
|
|
143
|
-
if (!value || typeof value !== "object") return false;
|
|
144
|
-
const candidate = value;
|
|
145
|
-
return typeof candidate.key === "string" && typeof candidate.name === "string" && typeof candidate.type === "string" && typeof candidate.checklist === "object" && Array.isArray(candidate.checklist?.steps);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
21
|
//#endregion
|
|
149
22
|
//#region src/version.ts
|
|
150
23
|
const SDK_VERSION = "0.0.0";
|
|
151
24
|
|
|
152
25
|
//#endregion
|
|
153
26
|
//#region src/components/SunboardProvider.tsx
|
|
154
|
-
function SunboardProvider({ spec: specProp, user, loading: loadingProp,
|
|
27
|
+
function SunboardProvider({ spec: specProp, user, loading: loadingProp, route, navigate, apiUrl, publishableKey, onTrack, children }) {
|
|
155
28
|
const currentRoute = route ?? getBrowserRoute();
|
|
156
29
|
const currentOrigin = getBrowserOrigin();
|
|
157
|
-
const preview = useSunboardPreviewSpec();
|
|
158
30
|
const client = useMemo(() => {
|
|
159
31
|
if (!publishableKey) return null;
|
|
160
32
|
return new SunboardRuntimeClient$1({
|
|
@@ -163,31 +35,44 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
163
35
|
sdkVersion: SDK_VERSION
|
|
164
36
|
});
|
|
165
37
|
}, [apiUrl, publishableKey]);
|
|
38
|
+
const [bootstrapNonce, setBootstrapNonce] = useState(0);
|
|
39
|
+
const refreshBootstrap = useCallback(() => {
|
|
40
|
+
setBootstrapNonce((nonce) => nonce + 1);
|
|
41
|
+
}, []);
|
|
166
42
|
const bootstrap = useSunboardBootstrap(client, {
|
|
167
|
-
enabled: !specProp
|
|
168
|
-
|
|
43
|
+
enabled: !specProp,
|
|
44
|
+
nonce: bootstrapNonce,
|
|
169
45
|
origin: currentOrigin,
|
|
170
46
|
route: currentRoute,
|
|
171
47
|
user
|
|
172
48
|
});
|
|
173
49
|
const activeExperience = bootstrap.experience;
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const hostedMode = client !== null && !specProp
|
|
50
|
+
const activeSurvey = bootstrap.survey;
|
|
51
|
+
const hostedSpec = activeSurvey?.spec ?? activeExperience?.spec ?? null;
|
|
52
|
+
const resolvedSpec = specProp ?? hostedSpec;
|
|
53
|
+
const resolvedLoading = loadingProp ?? bootstrap.loading;
|
|
54
|
+
const hostedMode = client !== null && !specProp;
|
|
55
|
+
const ownedAttributes = bootstrap.attributes;
|
|
56
|
+
const effectiveUser = useMemo(() => {
|
|
57
|
+
if (!user || !ownedAttributes || Object.keys(ownedAttributes).length === 0) return user;
|
|
58
|
+
return {
|
|
59
|
+
...user,
|
|
60
|
+
properties: {
|
|
61
|
+
...user.properties ?? {},
|
|
62
|
+
...ownedAttributes
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}, [user, ownedAttributes]);
|
|
179
66
|
const [completedStepKeys, setCompletedStepKeys] = useState(() => /* @__PURE__ */ new Set());
|
|
180
67
|
const [completedTourKeys, setCompletedTourKeys] = useState(() => /* @__PURE__ */ new Set());
|
|
181
68
|
const [activeTourState, setActiveTourState] = useState(null);
|
|
182
|
-
const [tourError, setTourError] = useState(null);
|
|
183
69
|
const lastNavigatedTourStep = useRef(null);
|
|
184
70
|
const storageKey = !hostedMode && resolvedSpec && user ? completedToursStorageKey(resolvedSpec.key, user.id) : null;
|
|
185
|
-
const experienceKey = activeExperience?.key ?? resolvedSpec?.key ?? null;
|
|
186
|
-
const versionId = activeExperience?.version.id ?? null;
|
|
71
|
+
const experienceKey = activeSurvey?.key ?? activeExperience?.key ?? resolvedSpec?.key ?? null;
|
|
72
|
+
const versionId = activeSurvey?.version.id ?? activeExperience?.version.id ?? null;
|
|
187
73
|
useEffect(() => {
|
|
188
74
|
setCompletedStepKeys(/* @__PURE__ */ new Set());
|
|
189
75
|
setActiveTourState(null);
|
|
190
|
-
setTourError(null);
|
|
191
76
|
lastNavigatedTourStep.current = null;
|
|
192
77
|
if (!storageKey) {
|
|
193
78
|
setCompletedTourKeys(/* @__PURE__ */ new Set());
|
|
@@ -221,7 +106,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
221
106
|
]);
|
|
222
107
|
useEffect(() => {
|
|
223
108
|
if (!resolvedSpec) return;
|
|
224
|
-
const tourCompletedStepKeys = resolvedSpec.checklist
|
|
109
|
+
const tourCompletedStepKeys = (resolvedSpec.checklist?.steps ?? []).filter((step) => step.completeWhen?.tour && effectiveCompletedTourKeys.has(step.completeWhen.tour)).map((step) => step.key);
|
|
225
110
|
if (tourCompletedStepKeys.length === 0) return;
|
|
226
111
|
setCompletedStepKeys((current) => mergeKeys(current, tourCompletedStepKeys));
|
|
227
112
|
}, [effectiveCompletedTourKeys, resolvedSpec]);
|
|
@@ -252,7 +137,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
252
137
|
useEffect(() => {
|
|
253
138
|
if (!hostedMode || !client || !activeTour || !user || !experienceKey) return;
|
|
254
139
|
client.recordTourStep({
|
|
255
|
-
environment,
|
|
256
140
|
experienceKey,
|
|
257
141
|
origin: currentOrigin,
|
|
258
142
|
route: currentRoute,
|
|
@@ -270,8 +154,8 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
270
154
|
hostedMode,
|
|
271
155
|
user?.id
|
|
272
156
|
]);
|
|
273
|
-
const totalSteps = resolvedSpec?.checklist
|
|
274
|
-
const completedCount = resolvedSpec?.checklist
|
|
157
|
+
const totalSteps = resolvedSpec?.checklist?.steps.length ?? 0;
|
|
158
|
+
const completedCount = resolvedSpec?.checklist?.steps.filter((step) => effectiveCompletedStepKeys.has(step.key)).length ?? 0;
|
|
275
159
|
const completeTour = useCallback((tourKey) => {
|
|
276
160
|
setCompletedTourKeys((current) => {
|
|
277
161
|
const next = new Set(current);
|
|
@@ -289,12 +173,11 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
289
173
|
ok: false,
|
|
290
174
|
reason: "missing-user"
|
|
291
175
|
};
|
|
292
|
-
const matchingStepKeys = resolvedSpec.checklist
|
|
176
|
+
const matchingStepKeys = (resolvedSpec.checklist?.steps ?? []).filter((step) => step.completeWhen?.event === eventName).map((step) => step.key);
|
|
293
177
|
if (matchingStepKeys.length > 0) setCompletedStepKeys((current) => mergeKeys(current, matchingStepKeys));
|
|
294
178
|
let networkOk = true;
|
|
295
179
|
if (hostedMode && client && experienceKey) try {
|
|
296
180
|
applyProgress((await client.track({
|
|
297
|
-
environment,
|
|
298
181
|
eventName,
|
|
299
182
|
experienceKey,
|
|
300
183
|
origin: currentOrigin,
|
|
@@ -323,7 +206,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
323
206
|
client,
|
|
324
207
|
currentRoute,
|
|
325
208
|
currentOrigin,
|
|
326
|
-
environment,
|
|
327
209
|
experienceKey,
|
|
328
210
|
hostedMode,
|
|
329
211
|
onTrack,
|
|
@@ -332,6 +214,52 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
332
214
|
user,
|
|
333
215
|
versionId
|
|
334
216
|
]);
|
|
217
|
+
const submitSurvey = useCallback(async (answers) => {
|
|
218
|
+
if (resolvedLoading || !resolvedSpec) return {
|
|
219
|
+
ok: false,
|
|
220
|
+
reason: "not-ready"
|
|
221
|
+
};
|
|
222
|
+
if (!user) return {
|
|
223
|
+
ok: false,
|
|
224
|
+
reason: "missing-user"
|
|
225
|
+
};
|
|
226
|
+
if (resolvedSpec.type !== "survey" || !resolvedSpec.survey) return {
|
|
227
|
+
ok: false,
|
|
228
|
+
reason: "not-survey"
|
|
229
|
+
};
|
|
230
|
+
if (!hostedMode || !client || !experienceKey) return {
|
|
231
|
+
ok: false,
|
|
232
|
+
reason: "not-ready"
|
|
233
|
+
};
|
|
234
|
+
try {
|
|
235
|
+
await client.submitSurvey({
|
|
236
|
+
answers,
|
|
237
|
+
experienceKey,
|
|
238
|
+
origin: currentOrigin,
|
|
239
|
+
route: currentRoute,
|
|
240
|
+
user: toRuntimeUser(user),
|
|
241
|
+
versionId: versionId ?? void 0
|
|
242
|
+
});
|
|
243
|
+
} catch {
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
reason: "network-error"
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
refreshBootstrap();
|
|
250
|
+
return { ok: true };
|
|
251
|
+
}, [
|
|
252
|
+
client,
|
|
253
|
+
currentOrigin,
|
|
254
|
+
currentRoute,
|
|
255
|
+
experienceKey,
|
|
256
|
+
hostedMode,
|
|
257
|
+
refreshBootstrap,
|
|
258
|
+
resolvedLoading,
|
|
259
|
+
resolvedSpec,
|
|
260
|
+
user,
|
|
261
|
+
versionId
|
|
262
|
+
]);
|
|
335
263
|
const navigateToStep = useCallback(async (step) => {
|
|
336
264
|
if (!step.route) return;
|
|
337
265
|
await navigateWithFallback(step.route, navigate);
|
|
@@ -349,14 +277,12 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
349
277
|
ok: false,
|
|
350
278
|
reason: "missing-tour"
|
|
351
279
|
};
|
|
352
|
-
setTourError(null);
|
|
353
280
|
lastNavigatedTourStep.current = null;
|
|
354
281
|
setActiveTourState({
|
|
355
282
|
key: tourKey,
|
|
356
283
|
stepIndex: 0
|
|
357
284
|
});
|
|
358
285
|
if (hostedMode && client && experienceKey && user) client.startTour({
|
|
359
|
-
environment,
|
|
360
286
|
experienceKey,
|
|
361
287
|
origin: currentOrigin,
|
|
362
288
|
route: currentRoute,
|
|
@@ -369,7 +295,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
369
295
|
client,
|
|
370
296
|
currentRoute,
|
|
371
297
|
currentOrigin,
|
|
372
|
-
environment,
|
|
373
298
|
experienceKey,
|
|
374
299
|
hostedMode,
|
|
375
300
|
resolvedLoading,
|
|
@@ -381,10 +306,8 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
381
306
|
const dismissed = activeTourState;
|
|
382
307
|
const dismissedStepKey = dismissed ? resolvedSpec?.tours[dismissed.key]?.steps[dismissed.stepIndex]?.key : void 0;
|
|
383
308
|
setActiveTourState(null);
|
|
384
|
-
setTourError(null);
|
|
385
309
|
lastNavigatedTourStep.current = null;
|
|
386
310
|
if (!options?.silent && hostedMode && client && experienceKey && user && dismissed && dismissedStepKey) client.dismissTour({
|
|
387
|
-
environment,
|
|
388
311
|
experienceKey,
|
|
389
312
|
origin: currentOrigin,
|
|
390
313
|
route: currentRoute,
|
|
@@ -399,7 +322,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
399
322
|
client,
|
|
400
323
|
currentOrigin,
|
|
401
324
|
currentRoute,
|
|
402
|
-
environment,
|
|
403
325
|
experienceKey,
|
|
404
326
|
hostedMode,
|
|
405
327
|
resolvedSpec,
|
|
@@ -411,7 +333,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
411
333
|
if (!hostedMode || !client || !experienceKey || !user) return;
|
|
412
334
|
try {
|
|
413
335
|
applyProgress((await client.completeTour({
|
|
414
|
-
environment,
|
|
415
336
|
experienceKey,
|
|
416
337
|
origin: currentOrigin,
|
|
417
338
|
route: currentRoute,
|
|
@@ -425,7 +346,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
425
346
|
completeTour,
|
|
426
347
|
currentRoute,
|
|
427
348
|
currentOrigin,
|
|
428
|
-
environment,
|
|
429
349
|
experienceKey,
|
|
430
350
|
hostedMode,
|
|
431
351
|
user,
|
|
@@ -445,7 +365,6 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
445
365
|
ok: false,
|
|
446
366
|
reason: "missing-tour"
|
|
447
367
|
};
|
|
448
|
-
setTourError(null);
|
|
449
368
|
lastNavigatedTourStep.current = null;
|
|
450
369
|
if (activeTourState.stepIndex >= tour.steps.length - 1) {
|
|
451
370
|
const completedKey = activeTourState.key;
|
|
@@ -481,48 +400,41 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
481
400
|
currentRoute,
|
|
482
401
|
resolvedSpec
|
|
483
402
|
]);
|
|
484
|
-
const reportTourError = useCallback((error) => {
|
|
485
|
-
setTourError(error);
|
|
486
|
-
}, []);
|
|
487
403
|
const value = useMemo(() => ({
|
|
488
404
|
spec: resolvedSpec,
|
|
489
|
-
user,
|
|
405
|
+
user: effectiveUser,
|
|
490
406
|
loading: resolvedLoading,
|
|
491
|
-
mode: resolvedMode,
|
|
492
407
|
route: currentRoute,
|
|
493
408
|
completedStepKeys: effectiveCompletedStepKeys,
|
|
494
409
|
completedTourKeys: effectiveCompletedTourKeys,
|
|
495
410
|
completedCount,
|
|
496
411
|
totalSteps,
|
|
497
412
|
activeTour,
|
|
498
|
-
tourError,
|
|
499
413
|
track,
|
|
414
|
+
submitSurvey,
|
|
500
415
|
startTour,
|
|
501
416
|
dismissTour,
|
|
502
417
|
advanceTour,
|
|
503
418
|
navigateToStep,
|
|
504
419
|
startChecklistStep,
|
|
505
|
-
reportTourError,
|
|
506
420
|
visibleHotspots
|
|
507
421
|
}), [
|
|
508
422
|
resolvedSpec,
|
|
509
|
-
|
|
423
|
+
effectiveUser,
|
|
510
424
|
resolvedLoading,
|
|
511
|
-
resolvedMode,
|
|
512
425
|
currentRoute,
|
|
513
426
|
effectiveCompletedStepKeys,
|
|
514
427
|
effectiveCompletedTourKeys,
|
|
515
428
|
completedCount,
|
|
516
429
|
totalSteps,
|
|
517
430
|
activeTour,
|
|
518
|
-
tourError,
|
|
519
431
|
track,
|
|
432
|
+
submitSurvey,
|
|
520
433
|
startTour,
|
|
521
434
|
dismissTour,
|
|
522
435
|
advanceTour,
|
|
523
436
|
navigateToStep,
|
|
524
437
|
startChecklistStep,
|
|
525
|
-
reportTourError,
|
|
526
438
|
visibleHotspots
|
|
527
439
|
]);
|
|
528
440
|
return /* @__PURE__ */ jsx(SunboardContext.Provider, {
|
|
@@ -531,47 +443,56 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
|
|
|
531
443
|
});
|
|
532
444
|
}
|
|
533
445
|
function useSunboardBootstrap(client, options) {
|
|
534
|
-
const { enabled,
|
|
446
|
+
const { enabled, nonce, origin, route, user } = options;
|
|
535
447
|
const [state, setState] = useState({
|
|
448
|
+
attributes: null,
|
|
536
449
|
experience: null,
|
|
537
|
-
loading: false
|
|
450
|
+
loading: false,
|
|
451
|
+
survey: null
|
|
538
452
|
});
|
|
539
453
|
const warnedSdkAdvisory = useRef(false);
|
|
540
454
|
useEffect(() => {
|
|
541
455
|
if (!client || !enabled || !user?.id) {
|
|
542
456
|
setState({
|
|
457
|
+
attributes: null,
|
|
543
458
|
experience: null,
|
|
544
|
-
loading: false
|
|
459
|
+
loading: false,
|
|
460
|
+
survey: null
|
|
545
461
|
});
|
|
546
462
|
return;
|
|
547
463
|
}
|
|
548
464
|
const controller = new AbortController();
|
|
549
465
|
setState({
|
|
466
|
+
attributes: null,
|
|
550
467
|
experience: null,
|
|
551
|
-
loading: true
|
|
468
|
+
loading: true,
|
|
469
|
+
survey: null
|
|
552
470
|
});
|
|
553
471
|
client.bootstrap({
|
|
554
|
-
environment,
|
|
555
472
|
origin,
|
|
556
473
|
route,
|
|
557
474
|
user: toRuntimeUser(user)
|
|
558
475
|
}).then((response) => {
|
|
559
476
|
if (controller.signal.aborted) return;
|
|
560
|
-
if (response.experiences.length === 0 && isDevEnvironment()) console.warn(`[sunboard] bootstrap returned no active experiences for route "${route}"
|
|
477
|
+
if (response.experiences.length === 0 && !response.survey && isDevEnvironment()) console.warn(`[sunboard] bootstrap returned no active experiences for route "${route}". Check the dashboard for an enabled deployment whose trigger.route matches (the publishable key selects the environment).`);
|
|
561
478
|
if (response.sdkAdvisory && isDevEnvironment() && !warnedSdkAdvisory.current) {
|
|
562
479
|
warnedSdkAdvisory.current = true;
|
|
563
480
|
console.warn(`[sunboard] ${response.sdkAdvisory.message}`);
|
|
564
481
|
}
|
|
565
482
|
setState({
|
|
483
|
+
attributes: response.attributes ?? null,
|
|
566
484
|
experience: response.experiences[0] ?? null,
|
|
567
|
-
loading: false
|
|
485
|
+
loading: false,
|
|
486
|
+
survey: response.survey ?? null
|
|
568
487
|
});
|
|
569
488
|
}).catch((error) => {
|
|
570
489
|
if (controller.signal.aborted) return;
|
|
571
490
|
if (isDevEnvironment()) console.warn("[sunboard] bootstrap failed", error);
|
|
572
491
|
setState({
|
|
492
|
+
attributes: null,
|
|
573
493
|
experience: null,
|
|
574
|
-
loading: false
|
|
494
|
+
loading: false,
|
|
495
|
+
survey: null
|
|
575
496
|
});
|
|
576
497
|
});
|
|
577
498
|
return () => {
|
|
@@ -580,7 +501,7 @@ function useSunboardBootstrap(client, options) {
|
|
|
580
501
|
}, [
|
|
581
502
|
client,
|
|
582
503
|
enabled,
|
|
583
|
-
|
|
504
|
+
nonce,
|
|
584
505
|
origin,
|
|
585
506
|
user?.id
|
|
586
507
|
]);
|
|
@@ -685,19 +606,18 @@ function useSunboard() {
|
|
|
685
606
|
|
|
686
607
|
//#endregion
|
|
687
608
|
//#region src/hooks/useChecklist.ts
|
|
688
|
-
const SUPPORTED_CHECKLIST_TYPES = new Set(["checklist", "hybrid"]);
|
|
689
609
|
function actionLabelFor(step) {
|
|
690
610
|
if (step.action?.type === "startTour") return "Start";
|
|
691
611
|
if (step.action || step.route) return "Open";
|
|
692
612
|
}
|
|
693
|
-
function interpolateOptional(text, user) {
|
|
613
|
+
function interpolateOptional$1(text, user) {
|
|
694
614
|
if (!text) return void 0;
|
|
695
615
|
return interpolateTokens(text, user) || void 0;
|
|
696
616
|
}
|
|
697
617
|
function useChecklist() {
|
|
698
618
|
const { spec, user, loading, completedStepKeys, completedCount, totalSteps, startChecklistStep } = useSunboard();
|
|
699
619
|
const [dismissedKey, setDismissedKey] = useState(null);
|
|
700
|
-
const hasChecklist = Boolean(spec &&
|
|
620
|
+
const hasChecklist = Boolean(spec && spec.checklist?.steps.length);
|
|
701
621
|
const allCompleted = totalSteps > 0 && completedCount >= totalSteps;
|
|
702
622
|
const ready = !loading && !!user && !!spec && hasChecklist;
|
|
703
623
|
const specKey = spec?.key ?? null;
|
|
@@ -712,17 +632,17 @@ function useChecklist() {
|
|
|
712
632
|
const sawIncompleteFirst = observationRef.current?.sawIncomplete ?? false;
|
|
713
633
|
const dismissed = dismissedKey !== null && dismissedKey === observationKey;
|
|
714
634
|
const rawSteps = useMemo(() => spec?.checklist?.steps ?? [], [spec]);
|
|
715
|
-
const title = useMemo(() => interpolateOptional(spec?.title, user ?? null), [spec?.title, user]);
|
|
716
|
-
const subtitle = useMemo(() => interpolateOptional(spec?.subtitle, user ?? null), [spec?.subtitle, user]);
|
|
635
|
+
const title = useMemo(() => interpolateOptional$1(spec?.title, user ?? null), [spec?.title, user]);
|
|
636
|
+
const subtitle = useMemo(() => interpolateOptional$1(spec?.subtitle, user ?? null), [spec?.subtitle, user]);
|
|
717
637
|
const completion = useMemo(() => {
|
|
718
638
|
const raw = spec?.completion;
|
|
719
639
|
if (!raw) return void 0;
|
|
720
|
-
const interpolatedTitle = interpolateOptional(raw.title, user ?? null);
|
|
640
|
+
const interpolatedTitle = interpolateOptional$1(raw.title, user ?? null);
|
|
721
641
|
if (!interpolatedTitle) return void 0;
|
|
722
642
|
return {
|
|
723
643
|
title: interpolatedTitle,
|
|
724
|
-
subtitle: interpolateOptional(raw.subtitle, user ?? null),
|
|
725
|
-
message: interpolateOptional(raw.message, user ?? null),
|
|
644
|
+
subtitle: interpolateOptional$1(raw.subtitle, user ?? null),
|
|
645
|
+
message: interpolateOptional$1(raw.message, user ?? null),
|
|
726
646
|
dismissAfterMs: raw.dismissAfterMs
|
|
727
647
|
};
|
|
728
648
|
}, [spec?.completion, user]);
|
|
@@ -768,6 +688,140 @@ function useChecklist() {
|
|
|
768
688
|
};
|
|
769
689
|
}
|
|
770
690
|
|
|
691
|
+
//#endregion
|
|
692
|
+
//#region src/hooks/useSurvey.ts
|
|
693
|
+
function interpolateOptional(text, user) {
|
|
694
|
+
if (!text) return void 0;
|
|
695
|
+
return interpolateTokens(text, user) || void 0;
|
|
696
|
+
}
|
|
697
|
+
function answered(value) {
|
|
698
|
+
if (value === void 0 || value === null) return false;
|
|
699
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
700
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
function useSurvey() {
|
|
704
|
+
const { spec, user, loading, submitSurvey } = useSunboard();
|
|
705
|
+
const [answers, setAnswers] = useState({});
|
|
706
|
+
const [submitting, setSubmitting] = useState(false);
|
|
707
|
+
const [submitted, setSubmitted] = useState(false);
|
|
708
|
+
const [error, setError] = useState(null);
|
|
709
|
+
const [stepIndex, setStepIndex] = useState(0);
|
|
710
|
+
const isSurvey = Boolean(spec && spec.type === "survey" && spec.survey);
|
|
711
|
+
const ready = !loading && !!user && isSurvey;
|
|
712
|
+
const title = useMemo(() => interpolateOptional(spec?.title, user ?? null), [spec?.title, user]);
|
|
713
|
+
const subtitle = useMemo(() => interpolateOptional(spec?.subtitle, user ?? null), [spec?.subtitle, user]);
|
|
714
|
+
const steps = useMemo(() => {
|
|
715
|
+
return (spec?.survey?.steps ?? []).map((step) => ({
|
|
716
|
+
title: interpolateOptional(step.title, user ?? null),
|
|
717
|
+
subtitle: interpolateOptional(step.subtitle, user ?? null),
|
|
718
|
+
questions: step.questions.map((question) => ({
|
|
719
|
+
key: question.key,
|
|
720
|
+
prompt: interpolateTokens(question.prompt, user ?? null),
|
|
721
|
+
type: question.type,
|
|
722
|
+
required: question.required,
|
|
723
|
+
options: (question.options ?? []).map((option) => ({
|
|
724
|
+
value: option.value,
|
|
725
|
+
label: interpolateTokens(option.label, user ?? null)
|
|
726
|
+
}))
|
|
727
|
+
}))
|
|
728
|
+
}));
|
|
729
|
+
}, [spec?.survey, user]);
|
|
730
|
+
const questions = useMemo(() => steps.flatMap((step) => step.questions), [steps]);
|
|
731
|
+
const signature = questions.map((question) => question.key).join("|");
|
|
732
|
+
const signatureRef = useRef(signature);
|
|
733
|
+
if (signatureRef.current !== signature) {
|
|
734
|
+
signatureRef.current = signature;
|
|
735
|
+
if (stepIndex !== 0) setStepIndex(0);
|
|
736
|
+
}
|
|
737
|
+
const totalSteps = steps.length;
|
|
738
|
+
const safeIndex = totalSteps > 0 ? Math.min(stepIndex, totalSteps - 1) : 0;
|
|
739
|
+
const currentStep = steps[safeIndex];
|
|
740
|
+
const setAnswer = useCallback((key, value) => {
|
|
741
|
+
setAnswers((current) => ({
|
|
742
|
+
...current,
|
|
743
|
+
[key]: value
|
|
744
|
+
}));
|
|
745
|
+
}, []);
|
|
746
|
+
const toggleAnswer = useCallback((key, value) => {
|
|
747
|
+
setAnswers((current) => {
|
|
748
|
+
const existing = Array.isArray(current[key]) ? current[key] : [];
|
|
749
|
+
const next$1 = existing.includes(value) ? existing.filter((item) => item !== value) : [...existing, value];
|
|
750
|
+
return {
|
|
751
|
+
...current,
|
|
752
|
+
[key]: next$1
|
|
753
|
+
};
|
|
754
|
+
});
|
|
755
|
+
}, []);
|
|
756
|
+
const isAnswered = useCallback((key) => answered(answers[key]), [answers]);
|
|
757
|
+
const canSubmit = useMemo(() => {
|
|
758
|
+
if (!ready || submitting) return false;
|
|
759
|
+
return questions.filter((question) => question.required).every((question) => answered(answers[question.key]));
|
|
760
|
+
}, [
|
|
761
|
+
answers,
|
|
762
|
+
questions,
|
|
763
|
+
ready,
|
|
764
|
+
submitting
|
|
765
|
+
]);
|
|
766
|
+
const canAdvance = useMemo(() => {
|
|
767
|
+
if (!currentStep) return false;
|
|
768
|
+
return currentStep.questions.filter((question) => question.required).every((question) => answered(answers[question.key]));
|
|
769
|
+
}, [answers, currentStep]);
|
|
770
|
+
const submit = useCallback(async () => {
|
|
771
|
+
if (!canSubmit) return;
|
|
772
|
+
setSubmitting(true);
|
|
773
|
+
setError(null);
|
|
774
|
+
const result = await submitSurvey(answers);
|
|
775
|
+
setSubmitting(false);
|
|
776
|
+
if (result.ok) {
|
|
777
|
+
setSubmitted(true);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
setError(result.reason === "network-error" ? "Something went wrong. Please try again." : "Survey is not ready.");
|
|
781
|
+
}, [
|
|
782
|
+
answers,
|
|
783
|
+
canSubmit,
|
|
784
|
+
submitSurvey
|
|
785
|
+
]);
|
|
786
|
+
const isLastStep = totalSteps === 0 || safeIndex >= totalSteps - 1;
|
|
787
|
+
const isFirstStep = safeIndex === 0;
|
|
788
|
+
const next = useCallback(() => {
|
|
789
|
+
if (isLastStep) submit();
|
|
790
|
+
else setStepIndex(safeIndex + 1);
|
|
791
|
+
}, [
|
|
792
|
+
isLastStep,
|
|
793
|
+
safeIndex,
|
|
794
|
+
submit
|
|
795
|
+
]);
|
|
796
|
+
const back = useCallback(() => {
|
|
797
|
+
setStepIndex((current) => Math.max(0, current - 1));
|
|
798
|
+
}, []);
|
|
799
|
+
return {
|
|
800
|
+
visible: ready && !submitted,
|
|
801
|
+
title,
|
|
802
|
+
subtitle,
|
|
803
|
+
questions,
|
|
804
|
+
steps,
|
|
805
|
+
stepIndex: safeIndex,
|
|
806
|
+
totalSteps,
|
|
807
|
+
currentStep,
|
|
808
|
+
progress: totalSteps > 0 ? (safeIndex + 1) / totalSteps * 100 : 0,
|
|
809
|
+
isFirstStep,
|
|
810
|
+
isLastStep,
|
|
811
|
+
canAdvance,
|
|
812
|
+
next,
|
|
813
|
+
back,
|
|
814
|
+
answers,
|
|
815
|
+
setAnswer,
|
|
816
|
+
toggleAnswer,
|
|
817
|
+
isAnswered,
|
|
818
|
+
canSubmit,
|
|
819
|
+
submitting,
|
|
820
|
+
error,
|
|
821
|
+
submit
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
771
825
|
//#endregion
|
|
772
826
|
//#region src/icons/index.tsx
|
|
773
827
|
const CheckIcon = () => {
|
|
@@ -932,7 +986,7 @@ function Hotspot({ hotspot, hotspotKey }) {
|
|
|
932
986
|
//#endregion
|
|
933
987
|
//#region src/components/SunboardTour.tsx
|
|
934
988
|
function SunboardTour() {
|
|
935
|
-
const { activeTour, advanceTour, dismissTour,
|
|
989
|
+
const { activeTour, advanceTour, dismissTour, route } = useSunboard();
|
|
936
990
|
const arrowRef = useRef(null);
|
|
937
991
|
const [spotlightRect, setSpotlightRect] = useState(null);
|
|
938
992
|
const routeReady = routeMatches(activeTour?.step.route, route ?? "/");
|
|
@@ -947,36 +1001,16 @@ function SunboardTour() {
|
|
|
947
1001
|
]
|
|
948
1002
|
});
|
|
949
1003
|
useEffect(() => {
|
|
950
|
-
if (!activeTour)
|
|
951
|
-
reportTourError(null);
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
if (!routeReady) return;
|
|
955
|
-
if (!target.timedOut) return;
|
|
956
|
-
const error = {
|
|
957
|
-
message: "Sunboard could not find the tour target.",
|
|
958
|
-
route: activeTour.step.route,
|
|
959
|
-
stepKey: activeTour.step.key,
|
|
960
|
-
target: activeTour.step.target,
|
|
961
|
-
tourKey: activeTour.key
|
|
962
|
-
};
|
|
963
|
-
if (mode === "preview") {
|
|
964
|
-
reportTourError(error);
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
reportTourError(null);
|
|
1004
|
+
if (!activeTour || !routeReady || !target.timedOut) return;
|
|
968
1005
|
dismissTour({ silent: true });
|
|
969
1006
|
}, [
|
|
970
1007
|
activeTour,
|
|
971
1008
|
dismissTour,
|
|
972
|
-
mode,
|
|
973
|
-
reportTourError,
|
|
974
1009
|
routeReady,
|
|
975
1010
|
target.timedOut
|
|
976
1011
|
]);
|
|
977
1012
|
useEffect(() => {
|
|
978
1013
|
if (!activeTour || !target.element) return;
|
|
979
|
-
reportTourError(null);
|
|
980
1014
|
refs.setReference(target.element);
|
|
981
1015
|
target.element.scrollIntoView({
|
|
982
1016
|
block: "center",
|
|
@@ -986,7 +1020,6 @@ function SunboardTour() {
|
|
|
986
1020
|
}, [
|
|
987
1021
|
activeTour,
|
|
988
1022
|
refs,
|
|
989
|
-
reportTourError,
|
|
990
1023
|
target.element,
|
|
991
1024
|
update
|
|
992
1025
|
]);
|
|
@@ -1029,22 +1062,6 @@ function SunboardTour() {
|
|
|
1029
1062
|
const stepTitle = useInterpolated(activeTour?.step.title);
|
|
1030
1063
|
const stepBody = useInterpolated(activeTour?.step.body);
|
|
1031
1064
|
if (!activeTour) return null;
|
|
1032
|
-
if (tourError && mode === "preview") return /* @__PURE__ */ jsxs("div", {
|
|
1033
|
-
className: "sunboard-debug",
|
|
1034
|
-
role: "alert",
|
|
1035
|
-
children: [
|
|
1036
|
-
/* @__PURE__ */ jsx("strong", { children: tourError.message }),
|
|
1037
|
-
/* @__PURE__ */ jsxs("span", { children: ["Tour: ", tourError.tourKey] }),
|
|
1038
|
-
/* @__PURE__ */ jsxs("span", { children: ["Step: ", tourError.stepKey] }),
|
|
1039
|
-
tourError.route ? /* @__PURE__ */ jsxs("span", { children: ["Route: ", tourError.route] }) : null,
|
|
1040
|
-
tourError.target ? /* @__PURE__ */ jsxs("span", { children: ["Target: ", tourError.target] }) : null,
|
|
1041
|
-
/* @__PURE__ */ jsx("button", {
|
|
1042
|
-
onClick: () => void dismissTour(),
|
|
1043
|
-
type: "button",
|
|
1044
|
-
children: "Dismiss"
|
|
1045
|
-
})
|
|
1046
|
-
]
|
|
1047
|
-
});
|
|
1048
1065
|
if (!routeReady || !target.element) return null;
|
|
1049
1066
|
const isLastStep = activeTour.stepIndex === activeTour.totalSteps - 1;
|
|
1050
1067
|
return /* @__PURE__ */ jsxs(FloatingPortal, { children: [/* @__PURE__ */ jsx(SunboardTourScrim, { rect: spotlightRect }), /* @__PURE__ */ jsxs("section", {
|
|
@@ -1254,12 +1271,130 @@ function SunboardChecklist() {
|
|
|
1254
1271
|
]
|
|
1255
1272
|
});
|
|
1256
1273
|
}
|
|
1274
|
+
function SunboardSurvey() {
|
|
1275
|
+
const { visible, title, subtitle, currentStep, stepIndex, totalSteps, progress, isFirstStep, isLastStep, canAdvance, canSubmit, next, back, answers, setAnswer, toggleAnswer, submitting, error } = useSurvey();
|
|
1276
|
+
if (!visible || !title || !currentStep) return null;
|
|
1277
|
+
const showProgress = totalSteps > 1;
|
|
1278
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1279
|
+
className: "sunboard-survey-overlay",
|
|
1280
|
+
children: /* @__PURE__ */ jsxs("section", {
|
|
1281
|
+
className: "sunboard-survey-card",
|
|
1282
|
+
role: "dialog",
|
|
1283
|
+
"aria-modal": "true",
|
|
1284
|
+
"aria-label": `${title} survey`,
|
|
1285
|
+
children: [
|
|
1286
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1287
|
+
className: "sunboard-survey-head",
|
|
1288
|
+
children: [subtitle ? /* @__PURE__ */ jsx("p", {
|
|
1289
|
+
className: "sunboard-eyebrow",
|
|
1290
|
+
children: subtitle
|
|
1291
|
+
}) : null, /* @__PURE__ */ jsx("h2", {
|
|
1292
|
+
className: "sunboard-title",
|
|
1293
|
+
children: title
|
|
1294
|
+
})]
|
|
1295
|
+
}),
|
|
1296
|
+
showProgress ? /* @__PURE__ */ jsxs("div", {
|
|
1297
|
+
className: "sunboard-survey-progress-row",
|
|
1298
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
1299
|
+
className: "sunboard-survey-stepcount",
|
|
1300
|
+
children: [
|
|
1301
|
+
"Step ",
|
|
1302
|
+
stepIndex + 1,
|
|
1303
|
+
" of ",
|
|
1304
|
+
totalSteps
|
|
1305
|
+
]
|
|
1306
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1307
|
+
className: "sunboard-progress",
|
|
1308
|
+
role: "progressbar",
|
|
1309
|
+
"aria-label": `${title} progress`,
|
|
1310
|
+
"aria-valuemin": 1,
|
|
1311
|
+
"aria-valuemax": totalSteps,
|
|
1312
|
+
"aria-valuenow": stepIndex + 1,
|
|
1313
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1314
|
+
className: "sunboard-progress-fill",
|
|
1315
|
+
style: { width: `${progress}%` }
|
|
1316
|
+
})
|
|
1317
|
+
})]
|
|
1318
|
+
}) : null,
|
|
1319
|
+
/* @__PURE__ */ jsxs("form", {
|
|
1320
|
+
className: "sunboard-survey-form",
|
|
1321
|
+
onSubmit: (event) => {
|
|
1322
|
+
event.preventDefault();
|
|
1323
|
+
next();
|
|
1324
|
+
},
|
|
1325
|
+
children: [
|
|
1326
|
+
currentStep.title ? /* @__PURE__ */ jsx("p", {
|
|
1327
|
+
className: "sunboard-survey-step-title",
|
|
1328
|
+
children: currentStep.title
|
|
1329
|
+
}) : null,
|
|
1330
|
+
/* @__PURE__ */ jsx("ol", {
|
|
1331
|
+
className: "sunboard-survey-questions",
|
|
1332
|
+
children: currentStep.questions.map((question) => /* @__PURE__ */ jsxs("li", {
|
|
1333
|
+
className: "sunboard-survey-question",
|
|
1334
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
1335
|
+
className: "sunboard-survey-prompt",
|
|
1336
|
+
children: [question.prompt, question.required ? /* @__PURE__ */ jsx("span", {
|
|
1337
|
+
"aria-hidden": "true",
|
|
1338
|
+
className: "sunboard-survey-required",
|
|
1339
|
+
children: " *"
|
|
1340
|
+
}) : null]
|
|
1341
|
+
}), question.type === "text" ? /* @__PURE__ */ jsx("input", {
|
|
1342
|
+
className: "sunboard-survey-input",
|
|
1343
|
+
type: "text",
|
|
1344
|
+
value: typeof answers[question.key] === "string" ? answers[question.key] : "",
|
|
1345
|
+
required: question.required,
|
|
1346
|
+
"aria-label": question.prompt,
|
|
1347
|
+
onChange: (event) => setAnswer(question.key, event.target.value)
|
|
1348
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1349
|
+
className: "sunboard-survey-options",
|
|
1350
|
+
role: "group",
|
|
1351
|
+
"aria-label": question.prompt,
|
|
1352
|
+
children: question.options.map((option) => {
|
|
1353
|
+
const selected = question.type === "multi-select" ? Array.isArray(answers[question.key]) && answers[question.key].includes(option.value) : answers[question.key] === option.value;
|
|
1354
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1355
|
+
className: "sunboard-survey-option",
|
|
1356
|
+
type: "button",
|
|
1357
|
+
"aria-pressed": selected,
|
|
1358
|
+
"data-selected": selected ? "true" : "false",
|
|
1359
|
+
onClick: () => question.type === "multi-select" ? toggleAnswer(question.key, option.value) : setAnswer(question.key, option.value),
|
|
1360
|
+
children: option.label
|
|
1361
|
+
}, option.value);
|
|
1362
|
+
})
|
|
1363
|
+
})]
|
|
1364
|
+
}, question.key))
|
|
1365
|
+
}),
|
|
1366
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
1367
|
+
className: "sunboard-survey-error",
|
|
1368
|
+
role: "alert",
|
|
1369
|
+
children: error
|
|
1370
|
+
}) : null,
|
|
1371
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1372
|
+
className: "sunboard-survey-nav",
|
|
1373
|
+
children: [!isFirstStep ? /* @__PURE__ */ jsx("button", {
|
|
1374
|
+
className: "sunboard-survey-back",
|
|
1375
|
+
type: "button",
|
|
1376
|
+
onClick: back,
|
|
1377
|
+
children: "Back"
|
|
1378
|
+
}) : /* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsx("button", {
|
|
1379
|
+
className: "sunboard-survey-submit",
|
|
1380
|
+
type: "submit",
|
|
1381
|
+
disabled: isLastStep ? !canSubmit : !canAdvance,
|
|
1382
|
+
children: isLastStep ? submitting ? "Submitting…" : "Finish" : "Continue"
|
|
1383
|
+
})]
|
|
1384
|
+
})
|
|
1385
|
+
]
|
|
1386
|
+
})
|
|
1387
|
+
]
|
|
1388
|
+
})
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1257
1391
|
const Sunboard = Object.assign(SunboardChecklist, {
|
|
1258
1392
|
Checklist: SunboardChecklist,
|
|
1393
|
+
Survey: SunboardSurvey,
|
|
1259
1394
|
Hotspots: SunboardHotspots,
|
|
1260
1395
|
Tour: SunboardTour
|
|
1261
1396
|
});
|
|
1262
1397
|
|
|
1263
1398
|
//#endregion
|
|
1264
|
-
export { Sunboard, SunboardChecklist, SunboardHotspots, SunboardProvider, SunboardRuntimeApiError, SunboardRuntimeClient, SunboardTour, useChecklist, useSunboard,
|
|
1399
|
+
export { Sunboard, SunboardChecklist, SunboardHotspots, SunboardProvider, SunboardRuntimeApiError, SunboardRuntimeClient, SunboardSurvey, SunboardTour, useChecklist, useSunboard, useSurvey };
|
|
1265
1400
|
//# sourceMappingURL=index.mjs.map
|