@landform.io/sdk 0.1.2
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/api/index.cjs +17 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +48 -0
- package/dist/api/index.d.ts +48 -0
- package/dist/api/index.js +4 -0
- package/dist/api/index.js.map +1 -0
- package/dist/chunk-52W6RI6C.cjs +4 -0
- package/dist/chunk-52W6RI6C.cjs.map +1 -0
- package/dist/chunk-DASQI7IA.cjs +4 -0
- package/dist/chunk-DASQI7IA.cjs.map +1 -0
- package/dist/chunk-EL7YGOTY.js +3 -0
- package/dist/chunk-EL7YGOTY.js.map +1 -0
- package/dist/chunk-GVOTY5NG.js +3 -0
- package/dist/chunk-GVOTY5NG.js.map +1 -0
- package/dist/chunk-HUIMGLDC.js +488 -0
- package/dist/chunk-HUIMGLDC.js.map +1 -0
- package/dist/chunk-I6L5OCM3.js +1657 -0
- package/dist/chunk-I6L5OCM3.js.map +1 -0
- package/dist/chunk-IQXKFO6Q.cjs +55 -0
- package/dist/chunk-IQXKFO6Q.cjs.map +1 -0
- package/dist/chunk-P2GUKJHH.cjs +117 -0
- package/dist/chunk-P2GUKJHH.cjs.map +1 -0
- package/dist/chunk-PDUJU32P.js +687 -0
- package/dist/chunk-PDUJU32P.js.map +1 -0
- package/dist/chunk-PKHTPGWQ.js +114 -0
- package/dist/chunk-PKHTPGWQ.js.map +1 -0
- package/dist/chunk-V7WAYO2Q.js +48 -0
- package/dist/chunk-V7WAYO2Q.js.map +1 -0
- package/dist/chunk-WHV333XL.cjs +1684 -0
- package/dist/chunk-WHV333XL.cjs.map +1 -0
- package/dist/chunk-WIFNU3FA.cjs +497 -0
- package/dist/chunk-WIFNU3FA.cjs.map +1 -0
- package/dist/chunk-ZLOP4BTK.cjs +695 -0
- package/dist/chunk-ZLOP4BTK.cjs.map +1 -0
- package/dist/components/index.cjs +99 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +166 -0
- package/dist/components/index.d.ts +166 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.cjs +35 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +75 -0
- package/dist/hooks/index.d.ts +75 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index-DoRZImTl.d.cts +590 -0
- package/dist/index-DoRZImTl.d.ts +590 -0
- package/dist/index.cjs +186 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/theme/index.cjs +61 -0
- package/dist/theme/index.cjs.map +1 -0
- package/dist/theme/index.d.cts +65 -0
- package/dist/theme/index.d.ts +65 -0
- package/dist/theme/index.js +4 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/useForm-D1mB6REv.d.ts +48 -0
- package/dist/useForm-kF8xK1mJ.d.cts +48 -0
- package/package.json +101 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('../chunk-52W6RI6C.cjs');
|
|
4
|
+
var chunkP2GUKJHH_cjs = require('../chunk-P2GUKJHH.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Object.defineProperty(exports, "LandformClient", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function () { return chunkP2GUKJHH_cjs.LandformClient; }
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "createClient", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () { return chunkP2GUKJHH_cjs.createClient; }
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=index.cjs.map
|
|
17
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { a3 as ResponseAnswers, a9 as ResponseOutcome, aa as ResponseMetadata } from '../index-DoRZImTl.cjs';
|
|
2
|
+
|
|
3
|
+
interface LandformClientConfig {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
onError?: (error: Error) => void;
|
|
7
|
+
}
|
|
8
|
+
interface StartResponseParams {
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
metadata?: ResponseMetadata;
|
|
11
|
+
hiddenFields?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
interface StartResponseResult {
|
|
14
|
+
id: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
}
|
|
17
|
+
interface UpdateAnswersParams {
|
|
18
|
+
responseId: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
answers: ResponseAnswers;
|
|
21
|
+
lastFieldRef?: string;
|
|
22
|
+
calculatedValues?: Record<string, number>;
|
|
23
|
+
}
|
|
24
|
+
interface CompleteResponseParams {
|
|
25
|
+
responseId: string;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
answers?: ResponseAnswers;
|
|
28
|
+
outcome?: ResponseOutcome;
|
|
29
|
+
calculatedValues?: Record<string, number>;
|
|
30
|
+
captchaToken?: string;
|
|
31
|
+
}
|
|
32
|
+
interface TrackEventParams {
|
|
33
|
+
event: "view" | "start";
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
}
|
|
36
|
+
declare class LandformClient {
|
|
37
|
+
private baseUrl;
|
|
38
|
+
private projectId;
|
|
39
|
+
private onError?;
|
|
40
|
+
constructor(config: LandformClientConfig);
|
|
41
|
+
startResponse(params?: StartResponseParams): Promise<StartResponseResult>;
|
|
42
|
+
updateAnswers(params: UpdateAnswersParams): Promise<void>;
|
|
43
|
+
completeResponse(params: CompleteResponseParams): Promise<void>;
|
|
44
|
+
trackEvent(params: TrackEventParams): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
declare function createClient(config: LandformClientConfig): LandformClient;
|
|
47
|
+
|
|
48
|
+
export { type CompleteResponseParams, LandformClient, type LandformClientConfig, type StartResponseParams, type StartResponseResult, type UpdateAnswersParams, createClient };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { a3 as ResponseAnswers, a9 as ResponseOutcome, aa as ResponseMetadata } from '../index-DoRZImTl.js';
|
|
2
|
+
|
|
3
|
+
interface LandformClientConfig {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
onError?: (error: Error) => void;
|
|
7
|
+
}
|
|
8
|
+
interface StartResponseParams {
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
metadata?: ResponseMetadata;
|
|
11
|
+
hiddenFields?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
interface StartResponseResult {
|
|
14
|
+
id: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
}
|
|
17
|
+
interface UpdateAnswersParams {
|
|
18
|
+
responseId: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
answers: ResponseAnswers;
|
|
21
|
+
lastFieldRef?: string;
|
|
22
|
+
calculatedValues?: Record<string, number>;
|
|
23
|
+
}
|
|
24
|
+
interface CompleteResponseParams {
|
|
25
|
+
responseId: string;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
answers?: ResponseAnswers;
|
|
28
|
+
outcome?: ResponseOutcome;
|
|
29
|
+
calculatedValues?: Record<string, number>;
|
|
30
|
+
captchaToken?: string;
|
|
31
|
+
}
|
|
32
|
+
interface TrackEventParams {
|
|
33
|
+
event: "view" | "start";
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
}
|
|
36
|
+
declare class LandformClient {
|
|
37
|
+
private baseUrl;
|
|
38
|
+
private projectId;
|
|
39
|
+
private onError?;
|
|
40
|
+
constructor(config: LandformClientConfig);
|
|
41
|
+
startResponse(params?: StartResponseParams): Promise<StartResponseResult>;
|
|
42
|
+
updateAnswers(params: UpdateAnswersParams): Promise<void>;
|
|
43
|
+
completeResponse(params: CompleteResponseParams): Promise<void>;
|
|
44
|
+
trackEvent(params: TrackEventParams): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
declare function createClient(config: LandformClientConfig): LandformClient;
|
|
47
|
+
|
|
48
|
+
export { type CompleteResponseParams, LandformClient, type LandformClientConfig, type StartResponseParams, type StartResponseResult, type UpdateAnswersParams, createClient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-52W6RI6C.cjs"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-DASQI7IA.cjs"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-EL7YGOTY.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-GVOTY5NG.js"}
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { generateThemeCSS, generateComponentStyles, isBrowser, injectThemeCSS } from './chunk-PDUJU32P.js';
|
|
2
|
+
import { LandformClient } from './chunk-PKHTPGWQ.js';
|
|
3
|
+
import { createContext, useState, useMemo, useRef, useEffect, useCallback, useContext } from 'react';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
|
|
6
|
+
// src/utils/storage.ts
|
|
7
|
+
var AUTOSAVE_PREFIX = "lf-autosave-";
|
|
8
|
+
var SUBMITTED_PREFIX = "lf-submitted-";
|
|
9
|
+
var COOKIE_CONSENT_KEY = "lf-cookie-consent";
|
|
10
|
+
function saveProgress(projectId, answers, currentIndex, responseId) {
|
|
11
|
+
if (typeof window === "undefined") return;
|
|
12
|
+
try {
|
|
13
|
+
const data = {
|
|
14
|
+
answers,
|
|
15
|
+
currentIndex,
|
|
16
|
+
responseId,
|
|
17
|
+
timestamp: Date.now()
|
|
18
|
+
};
|
|
19
|
+
localStorage.setItem(AUTOSAVE_PREFIX + projectId, JSON.stringify(data));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error("Error saving form progress:", error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function loadProgress(projectId) {
|
|
25
|
+
if (typeof window === "undefined") return null;
|
|
26
|
+
try {
|
|
27
|
+
const stored = localStorage.getItem(AUTOSAVE_PREFIX + projectId);
|
|
28
|
+
if (!stored) return null;
|
|
29
|
+
const data = JSON.parse(stored);
|
|
30
|
+
const maxAge = 7 * 24 * 60 * 60 * 1e3;
|
|
31
|
+
if (Date.now() - data.timestamp > maxAge) {
|
|
32
|
+
clearProgress(projectId);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("Error loading form progress:", error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function clearProgress(projectId) {
|
|
42
|
+
if (typeof window === "undefined") return;
|
|
43
|
+
try {
|
|
44
|
+
localStorage.removeItem(AUTOSAVE_PREFIX + projectId);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error("Error clearing form progress:", error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function markAsSubmitted(projectId) {
|
|
50
|
+
if (typeof window === "undefined") return;
|
|
51
|
+
try {
|
|
52
|
+
localStorage.setItem(SUBMITTED_PREFIX + projectId, String(Date.now()));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Error marking form as submitted:", error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function hasSubmitted(projectId) {
|
|
58
|
+
if (typeof window === "undefined") return false;
|
|
59
|
+
try {
|
|
60
|
+
return localStorage.getItem(SUBMITTED_PREFIX + projectId) !== null;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Error checking submission status:", error);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function hasCookieConsent() {
|
|
67
|
+
if (typeof window === "undefined") return false;
|
|
68
|
+
try {
|
|
69
|
+
return localStorage.getItem(COOKIE_CONSENT_KEY) === "true";
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("Error checking cookie consent:", error);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function setCookieConsent(consented) {
|
|
76
|
+
if (typeof window === "undefined") return;
|
|
77
|
+
try {
|
|
78
|
+
if (consented) {
|
|
79
|
+
localStorage.setItem(COOKIE_CONSENT_KEY, "true");
|
|
80
|
+
} else {
|
|
81
|
+
localStorage.removeItem(COOKIE_CONSENT_KEY);
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("Error setting cookie consent:", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/hooks/useForm.ts
|
|
89
|
+
function validateFieldValue(field, value) {
|
|
90
|
+
if (!field.validations?.required) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (value === void 0 || value === null || value === "") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
function useForm(options) {
|
|
102
|
+
const {
|
|
103
|
+
projectId,
|
|
104
|
+
content,
|
|
105
|
+
settings,
|
|
106
|
+
baseUrl = "",
|
|
107
|
+
initialAnswers = {},
|
|
108
|
+
onComplete,
|
|
109
|
+
onError
|
|
110
|
+
} = options;
|
|
111
|
+
const [responseId, setResponseId] = useState(null);
|
|
112
|
+
const [sessionId] = useState(() => nanoid());
|
|
113
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
114
|
+
const [answers, setAnswers] = useState(initialAnswers);
|
|
115
|
+
const [errors, setErrors] = useState({});
|
|
116
|
+
const [isStarted, setIsStarted] = useState(false);
|
|
117
|
+
const [isCompleted, setIsCompleted] = useState(false);
|
|
118
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
119
|
+
const [isDuplicateSubmission, setIsDuplicateSubmission] = useState(false);
|
|
120
|
+
const [captchaToken, setCaptchaToken] = useState(null);
|
|
121
|
+
const [showCaptcha, setShowCaptcha] = useState(false);
|
|
122
|
+
const client = useMemo(
|
|
123
|
+
() => new LandformClient({ baseUrl, projectId, onError }),
|
|
124
|
+
[baseUrl, projectId, onError]
|
|
125
|
+
);
|
|
126
|
+
const hasTrackedView = useRef(false);
|
|
127
|
+
const hasLoadedProgress = useRef(false);
|
|
128
|
+
const autosaveTimeoutRef = useRef(null);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!hasTrackedView.current) {
|
|
131
|
+
hasTrackedView.current = true;
|
|
132
|
+
client.trackEvent({ event: "view", sessionId });
|
|
133
|
+
}
|
|
134
|
+
}, [client, sessionId]);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (settings.duplicatePrevention && hasSubmitted(projectId)) {
|
|
137
|
+
setIsDuplicateSubmission(true);
|
|
138
|
+
}
|
|
139
|
+
}, [projectId, settings.duplicatePrevention]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (hasLoadedProgress.current || !settings.autosaveProgress) return;
|
|
142
|
+
hasLoadedProgress.current = true;
|
|
143
|
+
const savedProgress = loadProgress(projectId);
|
|
144
|
+
if (savedProgress) {
|
|
145
|
+
setAnswers(savedProgress.answers);
|
|
146
|
+
setCurrentIndex(savedProgress.currentIndex);
|
|
147
|
+
if (savedProgress.responseId) {
|
|
148
|
+
setResponseId(savedProgress.responseId);
|
|
149
|
+
setIsStarted(true);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}, [projectId, settings.autosaveProgress]);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (!settings.autosaveProgress || isCompleted) return;
|
|
155
|
+
if (autosaveTimeoutRef.current) {
|
|
156
|
+
clearTimeout(autosaveTimeoutRef.current);
|
|
157
|
+
}
|
|
158
|
+
autosaveTimeoutRef.current = setTimeout(() => {
|
|
159
|
+
saveProgress(projectId, answers, currentIndex, responseId);
|
|
160
|
+
}, 500);
|
|
161
|
+
return () => {
|
|
162
|
+
if (autosaveTimeoutRef.current) {
|
|
163
|
+
clearTimeout(autosaveTimeoutRef.current);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}, [projectId, answers, currentIndex, responseId, settings.autosaveProgress, isCompleted]);
|
|
167
|
+
const sequence = useMemo(() => {
|
|
168
|
+
const items = [];
|
|
169
|
+
for (const screen of content.welcomeScreens) {
|
|
170
|
+
items.push({ type: "welcome", screen });
|
|
171
|
+
}
|
|
172
|
+
content.fields.forEach((field, index) => {
|
|
173
|
+
items.push({ type: "field", field, index });
|
|
174
|
+
});
|
|
175
|
+
for (const screen of content.thankYouScreens) {
|
|
176
|
+
items.push({ type: "thankYou", screen });
|
|
177
|
+
}
|
|
178
|
+
return items;
|
|
179
|
+
}, [content]);
|
|
180
|
+
const currentItem = sequence[currentIndex] || null;
|
|
181
|
+
const totalFields = content.fields.length;
|
|
182
|
+
const answeredCount = Object.keys(answers).length;
|
|
183
|
+
const progress = totalFields > 0 ? answeredCount / totalFields * 100 : 0;
|
|
184
|
+
const firstFieldIndex = content.welcomeScreens.length;
|
|
185
|
+
const lastFieldIndex = firstFieldIndex + content.fields.length - 1;
|
|
186
|
+
const canGoBack = currentIndex > firstFieldIndex;
|
|
187
|
+
const canGoNext = currentIndex < sequence.length - 1;
|
|
188
|
+
const isOnWelcome = currentItem?.type === "welcome";
|
|
189
|
+
const isOnThankYou = currentItem?.type === "thankYou";
|
|
190
|
+
const isOnField = currentItem?.type === "field";
|
|
191
|
+
const start = useCallback(async () => {
|
|
192
|
+
try {
|
|
193
|
+
const result = await client.startResponse({ sessionId });
|
|
194
|
+
setResponseId(result.id);
|
|
195
|
+
setIsStarted(true);
|
|
196
|
+
client.trackEvent({ event: "start", sessionId });
|
|
197
|
+
if (currentItem?.type === "welcome") {
|
|
198
|
+
setCurrentIndex((prev) => prev + 1);
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
onError?.(error);
|
|
202
|
+
}
|
|
203
|
+
}, [client, sessionId, currentItem, onError]);
|
|
204
|
+
const validateCurrentWithValue = useCallback((pendingValue) => {
|
|
205
|
+
if (currentItem?.type !== "field") return true;
|
|
206
|
+
const field = currentItem.field;
|
|
207
|
+
const value = pendingValue !== void 0 ? pendingValue : answers[field.ref];
|
|
208
|
+
const isValid = validateFieldValue(field, value);
|
|
209
|
+
if (!isValid) {
|
|
210
|
+
const requiredText = settings.systemMessages?.requiredText || "This field is required";
|
|
211
|
+
setErrors((prev) => ({
|
|
212
|
+
...prev,
|
|
213
|
+
[field.ref]: requiredText
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
return isValid;
|
|
217
|
+
}, [currentItem, answers, settings.systemMessages?.requiredText]);
|
|
218
|
+
const validateCurrent = useCallback(() => {
|
|
219
|
+
return validateCurrentWithValue();
|
|
220
|
+
}, [validateCurrentWithValue]);
|
|
221
|
+
const submit = useCallback(async () => {
|
|
222
|
+
if (!responseId) return;
|
|
223
|
+
if (settings.captchaEnabled && !captchaToken) {
|
|
224
|
+
setShowCaptcha(true);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
setIsSubmitting(true);
|
|
228
|
+
try {
|
|
229
|
+
await client.completeResponse({
|
|
230
|
+
responseId,
|
|
231
|
+
sessionId,
|
|
232
|
+
answers,
|
|
233
|
+
captchaToken: captchaToken || void 0
|
|
234
|
+
});
|
|
235
|
+
setIsCompleted(true);
|
|
236
|
+
setCurrentIndex(sequence.length - 1);
|
|
237
|
+
clearProgress(projectId);
|
|
238
|
+
if (settings.duplicatePrevention) {
|
|
239
|
+
markAsSubmitted(projectId);
|
|
240
|
+
}
|
|
241
|
+
onComplete?.(answers);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
onError?.(error);
|
|
244
|
+
} finally {
|
|
245
|
+
setIsSubmitting(false);
|
|
246
|
+
setShowCaptcha(false);
|
|
247
|
+
}
|
|
248
|
+
}, [responseId, sessionId, answers, captchaToken, client, sequence.length, projectId, settings.captchaEnabled, settings.duplicatePrevention, onComplete, onError]);
|
|
249
|
+
const next = useCallback(async (pendingValue) => {
|
|
250
|
+
if (currentItem?.type === "field") {
|
|
251
|
+
const isValid = validateCurrentWithValue(pendingValue);
|
|
252
|
+
if (!isValid) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (currentIndex === lastFieldIndex) {
|
|
256
|
+
if (pendingValue !== void 0) {
|
|
257
|
+
setAnswers((prev) => ({ ...prev, [currentItem.field.ref]: pendingValue }));
|
|
258
|
+
}
|
|
259
|
+
await submit();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const answersToSave = pendingValue !== void 0 ? { ...answers, [currentItem.field.ref]: pendingValue } : answers;
|
|
263
|
+
if (responseId) {
|
|
264
|
+
client.updateAnswers({
|
|
265
|
+
responseId,
|
|
266
|
+
sessionId,
|
|
267
|
+
answers: answersToSave,
|
|
268
|
+
lastFieldRef: currentItem.field.ref
|
|
269
|
+
}).catch((error) => {
|
|
270
|
+
onError?.(error);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
setCurrentIndex((prev) => Math.min(prev + 1, sequence.length - 1));
|
|
275
|
+
}, [
|
|
276
|
+
currentItem,
|
|
277
|
+
currentIndex,
|
|
278
|
+
lastFieldIndex,
|
|
279
|
+
sequence.length,
|
|
280
|
+
responseId,
|
|
281
|
+
sessionId,
|
|
282
|
+
answers,
|
|
283
|
+
client,
|
|
284
|
+
onError,
|
|
285
|
+
validateCurrentWithValue,
|
|
286
|
+
submit
|
|
287
|
+
]);
|
|
288
|
+
const previous = useCallback(() => {
|
|
289
|
+
setCurrentIndex((prev) => Math.max(prev - 1, firstFieldIndex));
|
|
290
|
+
}, [firstFieldIndex]);
|
|
291
|
+
const goTo = useCallback(
|
|
292
|
+
(index) => {
|
|
293
|
+
if (index >= 0 && index < sequence.length) {
|
|
294
|
+
setCurrentIndex(index);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
[sequence.length]
|
|
298
|
+
);
|
|
299
|
+
const setAnswer = useCallback((fieldRef, value) => {
|
|
300
|
+
setAnswers((prev) => ({ ...prev, [fieldRef]: value }));
|
|
301
|
+
setErrors((prev) => {
|
|
302
|
+
const newErrors = { ...prev };
|
|
303
|
+
delete newErrors[fieldRef];
|
|
304
|
+
return newErrors;
|
|
305
|
+
});
|
|
306
|
+
}, []);
|
|
307
|
+
const reset = useCallback(() => {
|
|
308
|
+
setResponseId(null);
|
|
309
|
+
setCurrentIndex(0);
|
|
310
|
+
setAnswers(initialAnswers);
|
|
311
|
+
setErrors({});
|
|
312
|
+
setIsStarted(false);
|
|
313
|
+
setIsCompleted(false);
|
|
314
|
+
setIsSubmitting(false);
|
|
315
|
+
}, [initialAnswers]);
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (content.welcomeScreens.length === 0 && !isStarted) {
|
|
318
|
+
start();
|
|
319
|
+
}
|
|
320
|
+
}, [content.welcomeScreens.length, isStarted, start]);
|
|
321
|
+
return {
|
|
322
|
+
// State
|
|
323
|
+
currentIndex,
|
|
324
|
+
currentItem,
|
|
325
|
+
answers,
|
|
326
|
+
errors,
|
|
327
|
+
isStarted,
|
|
328
|
+
isCompleted,
|
|
329
|
+
isSubmitting,
|
|
330
|
+
responseId,
|
|
331
|
+
sessionId,
|
|
332
|
+
isDuplicateSubmission,
|
|
333
|
+
captchaToken,
|
|
334
|
+
showCaptcha,
|
|
335
|
+
// Computed
|
|
336
|
+
sequence,
|
|
337
|
+
totalFields,
|
|
338
|
+
answeredCount,
|
|
339
|
+
progress,
|
|
340
|
+
canGoBack,
|
|
341
|
+
canGoNext,
|
|
342
|
+
isOnWelcome,
|
|
343
|
+
isOnThankYou,
|
|
344
|
+
isOnField,
|
|
345
|
+
// Actions
|
|
346
|
+
start,
|
|
347
|
+
next,
|
|
348
|
+
previous,
|
|
349
|
+
goTo,
|
|
350
|
+
setAnswer,
|
|
351
|
+
validateCurrent,
|
|
352
|
+
submit,
|
|
353
|
+
reset,
|
|
354
|
+
setCaptchaToken
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
var ThemeContext = createContext(null);
|
|
358
|
+
function useThemeContext() {
|
|
359
|
+
const theme = useContext(ThemeContext);
|
|
360
|
+
if (!theme) {
|
|
361
|
+
throw new Error("useThemeContext must be used within a ThemeProvider");
|
|
362
|
+
}
|
|
363
|
+
return theme;
|
|
364
|
+
}
|
|
365
|
+
function useTheme(options) {
|
|
366
|
+
const { theme, inject = true } = options;
|
|
367
|
+
const cssVariables = useMemo(() => generateThemeCSS(theme), [theme]);
|
|
368
|
+
const componentStyles = useMemo(() => generateComponentStyles(), []);
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
if (inject && isBrowser()) {
|
|
371
|
+
const cleanup = injectThemeCSS(theme);
|
|
372
|
+
return cleanup;
|
|
373
|
+
}
|
|
374
|
+
}, [theme, inject]);
|
|
375
|
+
const backgroundStyle = useMemo(() => {
|
|
376
|
+
const bg = theme.background;
|
|
377
|
+
switch (bg.type) {
|
|
378
|
+
case "gradient":
|
|
379
|
+
return { background: bg.value };
|
|
380
|
+
case "image":
|
|
381
|
+
return {
|
|
382
|
+
backgroundImage: `url(${bg.value})`,
|
|
383
|
+
backgroundSize: "cover",
|
|
384
|
+
backgroundPosition: bg.position || "center"
|
|
385
|
+
};
|
|
386
|
+
case "video":
|
|
387
|
+
return {};
|
|
388
|
+
default:
|
|
389
|
+
return {};
|
|
390
|
+
}
|
|
391
|
+
}, [theme.background]);
|
|
392
|
+
return {
|
|
393
|
+
theme,
|
|
394
|
+
cssVariables,
|
|
395
|
+
componentStyles,
|
|
396
|
+
backgroundStyle
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function useField(options) {
|
|
400
|
+
const { field, value, error, onChange, onNext } = options;
|
|
401
|
+
const isEmpty = value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
|
|
402
|
+
const onKeyDown = useCallback(
|
|
403
|
+
(e) => {
|
|
404
|
+
if (e.key === "Enter" && !e.shiftKey && field.type !== "long_text") {
|
|
405
|
+
e.preventDefault();
|
|
406
|
+
onNext?.();
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
[field.type, onNext]
|
|
410
|
+
);
|
|
411
|
+
const placeholder = useMemo(() => {
|
|
412
|
+
if ("properties" in field && field.properties) {
|
|
413
|
+
const props = field.properties;
|
|
414
|
+
if ("placeholder" in props) {
|
|
415
|
+
return props.placeholder;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return void 0;
|
|
419
|
+
}, [field]);
|
|
420
|
+
const getInputProps = useCallback(() => {
|
|
421
|
+
return {
|
|
422
|
+
value: value || "",
|
|
423
|
+
onChange: (e) => {
|
|
424
|
+
onChange(e.target.value);
|
|
425
|
+
},
|
|
426
|
+
onKeyDown,
|
|
427
|
+
placeholder,
|
|
428
|
+
"aria-invalid": !!error,
|
|
429
|
+
"aria-describedby": error ? `${field.ref}-error` : void 0
|
|
430
|
+
};
|
|
431
|
+
}, [value, onChange, onKeyDown, field.ref, error, placeholder]);
|
|
432
|
+
const getTextareaProps = useCallback(() => {
|
|
433
|
+
return {
|
|
434
|
+
value: value || "",
|
|
435
|
+
onChange: (e) => {
|
|
436
|
+
onChange(e.target.value);
|
|
437
|
+
},
|
|
438
|
+
placeholder,
|
|
439
|
+
"aria-invalid": !!error,
|
|
440
|
+
"aria-describedby": error ? `${field.ref}-error` : void 0
|
|
441
|
+
};
|
|
442
|
+
}, [value, onChange, field.ref, error, placeholder]);
|
|
443
|
+
return {
|
|
444
|
+
// Field info
|
|
445
|
+
ref: field.ref,
|
|
446
|
+
type: field.type,
|
|
447
|
+
title: field.title,
|
|
448
|
+
description: field.description,
|
|
449
|
+
required: field.validations?.required || false,
|
|
450
|
+
// State
|
|
451
|
+
value,
|
|
452
|
+
error,
|
|
453
|
+
isEmpty,
|
|
454
|
+
// Handlers
|
|
455
|
+
onChange,
|
|
456
|
+
onKeyDown,
|
|
457
|
+
// Helpers
|
|
458
|
+
getInputProps,
|
|
459
|
+
getTextareaProps
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function useProgress(options) {
|
|
463
|
+
const { content, answers, currentIndex } = options;
|
|
464
|
+
return useMemo(() => {
|
|
465
|
+
const totalFields = content.fields.length;
|
|
466
|
+
const answeredCount = Object.keys(answers).length;
|
|
467
|
+
const welcomeCount = content.welcomeScreens.length;
|
|
468
|
+
const currentFieldIndex = Math.max(0, currentIndex - welcomeCount);
|
|
469
|
+
const percentage = totalFields > 0 ? answeredCount / totalFields * 100 : 0;
|
|
470
|
+
const segments = content.fields.map((field, index) => ({
|
|
471
|
+
index,
|
|
472
|
+
fieldRef: field.ref,
|
|
473
|
+
completed: !!answers[field.ref],
|
|
474
|
+
current: index === currentFieldIndex
|
|
475
|
+
}));
|
|
476
|
+
return {
|
|
477
|
+
totalFields,
|
|
478
|
+
answeredCount,
|
|
479
|
+
currentFieldIndex,
|
|
480
|
+
percentage,
|
|
481
|
+
segments
|
|
482
|
+
};
|
|
483
|
+
}, [content, answers, currentIndex]);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export { ThemeContext, hasCookieConsent, setCookieConsent, useField, useForm, useProgress, useTheme, useThemeContext };
|
|
487
|
+
//# sourceMappingURL=chunk-HUIMGLDC.js.map
|
|
488
|
+
//# sourceMappingURL=chunk-HUIMGLDC.js.map
|