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