@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.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