@aifeatures/react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +336 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +303 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
AiFeaturesForm: () => AiFeaturesForm,
|
|
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
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(index_exports);
|
|
34
|
+
|
|
35
|
+
// src/AiFeaturesForm.tsx
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
var import_react_hook_form = require("react-hook-form");
|
|
38
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
var AIFEATURES_API_URL = "https://aifeatures.dev";
|
|
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({
|
|
49
|
+
formId,
|
|
50
|
+
onSuccess,
|
|
51
|
+
onError,
|
|
52
|
+
className,
|
|
53
|
+
children
|
|
54
|
+
}) {
|
|
55
|
+
const [config, setConfig] = (0, import_react.useState)(null);
|
|
56
|
+
const [configError, setConfigError] = (0, import_react.useState)(null);
|
|
57
|
+
const [formState, setFormState] = (0, import_react.useState)({
|
|
58
|
+
isSubmitting: false,
|
|
59
|
+
isSuccess: false,
|
|
60
|
+
isError: false,
|
|
61
|
+
error: null
|
|
62
|
+
});
|
|
63
|
+
const form = (0, import_react_hook_form.useForm)();
|
|
64
|
+
(0, import_react.useEffect)(() => {
|
|
65
|
+
async function fetchConfig() {
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(`${AIFEATURES_API_URL}/f/${formId}/config`);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Failed to load form: ${response.status}`);
|
|
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
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
const formData = new FormData();
|
|
91
|
+
for (const [key, value] of Object.entries(data)) {
|
|
92
|
+
if (value instanceof FileList) {
|
|
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
|
+
}
|
|
129
|
+
};
|
|
130
|
+
if (!config && !configError) {
|
|
131
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: "Loading form..." });
|
|
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
|
|
170
|
+
};
|
|
171
|
+
if (type === "textarea") {
|
|
172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
173
|
+
label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: name, children: [
|
|
174
|
+
label,
|
|
175
|
+
required && " *"
|
|
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
|
+
] });
|
|
229
|
+
}
|
|
230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
231
|
+
label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: name, children: [
|
|
232
|
+
label,
|
|
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)(
|
|
272
|
+
import_react_turnstile.Turnstile,
|
|
273
|
+
{
|
|
274
|
+
siteKey: config.turnstile_sitekey,
|
|
275
|
+
onSuccess: handleTurnstileSuccess,
|
|
276
|
+
onError: handleTurnstileError,
|
|
277
|
+
onExpire: handleTurnstileExpire,
|
|
278
|
+
options: {
|
|
279
|
+
theme: "auto"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
) }),
|
|
283
|
+
children
|
|
284
|
+
] });
|
|
285
|
+
}
|
|
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
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
326
|
+
0 && (module.exports = {
|
|
327
|
+
AiFeaturesForm,
|
|
328
|
+
Controller,
|
|
329
|
+
FormActions,
|
|
330
|
+
FormField,
|
|
331
|
+
FormStatus,
|
|
332
|
+
SubmitButton,
|
|
333
|
+
useAiFeaturesForm,
|
|
334
|
+
useFormContext
|
|
335
|
+
});
|
|
336
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
export { Controller, useFormContext } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
interface FormConfig {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
endpoint_url: string;
|
|
8
|
+
turnstile_sitekey: string | null;
|
|
9
|
+
}
|
|
10
|
+
interface AiFeaturesFormProps {
|
|
11
|
+
/** Form ID from aifeatures.dev */
|
|
12
|
+
formId: string;
|
|
13
|
+
/** Called on successful submission */
|
|
14
|
+
onSuccess?: (data: Record<string, unknown>) => void;
|
|
15
|
+
/** Called on submission error */
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
/** Custom class name for the form element */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Children (FormField, SubmitButton, etc.) */
|
|
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 */
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
interface FormState {
|
|
54
|
+
isSubmitting: boolean;
|
|
55
|
+
isSuccess: boolean;
|
|
56
|
+
isError: boolean;
|
|
57
|
+
error: Error | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface FormContextValue {
|
|
61
|
+
formState: FormState;
|
|
62
|
+
config: FormConfig | null;
|
|
63
|
+
}
|
|
64
|
+
declare function useAiFeaturesForm(): FormContextValue;
|
|
65
|
+
declare function AiFeaturesForm({ formId, onSuccess, onError, className, children, }: AiFeaturesFormProps): react_jsx_runtime.JSX.Element;
|
|
66
|
+
|
|
67
|
+
declare function FormField({ name, label, type, placeholder, required, className, options, accept, multiple, }: FormFieldProps): react_jsx_runtime.JSX.Element;
|
|
68
|
+
|
|
69
|
+
interface FormActionsProps {
|
|
70
|
+
/** Children (typically SubmitButton) */
|
|
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;
|
|
90
|
+
|
|
91
|
+
export { AiFeaturesForm, type AiFeaturesFormProps, FormActions, type FormConfig, FormField, type FormFieldProps, type FormState, FormStatus, SubmitButton, type SubmitButtonProps, useAiFeaturesForm };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
export { Controller, useFormContext } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
interface FormConfig {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
endpoint_url: string;
|
|
8
|
+
turnstile_sitekey: string | null;
|
|
9
|
+
}
|
|
10
|
+
interface AiFeaturesFormProps {
|
|
11
|
+
/** Form ID from aifeatures.dev */
|
|
12
|
+
formId: string;
|
|
13
|
+
/** Called on successful submission */
|
|
14
|
+
onSuccess?: (data: Record<string, unknown>) => void;
|
|
15
|
+
/** Called on submission error */
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
/** Custom class name for the form element */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Children (FormField, SubmitButton, etc.) */
|
|
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 */
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
interface FormState {
|
|
54
|
+
isSubmitting: boolean;
|
|
55
|
+
isSuccess: boolean;
|
|
56
|
+
isError: boolean;
|
|
57
|
+
error: Error | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface FormContextValue {
|
|
61
|
+
formState: FormState;
|
|
62
|
+
config: FormConfig | null;
|
|
63
|
+
}
|
|
64
|
+
declare function useAiFeaturesForm(): FormContextValue;
|
|
65
|
+
declare function AiFeaturesForm({ formId, onSuccess, onError, className, children, }: AiFeaturesFormProps): react_jsx_runtime.JSX.Element;
|
|
66
|
+
|
|
67
|
+
declare function FormField({ name, label, type, placeholder, required, className, options, accept, multiple, }: FormFieldProps): react_jsx_runtime.JSX.Element;
|
|
68
|
+
|
|
69
|
+
interface FormActionsProps {
|
|
70
|
+
/** Children (typically SubmitButton) */
|
|
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;
|
|
90
|
+
|
|
91
|
+
export { AiFeaturesForm, type AiFeaturesFormProps, FormActions, type FormConfig, FormField, type FormFieldProps, type FormState, FormStatus, SubmitButton, type SubmitButtonProps, useAiFeaturesForm };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/AiFeaturesForm.tsx
|
|
4
|
+
import { useEffect, useState, createContext, useContext } from "react";
|
|
5
|
+
import { useForm, FormProvider } from "react-hook-form";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var AIFEATURES_API_URL = "https://aifeatures.dev";
|
|
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({
|
|
17
|
+
formId,
|
|
18
|
+
onSuccess,
|
|
19
|
+
onError,
|
|
20
|
+
className,
|
|
21
|
+
children
|
|
22
|
+
}) {
|
|
23
|
+
const [config, setConfig] = useState(null);
|
|
24
|
+
const [configError, setConfigError] = useState(null);
|
|
25
|
+
const [formState, setFormState] = useState({
|
|
26
|
+
isSubmitting: false,
|
|
27
|
+
isSuccess: false,
|
|
28
|
+
isError: false,
|
|
29
|
+
error: null
|
|
30
|
+
});
|
|
31
|
+
const form = useForm();
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
async function fetchConfig() {
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(`${AIFEATURES_API_URL}/f/${formId}/config`);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`Failed to load form: ${response.status}`);
|
|
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
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const formData = new FormData();
|
|
59
|
+
for (const [key, value] of Object.entries(data)) {
|
|
60
|
+
if (value instanceof FileList) {
|
|
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
|
+
}
|
|
97
|
+
};
|
|
98
|
+
if (!config && !configError) {
|
|
99
|
+
return /* @__PURE__ */ jsx("div", { className, children: "Loading form..." });
|
|
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
|
|
138
|
+
};
|
|
139
|
+
if (type === "textarea") {
|
|
140
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
141
|
+
label && /* @__PURE__ */ jsxs2("label", { htmlFor: name, children: [
|
|
142
|
+
label,
|
|
143
|
+
required && " *"
|
|
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
|
+
] });
|
|
197
|
+
}
|
|
198
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
199
|
+
label && /* @__PURE__ */ jsxs2("label", { htmlFor: name, children: [
|
|
200
|
+
label,
|
|
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(
|
|
240
|
+
Turnstile,
|
|
241
|
+
{
|
|
242
|
+
siteKey: config.turnstile_sitekey,
|
|
243
|
+
onSuccess: handleTurnstileSuccess,
|
|
244
|
+
onError: handleTurnstileError,
|
|
245
|
+
onExpire: handleTurnstileExpire,
|
|
246
|
+
options: {
|
|
247
|
+
theme: "auto"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
) }),
|
|
251
|
+
children
|
|
252
|
+
] });
|
|
253
|
+
}
|
|
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
|
+
export {
|
|
294
|
+
AiFeaturesForm,
|
|
295
|
+
Controller,
|
|
296
|
+
FormActions,
|
|
297
|
+
FormField,
|
|
298
|
+
FormStatus,
|
|
299
|
+
SubmitButton,
|
|
300
|
+
useAiFeaturesForm,
|
|
301
|
+
useFormContext3 as useFormContext
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aifeatures/react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React components for aifeatures forms with built-in Turnstile CAPTCHA",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=18",
|
|
26
|
+
"react-dom": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@marsidev/react-turnstile": "^1.1.0",
|
|
30
|
+
"react-hook-form": "^7.54.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@storybook/addon-essentials": "^8.4.2",
|
|
34
|
+
"@storybook/addon-interactions": "^8.4.2",
|
|
35
|
+
"@storybook/addon-links": "^8.4.2",
|
|
36
|
+
"@storybook/blocks": "^8.4.2",
|
|
37
|
+
"@storybook/react": "^8.4.2",
|
|
38
|
+
"@storybook/react-vite": "^8.4.2",
|
|
39
|
+
"@storybook/test": "^8.4.2",
|
|
40
|
+
"@types/react": "^18.3.12",
|
|
41
|
+
"@types/react-dom": "^18.3.1",
|
|
42
|
+
"msw": "^2.6.5",
|
|
43
|
+
"msw-storybook-addon": "^2.0.3",
|
|
44
|
+
"react": "^18.3.1",
|
|
45
|
+
"react-dom": "^18.3.1",
|
|
46
|
+
"storybook": "^8.4.2",
|
|
47
|
+
"tsup": "^8.3.5",
|
|
48
|
+
"typescript": "^5.3.3"
|
|
49
|
+
},
|
|
50
|
+
"msw": {
|
|
51
|
+
"workerDirectory": [
|
|
52
|
+
".storybook/public"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"lint": "eslint src/",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"storybook": "storybook dev -p 6007",
|
|
61
|
+
"build-storybook": "storybook build"
|
|
62
|
+
}
|
|
63
|
+
}
|