@aifeatures/react 0.1.4 → 0.1.6
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.cjs +39 -291
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -85
- package/dist/index.d.ts +28 -85
- package/dist/index.js +39 -284
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/dist/index.cjs
CHANGED
|
@@ -21,316 +21,64 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
-
|
|
25
|
-
Controller: () => import_react_hook_form4.Controller,
|
|
26
|
-
FormActions: () => FormActions,
|
|
27
|
-
FormField: () => FormField,
|
|
28
|
-
FormStatus: () => FormStatus,
|
|
29
|
-
SubmitButton: () => SubmitButton,
|
|
30
|
-
useAiFeaturesForm: () => useAiFeaturesForm,
|
|
31
|
-
useFormContext: () => import_react_hook_form4.useFormContext
|
|
24
|
+
AifeaturesCaptcha: () => AifeaturesCaptcha
|
|
32
25
|
});
|
|
33
26
|
module.exports = __toCommonJS(index_exports);
|
|
34
27
|
|
|
35
|
-
// src/
|
|
28
|
+
// src/AifeaturesCaptcha.tsx
|
|
36
29
|
var import_react = require("react");
|
|
37
|
-
var
|
|
30
|
+
var import_react_turnstile = require("@marsidev/react-turnstile");
|
|
38
31
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
-
|
|
40
|
-
var FormContext = (0, import_react.createContext)(null);
|
|
41
|
-
function useAiFeaturesForm() {
|
|
42
|
-
const context = (0, import_react.useContext)(FormContext);
|
|
43
|
-
if (!context) {
|
|
44
|
-
throw new Error("useAiFeaturesForm must be used within an AiFeaturesForm");
|
|
45
|
-
}
|
|
46
|
-
return context;
|
|
47
|
-
}
|
|
48
|
-
function AiFeaturesForm({
|
|
32
|
+
function AifeaturesCaptcha({
|
|
49
33
|
formId,
|
|
50
|
-
|
|
34
|
+
apiUrl = "https://aifeatures.dev",
|
|
35
|
+
onVerify,
|
|
51
36
|
onError,
|
|
52
|
-
className
|
|
53
|
-
children
|
|
37
|
+
className
|
|
54
38
|
}) {
|
|
55
|
-
const [
|
|
56
|
-
const [
|
|
57
|
-
const [
|
|
58
|
-
isSubmitting: false,
|
|
59
|
-
isSuccess: false,
|
|
60
|
-
isError: false,
|
|
61
|
-
error: null
|
|
62
|
-
});
|
|
63
|
-
const form = (0, import_react_hook_form.useForm)();
|
|
39
|
+
const [sitekey, setSitekey] = (0, import_react.useState)(null);
|
|
40
|
+
const [token, setToken] = (0, import_react.useState)("");
|
|
41
|
+
const [isLoading, setIsLoading] = (0, import_react.useState)(true);
|
|
64
42
|
(0, import_react.useEffect)(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
setConfig(data);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
const error = err instanceof Error ? err : new Error("Failed to load form config");
|
|
75
|
-
setConfigError(error);
|
|
76
|
-
onError?.(error);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
fetchConfig();
|
|
80
|
-
}, [formId, onError]);
|
|
81
|
-
const onSubmit = async (data) => {
|
|
82
|
-
if (!config) return;
|
|
83
|
-
setFormState({
|
|
84
|
-
isSubmitting: true,
|
|
85
|
-
isSuccess: false,
|
|
86
|
-
isError: false,
|
|
87
|
-
error: null
|
|
43
|
+
setIsLoading(true);
|
|
44
|
+
fetch(`${apiUrl}/f/${formId}/config`).then((r) => r.json()).then((config) => {
|
|
45
|
+
setSitekey(config.turnstile_sitekey);
|
|
46
|
+
setIsLoading(false);
|
|
47
|
+
}).catch(() => {
|
|
48
|
+
setIsLoading(false);
|
|
88
49
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
for (const file of Array.from(value)) {
|
|
94
|
-
formData.append(key, file);
|
|
95
|
-
}
|
|
96
|
-
} else if (value !== void 0 && value !== null) {
|
|
97
|
-
formData.append(key, String(value));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const response = await fetch(config.endpoint_url, {
|
|
101
|
-
method: "POST",
|
|
102
|
-
body: formData
|
|
103
|
-
});
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
const errorText = await response.text();
|
|
106
|
-
throw new Error(errorText || `Submission failed: ${response.status}`);
|
|
107
|
-
}
|
|
108
|
-
setFormState({
|
|
109
|
-
isSubmitting: false,
|
|
110
|
-
isSuccess: true,
|
|
111
|
-
isError: false,
|
|
112
|
-
error: null
|
|
113
|
-
});
|
|
114
|
-
form.reset();
|
|
115
|
-
if (typeof window !== "undefined" && window.turnstile) {
|
|
116
|
-
window.turnstile.reset();
|
|
117
|
-
}
|
|
118
|
-
onSuccess?.(data);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
const error = err instanceof Error ? err : new Error("Form submission failed");
|
|
121
|
-
setFormState({
|
|
122
|
-
isSubmitting: false,
|
|
123
|
-
isSuccess: false,
|
|
124
|
-
isError: true,
|
|
125
|
-
error
|
|
126
|
-
});
|
|
127
|
-
onError?.(error);
|
|
128
|
-
}
|
|
50
|
+
}, [formId, apiUrl]);
|
|
51
|
+
const handleSuccess = (newToken) => {
|
|
52
|
+
setToken(newToken);
|
|
53
|
+
onVerify?.(newToken);
|
|
129
54
|
};
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (configError) {
|
|
134
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Failed to load form. Please try again later." }) });
|
|
135
|
-
}
|
|
136
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormContext.Provider, { value: { formState, config }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_hook_form.FormProvider, { ...form, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: form.handleSubmit(onSubmit), className, children: [
|
|
137
|
-
children,
|
|
138
|
-
config?.turnstile_sitekey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
139
|
-
"input",
|
|
140
|
-
{
|
|
141
|
-
type: "hidden",
|
|
142
|
-
...form.register("cf-turnstile-response", {
|
|
143
|
-
required: "Please complete the CAPTCHA verification"
|
|
144
|
-
})
|
|
145
|
-
}
|
|
146
|
-
)
|
|
147
|
-
] }) }) });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// src/FormField.tsx
|
|
151
|
-
var import_react_hook_form2 = require("react-hook-form");
|
|
152
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
153
|
-
function FormField({
|
|
154
|
-
name,
|
|
155
|
-
label,
|
|
156
|
-
type = "text",
|
|
157
|
-
placeholder,
|
|
158
|
-
required = false,
|
|
159
|
-
className,
|
|
160
|
-
options,
|
|
161
|
-
accept,
|
|
162
|
-
multiple
|
|
163
|
-
}) {
|
|
164
|
-
const { register, formState: { errors } } = (0, import_react_hook_form2.useFormContext)();
|
|
165
|
-
const error = errors[name];
|
|
166
|
-
const errorMessage = error?.message;
|
|
167
|
-
const inputClassName = className || "";
|
|
168
|
-
const registerOptions = {
|
|
169
|
-
required: required ? `${label || name} is required` : false
|
|
55
|
+
const handleError = () => {
|
|
56
|
+
setToken("");
|
|
57
|
+
onError?.();
|
|
170
58
|
};
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
178
|
-
"textarea",
|
|
179
|
-
{
|
|
180
|
-
id: name,
|
|
181
|
-
placeholder,
|
|
182
|
-
className: inputClassName,
|
|
183
|
-
...register(name, registerOptions)
|
|
184
|
-
}
|
|
185
|
-
),
|
|
186
|
-
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: errorMessage })
|
|
187
|
-
] });
|
|
188
|
-
}
|
|
189
|
-
if (type === "select" && options) {
|
|
190
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
191
|
-
label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: name, children: [
|
|
192
|
-
label,
|
|
193
|
-
required && " *"
|
|
194
|
-
] }),
|
|
195
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
196
|
-
"select",
|
|
197
|
-
{
|
|
198
|
-
id: name,
|
|
199
|
-
className: inputClassName,
|
|
200
|
-
...register(name, registerOptions),
|
|
201
|
-
children: [
|
|
202
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: placeholder || "Select an option" }),
|
|
203
|
-
options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: option.value, children: option.label }, option.value))
|
|
204
|
-
]
|
|
205
|
-
}
|
|
206
|
-
),
|
|
207
|
-
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: errorMessage })
|
|
208
|
-
] });
|
|
209
|
-
}
|
|
210
|
-
if (type === "file") {
|
|
211
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
212
|
-
label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: name, children: [
|
|
213
|
-
label,
|
|
214
|
-
required && " *"
|
|
215
|
-
] }),
|
|
216
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
217
|
-
"input",
|
|
218
|
-
{
|
|
219
|
-
id: name,
|
|
220
|
-
type: "file",
|
|
221
|
-
accept,
|
|
222
|
-
multiple,
|
|
223
|
-
className: inputClassName,
|
|
224
|
-
...register(name, registerOptions)
|
|
225
|
-
}
|
|
226
|
-
),
|
|
227
|
-
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: errorMessage })
|
|
228
|
-
] });
|
|
59
|
+
const handleExpire = () => {
|
|
60
|
+
setToken("");
|
|
61
|
+
onError?.();
|
|
62
|
+
};
|
|
63
|
+
if (isLoading) {
|
|
64
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "hidden", name: "aifeatures-captcha-token", value: "" });
|
|
229
65
|
}
|
|
230
|
-
return /* @__PURE__ */ (0,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
required && " *"
|
|
234
|
-
] }),
|
|
235
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
236
|
-
"input",
|
|
237
|
-
{
|
|
238
|
-
id: name,
|
|
239
|
-
type,
|
|
240
|
-
placeholder,
|
|
241
|
-
className: inputClassName,
|
|
242
|
-
...register(name, registerOptions)
|
|
243
|
-
}
|
|
244
|
-
),
|
|
245
|
-
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: errorMessage })
|
|
246
|
-
] });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// src/FormActions.tsx
|
|
250
|
-
var import_react2 = require("react");
|
|
251
|
-
var import_react_hook_form3 = require("react-hook-form");
|
|
252
|
-
var import_react_turnstile = require("@marsidev/react-turnstile");
|
|
253
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
254
|
-
function FormActions({
|
|
255
|
-
children,
|
|
256
|
-
className,
|
|
257
|
-
turnstileClassName
|
|
258
|
-
}) {
|
|
259
|
-
const { config } = useAiFeaturesForm();
|
|
260
|
-
const form = (0, import_react_hook_form3.useFormContext)();
|
|
261
|
-
const handleTurnstileSuccess = (0, import_react2.useCallback)((token) => {
|
|
262
|
-
form.setValue("cf-turnstile-response", token, { shouldValidate: true });
|
|
263
|
-
}, [form]);
|
|
264
|
-
const handleTurnstileError = (0, import_react2.useCallback)(() => {
|
|
265
|
-
form.setValue("cf-turnstile-response", "", { shouldValidate: true });
|
|
266
|
-
}, [form]);
|
|
267
|
-
const handleTurnstileExpire = (0, import_react2.useCallback)(() => {
|
|
268
|
-
form.setValue("cf-turnstile-response", "", { shouldValidate: true });
|
|
269
|
-
}, [form]);
|
|
270
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, children: [
|
|
271
|
-
config?.turnstile_sitekey && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: turnstileClassName, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
66
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
|
|
67
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "hidden", name: "aifeatures-captcha-token", value: token }),
|
|
68
|
+
sitekey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
272
69
|
import_react_turnstile.Turnstile,
|
|
273
70
|
{
|
|
274
|
-
siteKey:
|
|
275
|
-
onSuccess:
|
|
276
|
-
onError:
|
|
277
|
-
onExpire:
|
|
278
|
-
options: {
|
|
279
|
-
theme: "auto"
|
|
280
|
-
}
|
|
71
|
+
siteKey: sitekey,
|
|
72
|
+
onSuccess: handleSuccess,
|
|
73
|
+
onError: handleError,
|
|
74
|
+
onExpire: handleExpire,
|
|
75
|
+
options: { theme: "light" }
|
|
281
76
|
}
|
|
282
|
-
)
|
|
283
|
-
children
|
|
77
|
+
)
|
|
284
78
|
] });
|
|
285
79
|
}
|
|
286
|
-
|
|
287
|
-
// src/SubmitButton.tsx
|
|
288
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
289
|
-
function SubmitButton({
|
|
290
|
-
children,
|
|
291
|
-
loadingText = "Sending...",
|
|
292
|
-
className
|
|
293
|
-
}) {
|
|
294
|
-
const { formState } = useAiFeaturesForm();
|
|
295
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
296
|
-
"button",
|
|
297
|
-
{
|
|
298
|
-
type: "submit",
|
|
299
|
-
disabled: formState.isSubmitting,
|
|
300
|
-
className,
|
|
301
|
-
children: formState.isSubmitting ? loadingText : children
|
|
302
|
-
}
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// src/FormStatus.tsx
|
|
307
|
-
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
308
|
-
function FormStatus({
|
|
309
|
-
successMessage = "Thank you! Your message has been sent.",
|
|
310
|
-
successClassName,
|
|
311
|
-
errorClassName
|
|
312
|
-
}) {
|
|
313
|
-
const { formState } = useAiFeaturesForm();
|
|
314
|
-
if (formState.isSuccess) {
|
|
315
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: successClassName, children: successMessage });
|
|
316
|
-
}
|
|
317
|
-
if (formState.isError && formState.error) {
|
|
318
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: errorClassName, children: formState.error.message });
|
|
319
|
-
}
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// src/index.ts
|
|
324
|
-
var import_react_hook_form4 = require("react-hook-form");
|
|
325
80
|
// Annotate the CommonJS export names for ESM import in node:
|
|
326
81
|
0 && (module.exports = {
|
|
327
|
-
|
|
328
|
-
Controller,
|
|
329
|
-
FormActions,
|
|
330
|
-
FormField,
|
|
331
|
-
FormStatus,
|
|
332
|
-
SubmitButton,
|
|
333
|
-
useAiFeaturesForm,
|
|
334
|
-
useFormContext
|
|
82
|
+
AifeaturesCaptcha
|
|
335
83
|
});
|
|
336
84
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/AiFeaturesForm.tsx","../src/FormField.tsx","../src/FormActions.tsx","../src/SubmitButton.tsx","../src/FormStatus.tsx"],"sourcesContent":["// Components\nexport { AiFeaturesForm, useAiFeaturesForm } from './AiFeaturesForm'\nexport { FormField } from './FormField'\nexport { FormActions } from './FormActions'\nexport { SubmitButton } from './SubmitButton'\nexport { FormStatus } from './FormStatus'\n\n// Types\nexport type {\n FormConfig,\n AiFeaturesFormProps,\n FormFieldProps,\n SubmitButtonProps,\n FormState,\n} from './types'\n\n// Re-export react-hook-form utilities for advanced usage\nexport { useFormContext, Controller } from 'react-hook-form'\n","import { useEffect, useState, createContext, useContext } from 'react'\nimport { useForm, FormProvider } from 'react-hook-form'\nimport type { FormConfig, AiFeaturesFormProps, FormState } from './types'\n\nconst AIFEATURES_API_URL = 'https://aifeatures.dev'\n\ninterface FormContextValue {\n formState: FormState\n config: FormConfig | null\n}\n\nconst FormContext = createContext<FormContextValue | null>(null)\n\nexport function useAiFeaturesForm() {\n const context = useContext(FormContext)\n if (!context) {\n throw new Error('useAiFeaturesForm must be used within an AiFeaturesForm')\n }\n return context\n}\n\nexport function AiFeaturesForm({\n formId,\n onSuccess,\n onError,\n className,\n children,\n}: AiFeaturesFormProps) {\n const [config, setConfig] = useState<FormConfig | null>(null)\n const [configError, setConfigError] = useState<Error | null>(null)\n const [formState, setFormState] = useState<FormState>({\n isSubmitting: false,\n isSuccess: false,\n isError: false,\n error: null,\n })\n\n const form = useForm()\n\n // Fetch form config on mount\n useEffect(() => {\n async function fetchConfig() {\n try {\n const response = await fetch(`${AIFEATURES_API_URL}/f/${formId}/config`)\n if (!response.ok) {\n throw new Error(`Failed to load form: ${response.status}`)\n }\n const data = await response.json()\n setConfig(data)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to load form config')\n setConfigError(error)\n onError?.(error)\n }\n }\n fetchConfig()\n }, [formId, onError])\n\n const onSubmit = async (data: Record<string, unknown>) => {\n if (!config) return\n\n setFormState({\n isSubmitting: true,\n isSuccess: false,\n isError: false,\n error: null,\n })\n\n try {\n const formData = new FormData()\n\n for (const [key, value] of Object.entries(data)) {\n if (value instanceof FileList) {\n for (const file of Array.from(value)) {\n formData.append(key, file)\n }\n } else if (value !== undefined && value !== null) {\n formData.append(key, String(value))\n }\n }\n\n const response = await fetch(config.endpoint_url, {\n method: 'POST',\n body: formData,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(errorText || `Submission failed: ${response.status}`)\n }\n\n setFormState({\n isSubmitting: false,\n isSuccess: true,\n isError: false,\n error: null,\n })\n\n form.reset()\n // Reset Turnstile widget\n if (typeof window !== 'undefined' && (window as unknown as { turnstile?: { reset: () => void } }).turnstile) {\n (window as unknown as { turnstile: { reset: () => void } }).turnstile.reset()\n }\n\n onSuccess?.(data)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Form submission failed')\n setFormState({\n isSubmitting: false,\n isSuccess: false,\n isError: true,\n error,\n })\n onError?.(error)\n }\n }\n\n // Show loading state while fetching config\n if (!config && !configError) {\n return <div className={className}>Loading form...</div>\n }\n\n // Show error if config fetch failed\n if (configError) {\n return (\n <div className={className}>\n <p>Failed to load form. Please try again later.</p>\n </div>\n )\n }\n\n return (\n <FormContext.Provider value={{ formState, config }}>\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className={className}>\n {children}\n\n {/* Hidden field for Turnstile token - registered for validation */}\n {/* The actual Turnstile widget is rendered by FormActions */}\n {config?.turnstile_sitekey && (\n <input\n type=\"hidden\"\n {...form.register('cf-turnstile-response', {\n required: 'Please complete the CAPTCHA verification',\n })}\n />\n )}\n </form>\n </FormProvider>\n </FormContext.Provider>\n )\n}\n","import { useFormContext } from 'react-hook-form'\nimport type { FormFieldProps } from './types'\n\nexport function FormField({\n name,\n label,\n type = 'text',\n placeholder,\n required = false,\n className,\n options,\n accept,\n multiple,\n}: FormFieldProps) {\n const { register, formState: { errors } } = useFormContext()\n\n const error = errors[name]\n const errorMessage = error?.message as string | undefined\n\n const inputClassName = className || ''\n\n const registerOptions = {\n required: required ? `${label || name} is required` : false,\n }\n\n // Textarea\n if (type === 'textarea') {\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <textarea\n id={name}\n placeholder={placeholder}\n className={inputClassName}\n {...register(name, registerOptions)}\n />\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n }\n\n // Select\n if (type === 'select' && options) {\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <select\n id={name}\n className={inputClassName}\n {...register(name, registerOptions)}\n >\n <option value=\"\">{placeholder || 'Select an option'}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n }\n\n // File input\n if (type === 'file') {\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <input\n id={name}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n className={inputClassName}\n {...register(name, registerOptions)}\n />\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n }\n\n // Standard input (text, email, tel, url, number)\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <input\n id={name}\n type={type}\n placeholder={placeholder}\n className={inputClassName}\n {...register(name, registerOptions)}\n />\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n}\n","import { useCallback } from 'react'\nimport { useFormContext } from 'react-hook-form'\nimport { Turnstile } from '@marsidev/react-turnstile'\nimport { useAiFeaturesForm } from './AiFeaturesForm'\n\ninterface FormActionsProps {\n /** Children (typically SubmitButton) */\n children: React.ReactNode\n /** Custom class name for the container */\n className?: string\n /** Custom class name for the Turnstile container */\n turnstileClassName?: string\n}\n\nexport function FormActions({\n children,\n className,\n turnstileClassName,\n}: FormActionsProps) {\n const { config } = useAiFeaturesForm()\n const form = useFormContext()\n\n const handleTurnstileSuccess = useCallback((token: string) => {\n form.setValue('cf-turnstile-response', token, { shouldValidate: true })\n }, [form])\n\n const handleTurnstileError = useCallback(() => {\n form.setValue('cf-turnstile-response', '', { shouldValidate: true })\n }, [form])\n\n const handleTurnstileExpire = useCallback(() => {\n form.setValue('cf-turnstile-response', '', { shouldValidate: true })\n }, [form])\n\n return (\n <div className={className}>\n {/* Turnstile CAPTCHA - rendered above submit button */}\n {config?.turnstile_sitekey && (\n <div className={turnstileClassName}>\n <Turnstile\n siteKey={config.turnstile_sitekey}\n onSuccess={handleTurnstileSuccess}\n onError={handleTurnstileError}\n onExpire={handleTurnstileExpire}\n options={{\n theme: 'auto',\n }}\n />\n </div>\n )}\n\n {/* Submit button and any other actions */}\n {children}\n </div>\n )\n}\n","import { useAiFeaturesForm } from './AiFeaturesForm'\nimport type { SubmitButtonProps } from './types'\n\nexport function SubmitButton({\n children,\n loadingText = 'Sending...',\n className,\n}: SubmitButtonProps) {\n const { formState } = useAiFeaturesForm()\n\n return (\n <button\n type=\"submit\"\n disabled={formState.isSubmitting}\n className={className}\n >\n {formState.isSubmitting ? loadingText : children}\n </button>\n )\n}\n","import { useAiFeaturesForm } from './AiFeaturesForm'\n\ninterface FormStatusProps {\n /** Message to show on success */\n successMessage?: string\n /** Custom class name for success state */\n successClassName?: string\n /** Custom class name for error state */\n errorClassName?: string\n}\n\nexport function FormStatus({\n successMessage = 'Thank you! Your message has been sent.',\n successClassName,\n errorClassName,\n}: FormStatusProps) {\n const { formState } = useAiFeaturesForm()\n\n if (formState.isSuccess) {\n return (\n <div className={successClassName}>\n {successMessage}\n </div>\n )\n }\n\n if (formState.isError && formState.error) {\n return (\n <div className={errorClassName}>\n {formState.error.message}\n </div>\n )\n }\n\n return null\n}\n"],"mappings":"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","names":["import_react_hook_form","import_jsx_runtime","import_react","import_react_hook_form","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_react_hook_form"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/AifeaturesCaptcha.tsx"],"sourcesContent":["export { AifeaturesCaptcha } from './AifeaturesCaptcha';\nexport type { AifeaturesCaptchaProps } from './AifeaturesCaptcha';\n","import { useState, useEffect } from 'react';\nimport { Turnstile } from '@marsidev/react-turnstile';\n\nexport interface AifeaturesCaptchaProps {\n /** Form ID from aifeatures - used to fetch the Turnstile sitekey */\n formId: string;\n /** Base URL for the aifeatures API (defaults to https://aifeatures.dev) */\n apiUrl?: string;\n /** Called when CAPTCHA verification succeeds */\n onVerify?: (token: string) => void;\n /** Called when CAPTCHA verification fails or expires */\n onError?: () => void;\n /** Additional class name for the container */\n className?: string;\n}\n\ninterface FormConfig {\n id: string;\n name: string;\n endpoint_url: string;\n turnstile_sitekey: string | null;\n}\n\n/**\n * A CAPTCHA component that handles Turnstile verification for aifeatures forms.\n *\n * Drop this into any form to add CAPTCHA protection. It renders:\n * - A hidden input field named \"cf-turnstile-response\" with the verification token\n * - The Turnstile widget (only if CAPTCHA is enabled for the form)\n *\n * @example\n * ```tsx\n * <form action={`https://aifeatures.dev/f/${formId}`} method=\"POST\">\n * <input name=\"email\" type=\"email\" required />\n * <textarea name=\"message\" />\n * <AifeaturesCaptcha formId={formId} />\n * <button type=\"submit\">Send</button>\n * </form>\n * ```\n */\nexport function AifeaturesCaptcha({\n formId,\n apiUrl = 'https://aifeatures.dev',\n onVerify,\n onError,\n className,\n}: AifeaturesCaptchaProps) {\n const [sitekey, setSitekey] = useState<string | null>(null);\n const [token, setToken] = useState('');\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() => {\n setIsLoading(true);\n fetch(`${apiUrl}/f/${formId}/config`)\n .then((r) => r.json())\n .then((config: FormConfig) => {\n setSitekey(config.turnstile_sitekey);\n setIsLoading(false);\n })\n .catch(() => {\n setIsLoading(false);\n });\n }, [formId, apiUrl]);\n\n const handleSuccess = (newToken: string) => {\n setToken(newToken);\n onVerify?.(newToken);\n };\n\n const handleError = () => {\n setToken('');\n onError?.();\n };\n\n const handleExpire = () => {\n setToken('');\n onError?.();\n };\n\n // Don't render anything while loading or if CAPTCHA is disabled\n if (isLoading) {\n return <input type=\"hidden\" name=\"aifeatures-captcha-token\" value=\"\" />;\n }\n\n return (\n <div className={className}>\n <input type=\"hidden\" name=\"aifeatures-captcha-token\" value={token} />\n {sitekey && (\n <Turnstile\n siteKey={sitekey}\n onSuccess={handleSuccess}\n onError={handleError}\n onExpire={handleExpire}\n options={{ theme: 'light' }}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAoC;AACpC,6BAA0B;AAgFf;AAzCJ,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAwB,IAAI;AAC1D,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,EAAE;AACrC,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,IAAI;AAE/C,8BAAU,MAAM;AACd,iBAAa,IAAI;AACjB,UAAM,GAAG,MAAM,MAAM,MAAM,SAAS,EACjC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EACpB,KAAK,CAAC,WAAuB;AAC5B,iBAAW,OAAO,iBAAiB;AACnC,mBAAa,KAAK;AAAA,IACpB,CAAC,EACA,MAAM,MAAM;AACX,mBAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACL,GAAG,CAAC,QAAQ,MAAM,CAAC;AAEnB,QAAM,gBAAgB,CAAC,aAAqB;AAC1C,aAAS,QAAQ;AACjB,eAAW,QAAQ;AAAA,EACrB;AAEA,QAAM,cAAc,MAAM;AACxB,aAAS,EAAE;AACX,cAAU;AAAA,EACZ;AAEA,QAAM,eAAe,MAAM;AACzB,aAAS,EAAE;AACX,cAAU;AAAA,EACZ;AAGA,MAAI,WAAW;AACb,WAAO,4CAAC,WAAM,MAAK,UAAS,MAAK,4BAA2B,OAAM,IAAG;AAAA,EACvE;AAEA,SACE,6CAAC,SAAI,WACH;AAAA,gDAAC,WAAM,MAAK,UAAS,MAAK,4BAA2B,OAAO,OAAO;AAAA,IAClE,WACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW;AAAA,QACX,SAAS;AAAA,QACT,UAAU;AAAA,QACV,SAAS,EAAE,OAAO,QAAQ;AAAA;AAAA,IAC5B;AAAA,KAEJ;AAEJ;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,91 +1,34 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
export { Controller, useFormContext } from 'react-hook-form';
|
|
3
2
|
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
name: string;
|
|
7
|
-
endpoint_url: string;
|
|
8
|
-
turnstile_sitekey: string | null;
|
|
9
|
-
}
|
|
10
|
-
interface AiFeaturesFormProps {
|
|
11
|
-
/** Form ID from aifeatures.dev */
|
|
3
|
+
interface AifeaturesCaptchaProps {
|
|
4
|
+
/** Form ID from aifeatures - used to fetch the Turnstile sitekey */
|
|
12
5
|
formId: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/** Called
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
children: React.ReactNode;
|
|
21
|
-
}
|
|
22
|
-
interface FormFieldProps {
|
|
23
|
-
/** Field name - used as the form data key */
|
|
24
|
-
name: string;
|
|
25
|
-
/** Field label */
|
|
26
|
-
label?: string;
|
|
27
|
-
/** Field type */
|
|
28
|
-
type?: 'text' | 'email' | 'tel' | 'url' | 'number' | 'textarea' | 'select' | 'file';
|
|
29
|
-
/** Placeholder text */
|
|
30
|
-
placeholder?: string;
|
|
31
|
-
/** Whether the field is required */
|
|
32
|
-
required?: boolean;
|
|
33
|
-
/** Custom class name */
|
|
34
|
-
className?: string;
|
|
35
|
-
/** Options for select fields */
|
|
36
|
-
options?: {
|
|
37
|
-
value: string;
|
|
38
|
-
label: string;
|
|
39
|
-
}[];
|
|
40
|
-
/** Accept attribute for file inputs */
|
|
41
|
-
accept?: string;
|
|
42
|
-
/** Multiple files allowed */
|
|
43
|
-
multiple?: boolean;
|
|
44
|
-
}
|
|
45
|
-
interface SubmitButtonProps {
|
|
46
|
-
/** Button text (or children) */
|
|
47
|
-
children: React.ReactNode;
|
|
48
|
-
/** Text to show while submitting */
|
|
49
|
-
loadingText?: string;
|
|
50
|
-
/** Custom class name */
|
|
6
|
+
/** Base URL for the aifeatures API (defaults to https://aifeatures.dev) */
|
|
7
|
+
apiUrl?: string;
|
|
8
|
+
/** Called when CAPTCHA verification succeeds */
|
|
9
|
+
onVerify?: (token: string) => void;
|
|
10
|
+
/** Called when CAPTCHA verification fails or expires */
|
|
11
|
+
onError?: () => void;
|
|
12
|
+
/** Additional class name for the container */
|
|
51
13
|
className?: string;
|
|
52
14
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
children: React.ReactNode;
|
|
72
|
-
/** Custom class name for the container */
|
|
73
|
-
className?: string;
|
|
74
|
-
/** Custom class name for the Turnstile container */
|
|
75
|
-
turnstileClassName?: string;
|
|
76
|
-
}
|
|
77
|
-
declare function FormActions({ children, className, turnstileClassName, }: FormActionsProps): react_jsx_runtime.JSX.Element;
|
|
78
|
-
|
|
79
|
-
declare function SubmitButton({ children, loadingText, className, }: SubmitButtonProps): react_jsx_runtime.JSX.Element;
|
|
80
|
-
|
|
81
|
-
interface FormStatusProps {
|
|
82
|
-
/** Message to show on success */
|
|
83
|
-
successMessage?: string;
|
|
84
|
-
/** Custom class name for success state */
|
|
85
|
-
successClassName?: string;
|
|
86
|
-
/** Custom class name for error state */
|
|
87
|
-
errorClassName?: string;
|
|
88
|
-
}
|
|
89
|
-
declare function FormStatus({ successMessage, successClassName, errorClassName, }: FormStatusProps): react_jsx_runtime.JSX.Element | null;
|
|
15
|
+
/**
|
|
16
|
+
* A CAPTCHA component that handles Turnstile verification for aifeatures forms.
|
|
17
|
+
*
|
|
18
|
+
* Drop this into any form to add CAPTCHA protection. It renders:
|
|
19
|
+
* - A hidden input field named "cf-turnstile-response" with the verification token
|
|
20
|
+
* - The Turnstile widget (only if CAPTCHA is enabled for the form)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <form action={`https://aifeatures.dev/f/${formId}`} method="POST">
|
|
25
|
+
* <input name="email" type="email" required />
|
|
26
|
+
* <textarea name="message" />
|
|
27
|
+
* <AifeaturesCaptcha formId={formId} />
|
|
28
|
+
* <button type="submit">Send</button>
|
|
29
|
+
* </form>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function AifeaturesCaptcha({ formId, apiUrl, onVerify, onError, className, }: AifeaturesCaptchaProps): react_jsx_runtime.JSX.Element;
|
|
90
33
|
|
|
91
|
-
export {
|
|
34
|
+
export { AifeaturesCaptcha, type AifeaturesCaptchaProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,91 +1,34 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
export { Controller, useFormContext } from 'react-hook-form';
|
|
3
2
|
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
name: string;
|
|
7
|
-
endpoint_url: string;
|
|
8
|
-
turnstile_sitekey: string | null;
|
|
9
|
-
}
|
|
10
|
-
interface AiFeaturesFormProps {
|
|
11
|
-
/** Form ID from aifeatures.dev */
|
|
3
|
+
interface AifeaturesCaptchaProps {
|
|
4
|
+
/** Form ID from aifeatures - used to fetch the Turnstile sitekey */
|
|
12
5
|
formId: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/** Called
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
children: React.ReactNode;
|
|
21
|
-
}
|
|
22
|
-
interface FormFieldProps {
|
|
23
|
-
/** Field name - used as the form data key */
|
|
24
|
-
name: string;
|
|
25
|
-
/** Field label */
|
|
26
|
-
label?: string;
|
|
27
|
-
/** Field type */
|
|
28
|
-
type?: 'text' | 'email' | 'tel' | 'url' | 'number' | 'textarea' | 'select' | 'file';
|
|
29
|
-
/** Placeholder text */
|
|
30
|
-
placeholder?: string;
|
|
31
|
-
/** Whether the field is required */
|
|
32
|
-
required?: boolean;
|
|
33
|
-
/** Custom class name */
|
|
34
|
-
className?: string;
|
|
35
|
-
/** Options for select fields */
|
|
36
|
-
options?: {
|
|
37
|
-
value: string;
|
|
38
|
-
label: string;
|
|
39
|
-
}[];
|
|
40
|
-
/** Accept attribute for file inputs */
|
|
41
|
-
accept?: string;
|
|
42
|
-
/** Multiple files allowed */
|
|
43
|
-
multiple?: boolean;
|
|
44
|
-
}
|
|
45
|
-
interface SubmitButtonProps {
|
|
46
|
-
/** Button text (or children) */
|
|
47
|
-
children: React.ReactNode;
|
|
48
|
-
/** Text to show while submitting */
|
|
49
|
-
loadingText?: string;
|
|
50
|
-
/** Custom class name */
|
|
6
|
+
/** Base URL for the aifeatures API (defaults to https://aifeatures.dev) */
|
|
7
|
+
apiUrl?: string;
|
|
8
|
+
/** Called when CAPTCHA verification succeeds */
|
|
9
|
+
onVerify?: (token: string) => void;
|
|
10
|
+
/** Called when CAPTCHA verification fails or expires */
|
|
11
|
+
onError?: () => void;
|
|
12
|
+
/** Additional class name for the container */
|
|
51
13
|
className?: string;
|
|
52
14
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
children: React.ReactNode;
|
|
72
|
-
/** Custom class name for the container */
|
|
73
|
-
className?: string;
|
|
74
|
-
/** Custom class name for the Turnstile container */
|
|
75
|
-
turnstileClassName?: string;
|
|
76
|
-
}
|
|
77
|
-
declare function FormActions({ children, className, turnstileClassName, }: FormActionsProps): react_jsx_runtime.JSX.Element;
|
|
78
|
-
|
|
79
|
-
declare function SubmitButton({ children, loadingText, className, }: SubmitButtonProps): react_jsx_runtime.JSX.Element;
|
|
80
|
-
|
|
81
|
-
interface FormStatusProps {
|
|
82
|
-
/** Message to show on success */
|
|
83
|
-
successMessage?: string;
|
|
84
|
-
/** Custom class name for success state */
|
|
85
|
-
successClassName?: string;
|
|
86
|
-
/** Custom class name for error state */
|
|
87
|
-
errorClassName?: string;
|
|
88
|
-
}
|
|
89
|
-
declare function FormStatus({ successMessage, successClassName, errorClassName, }: FormStatusProps): react_jsx_runtime.JSX.Element | null;
|
|
15
|
+
/**
|
|
16
|
+
* A CAPTCHA component that handles Turnstile verification for aifeatures forms.
|
|
17
|
+
*
|
|
18
|
+
* Drop this into any form to add CAPTCHA protection. It renders:
|
|
19
|
+
* - A hidden input field named "cf-turnstile-response" with the verification token
|
|
20
|
+
* - The Turnstile widget (only if CAPTCHA is enabled for the form)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <form action={`https://aifeatures.dev/f/${formId}`} method="POST">
|
|
25
|
+
* <input name="email" type="email" required />
|
|
26
|
+
* <textarea name="message" />
|
|
27
|
+
* <AifeaturesCaptcha formId={formId} />
|
|
28
|
+
* <button type="submit">Send</button>
|
|
29
|
+
* </form>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function AifeaturesCaptcha({ formId, apiUrl, onVerify, onError, className, }: AifeaturesCaptchaProps): react_jsx_runtime.JSX.Element;
|
|
90
33
|
|
|
91
|
-
export {
|
|
34
|
+
export { AifeaturesCaptcha, type AifeaturesCaptchaProps };
|
package/dist/index.js
CHANGED
|
@@ -1,303 +1,58 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
// src/AifeaturesCaptcha.tsx
|
|
4
|
+
import { useState, useEffect } from "react";
|
|
5
|
+
import { Turnstile } from "@marsidev/react-turnstile";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
|
|
8
|
-
var FormContext = createContext(null);
|
|
9
|
-
function useAiFeaturesForm() {
|
|
10
|
-
const context = useContext(FormContext);
|
|
11
|
-
if (!context) {
|
|
12
|
-
throw new Error("useAiFeaturesForm must be used within an AiFeaturesForm");
|
|
13
|
-
}
|
|
14
|
-
return context;
|
|
15
|
-
}
|
|
16
|
-
function AiFeaturesForm({
|
|
7
|
+
function AifeaturesCaptcha({
|
|
17
8
|
formId,
|
|
18
|
-
|
|
9
|
+
apiUrl = "https://aifeatures.dev",
|
|
10
|
+
onVerify,
|
|
19
11
|
onError,
|
|
20
|
-
className
|
|
21
|
-
children
|
|
12
|
+
className
|
|
22
13
|
}) {
|
|
23
|
-
const [
|
|
24
|
-
const [
|
|
25
|
-
const [
|
|
26
|
-
isSubmitting: false,
|
|
27
|
-
isSuccess: false,
|
|
28
|
-
isError: false,
|
|
29
|
-
error: null
|
|
30
|
-
});
|
|
31
|
-
const form = useForm();
|
|
14
|
+
const [sitekey, setSitekey] = useState(null);
|
|
15
|
+
const [token, setToken] = useState("");
|
|
16
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
32
17
|
useEffect(() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const data = await response.json();
|
|
40
|
-
setConfig(data);
|
|
41
|
-
} catch (err) {
|
|
42
|
-
const error = err instanceof Error ? err : new Error("Failed to load form config");
|
|
43
|
-
setConfigError(error);
|
|
44
|
-
onError?.(error);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
fetchConfig();
|
|
48
|
-
}, [formId, onError]);
|
|
49
|
-
const onSubmit = async (data) => {
|
|
50
|
-
if (!config) return;
|
|
51
|
-
setFormState({
|
|
52
|
-
isSubmitting: true,
|
|
53
|
-
isSuccess: false,
|
|
54
|
-
isError: false,
|
|
55
|
-
error: null
|
|
18
|
+
setIsLoading(true);
|
|
19
|
+
fetch(`${apiUrl}/f/${formId}/config`).then((r) => r.json()).then((config) => {
|
|
20
|
+
setSitekey(config.turnstile_sitekey);
|
|
21
|
+
setIsLoading(false);
|
|
22
|
+
}).catch(() => {
|
|
23
|
+
setIsLoading(false);
|
|
56
24
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
for (const file of Array.from(value)) {
|
|
62
|
-
formData.append(key, file);
|
|
63
|
-
}
|
|
64
|
-
} else if (value !== void 0 && value !== null) {
|
|
65
|
-
formData.append(key, String(value));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const response = await fetch(config.endpoint_url, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
body: formData
|
|
71
|
-
});
|
|
72
|
-
if (!response.ok) {
|
|
73
|
-
const errorText = await response.text();
|
|
74
|
-
throw new Error(errorText || `Submission failed: ${response.status}`);
|
|
75
|
-
}
|
|
76
|
-
setFormState({
|
|
77
|
-
isSubmitting: false,
|
|
78
|
-
isSuccess: true,
|
|
79
|
-
isError: false,
|
|
80
|
-
error: null
|
|
81
|
-
});
|
|
82
|
-
form.reset();
|
|
83
|
-
if (typeof window !== "undefined" && window.turnstile) {
|
|
84
|
-
window.turnstile.reset();
|
|
85
|
-
}
|
|
86
|
-
onSuccess?.(data);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
const error = err instanceof Error ? err : new Error("Form submission failed");
|
|
89
|
-
setFormState({
|
|
90
|
-
isSubmitting: false,
|
|
91
|
-
isSuccess: false,
|
|
92
|
-
isError: true,
|
|
93
|
-
error
|
|
94
|
-
});
|
|
95
|
-
onError?.(error);
|
|
96
|
-
}
|
|
25
|
+
}, [formId, apiUrl]);
|
|
26
|
+
const handleSuccess = (newToken) => {
|
|
27
|
+
setToken(newToken);
|
|
28
|
+
onVerify?.(newToken);
|
|
97
29
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (configError) {
|
|
102
|
-
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("p", { children: "Failed to load form. Please try again later." }) });
|
|
103
|
-
}
|
|
104
|
-
return /* @__PURE__ */ jsx(FormContext.Provider, { value: { formState, config }, children: /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className, children: [
|
|
105
|
-
children,
|
|
106
|
-
config?.turnstile_sitekey && /* @__PURE__ */ jsx(
|
|
107
|
-
"input",
|
|
108
|
-
{
|
|
109
|
-
type: "hidden",
|
|
110
|
-
...form.register("cf-turnstile-response", {
|
|
111
|
-
required: "Please complete the CAPTCHA verification"
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
)
|
|
115
|
-
] }) }) });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// src/FormField.tsx
|
|
119
|
-
import { useFormContext } from "react-hook-form";
|
|
120
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
121
|
-
function FormField({
|
|
122
|
-
name,
|
|
123
|
-
label,
|
|
124
|
-
type = "text",
|
|
125
|
-
placeholder,
|
|
126
|
-
required = false,
|
|
127
|
-
className,
|
|
128
|
-
options,
|
|
129
|
-
accept,
|
|
130
|
-
multiple
|
|
131
|
-
}) {
|
|
132
|
-
const { register, formState: { errors } } = useFormContext();
|
|
133
|
-
const error = errors[name];
|
|
134
|
-
const errorMessage = error?.message;
|
|
135
|
-
const inputClassName = className || "";
|
|
136
|
-
const registerOptions = {
|
|
137
|
-
required: required ? `${label || name} is required` : false
|
|
30
|
+
const handleError = () => {
|
|
31
|
+
setToken("");
|
|
32
|
+
onError?.();
|
|
138
33
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
/* @__PURE__ */ jsx2(
|
|
146
|
-
"textarea",
|
|
147
|
-
{
|
|
148
|
-
id: name,
|
|
149
|
-
placeholder,
|
|
150
|
-
className: inputClassName,
|
|
151
|
-
...register(name, registerOptions)
|
|
152
|
-
}
|
|
153
|
-
),
|
|
154
|
-
errorMessage && /* @__PURE__ */ jsx2("span", { children: errorMessage })
|
|
155
|
-
] });
|
|
156
|
-
}
|
|
157
|
-
if (type === "select" && options) {
|
|
158
|
-
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
159
|
-
label && /* @__PURE__ */ jsxs2("label", { htmlFor: name, children: [
|
|
160
|
-
label,
|
|
161
|
-
required && " *"
|
|
162
|
-
] }),
|
|
163
|
-
/* @__PURE__ */ jsxs2(
|
|
164
|
-
"select",
|
|
165
|
-
{
|
|
166
|
-
id: name,
|
|
167
|
-
className: inputClassName,
|
|
168
|
-
...register(name, registerOptions),
|
|
169
|
-
children: [
|
|
170
|
-
/* @__PURE__ */ jsx2("option", { value: "", children: placeholder || "Select an option" }),
|
|
171
|
-
options.map((option) => /* @__PURE__ */ jsx2("option", { value: option.value, children: option.label }, option.value))
|
|
172
|
-
]
|
|
173
|
-
}
|
|
174
|
-
),
|
|
175
|
-
errorMessage && /* @__PURE__ */ jsx2("span", { children: errorMessage })
|
|
176
|
-
] });
|
|
177
|
-
}
|
|
178
|
-
if (type === "file") {
|
|
179
|
-
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
180
|
-
label && /* @__PURE__ */ jsxs2("label", { htmlFor: name, children: [
|
|
181
|
-
label,
|
|
182
|
-
required && " *"
|
|
183
|
-
] }),
|
|
184
|
-
/* @__PURE__ */ jsx2(
|
|
185
|
-
"input",
|
|
186
|
-
{
|
|
187
|
-
id: name,
|
|
188
|
-
type: "file",
|
|
189
|
-
accept,
|
|
190
|
-
multiple,
|
|
191
|
-
className: inputClassName,
|
|
192
|
-
...register(name, registerOptions)
|
|
193
|
-
}
|
|
194
|
-
),
|
|
195
|
-
errorMessage && /* @__PURE__ */ jsx2("span", { children: errorMessage })
|
|
196
|
-
] });
|
|
34
|
+
const handleExpire = () => {
|
|
35
|
+
setToken("");
|
|
36
|
+
onError?.();
|
|
37
|
+
};
|
|
38
|
+
if (isLoading) {
|
|
39
|
+
return /* @__PURE__ */ jsx("input", { type: "hidden", name: "aifeatures-captcha-token", value: "" });
|
|
197
40
|
}
|
|
198
|
-
return /* @__PURE__ */
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
required && " *"
|
|
202
|
-
] }),
|
|
203
|
-
/* @__PURE__ */ jsx2(
|
|
204
|
-
"input",
|
|
205
|
-
{
|
|
206
|
-
id: name,
|
|
207
|
-
type,
|
|
208
|
-
placeholder,
|
|
209
|
-
className: inputClassName,
|
|
210
|
-
...register(name, registerOptions)
|
|
211
|
-
}
|
|
212
|
-
),
|
|
213
|
-
errorMessage && /* @__PURE__ */ jsx2("span", { children: errorMessage })
|
|
214
|
-
] });
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// src/FormActions.tsx
|
|
218
|
-
import { useCallback } from "react";
|
|
219
|
-
import { useFormContext as useFormContext2 } from "react-hook-form";
|
|
220
|
-
import { Turnstile } from "@marsidev/react-turnstile";
|
|
221
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
222
|
-
function FormActions({
|
|
223
|
-
children,
|
|
224
|
-
className,
|
|
225
|
-
turnstileClassName
|
|
226
|
-
}) {
|
|
227
|
-
const { config } = useAiFeaturesForm();
|
|
228
|
-
const form = useFormContext2();
|
|
229
|
-
const handleTurnstileSuccess = useCallback((token) => {
|
|
230
|
-
form.setValue("cf-turnstile-response", token, { shouldValidate: true });
|
|
231
|
-
}, [form]);
|
|
232
|
-
const handleTurnstileError = useCallback(() => {
|
|
233
|
-
form.setValue("cf-turnstile-response", "", { shouldValidate: true });
|
|
234
|
-
}, [form]);
|
|
235
|
-
const handleTurnstileExpire = useCallback(() => {
|
|
236
|
-
form.setValue("cf-turnstile-response", "", { shouldValidate: true });
|
|
237
|
-
}, [form]);
|
|
238
|
-
return /* @__PURE__ */ jsxs3("div", { className, children: [
|
|
239
|
-
config?.turnstile_sitekey && /* @__PURE__ */ jsx3("div", { className: turnstileClassName, children: /* @__PURE__ */ jsx3(
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
42
|
+
/* @__PURE__ */ jsx("input", { type: "hidden", name: "aifeatures-captcha-token", value: token }),
|
|
43
|
+
sitekey && /* @__PURE__ */ jsx(
|
|
240
44
|
Turnstile,
|
|
241
45
|
{
|
|
242
|
-
siteKey:
|
|
243
|
-
onSuccess:
|
|
244
|
-
onError:
|
|
245
|
-
onExpire:
|
|
246
|
-
options: {
|
|
247
|
-
theme: "auto"
|
|
248
|
-
}
|
|
46
|
+
siteKey: sitekey,
|
|
47
|
+
onSuccess: handleSuccess,
|
|
48
|
+
onError: handleError,
|
|
49
|
+
onExpire: handleExpire,
|
|
50
|
+
options: { theme: "light" }
|
|
249
51
|
}
|
|
250
|
-
)
|
|
251
|
-
children
|
|
52
|
+
)
|
|
252
53
|
] });
|
|
253
54
|
}
|
|
254
|
-
|
|
255
|
-
// src/SubmitButton.tsx
|
|
256
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
257
|
-
function SubmitButton({
|
|
258
|
-
children,
|
|
259
|
-
loadingText = "Sending...",
|
|
260
|
-
className
|
|
261
|
-
}) {
|
|
262
|
-
const { formState } = useAiFeaturesForm();
|
|
263
|
-
return /* @__PURE__ */ jsx4(
|
|
264
|
-
"button",
|
|
265
|
-
{
|
|
266
|
-
type: "submit",
|
|
267
|
-
disabled: formState.isSubmitting,
|
|
268
|
-
className,
|
|
269
|
-
children: formState.isSubmitting ? loadingText : children
|
|
270
|
-
}
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// src/FormStatus.tsx
|
|
275
|
-
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
276
|
-
function FormStatus({
|
|
277
|
-
successMessage = "Thank you! Your message has been sent.",
|
|
278
|
-
successClassName,
|
|
279
|
-
errorClassName
|
|
280
|
-
}) {
|
|
281
|
-
const { formState } = useAiFeaturesForm();
|
|
282
|
-
if (formState.isSuccess) {
|
|
283
|
-
return /* @__PURE__ */ jsx5("div", { className: successClassName, children: successMessage });
|
|
284
|
-
}
|
|
285
|
-
if (formState.isError && formState.error) {
|
|
286
|
-
return /* @__PURE__ */ jsx5("div", { className: errorClassName, children: formState.error.message });
|
|
287
|
-
}
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/index.ts
|
|
292
|
-
import { useFormContext as useFormContext3, Controller } from "react-hook-form";
|
|
293
55
|
export {
|
|
294
|
-
|
|
295
|
-
Controller,
|
|
296
|
-
FormActions,
|
|
297
|
-
FormField,
|
|
298
|
-
FormStatus,
|
|
299
|
-
SubmitButton,
|
|
300
|
-
useAiFeaturesForm,
|
|
301
|
-
useFormContext3 as useFormContext
|
|
56
|
+
AifeaturesCaptcha
|
|
302
57
|
};
|
|
303
58
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/AiFeaturesForm.tsx","../src/FormField.tsx","../src/FormActions.tsx","../src/SubmitButton.tsx","../src/FormStatus.tsx","../src/index.ts"],"sourcesContent":["import { useEffect, useState, createContext, useContext } from 'react'\nimport { useForm, FormProvider } from 'react-hook-form'\nimport type { FormConfig, AiFeaturesFormProps, FormState } from './types'\n\nconst AIFEATURES_API_URL = 'https://aifeatures.dev'\n\ninterface FormContextValue {\n formState: FormState\n config: FormConfig | null\n}\n\nconst FormContext = createContext<FormContextValue | null>(null)\n\nexport function useAiFeaturesForm() {\n const context = useContext(FormContext)\n if (!context) {\n throw new Error('useAiFeaturesForm must be used within an AiFeaturesForm')\n }\n return context\n}\n\nexport function AiFeaturesForm({\n formId,\n onSuccess,\n onError,\n className,\n children,\n}: AiFeaturesFormProps) {\n const [config, setConfig] = useState<FormConfig | null>(null)\n const [configError, setConfigError] = useState<Error | null>(null)\n const [formState, setFormState] = useState<FormState>({\n isSubmitting: false,\n isSuccess: false,\n isError: false,\n error: null,\n })\n\n const form = useForm()\n\n // Fetch form config on mount\n useEffect(() => {\n async function fetchConfig() {\n try {\n const response = await fetch(`${AIFEATURES_API_URL}/f/${formId}/config`)\n if (!response.ok) {\n throw new Error(`Failed to load form: ${response.status}`)\n }\n const data = await response.json()\n setConfig(data)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to load form config')\n setConfigError(error)\n onError?.(error)\n }\n }\n fetchConfig()\n }, [formId, onError])\n\n const onSubmit = async (data: Record<string, unknown>) => {\n if (!config) return\n\n setFormState({\n isSubmitting: true,\n isSuccess: false,\n isError: false,\n error: null,\n })\n\n try {\n const formData = new FormData()\n\n for (const [key, value] of Object.entries(data)) {\n if (value instanceof FileList) {\n for (const file of Array.from(value)) {\n formData.append(key, file)\n }\n } else if (value !== undefined && value !== null) {\n formData.append(key, String(value))\n }\n }\n\n const response = await fetch(config.endpoint_url, {\n method: 'POST',\n body: formData,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(errorText || `Submission failed: ${response.status}`)\n }\n\n setFormState({\n isSubmitting: false,\n isSuccess: true,\n isError: false,\n error: null,\n })\n\n form.reset()\n // Reset Turnstile widget\n if (typeof window !== 'undefined' && (window as unknown as { turnstile?: { reset: () => void } }).turnstile) {\n (window as unknown as { turnstile: { reset: () => void } }).turnstile.reset()\n }\n\n onSuccess?.(data)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Form submission failed')\n setFormState({\n isSubmitting: false,\n isSuccess: false,\n isError: true,\n error,\n })\n onError?.(error)\n }\n }\n\n // Show loading state while fetching config\n if (!config && !configError) {\n return <div className={className}>Loading form...</div>\n }\n\n // Show error if config fetch failed\n if (configError) {\n return (\n <div className={className}>\n <p>Failed to load form. Please try again later.</p>\n </div>\n )\n }\n\n return (\n <FormContext.Provider value={{ formState, config }}>\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className={className}>\n {children}\n\n {/* Hidden field for Turnstile token - registered for validation */}\n {/* The actual Turnstile widget is rendered by FormActions */}\n {config?.turnstile_sitekey && (\n <input\n type=\"hidden\"\n {...form.register('cf-turnstile-response', {\n required: 'Please complete the CAPTCHA verification',\n })}\n />\n )}\n </form>\n </FormProvider>\n </FormContext.Provider>\n )\n}\n","import { useFormContext } from 'react-hook-form'\nimport type { FormFieldProps } from './types'\n\nexport function FormField({\n name,\n label,\n type = 'text',\n placeholder,\n required = false,\n className,\n options,\n accept,\n multiple,\n}: FormFieldProps) {\n const { register, formState: { errors } } = useFormContext()\n\n const error = errors[name]\n const errorMessage = error?.message as string | undefined\n\n const inputClassName = className || ''\n\n const registerOptions = {\n required: required ? `${label || name} is required` : false,\n }\n\n // Textarea\n if (type === 'textarea') {\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <textarea\n id={name}\n placeholder={placeholder}\n className={inputClassName}\n {...register(name, registerOptions)}\n />\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n }\n\n // Select\n if (type === 'select' && options) {\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <select\n id={name}\n className={inputClassName}\n {...register(name, registerOptions)}\n >\n <option value=\"\">{placeholder || 'Select an option'}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n }\n\n // File input\n if (type === 'file') {\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <input\n id={name}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n className={inputClassName}\n {...register(name, registerOptions)}\n />\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n }\n\n // Standard input (text, email, tel, url, number)\n return (\n <div>\n {label && <label htmlFor={name}>{label}{required && ' *'}</label>}\n <input\n id={name}\n type={type}\n placeholder={placeholder}\n className={inputClassName}\n {...register(name, registerOptions)}\n />\n {errorMessage && <span>{errorMessage}</span>}\n </div>\n )\n}\n","import { useCallback } from 'react'\nimport { useFormContext } from 'react-hook-form'\nimport { Turnstile } from '@marsidev/react-turnstile'\nimport { useAiFeaturesForm } from './AiFeaturesForm'\n\ninterface FormActionsProps {\n /** Children (typically SubmitButton) */\n children: React.ReactNode\n /** Custom class name for the container */\n className?: string\n /** Custom class name for the Turnstile container */\n turnstileClassName?: string\n}\n\nexport function FormActions({\n children,\n className,\n turnstileClassName,\n}: FormActionsProps) {\n const { config } = useAiFeaturesForm()\n const form = useFormContext()\n\n const handleTurnstileSuccess = useCallback((token: string) => {\n form.setValue('cf-turnstile-response', token, { shouldValidate: true })\n }, [form])\n\n const handleTurnstileError = useCallback(() => {\n form.setValue('cf-turnstile-response', '', { shouldValidate: true })\n }, [form])\n\n const handleTurnstileExpire = useCallback(() => {\n form.setValue('cf-turnstile-response', '', { shouldValidate: true })\n }, [form])\n\n return (\n <div className={className}>\n {/* Turnstile CAPTCHA - rendered above submit button */}\n {config?.turnstile_sitekey && (\n <div className={turnstileClassName}>\n <Turnstile\n siteKey={config.turnstile_sitekey}\n onSuccess={handleTurnstileSuccess}\n onError={handleTurnstileError}\n onExpire={handleTurnstileExpire}\n options={{\n theme: 'auto',\n }}\n />\n </div>\n )}\n\n {/* Submit button and any other actions */}\n {children}\n </div>\n )\n}\n","import { useAiFeaturesForm } from './AiFeaturesForm'\nimport type { SubmitButtonProps } from './types'\n\nexport function SubmitButton({\n children,\n loadingText = 'Sending...',\n className,\n}: SubmitButtonProps) {\n const { formState } = useAiFeaturesForm()\n\n return (\n <button\n type=\"submit\"\n disabled={formState.isSubmitting}\n className={className}\n >\n {formState.isSubmitting ? loadingText : children}\n </button>\n )\n}\n","import { useAiFeaturesForm } from './AiFeaturesForm'\n\ninterface FormStatusProps {\n /** Message to show on success */\n successMessage?: string\n /** Custom class name for success state */\n successClassName?: string\n /** Custom class name for error state */\n errorClassName?: string\n}\n\nexport function FormStatus({\n successMessage = 'Thank you! Your message has been sent.',\n successClassName,\n errorClassName,\n}: FormStatusProps) {\n const { formState } = useAiFeaturesForm()\n\n if (formState.isSuccess) {\n return (\n <div className={successClassName}>\n {successMessage}\n </div>\n )\n }\n\n if (formState.isError && formState.error) {\n return (\n <div className={errorClassName}>\n {formState.error.message}\n </div>\n )\n }\n\n return null\n}\n","// Components\nexport { AiFeaturesForm, useAiFeaturesForm } from './AiFeaturesForm'\nexport { FormField } from './FormField'\nexport { FormActions } from './FormActions'\nexport { SubmitButton } from './SubmitButton'\nexport { FormStatus } from './FormStatus'\n\n// Types\nexport type {\n FormConfig,\n AiFeaturesFormProps,\n FormFieldProps,\n SubmitButtonProps,\n FormState,\n} from './types'\n\n// Re-export react-hook-form utilities for advanced usage\nexport { useFormContext, Controller } from 'react-hook-form'\n"],"mappings":"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","names":["jsx","jsxs","useFormContext","jsx","jsxs","useFormContext","jsx","jsx","useFormContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/AifeaturesCaptcha.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { Turnstile } from '@marsidev/react-turnstile';\n\nexport interface AifeaturesCaptchaProps {\n /** Form ID from aifeatures - used to fetch the Turnstile sitekey */\n formId: string;\n /** Base URL for the aifeatures API (defaults to https://aifeatures.dev) */\n apiUrl?: string;\n /** Called when CAPTCHA verification succeeds */\n onVerify?: (token: string) => void;\n /** Called when CAPTCHA verification fails or expires */\n onError?: () => void;\n /** Additional class name for the container */\n className?: string;\n}\n\ninterface FormConfig {\n id: string;\n name: string;\n endpoint_url: string;\n turnstile_sitekey: string | null;\n}\n\n/**\n * A CAPTCHA component that handles Turnstile verification for aifeatures forms.\n *\n * Drop this into any form to add CAPTCHA protection. It renders:\n * - A hidden input field named \"cf-turnstile-response\" with the verification token\n * - The Turnstile widget (only if CAPTCHA is enabled for the form)\n *\n * @example\n * ```tsx\n * <form action={`https://aifeatures.dev/f/${formId}`} method=\"POST\">\n * <input name=\"email\" type=\"email\" required />\n * <textarea name=\"message\" />\n * <AifeaturesCaptcha formId={formId} />\n * <button type=\"submit\">Send</button>\n * </form>\n * ```\n */\nexport function AifeaturesCaptcha({\n formId,\n apiUrl = 'https://aifeatures.dev',\n onVerify,\n onError,\n className,\n}: AifeaturesCaptchaProps) {\n const [sitekey, setSitekey] = useState<string | null>(null);\n const [token, setToken] = useState('');\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() => {\n setIsLoading(true);\n fetch(`${apiUrl}/f/${formId}/config`)\n .then((r) => r.json())\n .then((config: FormConfig) => {\n setSitekey(config.turnstile_sitekey);\n setIsLoading(false);\n })\n .catch(() => {\n setIsLoading(false);\n });\n }, [formId, apiUrl]);\n\n const handleSuccess = (newToken: string) => {\n setToken(newToken);\n onVerify?.(newToken);\n };\n\n const handleError = () => {\n setToken('');\n onError?.();\n };\n\n const handleExpire = () => {\n setToken('');\n onError?.();\n };\n\n // Don't render anything while loading or if CAPTCHA is disabled\n if (isLoading) {\n return <input type=\"hidden\" name=\"aifeatures-captcha-token\" value=\"\" />;\n }\n\n return (\n <div className={className}>\n <input type=\"hidden\" name=\"aifeatures-captcha-token\" value={token} />\n {sitekey && (\n <Turnstile\n siteKey={sitekey}\n onSuccess={handleSuccess}\n onError={handleError}\n onExpire={handleExpire}\n options={{ theme: 'light' }}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;AAAA,SAAS,UAAU,iBAAiB;AACpC,SAAS,iBAAiB;AAgFf,cAIP,YAJO;AAzCJ,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAwB,IAAI;AAC1D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAE/C,YAAU,MAAM;AACd,iBAAa,IAAI;AACjB,UAAM,GAAG,MAAM,MAAM,MAAM,SAAS,EACjC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EACpB,KAAK,CAAC,WAAuB;AAC5B,iBAAW,OAAO,iBAAiB;AACnC,mBAAa,KAAK;AAAA,IACpB,CAAC,EACA,MAAM,MAAM;AACX,mBAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACL,GAAG,CAAC,QAAQ,MAAM,CAAC;AAEnB,QAAM,gBAAgB,CAAC,aAAqB;AAC1C,aAAS,QAAQ;AACjB,eAAW,QAAQ;AAAA,EACrB;AAEA,QAAM,cAAc,MAAM;AACxB,aAAS,EAAE;AACX,cAAU;AAAA,EACZ;AAEA,QAAM,eAAe,MAAM;AACzB,aAAS,EAAE;AACX,cAAU;AAAA,EACZ;AAGA,MAAI,WAAW;AACb,WAAO,oBAAC,WAAM,MAAK,UAAS,MAAK,4BAA2B,OAAM,IAAG;AAAA,EACvE;AAEA,SACE,qBAAC,SAAI,WACH;AAAA,wBAAC,WAAM,MAAK,UAAS,MAAK,4BAA2B,OAAO,OAAO;AAAA,IAClE,WACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW;AAAA,QACX,SAAS;AAAA,QACT,UAAU;AAAA,QACV,SAAS,EAAE,OAAO,QAAQ;AAAA;AAAA,IAC5B;AAAA,KAEJ;AAEJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifeatures/react",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "React
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "React CAPTCHA component for aifeatures forms using Cloudflare Turnstile",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
"react-dom": ">=18"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@marsidev/react-turnstile": "^1.1.0"
|
|
30
|
-
"react-hook-form": "^7.54.2"
|
|
29
|
+
"@marsidev/react-turnstile": "^1.1.0"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
32
|
"@storybook/addon-essentials": "^8.4.2",
|