@aifeatures/admin-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1345 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +206 -0
- package/dist/index.d.ts +206 -0
- package/dist/index.js +1304 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +79 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1345 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
AifeaturesApiError: () => AifeaturesApiError,
|
|
35
|
+
AifeaturesProvider: () => AifeaturesProvider,
|
|
36
|
+
FormSettings: () => FormSettings,
|
|
37
|
+
FormSubmissions: () => FormSubmissions,
|
|
38
|
+
FormsDashboard: () => FormsDashboard,
|
|
39
|
+
FormsList: () => FormsList,
|
|
40
|
+
SubmissionDetail: () => SubmissionDetail,
|
|
41
|
+
createApiClient: () => createApiClient,
|
|
42
|
+
useAifeatures: () => useAifeaturesContext,
|
|
43
|
+
useAifeaturesContext: () => useAifeaturesContext,
|
|
44
|
+
useForms: () => useForms,
|
|
45
|
+
useSubmissions: () => useSubmissions
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/provider/AifeaturesProvider.tsx
|
|
50
|
+
var React = __toESM(require("react"), 1);
|
|
51
|
+
|
|
52
|
+
// src/lib/api.ts
|
|
53
|
+
var AifeaturesApiError = class extends Error {
|
|
54
|
+
constructor(message, status, details) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.status = status;
|
|
57
|
+
this.details = details;
|
|
58
|
+
this.name = "AifeaturesApiError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
function createApiClient(siteToken, apiUrl = "https://aifeatures.dev") {
|
|
62
|
+
async function request(path, options = {}) {
|
|
63
|
+
const url = `${apiUrl}${path}`;
|
|
64
|
+
const response = await fetch(url, {
|
|
65
|
+
...options,
|
|
66
|
+
headers: {
|
|
67
|
+
"Authorization": `Bearer ${siteToken}`,
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...options.headers
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
let details;
|
|
74
|
+
try {
|
|
75
|
+
details = await response.json();
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
throw new AifeaturesApiError(
|
|
79
|
+
details?.error || `Request failed with status ${response.status}`,
|
|
80
|
+
response.status,
|
|
81
|
+
details
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (response.status === 204) {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
return response.json();
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
// Forms
|
|
91
|
+
async getForms() {
|
|
92
|
+
const data = await request("/api/v1/forms");
|
|
93
|
+
return data.forms;
|
|
94
|
+
},
|
|
95
|
+
async getForm(formId) {
|
|
96
|
+
return request(`/api/v1/forms/${formId}`);
|
|
97
|
+
},
|
|
98
|
+
async createForm(input) {
|
|
99
|
+
return request("/api/v1/forms", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: JSON.stringify(input)
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
async updateForm(formId, input) {
|
|
105
|
+
return request(`/api/v1/forms/${formId}`, {
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
body: JSON.stringify(input)
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
async deleteForm(formId) {
|
|
111
|
+
await request(`/api/v1/forms/${formId}`, {
|
|
112
|
+
method: "DELETE"
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
// Submissions
|
|
116
|
+
async getSubmissions(formId, options = {}) {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
if (options.limit !== void 0) {
|
|
119
|
+
params.set("limit", String(options.limit));
|
|
120
|
+
}
|
|
121
|
+
if (options.offset !== void 0) {
|
|
122
|
+
params.set("offset", String(options.offset));
|
|
123
|
+
}
|
|
124
|
+
const query = params.toString();
|
|
125
|
+
const path = `/api/v1/forms/${formId}/submissions${query ? `?${query}` : ""}`;
|
|
126
|
+
return request(path);
|
|
127
|
+
},
|
|
128
|
+
async getSubmission(submissionId) {
|
|
129
|
+
return request(`/api/v1/submissions/${submissionId}`);
|
|
130
|
+
},
|
|
131
|
+
async deleteSubmission(submissionId) {
|
|
132
|
+
await request(`/api/v1/submissions/${submissionId}`, {
|
|
133
|
+
method: "DELETE"
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
// Attachments
|
|
137
|
+
async downloadAttachment(submissionId, filename) {
|
|
138
|
+
const url = `${apiUrl}/api/v1/submissions/${submissionId}/attachments/${encodeURIComponent(filename)}`;
|
|
139
|
+
const response = await fetch(url, {
|
|
140
|
+
headers: {
|
|
141
|
+
"Authorization": `Bearer ${siteToken}`
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
let details;
|
|
146
|
+
try {
|
|
147
|
+
details = await response.json();
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
throw new AifeaturesApiError(
|
|
151
|
+
details?.error || `Download failed with status ${response.status}`,
|
|
152
|
+
response.status,
|
|
153
|
+
details
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const blob = await response.blob();
|
|
157
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
158
|
+
const link = document.createElement("a");
|
|
159
|
+
link.href = blobUrl;
|
|
160
|
+
link.download = filename;
|
|
161
|
+
document.body.appendChild(link);
|
|
162
|
+
link.click();
|
|
163
|
+
document.body.removeChild(link);
|
|
164
|
+
URL.revokeObjectURL(blobUrl);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/provider/AifeaturesProvider.tsx
|
|
170
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
171
|
+
var AifeaturesContext = React.createContext(
|
|
172
|
+
null
|
|
173
|
+
);
|
|
174
|
+
function validateToken(token) {
|
|
175
|
+
if (!token) {
|
|
176
|
+
return "No siteToken provided to AifeaturesProvider";
|
|
177
|
+
}
|
|
178
|
+
if (token.startsWith("sk_")) {
|
|
179
|
+
return "Invalid token type: You passed an organization API key (sk_xxx) but AifeaturesProvider requires a site token (st_xxx). Site tokens are returned when you create a site via the API.";
|
|
180
|
+
}
|
|
181
|
+
if (!token.startsWith("st_")) {
|
|
182
|
+
return `Invalid token format: Expected a site token starting with "st_" but got "${token.slice(0, 10)}...". Site tokens are returned when you create a site via the API.`;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function AifeaturesProvider({
|
|
187
|
+
siteToken,
|
|
188
|
+
apiUrl = "https://aifeatures.dev",
|
|
189
|
+
dark = false,
|
|
190
|
+
className,
|
|
191
|
+
children
|
|
192
|
+
}) {
|
|
193
|
+
const tokenError = React.useMemo(() => validateToken(siteToken), [siteToken]);
|
|
194
|
+
const api = React.useMemo(
|
|
195
|
+
() => createApiClient(siteToken, apiUrl),
|
|
196
|
+
[siteToken, apiUrl]
|
|
197
|
+
);
|
|
198
|
+
const value = React.useMemo(
|
|
199
|
+
() => ({
|
|
200
|
+
siteToken,
|
|
201
|
+
apiUrl,
|
|
202
|
+
api
|
|
203
|
+
}),
|
|
204
|
+
[siteToken, apiUrl, api]
|
|
205
|
+
);
|
|
206
|
+
if (tokenError) {
|
|
207
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `aifeatures-admin ${dark ? "dark" : ""} ${className || ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "af-rounded-md af-border af-border-destructive af-bg-destructive/10 af-p-4", children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "af-text-sm af-font-semibold af-text-destructive af-mb-2", children: "AifeaturesProvider Configuration Error" }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "af-text-sm af-text-destructive", children: tokenError })
|
|
210
|
+
] }) });
|
|
211
|
+
}
|
|
212
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AifeaturesContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `aifeatures-admin ${dark ? "dark" : ""} ${className || ""}`, children }) });
|
|
213
|
+
}
|
|
214
|
+
function useAifeaturesContext() {
|
|
215
|
+
const context = React.useContext(AifeaturesContext);
|
|
216
|
+
if (!context) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
"useAifeaturesContext must be used within an AifeaturesProvider"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return context;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/components/FormsDashboard.tsx
|
|
225
|
+
var React13 = __toESM(require("react"), 1);
|
|
226
|
+
var import_lucide_react6 = require("lucide-react");
|
|
227
|
+
|
|
228
|
+
// src/ui/tabs.tsx
|
|
229
|
+
var React2 = __toESM(require("react"), 1);
|
|
230
|
+
var TabsPrimitive = __toESM(require("@radix-ui/react-tabs"), 1);
|
|
231
|
+
|
|
232
|
+
// src/lib/cn.ts
|
|
233
|
+
var import_clsx = require("clsx");
|
|
234
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
235
|
+
function cn(...inputs) {
|
|
236
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/ui/tabs.tsx
|
|
240
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
241
|
+
var Tabs = TabsPrimitive.Root;
|
|
242
|
+
var TabsList = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
243
|
+
TabsPrimitive.List,
|
|
244
|
+
{
|
|
245
|
+
ref,
|
|
246
|
+
className: cn(
|
|
247
|
+
"af-inline-flex af-h-9 af-items-center af-justify-center af-rounded-lg af-bg-muted af-p-1 af-text-muted-foreground",
|
|
248
|
+
className
|
|
249
|
+
),
|
|
250
|
+
...props
|
|
251
|
+
}
|
|
252
|
+
));
|
|
253
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
254
|
+
var TabsTrigger = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
255
|
+
TabsPrimitive.Trigger,
|
|
256
|
+
{
|
|
257
|
+
ref,
|
|
258
|
+
className: cn(
|
|
259
|
+
"af-inline-flex af-items-center af-justify-center af-whitespace-nowrap af-rounded-md af-px-3 af-py-1 af-text-sm af-font-medium af-ring-offset-background af-transition-all focus-visible:af-outline-none focus-visible:af-ring-2 focus-visible:af-ring-ring focus-visible:af-ring-offset-2 disabled:af-pointer-events-none disabled:af-opacity-50 data-[state=active]:af-bg-background data-[state=active]:af-text-foreground data-[state=active]:af-shadow",
|
|
260
|
+
className
|
|
261
|
+
),
|
|
262
|
+
...props
|
|
263
|
+
}
|
|
264
|
+
));
|
|
265
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
266
|
+
var TabsContent = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
267
|
+
TabsPrimitive.Content,
|
|
268
|
+
{
|
|
269
|
+
ref,
|
|
270
|
+
className: cn(
|
|
271
|
+
"af-mt-2 af-ring-offset-background focus-visible:af-outline-none focus-visible:af-ring-2 focus-visible:af-ring-ring focus-visible:af-ring-offset-2",
|
|
272
|
+
className
|
|
273
|
+
),
|
|
274
|
+
...props
|
|
275
|
+
}
|
|
276
|
+
));
|
|
277
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
278
|
+
|
|
279
|
+
// src/ui/button.tsx
|
|
280
|
+
var React3 = __toESM(require("react"), 1);
|
|
281
|
+
var import_react_slot = require("@radix-ui/react-slot");
|
|
282
|
+
var import_class_variance_authority = require("class-variance-authority");
|
|
283
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
284
|
+
var buttonVariants = (0, import_class_variance_authority.cva)(
|
|
285
|
+
"af-inline-flex af-items-center af-justify-center af-gap-2 af-whitespace-nowrap af-rounded-md af-text-sm af-font-medium af-transition-colors focus-visible:af-outline-none focus-visible:af-ring-1 focus-visible:af-ring-ring disabled:af-pointer-events-none disabled:af-opacity-50 [&_svg]:af-pointer-events-none [&_svg]:af-size-4 [&_svg]:af-shrink-0",
|
|
286
|
+
{
|
|
287
|
+
variants: {
|
|
288
|
+
variant: {
|
|
289
|
+
default: "af-bg-primary af-text-primary-foreground af-shadow hover:af-bg-primary/90",
|
|
290
|
+
destructive: "af-bg-destructive af-text-destructive-foreground af-shadow-sm hover:af-bg-destructive/90",
|
|
291
|
+
outline: "af-border af-border-input af-bg-background af-shadow-sm hover:af-bg-accent hover:af-text-accent-foreground",
|
|
292
|
+
secondary: "af-bg-secondary af-text-secondary-foreground af-shadow-sm hover:af-bg-secondary/80",
|
|
293
|
+
ghost: "hover:af-bg-accent hover:af-text-accent-foreground",
|
|
294
|
+
link: "af-text-primary af-underline-offset-4 hover:af-underline"
|
|
295
|
+
},
|
|
296
|
+
size: {
|
|
297
|
+
default: "af-h-9 af-px-4 af-py-2",
|
|
298
|
+
sm: "af-h-8 af-rounded-md af-px-3 af-text-xs",
|
|
299
|
+
lg: "af-h-10 af-rounded-md af-px-8",
|
|
300
|
+
icon: "af-h-9 af-w-9"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
defaultVariants: {
|
|
304
|
+
variant: "default",
|
|
305
|
+
size: "default"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
var Button = React3.forwardRef(
|
|
310
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
311
|
+
const Comp = asChild ? import_react_slot.Slot : "button";
|
|
312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
313
|
+
Comp,
|
|
314
|
+
{
|
|
315
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
316
|
+
ref,
|
|
317
|
+
...props
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
Button.displayName = "Button";
|
|
323
|
+
|
|
324
|
+
// src/components/FormsList.tsx
|
|
325
|
+
var import_lucide_react = require("lucide-react");
|
|
326
|
+
|
|
327
|
+
// src/ui/table.tsx
|
|
328
|
+
var React4 = __toESM(require("react"), 1);
|
|
329
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
330
|
+
var Table = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "af-relative af-w-full af-overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
331
|
+
"table",
|
|
332
|
+
{
|
|
333
|
+
ref,
|
|
334
|
+
className: cn("af-w-full af-caption-bottom af-text-sm", className),
|
|
335
|
+
...props
|
|
336
|
+
}
|
|
337
|
+
) }));
|
|
338
|
+
Table.displayName = "Table";
|
|
339
|
+
var TableHeader = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("thead", { ref, className: cn("[&_tr]:af-border-b", className), ...props }));
|
|
340
|
+
TableHeader.displayName = "TableHeader";
|
|
341
|
+
var TableBody = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
342
|
+
"tbody",
|
|
343
|
+
{
|
|
344
|
+
ref,
|
|
345
|
+
className: cn("[&_tr:last-child]:af-border-0", className),
|
|
346
|
+
...props
|
|
347
|
+
}
|
|
348
|
+
));
|
|
349
|
+
TableBody.displayName = "TableBody";
|
|
350
|
+
var TableFooter = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
351
|
+
"tfoot",
|
|
352
|
+
{
|
|
353
|
+
ref,
|
|
354
|
+
className: cn(
|
|
355
|
+
"af-border-t af-bg-muted/50 af-font-medium [&>tr]:last:af-border-b-0",
|
|
356
|
+
className
|
|
357
|
+
),
|
|
358
|
+
...props
|
|
359
|
+
}
|
|
360
|
+
));
|
|
361
|
+
TableFooter.displayName = "TableFooter";
|
|
362
|
+
var TableRow = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
363
|
+
"tr",
|
|
364
|
+
{
|
|
365
|
+
ref,
|
|
366
|
+
className: cn(
|
|
367
|
+
"af-border-b af-transition-colors hover:af-bg-muted/50 data-[state=selected]:af-bg-muted",
|
|
368
|
+
className
|
|
369
|
+
),
|
|
370
|
+
...props
|
|
371
|
+
}
|
|
372
|
+
));
|
|
373
|
+
TableRow.displayName = "TableRow";
|
|
374
|
+
var TableHead = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
375
|
+
"th",
|
|
376
|
+
{
|
|
377
|
+
ref,
|
|
378
|
+
className: cn(
|
|
379
|
+
"af-h-10 af-px-2 af-text-left af-align-middle af-font-medium af-text-muted-foreground [&:has([role=checkbox])]:af-pr-0 [&>[role=checkbox]]:af-translate-y-[2px]",
|
|
380
|
+
className
|
|
381
|
+
),
|
|
382
|
+
...props
|
|
383
|
+
}
|
|
384
|
+
));
|
|
385
|
+
TableHead.displayName = "TableHead";
|
|
386
|
+
var TableCell = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
387
|
+
"td",
|
|
388
|
+
{
|
|
389
|
+
ref,
|
|
390
|
+
className: cn(
|
|
391
|
+
"af-p-2 af-align-middle [&:has([role=checkbox])]:af-pr-0 [&>[role=checkbox]]:af-translate-y-[2px]",
|
|
392
|
+
className
|
|
393
|
+
),
|
|
394
|
+
...props
|
|
395
|
+
}
|
|
396
|
+
));
|
|
397
|
+
TableCell.displayName = "TableCell";
|
|
398
|
+
var TableCaption = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
399
|
+
"caption",
|
|
400
|
+
{
|
|
401
|
+
ref,
|
|
402
|
+
className: cn("af-mt-4 af-text-sm af-text-muted-foreground", className),
|
|
403
|
+
...props
|
|
404
|
+
}
|
|
405
|
+
));
|
|
406
|
+
TableCaption.displayName = "TableCaption";
|
|
407
|
+
|
|
408
|
+
// src/hooks/useForms.ts
|
|
409
|
+
var React5 = __toESM(require("react"), 1);
|
|
410
|
+
function useForms() {
|
|
411
|
+
const { api } = useAifeaturesContext();
|
|
412
|
+
const [forms, setForms] = React5.useState([]);
|
|
413
|
+
const [isLoading, setIsLoading] = React5.useState(true);
|
|
414
|
+
const [error, setError] = React5.useState(null);
|
|
415
|
+
const fetchForms = React5.useCallback(async () => {
|
|
416
|
+
try {
|
|
417
|
+
setIsLoading(true);
|
|
418
|
+
setError(null);
|
|
419
|
+
const data = await api.getForms();
|
|
420
|
+
setForms(data);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch forms"));
|
|
423
|
+
} finally {
|
|
424
|
+
setIsLoading(false);
|
|
425
|
+
}
|
|
426
|
+
}, [api]);
|
|
427
|
+
React5.useEffect(() => {
|
|
428
|
+
fetchForms();
|
|
429
|
+
}, [fetchForms]);
|
|
430
|
+
const createForm = React5.useCallback(
|
|
431
|
+
async (input) => {
|
|
432
|
+
const form = await api.createForm(input);
|
|
433
|
+
setForms((prev) => [...prev, form]);
|
|
434
|
+
return form;
|
|
435
|
+
},
|
|
436
|
+
[api]
|
|
437
|
+
);
|
|
438
|
+
const updateForm = React5.useCallback(
|
|
439
|
+
async (formId, input) => {
|
|
440
|
+
const form = await api.updateForm(formId, input);
|
|
441
|
+
setForms((prev) => prev.map((f) => f.id === formId ? form : f));
|
|
442
|
+
return form;
|
|
443
|
+
},
|
|
444
|
+
[api]
|
|
445
|
+
);
|
|
446
|
+
const deleteForm = React5.useCallback(
|
|
447
|
+
async (formId) => {
|
|
448
|
+
await api.deleteForm(formId);
|
|
449
|
+
setForms((prev) => prev.filter((f) => f.id !== formId));
|
|
450
|
+
},
|
|
451
|
+
[api]
|
|
452
|
+
);
|
|
453
|
+
return {
|
|
454
|
+
forms,
|
|
455
|
+
isLoading,
|
|
456
|
+
error,
|
|
457
|
+
refetch: fetchForms,
|
|
458
|
+
createForm,
|
|
459
|
+
updateForm,
|
|
460
|
+
deleteForm
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/components/FormsList.tsx
|
|
465
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
466
|
+
function FormsList({ onSelectForm, className }) {
|
|
467
|
+
const { forms, isLoading, error } = useForms();
|
|
468
|
+
if (isLoading) {
|
|
469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `af-flex af-items-center af-justify-center af-py-8 ${className || ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "af-text-muted-foreground", children: "Loading forms..." }) });
|
|
470
|
+
}
|
|
471
|
+
if (error) {
|
|
472
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `af-flex af-items-center af-justify-center af-py-8 ${className || ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "af-text-destructive", children: [
|
|
473
|
+
"Error: ",
|
|
474
|
+
error.message
|
|
475
|
+
] }) });
|
|
476
|
+
}
|
|
477
|
+
if (forms.length === 0) {
|
|
478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `af-flex af-flex-col af-items-center af-justify-center af-py-12 ${className || ""}`, children: [
|
|
479
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Inbox, { className: "af-h-12 af-w-12 af-text-muted-foreground af-mb-4" }),
|
|
480
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "af-text-muted-foreground af-text-center", children: [
|
|
481
|
+
"No forms yet.",
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("br", {}),
|
|
483
|
+
"Forms will appear here when the AI creates contact forms on your website."
|
|
484
|
+
] })
|
|
485
|
+
] });
|
|
486
|
+
}
|
|
487
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Table, { children: [
|
|
488
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(TableRow, { children: [
|
|
489
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableHead, { children: "Name" }),
|
|
490
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableHead, { children: "Endpoint" }),
|
|
491
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableHead, { className: "af-text-right", children: "Actions" })
|
|
492
|
+
] }) }),
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableBody, { children: forms.map((form) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(TableRow, { children: [
|
|
494
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableCell, { className: "af-font-medium", children: form.name }),
|
|
495
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { className: "af-text-xs af-bg-muted af-px-1.5 af-py-0.5 af-rounded", children: form.endpoint_url.replace("https://aifeatures.dev", "") }) }),
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TableCell, { className: "af-text-right", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "af-flex af-items-center af-justify-end af-gap-2", children: [
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
498
|
+
Button,
|
|
499
|
+
{
|
|
500
|
+
variant: "ghost",
|
|
501
|
+
size: "sm",
|
|
502
|
+
onClick: () => onSelectForm?.(form, "submissions"),
|
|
503
|
+
children: [
|
|
504
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Inbox, { className: "af-h-4 af-w-4 af-mr-1" }),
|
|
505
|
+
"Submissions"
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
),
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
510
|
+
Button,
|
|
511
|
+
{
|
|
512
|
+
variant: "ghost",
|
|
513
|
+
size: "sm",
|
|
514
|
+
onClick: () => onSelectForm?.(form, "settings"),
|
|
515
|
+
children: [
|
|
516
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Settings, { className: "af-h-4 af-w-4 af-mr-1" }),
|
|
517
|
+
"Settings"
|
|
518
|
+
]
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
] }) })
|
|
522
|
+
] }, form.id)) })
|
|
523
|
+
] }) });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/components/FormSubmissions.tsx
|
|
527
|
+
var React9 = __toESM(require("react"), 1);
|
|
528
|
+
var import_lucide_react4 = require("lucide-react");
|
|
529
|
+
|
|
530
|
+
// src/ui/select.tsx
|
|
531
|
+
var React6 = __toESM(require("react"), 1);
|
|
532
|
+
var SelectPrimitive = __toESM(require("@radix-ui/react-select"), 1);
|
|
533
|
+
var import_lucide_react2 = require("lucide-react");
|
|
534
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
535
|
+
var Select = SelectPrimitive.Root;
|
|
536
|
+
var SelectValue = SelectPrimitive.Value;
|
|
537
|
+
var SelectTrigger = React6.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
538
|
+
SelectPrimitive.Trigger,
|
|
539
|
+
{
|
|
540
|
+
ref,
|
|
541
|
+
className: cn(
|
|
542
|
+
"af-flex af-h-9 af-w-full af-items-center af-justify-between af-whitespace-nowrap af-rounded-md af-border af-border-input af-bg-transparent af-px-3 af-py-2 af-text-sm af-shadow-sm af-ring-offset-background placeholder:af-text-muted-foreground focus:af-outline-none focus:af-ring-1 focus:af-ring-ring disabled:af-cursor-not-allowed disabled:af-opacity-50 [&>span]:af-line-clamp-1",
|
|
543
|
+
className
|
|
544
|
+
),
|
|
545
|
+
...props,
|
|
546
|
+
children: [
|
|
547
|
+
children,
|
|
548
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ChevronDown, { className: "af-h-4 af-w-4 af-opacity-50" }) })
|
|
549
|
+
]
|
|
550
|
+
}
|
|
551
|
+
));
|
|
552
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
553
|
+
var SelectScrollUpButton = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
554
|
+
SelectPrimitive.ScrollUpButton,
|
|
555
|
+
{
|
|
556
|
+
ref,
|
|
557
|
+
className: cn(
|
|
558
|
+
"af-flex af-cursor-default af-items-center af-justify-center af-py-1",
|
|
559
|
+
className
|
|
560
|
+
),
|
|
561
|
+
...props,
|
|
562
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ChevronUp, { className: "af-h-4 af-w-4" })
|
|
563
|
+
}
|
|
564
|
+
));
|
|
565
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
566
|
+
var SelectScrollDownButton = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
567
|
+
SelectPrimitive.ScrollDownButton,
|
|
568
|
+
{
|
|
569
|
+
ref,
|
|
570
|
+
className: cn(
|
|
571
|
+
"af-flex af-cursor-default af-items-center af-justify-center af-py-1",
|
|
572
|
+
className
|
|
573
|
+
),
|
|
574
|
+
...props,
|
|
575
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ChevronDown, { className: "af-h-4 af-w-4" })
|
|
576
|
+
}
|
|
577
|
+
));
|
|
578
|
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
579
|
+
var SelectContent = React6.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SelectPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
580
|
+
SelectPrimitive.Content,
|
|
581
|
+
{
|
|
582
|
+
ref,
|
|
583
|
+
className: cn(
|
|
584
|
+
"af-relative af-z-50 af-max-h-96 af-min-w-[8rem] af-overflow-hidden af-rounded-md af-border af-bg-popover af-text-popover-foreground af-shadow-md data-[state=open]:af-animate-in data-[state=closed]:af-animate-out data-[state=closed]:af-fade-out-0 data-[state=open]:af-fade-in-0 data-[state=closed]:af-zoom-out-95 data-[state=open]:af-zoom-in-95 data-[side=bottom]:af-slide-in-from-top-2 data-[side=left]:af-slide-in-from-right-2 data-[side=right]:af-slide-in-from-left-2 data-[side=top]:af-slide-in-from-bottom-2",
|
|
585
|
+
position === "popper" && "data-[side=bottom]:af-translate-y-1 data-[side=left]:af--translate-x-1 data-[side=right]:af-translate-x-1 data-[side=top]:af--translate-y-1",
|
|
586
|
+
className
|
|
587
|
+
),
|
|
588
|
+
position,
|
|
589
|
+
...props,
|
|
590
|
+
children: [
|
|
591
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SelectScrollUpButton, {}),
|
|
592
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
593
|
+
SelectPrimitive.Viewport,
|
|
594
|
+
{
|
|
595
|
+
className: cn(
|
|
596
|
+
"af-p-1",
|
|
597
|
+
position === "popper" && "af-h-[var(--radix-select-trigger-height)] af-w-full af-min-w-[var(--radix-select-trigger-width)]"
|
|
598
|
+
),
|
|
599
|
+
children
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SelectScrollDownButton, {})
|
|
603
|
+
]
|
|
604
|
+
}
|
|
605
|
+
) }));
|
|
606
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
607
|
+
var SelectLabel = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
608
|
+
SelectPrimitive.Label,
|
|
609
|
+
{
|
|
610
|
+
ref,
|
|
611
|
+
className: cn("af-px-2 af-py-1.5 af-text-sm af-font-semibold", className),
|
|
612
|
+
...props
|
|
613
|
+
}
|
|
614
|
+
));
|
|
615
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
616
|
+
var SelectItem = React6.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
617
|
+
SelectPrimitive.Item,
|
|
618
|
+
{
|
|
619
|
+
ref,
|
|
620
|
+
className: cn(
|
|
621
|
+
"af-relative af-flex af-w-full af-cursor-default af-select-none af-items-center af-rounded-sm af-py-1.5 af-pl-2 af-pr-8 af-text-sm af-outline-none focus:af-bg-accent focus:af-text-accent-foreground data-[disabled]:af-pointer-events-none data-[disabled]:af-opacity-50",
|
|
622
|
+
className
|
|
623
|
+
),
|
|
624
|
+
...props,
|
|
625
|
+
children: [
|
|
626
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "af-absolute af-right-2 af-flex af-h-3.5 af-w-3.5 af-items-center af-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Check, { className: "af-h-4 af-w-4" }) }) }),
|
|
627
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SelectPrimitive.ItemText, { children })
|
|
628
|
+
]
|
|
629
|
+
}
|
|
630
|
+
));
|
|
631
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
632
|
+
var SelectSeparator = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
633
|
+
SelectPrimitive.Separator,
|
|
634
|
+
{
|
|
635
|
+
ref,
|
|
636
|
+
className: cn("af--mx-1 af-my-1 af-h-px af-bg-muted", className),
|
|
637
|
+
...props
|
|
638
|
+
}
|
|
639
|
+
));
|
|
640
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
641
|
+
|
|
642
|
+
// src/hooks/useSubmissions.ts
|
|
643
|
+
var React7 = __toESM(require("react"), 1);
|
|
644
|
+
function useSubmissions({
|
|
645
|
+
formId,
|
|
646
|
+
initialPageSize = 25
|
|
647
|
+
}) {
|
|
648
|
+
const { api } = useAifeaturesContext();
|
|
649
|
+
const [submissions, setSubmissions] = React7.useState([]);
|
|
650
|
+
const [total, setTotal] = React7.useState(0);
|
|
651
|
+
const [isLoading, setIsLoading] = React7.useState(true);
|
|
652
|
+
const [error, setError] = React7.useState(null);
|
|
653
|
+
const [page, setPage] = React7.useState(0);
|
|
654
|
+
const [pageSize, setPageSize] = React7.useState(initialPageSize);
|
|
655
|
+
const fetchSubmissions = React7.useCallback(async () => {
|
|
656
|
+
if (!formId) return;
|
|
657
|
+
try {
|
|
658
|
+
setIsLoading(true);
|
|
659
|
+
setError(null);
|
|
660
|
+
const options = {
|
|
661
|
+
limit: pageSize,
|
|
662
|
+
offset: page * pageSize
|
|
663
|
+
};
|
|
664
|
+
const data = await api.getSubmissions(
|
|
665
|
+
formId,
|
|
666
|
+
options
|
|
667
|
+
);
|
|
668
|
+
setSubmissions(data.submissions);
|
|
669
|
+
setTotal(data.total);
|
|
670
|
+
} catch (err) {
|
|
671
|
+
setError(
|
|
672
|
+
err instanceof Error ? err : new Error("Failed to fetch submissions")
|
|
673
|
+
);
|
|
674
|
+
} finally {
|
|
675
|
+
setIsLoading(false);
|
|
676
|
+
}
|
|
677
|
+
}, [api, formId, page, pageSize]);
|
|
678
|
+
React7.useEffect(() => {
|
|
679
|
+
fetchSubmissions();
|
|
680
|
+
}, [fetchSubmissions]);
|
|
681
|
+
const deleteSubmission = React7.useCallback(
|
|
682
|
+
async (submissionId) => {
|
|
683
|
+
await api.deleteSubmission(submissionId);
|
|
684
|
+
setSubmissions((prev) => prev.filter((s) => s.id !== submissionId));
|
|
685
|
+
setTotal((prev) => prev - 1);
|
|
686
|
+
},
|
|
687
|
+
[api]
|
|
688
|
+
);
|
|
689
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
690
|
+
return {
|
|
691
|
+
submissions,
|
|
692
|
+
total,
|
|
693
|
+
isLoading,
|
|
694
|
+
error,
|
|
695
|
+
page,
|
|
696
|
+
pageSize,
|
|
697
|
+
hasNextPage: page < totalPages - 1,
|
|
698
|
+
hasPreviousPage: page > 0,
|
|
699
|
+
setPage,
|
|
700
|
+
setPageSize: (size) => {
|
|
701
|
+
setPageSize(size);
|
|
702
|
+
setPage(0);
|
|
703
|
+
},
|
|
704
|
+
refetch: fetchSubmissions,
|
|
705
|
+
deleteSubmission
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/components/SubmissionDetail.tsx
|
|
710
|
+
var React8 = __toESM(require("react"), 1);
|
|
711
|
+
var import_lucide_react3 = require("lucide-react");
|
|
712
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
713
|
+
function formatDate(dateString) {
|
|
714
|
+
return new Date(dateString).toLocaleString(void 0, {
|
|
715
|
+
year: "numeric",
|
|
716
|
+
month: "short",
|
|
717
|
+
day: "numeric",
|
|
718
|
+
hour: "2-digit",
|
|
719
|
+
minute: "2-digit"
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function formatBytes(bytes) {
|
|
723
|
+
if (bytes === 0) return "0 B";
|
|
724
|
+
const k = 1024;
|
|
725
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
726
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
727
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
728
|
+
}
|
|
729
|
+
function SubmissionDetail({
|
|
730
|
+
submission,
|
|
731
|
+
onBack
|
|
732
|
+
}) {
|
|
733
|
+
const { api } = useAifeaturesContext();
|
|
734
|
+
const [downloadingFile, setDownloadingFile] = React8.useState(null);
|
|
735
|
+
const handleDownload = async (filename) => {
|
|
736
|
+
setDownloadingFile(filename);
|
|
737
|
+
try {
|
|
738
|
+
await api.downloadAttachment(submission.id, filename);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.error("Download failed:", error);
|
|
741
|
+
} finally {
|
|
742
|
+
setDownloadingFile(null);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
const dataEntries = Object.entries(submission.data).filter(
|
|
746
|
+
([key]) => !key.startsWith("_")
|
|
747
|
+
);
|
|
748
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
749
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-mb-6", children: [
|
|
750
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
751
|
+
Button,
|
|
752
|
+
{
|
|
753
|
+
variant: "ghost",
|
|
754
|
+
size: "sm",
|
|
755
|
+
onClick: onBack,
|
|
756
|
+
className: "af-mb-2 af--ml-2",
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.ArrowLeft, { className: "af-h-4 af-w-4 af-mr-1" }),
|
|
759
|
+
"Back to Submissions"
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
),
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "af-text-lg af-font-semibold", children: "Submission Details" }),
|
|
764
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { className: "af-text-sm af-text-muted-foreground", children: [
|
|
765
|
+
"Submitted ",
|
|
766
|
+
formatDate(submission.metadata.submitted_at)
|
|
767
|
+
] })
|
|
768
|
+
] }),
|
|
769
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-space-y-6", children: [
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { className: "af-text-sm af-font-medium af-mb-3", children: "Form Data" }),
|
|
772
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-space-y-3", children: [
|
|
773
|
+
dataEntries.map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-grid af-grid-cols-3 af-gap-2", children: [
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "af-text-sm af-text-muted-foreground af-capitalize", children: key.replace(/_/g, " ") }),
|
|
775
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "af-col-span-2 af-text-sm af-break-words", children: typeof value === "string" && value.includes("\n") ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { className: "af-whitespace-pre-wrap af-font-sans af-bg-muted af-p-2 af-rounded af-text-xs", children: value }) : String(value) })
|
|
776
|
+
] }, key)),
|
|
777
|
+
dataEntries.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "af-text-sm af-text-muted-foreground", children: "No data submitted" })
|
|
778
|
+
] })
|
|
779
|
+
] }),
|
|
780
|
+
submission.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
781
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { className: "af-text-sm af-font-medium af-mb-3", children: "Attachments" }),
|
|
782
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "af-space-y-2", children: submission.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
783
|
+
"div",
|
|
784
|
+
{
|
|
785
|
+
className: "af-flex af-items-center af-justify-between af-p-2 af-bg-muted af-rounded",
|
|
786
|
+
children: [
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-flex af-items-center af-gap-2", children: [
|
|
788
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.FileText, { className: "af-h-4 af-w-4 af-text-muted-foreground" }),
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "af-text-sm", children: attachment.name }),
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "af-text-xs af-text-muted-foreground", children: [
|
|
791
|
+
"(",
|
|
792
|
+
formatBytes(attachment.size),
|
|
793
|
+
")"
|
|
794
|
+
] })
|
|
795
|
+
] }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
797
|
+
Button,
|
|
798
|
+
{
|
|
799
|
+
variant: "ghost",
|
|
800
|
+
size: "sm",
|
|
801
|
+
onClick: () => handleDownload(attachment.name),
|
|
802
|
+
disabled: downloadingFile === attachment.name,
|
|
803
|
+
children: [
|
|
804
|
+
downloadingFile === attachment.name ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Loader2, { className: "af-h-4 af-w-4 af-mr-1 af-animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Download, { className: "af-h-4 af-w-4 af-mr-1" }),
|
|
805
|
+
downloadingFile === attachment.name ? "Downloading..." : "Download"
|
|
806
|
+
]
|
|
807
|
+
}
|
|
808
|
+
)
|
|
809
|
+
]
|
|
810
|
+
},
|
|
811
|
+
index
|
|
812
|
+
)) })
|
|
813
|
+
] }),
|
|
814
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { className: "af-text-sm af-font-medium af-mb-3", children: "Metadata" }),
|
|
816
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-space-y-2 af-text-sm", children: [
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-flex af-items-center af-gap-2 af-text-muted-foreground", children: [
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Clock, { className: "af-h-4 af-w-4" }),
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: formatDate(submission.metadata.submitted_at) })
|
|
820
|
+
] }),
|
|
821
|
+
submission.metadata.ip_address && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-flex af-items-center af-gap-2 af-text-muted-foreground", children: [
|
|
822
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Globe, { className: "af-h-4 af-w-4" }),
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
|
|
824
|
+
"IP: ",
|
|
825
|
+
submission.metadata.ip_address
|
|
826
|
+
] })
|
|
827
|
+
] }),
|
|
828
|
+
submission.metadata.user_agent && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-flex af-items-start af-gap-2 af-text-muted-foreground", children: [
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Monitor, { className: "af-h-4 af-w-4 af-mt-0.5" }),
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "af-text-xs af-break-all", children: submission.metadata.user_agent })
|
|
831
|
+
] }),
|
|
832
|
+
submission.resend_id && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "af-flex af-items-center af-gap-2 af-text-muted-foreground", children: [
|
|
833
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Mail, { className: "af-h-4 af-w-4" }),
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
|
|
835
|
+
"Email ID: ",
|
|
836
|
+
submission.resend_id
|
|
837
|
+
] })
|
|
838
|
+
] })
|
|
839
|
+
] })
|
|
840
|
+
] })
|
|
841
|
+
] })
|
|
842
|
+
] });
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/components/FormSubmissions.tsx
|
|
846
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
847
|
+
function formatDate2(dateString) {
|
|
848
|
+
return new Date(dateString).toLocaleString(void 0, {
|
|
849
|
+
month: "short",
|
|
850
|
+
day: "numeric",
|
|
851
|
+
hour: "2-digit",
|
|
852
|
+
minute: "2-digit"
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
function getEmailFromData(data) {
|
|
856
|
+
const emailFields = ["email", "Email", "EMAIL", "e-mail", "mail"];
|
|
857
|
+
for (const field of emailFields) {
|
|
858
|
+
if (typeof data[field] === "string") {
|
|
859
|
+
return data[field];
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
function getPreviewText(data) {
|
|
865
|
+
const entries = Object.entries(data).filter(
|
|
866
|
+
([key]) => !key.startsWith("_") && !key.toLowerCase().includes("email")
|
|
867
|
+
);
|
|
868
|
+
if (entries.length === 0) return "";
|
|
869
|
+
const [, value] = entries[0];
|
|
870
|
+
const text = String(value);
|
|
871
|
+
return text.length > 50 ? text.slice(0, 50) + "..." : text;
|
|
872
|
+
}
|
|
873
|
+
function FormSubmissions({ formId, className }) {
|
|
874
|
+
const {
|
|
875
|
+
submissions,
|
|
876
|
+
total,
|
|
877
|
+
isLoading,
|
|
878
|
+
error,
|
|
879
|
+
page,
|
|
880
|
+
pageSize,
|
|
881
|
+
hasNextPage,
|
|
882
|
+
hasPreviousPage,
|
|
883
|
+
setPage,
|
|
884
|
+
setPageSize
|
|
885
|
+
} = useSubmissions({ formId });
|
|
886
|
+
const [view, setView] = React9.useState("list");
|
|
887
|
+
const [selectedSubmission, setSelectedSubmission] = React9.useState(null);
|
|
888
|
+
const handleViewSubmission = (submission) => {
|
|
889
|
+
setSelectedSubmission(submission);
|
|
890
|
+
setView("detail");
|
|
891
|
+
};
|
|
892
|
+
const handleBack = () => {
|
|
893
|
+
setView("list");
|
|
894
|
+
setSelectedSubmission(null);
|
|
895
|
+
};
|
|
896
|
+
if (view === "detail" && selectedSubmission) {
|
|
897
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
898
|
+
SubmissionDetail,
|
|
899
|
+
{
|
|
900
|
+
submission: selectedSubmission,
|
|
901
|
+
onBack: handleBack
|
|
902
|
+
}
|
|
903
|
+
) });
|
|
904
|
+
}
|
|
905
|
+
if (isLoading) {
|
|
906
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: `af-flex af-items-center af-justify-center af-py-8 ${className || ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "af-text-muted-foreground", children: "Loading submissions..." }) });
|
|
907
|
+
}
|
|
908
|
+
if (error) {
|
|
909
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: `af-flex af-items-center af-justify-center af-py-8 ${className || ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "af-text-destructive", children: [
|
|
910
|
+
"Error: ",
|
|
911
|
+
error.message
|
|
912
|
+
] }) });
|
|
913
|
+
}
|
|
914
|
+
if (submissions.length === 0 && page === 0) {
|
|
915
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `af-flex af-flex-col af-items-center af-justify-center af-py-12 ${className || ""}`, children: [
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Inbox, { className: "af-h-12 af-w-12 af-text-muted-foreground af-mb-4" }),
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "af-text-muted-foreground af-text-center", children: [
|
|
918
|
+
"No submissions yet.",
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("br", {}),
|
|
920
|
+
"Submissions will appear here when visitors use your form."
|
|
921
|
+
] })
|
|
922
|
+
] });
|
|
923
|
+
}
|
|
924
|
+
const startIndex = page * pageSize + 1;
|
|
925
|
+
const endIndex = Math.min((page + 1) * pageSize, total);
|
|
926
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className, children: [
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Table, { children: [
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(TableRow, { children: [
|
|
929
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableHead, { children: "Email" }),
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableHead, { children: "Preview" }),
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableHead, { children: "Date" }),
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableHead, { className: "af-text-right", children: "Actions" })
|
|
933
|
+
] }) }),
|
|
934
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableBody, { children: submissions.map((submission) => {
|
|
935
|
+
const email = getEmailFromData(submission.data);
|
|
936
|
+
const preview = getPreviewText(submission.data);
|
|
937
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(TableRow, { children: [
|
|
938
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableCell, { className: "af-font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "af-flex af-items-center af-gap-2", children: [
|
|
939
|
+
email || /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "af-text-muted-foreground af-italic", children: "No email" }),
|
|
940
|
+
submission.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "af-flex af-items-center af-gap-1 af-text-muted-foreground", title: `${submission.attachments.length} attachment(s)`, children: [
|
|
941
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Paperclip, { className: "af-h-3 af-w-3" }),
|
|
942
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "af-text-xs", children: submission.attachments.length })
|
|
943
|
+
] })
|
|
944
|
+
] }) }),
|
|
945
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableCell, { className: "af-text-muted-foreground af-max-w-[200px] af-truncate", children: preview || /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "af-italic", children: "No preview available" }) }),
|
|
946
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableCell, { className: "af-text-muted-foreground", children: formatDate2(submission.metadata.submitted_at) }),
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableCell, { className: "af-text-right", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
948
|
+
Button,
|
|
949
|
+
{
|
|
950
|
+
variant: "ghost",
|
|
951
|
+
size: "icon",
|
|
952
|
+
onClick: () => handleViewSubmission(submission),
|
|
953
|
+
title: "View details",
|
|
954
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Eye, { className: "af-h-4 af-w-4" })
|
|
955
|
+
}
|
|
956
|
+
) })
|
|
957
|
+
] }, submission.id);
|
|
958
|
+
}) })
|
|
959
|
+
] }),
|
|
960
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "af-flex af-items-center af-justify-between af-px-2 af-py-4", children: [
|
|
961
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "af-flex af-items-center af-gap-2 af-text-sm af-text-muted-foreground", children: [
|
|
962
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
963
|
+
"Showing ",
|
|
964
|
+
startIndex,
|
|
965
|
+
"-",
|
|
966
|
+
endIndex,
|
|
967
|
+
" of ",
|
|
968
|
+
total
|
|
969
|
+
] }),
|
|
970
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
971
|
+
Select,
|
|
972
|
+
{
|
|
973
|
+
value: String(pageSize),
|
|
974
|
+
onValueChange: (value) => setPageSize(Number(value)),
|
|
975
|
+
children: [
|
|
976
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SelectTrigger, { className: "af-w-[70px] af-h-8", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SelectValue, {}) }),
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(SelectContent, { children: [
|
|
978
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SelectItem, { value: "10", children: "10" }),
|
|
979
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SelectItem, { value: "25", children: "25" }),
|
|
980
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SelectItem, { value: "50", children: "50" }),
|
|
981
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SelectItem, { value: "100", children: "100" })
|
|
982
|
+
] })
|
|
983
|
+
]
|
|
984
|
+
}
|
|
985
|
+
),
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "per page" })
|
|
987
|
+
] }),
|
|
988
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "af-flex af-items-center af-gap-2", children: [
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
990
|
+
Button,
|
|
991
|
+
{
|
|
992
|
+
variant: "outline",
|
|
993
|
+
size: "sm",
|
|
994
|
+
onClick: () => setPage(page - 1),
|
|
995
|
+
disabled: !hasPreviousPage,
|
|
996
|
+
children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronLeft, { className: "af-h-4 af-w-4 af-mr-1" }),
|
|
998
|
+
"Previous"
|
|
999
|
+
]
|
|
1000
|
+
}
|
|
1001
|
+
),
|
|
1002
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1003
|
+
Button,
|
|
1004
|
+
{
|
|
1005
|
+
variant: "outline",
|
|
1006
|
+
size: "sm",
|
|
1007
|
+
onClick: () => setPage(page + 1),
|
|
1008
|
+
disabled: !hasNextPage,
|
|
1009
|
+
children: [
|
|
1010
|
+
"Next",
|
|
1011
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronRight, { className: "af-h-4 af-w-4 af-ml-1" })
|
|
1012
|
+
]
|
|
1013
|
+
}
|
|
1014
|
+
)
|
|
1015
|
+
] })
|
|
1016
|
+
] })
|
|
1017
|
+
] });
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/components/FormSettings.tsx
|
|
1021
|
+
var React12 = __toESM(require("react"), 1);
|
|
1022
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1023
|
+
|
|
1024
|
+
// src/ui/input.tsx
|
|
1025
|
+
var React10 = __toESM(require("react"), 1);
|
|
1026
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1027
|
+
var Input = React10.forwardRef(
|
|
1028
|
+
({ className, type, ...props }, ref) => {
|
|
1029
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1030
|
+
"input",
|
|
1031
|
+
{
|
|
1032
|
+
type,
|
|
1033
|
+
className: cn(
|
|
1034
|
+
"af-flex af-h-9 af-w-full af-rounded-md af-border af-border-input af-bg-transparent af-px-3 af-py-1 af-text-sm af-shadow-sm af-transition-colors file:af-border-0 file:af-bg-transparent file:af-text-sm file:af-font-medium file:af-text-foreground placeholder:af-text-muted-foreground focus-visible:af-outline-none focus-visible:af-ring-1 focus-visible:af-ring-ring disabled:af-cursor-not-allowed disabled:af-opacity-50",
|
|
1035
|
+
className
|
|
1036
|
+
),
|
|
1037
|
+
ref,
|
|
1038
|
+
...props
|
|
1039
|
+
}
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
);
|
|
1043
|
+
Input.displayName = "Input";
|
|
1044
|
+
|
|
1045
|
+
// src/ui/label.tsx
|
|
1046
|
+
var React11 = __toESM(require("react"), 1);
|
|
1047
|
+
var import_class_variance_authority2 = require("class-variance-authority");
|
|
1048
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1049
|
+
var labelVariants = (0, import_class_variance_authority2.cva)(
|
|
1050
|
+
"af-text-sm af-font-medium af-leading-none peer-disabled:af-cursor-not-allowed peer-disabled:af-opacity-70"
|
|
1051
|
+
);
|
|
1052
|
+
var Label2 = React11.forwardRef(
|
|
1053
|
+
({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { ref, className: cn(labelVariants(), className), ...props })
|
|
1054
|
+
);
|
|
1055
|
+
Label2.displayName = "Label";
|
|
1056
|
+
|
|
1057
|
+
// src/ui/badge.tsx
|
|
1058
|
+
var import_class_variance_authority3 = require("class-variance-authority");
|
|
1059
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1060
|
+
var badgeVariants = (0, import_class_variance_authority3.cva)(
|
|
1061
|
+
"af-inline-flex af-items-center af-rounded-md af-border af-px-2.5 af-py-0.5 af-text-xs af-font-semibold af-transition-colors focus:af-outline-none focus:af-ring-2 focus:af-ring-ring focus:af-ring-offset-2",
|
|
1062
|
+
{
|
|
1063
|
+
variants: {
|
|
1064
|
+
variant: {
|
|
1065
|
+
default: "af-border-transparent af-bg-primary af-text-primary-foreground af-shadow hover:af-bg-primary/80",
|
|
1066
|
+
secondary: "af-border-transparent af-bg-secondary af-text-secondary-foreground hover:af-bg-secondary/80",
|
|
1067
|
+
destructive: "af-border-transparent af-bg-destructive af-text-destructive-foreground af-shadow hover:af-bg-destructive/80",
|
|
1068
|
+
outline: "af-text-foreground",
|
|
1069
|
+
success: "af-border-transparent af-bg-green-100 af-text-green-800",
|
|
1070
|
+
warning: "af-border-transparent af-bg-yellow-100 af-text-yellow-800"
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
defaultVariants: {
|
|
1074
|
+
variant: "default"
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
);
|
|
1078
|
+
function Badge({ className, variant, ...props }) {
|
|
1079
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/components/FormSettings.tsx
|
|
1083
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1084
|
+
function FormSettings({ form, onSaved, className }) {
|
|
1085
|
+
const { updateForm } = useForms();
|
|
1086
|
+
const [isSaving, setIsSaving] = React12.useState(false);
|
|
1087
|
+
const [lastSaved, setLastSaved] = React12.useState(null);
|
|
1088
|
+
const [error, setError] = React12.useState(null);
|
|
1089
|
+
const [name, setName] = React12.useState(form.name);
|
|
1090
|
+
const [redirectUrl, setRedirectUrl] = React12.useState(form.redirect_url || "");
|
|
1091
|
+
const [emailRecipients, setEmailRecipients] = React12.useState(
|
|
1092
|
+
form.email_recipients
|
|
1093
|
+
);
|
|
1094
|
+
const [newEmail, setNewEmail] = React12.useState("");
|
|
1095
|
+
React12.useEffect(() => {
|
|
1096
|
+
setName(form.name);
|
|
1097
|
+
setRedirectUrl(form.redirect_url || "");
|
|
1098
|
+
setEmailRecipients(form.email_recipients);
|
|
1099
|
+
setError(null);
|
|
1100
|
+
}, [form]);
|
|
1101
|
+
const save = React12.useCallback(
|
|
1102
|
+
async (updates) => {
|
|
1103
|
+
try {
|
|
1104
|
+
setIsSaving(true);
|
|
1105
|
+
setError(null);
|
|
1106
|
+
const updatedForm = await updateForm(form.id, updates);
|
|
1107
|
+
setLastSaved(/* @__PURE__ */ new Date());
|
|
1108
|
+
onSaved?.(updatedForm);
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
setError(err instanceof Error ? err.message : "Failed to save");
|
|
1111
|
+
} finally {
|
|
1112
|
+
setIsSaving(false);
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
[form.id, updateForm, onSaved]
|
|
1116
|
+
);
|
|
1117
|
+
React12.useEffect(() => {
|
|
1118
|
+
if (lastSaved) {
|
|
1119
|
+
const timer = setTimeout(() => setLastSaved(null), 2e3);
|
|
1120
|
+
return () => clearTimeout(timer);
|
|
1121
|
+
}
|
|
1122
|
+
}, [lastSaved]);
|
|
1123
|
+
const handleAddEmail = async () => {
|
|
1124
|
+
const email = newEmail.trim().toLowerCase();
|
|
1125
|
+
if (!email) return;
|
|
1126
|
+
if (!email.includes("@")) {
|
|
1127
|
+
setError("Please enter a valid email address");
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (emailRecipients.includes(email)) {
|
|
1131
|
+
setError("This email is already added");
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
const newRecipients = [...emailRecipients, email];
|
|
1135
|
+
setEmailRecipients(newRecipients);
|
|
1136
|
+
setNewEmail("");
|
|
1137
|
+
setError(null);
|
|
1138
|
+
await save({ email_recipients: newRecipients });
|
|
1139
|
+
};
|
|
1140
|
+
const handleRemoveEmail = async (email) => {
|
|
1141
|
+
const newRecipients = emailRecipients.filter((e) => e !== email);
|
|
1142
|
+
setEmailRecipients(newRecipients);
|
|
1143
|
+
await save({ email_recipients: newRecipients });
|
|
1144
|
+
};
|
|
1145
|
+
const handleNameBlur = async () => {
|
|
1146
|
+
if (name !== form.name) {
|
|
1147
|
+
await save({ name });
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
const handleRedirectBlur = async () => {
|
|
1151
|
+
const newValue = redirectUrl || null;
|
|
1152
|
+
if (newValue !== form.redirect_url) {
|
|
1153
|
+
await save({ redirect_url: newValue });
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `af-space-y-6 af-relative ${className || ""}`, children: [
|
|
1157
|
+
(isSaving || lastSaved) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "aifeatures-admin af-fixed af-bottom-4 af-right-4 af-z-50", children: isSaving ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-flex af-items-center af-gap-2 af-bg-background af-border af-shadow-lg af-rounded-lg af-px-4 af-py-3", children: [
|
|
1158
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.Loader2, { className: "af-h-4 af-w-4 af-animate-spin af-text-muted-foreground" }),
|
|
1159
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "af-text-sm", children: "Saving..." })
|
|
1160
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-flex af-items-center af-gap-2 af-bg-green-50 af-border af-border-green-200 af-shadow-lg af-rounded-lg af-px-4 af-py-3", children: [
|
|
1161
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.Check, { className: "af-h-4 af-w-4 af-text-green-600" }),
|
|
1162
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "af-text-sm af-text-green-800", children: "Changes saved" })
|
|
1163
|
+
] }) }),
|
|
1164
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-space-y-2", children: [
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label2, { htmlFor: "form-name", children: "Form Name" }),
|
|
1166
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1167
|
+
Input,
|
|
1168
|
+
{
|
|
1169
|
+
id: "form-name",
|
|
1170
|
+
value: name,
|
|
1171
|
+
onChange: (e) => setName(e.target.value),
|
|
1172
|
+
onBlur: handleNameBlur,
|
|
1173
|
+
placeholder: "Contact Form"
|
|
1174
|
+
}
|
|
1175
|
+
),
|
|
1176
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "af-text-xs af-text-muted-foreground", children: "A name to identify this form in your dashboard." })
|
|
1177
|
+
] }),
|
|
1178
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-space-y-2", children: [
|
|
1179
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label2, { children: "Email Recipients" }),
|
|
1180
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "af-text-xs af-text-muted-foreground af-mb-2", children: "Form submissions will be sent to these email addresses." }),
|
|
1181
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-flex af-flex-wrap af-gap-2 af-mb-2", children: [
|
|
1182
|
+
emailRecipients.map((email) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Badge, { variant: "secondary", className: "af-gap-1", children: [
|
|
1183
|
+
email,
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1185
|
+
"button",
|
|
1186
|
+
{
|
|
1187
|
+
type: "button",
|
|
1188
|
+
onClick: () => handleRemoveEmail(email),
|
|
1189
|
+
className: "af-ml-1 hover:af-text-destructive",
|
|
1190
|
+
disabled: isSaving,
|
|
1191
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.X, { className: "af-h-3 af-w-3" })
|
|
1192
|
+
}
|
|
1193
|
+
)
|
|
1194
|
+
] }, email)),
|
|
1195
|
+
emailRecipients.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "af-text-sm af-text-muted-foreground af-italic", children: "No recipients configured" })
|
|
1196
|
+
] }),
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-flex af-gap-2", children: [
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1199
|
+
Input,
|
|
1200
|
+
{
|
|
1201
|
+
type: "email",
|
|
1202
|
+
value: newEmail,
|
|
1203
|
+
onChange: (e) => setNewEmail(e.target.value),
|
|
1204
|
+
placeholder: "Add email address",
|
|
1205
|
+
onKeyDown: (e) => {
|
|
1206
|
+
if (e.key === "Enter") {
|
|
1207
|
+
e.preventDefault();
|
|
1208
|
+
handleAddEmail();
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
disabled: isSaving
|
|
1212
|
+
}
|
|
1213
|
+
),
|
|
1214
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1215
|
+
Button,
|
|
1216
|
+
{
|
|
1217
|
+
type: "button",
|
|
1218
|
+
variant: "outline",
|
|
1219
|
+
onClick: handleAddEmail,
|
|
1220
|
+
disabled: !newEmail.trim() || isSaving,
|
|
1221
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.Plus, { className: "af-h-4 af-w-4" })
|
|
1222
|
+
}
|
|
1223
|
+
)
|
|
1224
|
+
] })
|
|
1225
|
+
] }),
|
|
1226
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-space-y-2", children: [
|
|
1227
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label2, { htmlFor: "redirect-url", children: "Redirect URL" }),
|
|
1228
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1229
|
+
Input,
|
|
1230
|
+
{
|
|
1231
|
+
id: "redirect-url",
|
|
1232
|
+
value: redirectUrl,
|
|
1233
|
+
onChange: (e) => setRedirectUrl(e.target.value),
|
|
1234
|
+
onBlur: handleRedirectBlur,
|
|
1235
|
+
placeholder: "/thank-you"
|
|
1236
|
+
}
|
|
1237
|
+
),
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "af-text-xs af-text-muted-foreground", children: "URL to redirect visitors to after successful submission. Leave empty for a default success page." })
|
|
1239
|
+
] }),
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-space-y-2", children: [
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label2, { children: "Form Endpoint" }),
|
|
1242
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "af-flex af-items-center af-gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { className: "af-flex-1 af-text-xs af-bg-muted af-px-3 af-py-2 af-rounded af-border", children: form.endpoint_url }) }),
|
|
1243
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "af-text-xs af-text-muted-foreground", children: "Use this URL as the form action attribute." })
|
|
1244
|
+
] }),
|
|
1245
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-space-y-2", children: [
|
|
1246
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label2, { children: "Captcha Protection" }),
|
|
1247
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "af-flex af-items-center af-gap-2", children: [
|
|
1248
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: form.captcha.enabled ? "success" : "secondary", children: form.captcha.enabled ? "Enabled" : "Disabled" }),
|
|
1249
|
+
form.captcha.enabled && form.captcha.provider && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "af-text-xs af-text-muted-foreground", children: [
|
|
1250
|
+
"(",
|
|
1251
|
+
form.captcha.provider,
|
|
1252
|
+
")"
|
|
1253
|
+
] })
|
|
1254
|
+
] })
|
|
1255
|
+
] }),
|
|
1256
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "af-text-sm af-text-destructive af-bg-destructive/10 af-px-3 af-py-2 af-rounded", children: error })
|
|
1257
|
+
] });
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/components/FormsDashboard.tsx
|
|
1261
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1262
|
+
function FormsDashboard({ className }) {
|
|
1263
|
+
const [view, setView] = React13.useState("list");
|
|
1264
|
+
const [selectedForm, setSelectedForm] = React13.useState(null);
|
|
1265
|
+
const [activeTab, setActiveTab] = React13.useState(
|
|
1266
|
+
"submissions"
|
|
1267
|
+
);
|
|
1268
|
+
const handleSelectForm = (form, tab) => {
|
|
1269
|
+
setSelectedForm(form);
|
|
1270
|
+
setView("detail");
|
|
1271
|
+
setActiveTab(tab);
|
|
1272
|
+
};
|
|
1273
|
+
const handleBack = () => {
|
|
1274
|
+
setView("list");
|
|
1275
|
+
setSelectedForm(null);
|
|
1276
|
+
};
|
|
1277
|
+
const handleFormSaved = (updatedForm) => {
|
|
1278
|
+
setSelectedForm(updatedForm);
|
|
1279
|
+
};
|
|
1280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className, children: [
|
|
1281
|
+
view === "list" && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "af-mb-6", children: [
|
|
1283
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "af-text-lg af-font-semibold", children: "Forms" }),
|
|
1284
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "af-text-sm af-text-muted-foreground", children: "Manage your contact forms and view submissions." })
|
|
1285
|
+
] }),
|
|
1286
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FormsList, { onSelectForm: handleSelectForm })
|
|
1287
|
+
] }),
|
|
1288
|
+
view === "detail" && selectedForm && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
1289
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "af-mb-6", children: [
|
|
1290
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1291
|
+
Button,
|
|
1292
|
+
{
|
|
1293
|
+
variant: "ghost",
|
|
1294
|
+
size: "sm",
|
|
1295
|
+
onClick: handleBack,
|
|
1296
|
+
className: "af-mb-2 af--ml-2",
|
|
1297
|
+
children: [
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react6.ArrowLeft, { className: "af-h-4 af-w-4 af-mr-1" }),
|
|
1299
|
+
"Back to Forms"
|
|
1300
|
+
]
|
|
1301
|
+
}
|
|
1302
|
+
),
|
|
1303
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "af-text-lg af-font-semibold", children: selectedForm.name }),
|
|
1304
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "af-text-sm af-text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { className: "af-text-xs af-bg-muted af-px-1.5 af-py-0.5 af-rounded", children: selectedForm.endpoint_url }) })
|
|
1305
|
+
] }),
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1307
|
+
Tabs,
|
|
1308
|
+
{
|
|
1309
|
+
value: activeTab,
|
|
1310
|
+
onValueChange: (v) => setActiveTab(v),
|
|
1311
|
+
children: [
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TabsList, { className: "af-mb-4", children: [
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TabsTrigger, { value: "submissions", className: "af-gap-2", children: [
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react6.Inbox, { className: "af-h-4 af-w-4" }),
|
|
1315
|
+
"Submissions"
|
|
1316
|
+
] }),
|
|
1317
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TabsTrigger, { value: "settings", className: "af-gap-2", children: [
|
|
1318
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react6.Settings, { className: "af-h-4 af-w-4" }),
|
|
1319
|
+
"Settings"
|
|
1320
|
+
] })
|
|
1321
|
+
] }),
|
|
1322
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TabsContent, { value: "submissions", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FormSubmissions, { formId: selectedForm.id }) }),
|
|
1323
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TabsContent, { value: "settings", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FormSettings, { form: selectedForm, onSaved: handleFormSaved }) })
|
|
1324
|
+
]
|
|
1325
|
+
}
|
|
1326
|
+
)
|
|
1327
|
+
] })
|
|
1328
|
+
] });
|
|
1329
|
+
}
|
|
1330
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1331
|
+
0 && (module.exports = {
|
|
1332
|
+
AifeaturesApiError,
|
|
1333
|
+
AifeaturesProvider,
|
|
1334
|
+
FormSettings,
|
|
1335
|
+
FormSubmissions,
|
|
1336
|
+
FormsDashboard,
|
|
1337
|
+
FormsList,
|
|
1338
|
+
SubmissionDetail,
|
|
1339
|
+
createApiClient,
|
|
1340
|
+
useAifeatures,
|
|
1341
|
+
useAifeaturesContext,
|
|
1342
|
+
useForms,
|
|
1343
|
+
useSubmissions
|
|
1344
|
+
});
|
|
1345
|
+
//# sourceMappingURL=index.cjs.map
|