@wealthx/shadcn 1.5.27 → 1.5.29

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.
@@ -0,0 +1,709 @@
1
+ import React from "react";
2
+ import {
3
+ closestCenter,
4
+ DndContext,
5
+ KeyboardSensor,
6
+ PointerSensor,
7
+ useSensor,
8
+ useSensors,
9
+ type DragEndEvent,
10
+ } from "@dnd-kit/core";
11
+ import {
12
+ SortableContext,
13
+ sortableKeyboardCoordinates,
14
+ useSortable,
15
+ verticalListSortingStrategy,
16
+ } from "@dnd-kit/sortable";
17
+ import { CSS } from "@dnd-kit/utilities";
18
+ import {
19
+ Download,
20
+ GripVertical,
21
+ Info,
22
+ Pencil,
23
+ Plus,
24
+ Trash2,
25
+ Upload,
26
+ } from "lucide-react";
27
+ import { Badge } from "@/components/ui/badge";
28
+ import { Button } from "@/components/ui/button";
29
+ import {
30
+ Dialog,
31
+ DialogContent,
32
+ DialogFooter,
33
+ DialogHeader,
34
+ DialogTitle,
35
+ } from "@/components/ui/dialog";
36
+ import { Input } from "@/components/ui/input";
37
+ import { Label } from "@/components/ui/label";
38
+ import {
39
+ Select,
40
+ SelectContent,
41
+ SelectItem,
42
+ SelectTrigger,
43
+ SelectValue,
44
+ } from "@/components/ui/select";
45
+ import { Separator } from "@/components/ui/separator";
46
+ import { Switch } from "@/components/ui/switch";
47
+ import {
48
+ Table,
49
+ TableBody,
50
+ TableCell,
51
+ TableHead,
52
+ TableHeader,
53
+ TableRow,
54
+ } from "@/components/ui/table";
55
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
56
+ import { Textarea } from "@/components/ui/textarea";
57
+ import {
58
+ Tooltip,
59
+ TooltipContent,
60
+ TooltipProvider,
61
+ TooltipTrigger,
62
+ } from "@/components/ui/tooltip";
63
+ import { cn } from "@/lib/utils";
64
+ import type { AiBuilderAgentConfig, AiBuilderRuleItem } from "./types";
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // SectionHeader
68
+ // ---------------------------------------------------------------------------
69
+
70
+ export type SectionHeaderProps = {
71
+ title: string;
72
+ description: string;
73
+ className?: string;
74
+ };
75
+
76
+ export function SectionHeader({
77
+ title,
78
+ description,
79
+ className,
80
+ }: SectionHeaderProps) {
81
+ return (
82
+ <div className={cn("flex flex-col gap-0.5", className)}>
83
+ <h2 className="text-h5">{title}</h2>
84
+ <p className="text-body-small text-muted-foreground">{description}</p>
85
+ </div>
86
+ );
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // SettingRow
91
+ // ---------------------------------------------------------------------------
92
+
93
+ export type SettingRowProps = {
94
+ icon: React.ReactNode;
95
+ label: string;
96
+ description: string;
97
+ control: React.ReactNode;
98
+ className?: string;
99
+ };
100
+
101
+ export function SettingRow({
102
+ icon,
103
+ label,
104
+ description,
105
+ control,
106
+ className,
107
+ }: SettingRowProps) {
108
+ return (
109
+ <div className={cn("flex items-center gap-4 py-4", className)}>
110
+ <div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
111
+ {icon}
112
+ </div>
113
+ <div className="flex flex-1 flex-col gap-0.5">
114
+ <span className="text-sm font-semibold">{label}</span>
115
+ <span className="text-caption text-muted-foreground">
116
+ {description}
117
+ </span>
118
+ </div>
119
+ <div className="shrink-0">{control}</div>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // SettingCard — wraps SettingRows with a bordered card + dividers
126
+ // ---------------------------------------------------------------------------
127
+
128
+ export type SettingCardProps = {
129
+ rows: SettingRowProps[];
130
+ className?: string;
131
+ };
132
+
133
+ export function SettingCard({ rows, className }: SettingCardProps) {
134
+ return (
135
+ <div className={cn("rounded-none border px-4", className)}>
136
+ {rows.map((row, i) => (
137
+ <React.Fragment key={row.label}>
138
+ <SettingRow {...row} />
139
+ {i < rows.length - 1 && <Separator />}
140
+ </React.Fragment>
141
+ ))}
142
+ </div>
143
+ );
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // AgentConfigForm
148
+ // ---------------------------------------------------------------------------
149
+
150
+ export type AgentConfigFormProps = {
151
+ config: AiBuilderAgentConfig;
152
+ onChange: (config: AiBuilderAgentConfig) => void;
153
+ className?: string;
154
+ };
155
+
156
+ export function AgentConfigForm({
157
+ config,
158
+ onChange,
159
+ className,
160
+ }: AgentConfigFormProps) {
161
+ return (
162
+ <div
163
+ className={cn("flex flex-col gap-4 rounded-none border p-6", className)}
164
+ >
165
+ <div className="flex flex-col gap-1.5">
166
+ <Label htmlFor="agent-name">Agent Name</Label>
167
+ <Input
168
+ id="agent-name"
169
+ value={config.name}
170
+ onChange={(e) => onChange({ ...config, name: e.target.value })}
171
+ />
172
+ </div>
173
+ <div className="flex flex-col gap-1.5">
174
+ <Label htmlFor="agent-desc">Business Description</Label>
175
+ <Textarea
176
+ id="agent-desc"
177
+ rows={4}
178
+ placeholder="What business is this agent for? What industry?"
179
+ value={config.businessDescription}
180
+ onChange={(e) =>
181
+ onChange({ ...config, businessDescription: e.target.value })
182
+ }
183
+ />
184
+ </div>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // ResponseTemplateEditModal
191
+ // ---------------------------------------------------------------------------
192
+
193
+ export type ResponseTemplateEditModalProps = {
194
+ open: boolean;
195
+ onOpenChange: (open: boolean) => void;
196
+ channel: "email" | "chat";
197
+ content: string;
198
+ onConfirm: (content: string) => void;
199
+ };
200
+
201
+ export function ResponseTemplateEditModal({
202
+ open,
203
+ onOpenChange,
204
+ channel,
205
+ content,
206
+ onConfirm,
207
+ }: ResponseTemplateEditModalProps) {
208
+ const [draft, setDraft] = React.useState(content);
209
+
210
+ React.useEffect(() => {
211
+ if (open) setDraft(content);
212
+ }, [open, content]);
213
+
214
+ const isEmail = channel === "email";
215
+ const channelLabel = isEmail ? "Email" : "Chat";
216
+ const helperText = isEmail
217
+ ? "Use {customer} to insert the customer name and {answer} to insert the response"
218
+ : "Use {answer} to insert the response";
219
+
220
+ return (
221
+ <Dialog open={open} onOpenChange={onOpenChange}>
222
+ <DialogContent className="max-w-lg">
223
+ <DialogHeader>
224
+ <DialogTitle>Edit {channelLabel} response template</DialogTitle>
225
+ </DialogHeader>
226
+
227
+ <div className="flex flex-col gap-1.5 py-2">
228
+ <Label htmlFor="template-content">
229
+ {channelLabel} template content{" "}
230
+ <span className="text-destructive">*</span>
231
+ </Label>
232
+ <Textarea
233
+ id="template-content"
234
+ rows={6}
235
+ placeholder={`Enter content for ${channelLabel} template`}
236
+ value={draft}
237
+ onChange={(e) => setDraft(e.target.value)}
238
+ />
239
+ <p className="text-caption text-muted-foreground">{helperText}</p>
240
+ </div>
241
+
242
+ <DialogFooter>
243
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
244
+ Cancel
245
+ </Button>
246
+ <Button
247
+ onClick={() => {
248
+ onConfirm(draft);
249
+ onOpenChange(false);
250
+ }}
251
+ >
252
+ Confirm
253
+ </Button>
254
+ </DialogFooter>
255
+ </DialogContent>
256
+ </Dialog>
257
+ );
258
+ }
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // RuleOrderBadge
262
+ // ---------------------------------------------------------------------------
263
+
264
+ export type RuleOrderBadgeProps = {
265
+ order: number;
266
+ className?: string;
267
+ };
268
+
269
+ export function RuleOrderBadge({ order, className }: RuleOrderBadgeProps) {
270
+ return (
271
+ <Badge variant="secondary" className={className}>
272
+ #{order}
273
+ </Badge>
274
+ );
275
+ }
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // RuleSetSection
279
+ // ---------------------------------------------------------------------------
280
+
281
+ export type RuleSetSectionProps = {
282
+ standardRules: AiBuilderRuleItem[];
283
+ handoffRules: AiBuilderRuleItem[];
284
+ onAddRule: (defaultType: "standard" | "handoff") => void;
285
+ onEditRule: (id: string) => void;
286
+ onDeleteRule: (id: string) => void;
287
+ onToggleRule: (id: string, enabled: boolean) => void;
288
+ /** Called when the user drag-reorders a rule. activeId dropped over overId. */
289
+ onReorderRules?: (activeId: string, overId: string) => void;
290
+ onExport: () => void;
291
+ onImport: () => void;
292
+ rowsPerPage?: number;
293
+ className?: string;
294
+ };
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // SortableRuleRow — individual draggable row
298
+ // ---------------------------------------------------------------------------
299
+
300
+ function SortableRuleRow({
301
+ rule,
302
+ index,
303
+ onEdit,
304
+ onDelete,
305
+ onToggle,
306
+ }: {
307
+ rule: AiBuilderRuleItem;
308
+ index: number;
309
+ onEdit: (id: string) => void;
310
+ onDelete: (id: string) => void;
311
+ onToggle: (id: string, enabled: boolean) => void;
312
+ }) {
313
+ const {
314
+ attributes,
315
+ listeners,
316
+ setNodeRef,
317
+ transform,
318
+ transition,
319
+ isDragging,
320
+ } = useSortable({ id: rule.id });
321
+
322
+ const style = {
323
+ transform: CSS.Transform.toString(transform),
324
+ transition,
325
+ };
326
+
327
+ return (
328
+ <TableRow
329
+ ref={setNodeRef}
330
+ style={style}
331
+ className={cn(isDragging && "opacity-50 bg-muted/40")}
332
+ >
333
+ {/* Order: drag handle + badge */}
334
+ <TableCell>
335
+ <div className="flex items-center gap-2">
336
+ <button
337
+ type="button"
338
+ className="cursor-grab touch-none text-muted-foreground hover:text-foreground active:cursor-grabbing"
339
+ aria-label={`Drag to reorder rule ${index + 1}`}
340
+ {...attributes}
341
+ {...listeners}
342
+ >
343
+ <GripVertical className="h-4 w-4" />
344
+ </button>
345
+ <RuleOrderBadge order={index + 1} />
346
+ </div>
347
+ </TableCell>
348
+
349
+ {/* Rule text */}
350
+ <TableCell className="text-body-small overflow-hidden">
351
+ <div className="break-words whitespace-normal">{rule.text}</div>
352
+ </TableCell>
353
+
354
+ {/* Actions */}
355
+ <TableCell>
356
+ <div className="flex items-center justify-end gap-1">
357
+ <Switch
358
+ checked={rule.isEnabled}
359
+ onCheckedChange={(checked) => onToggle(rule.id, checked)}
360
+ aria-label={`Toggle rule ${index + 1}`}
361
+ />
362
+ <Button
363
+ variant="ghost"
364
+ size="icon"
365
+ className="h-8 w-8"
366
+ onClick={() => onEdit(rule.id)}
367
+ aria-label={`Edit rule ${index + 1}`}
368
+ >
369
+ <Pencil className="h-3.5 w-3.5" />
370
+ </Button>
371
+ <Button
372
+ variant="ghost"
373
+ size="icon"
374
+ className="h-8 w-8 text-destructive hover:text-destructive"
375
+ onClick={() => onDelete(rule.id)}
376
+ aria-label={`Delete rule ${index + 1}`}
377
+ >
378
+ <Trash2 className="h-3.5 w-3.5" />
379
+ </Button>
380
+ </div>
381
+ </TableCell>
382
+ </TableRow>
383
+ );
384
+ }
385
+
386
+ // ---------------------------------------------------------------------------
387
+ // RuleTable — sortable list with DndContext
388
+ // ---------------------------------------------------------------------------
389
+
390
+ function RuleTable({
391
+ rules,
392
+ onEdit,
393
+ onDelete,
394
+ onToggle,
395
+ onReorder,
396
+ }: {
397
+ rules: AiBuilderRuleItem[];
398
+ onEdit: (id: string) => void;
399
+ onDelete: (id: string) => void;
400
+ onToggle: (id: string, enabled: boolean) => void;
401
+ onReorder?: (activeId: string, overId: string) => void;
402
+ }) {
403
+ const sensors = useSensors(
404
+ useSensor(PointerSensor),
405
+ useSensor(KeyboardSensor, {
406
+ coordinateGetter: sortableKeyboardCoordinates,
407
+ })
408
+ );
409
+
410
+ function handleDragEnd(event: DragEndEvent) {
411
+ const { active, over } = event;
412
+ if (over && active.id !== over.id && onReorder) {
413
+ onReorder(String(active.id), String(over.id));
414
+ }
415
+ }
416
+
417
+ if (rules.length === 0) {
418
+ return (
419
+ <div className="flex h-24 items-center justify-center text-body-small text-muted-foreground">
420
+ No rules added yet.
421
+ </div>
422
+ );
423
+ }
424
+
425
+ return (
426
+ <DndContext
427
+ sensors={sensors}
428
+ collisionDetection={closestCenter}
429
+ onDragEnd={handleDragEnd}
430
+ >
431
+ <SortableContext
432
+ items={rules.map((r) => r.id)}
433
+ strategy={verticalListSortingStrategy}
434
+ >
435
+ <Table className="table-fixed">
436
+ <TableHeader>
437
+ <TableRow>
438
+ <TableHead className="w-28">
439
+ <span className="flex items-center gap-1.5">
440
+ Order
441
+ <TooltipProvider>
442
+ <Tooltip>
443
+ <TooltipTrigger asChild>
444
+ <Info className="h-3.5 w-3.5 text-muted-foreground cursor-default" />
445
+ </TooltipTrigger>
446
+ <TooltipContent>
447
+ Drag rows to reorder rules
448
+ </TooltipContent>
449
+ </Tooltip>
450
+ </TooltipProvider>
451
+ </span>
452
+ </TableHead>
453
+ <TableHead>Rule Set</TableHead>
454
+ <TableHead className="w-36 text-right">Actions</TableHead>
455
+ </TableRow>
456
+ </TableHeader>
457
+ <TableBody>
458
+ {rules.map((rule, index) => (
459
+ <SortableRuleRow
460
+ key={rule.id}
461
+ rule={rule}
462
+ index={index}
463
+ onEdit={onEdit}
464
+ onDelete={onDelete}
465
+ onToggle={onToggle}
466
+ />
467
+ ))}
468
+ </TableBody>
469
+ </Table>
470
+ </SortableContext>
471
+ </DndContext>
472
+ );
473
+ }
474
+
475
+ function RuleTabContent({
476
+ rules,
477
+ description,
478
+ onEditRule,
479
+ onDeleteRule,
480
+ onToggleRule,
481
+ onReorderRules,
482
+ rowsPerPage,
483
+ }: {
484
+ rules: AiBuilderRuleItem[];
485
+ description: string;
486
+ onEditRule: (id: string) => void;
487
+ onDeleteRule: (id: string) => void;
488
+ onToggleRule: (id: string, enabled: boolean) => void;
489
+ onReorderRules?: (activeId: string, overId: string) => void;
490
+ rowsPerPage: number;
491
+ }) {
492
+ return (
493
+ <div className="mt-4 flex flex-col gap-3">
494
+ <div className="flex items-start gap-2 rounded-none border border-blue-200 bg-blue-50 p-3">
495
+ <Info className="mt-0.5 h-4 w-4 shrink-0 text-blue-600" />
496
+ <p className="text-caption text-blue-700">{description}</p>
497
+ </div>
498
+ <RuleTable
499
+ rules={rules}
500
+ onEdit={onEditRule}
501
+ onDelete={onDeleteRule}
502
+ onToggle={onToggleRule}
503
+ onReorder={onReorderRules}
504
+ />
505
+ <div className="flex items-center justify-end gap-2 pt-2 text-body-small text-muted-foreground">
506
+ <span>Rows per page:</span>
507
+ <Select defaultValue={String(rowsPerPage)}>
508
+ <SelectTrigger className="h-8 w-20">
509
+ <SelectValue />
510
+ </SelectTrigger>
511
+ <SelectContent>
512
+ <SelectItem value="5">5</SelectItem>
513
+ <SelectItem value="10">10</SelectItem>
514
+ <SelectItem value="20">20</SelectItem>
515
+ <SelectItem value="50">50</SelectItem>
516
+ </SelectContent>
517
+ </Select>
518
+ </div>
519
+ </div>
520
+ );
521
+ }
522
+
523
+ export function RuleSetSection({
524
+ standardRules,
525
+ handoffRules,
526
+ onAddRule,
527
+ onEditRule,
528
+ onDeleteRule,
529
+ onToggleRule,
530
+ onReorderRules,
531
+ onExport,
532
+ onImport,
533
+ rowsPerPage = 10,
534
+ className,
535
+ }: RuleSetSectionProps) {
536
+ const [activeTab, setActiveTab] = React.useState<"standard" | "handoff">(
537
+ "standard"
538
+ );
539
+
540
+ return (
541
+ <div className={cn("flex flex-col gap-4", className)}>
542
+ {/* Toolbar */}
543
+ <div className="flex items-start justify-between gap-4">
544
+ <SectionHeader
545
+ title="Rule Set"
546
+ description="You can adjust the behavior and response method of the Agent through rules."
547
+ />
548
+ <div className="flex shrink-0 items-center gap-2">
549
+ <Button variant="outline" size="sm" onClick={onExport}>
550
+ <Download className="mr-1.5 h-3.5 w-3.5" />
551
+ Export
552
+ </Button>
553
+ <Button variant="outline" size="sm" onClick={onImport}>
554
+ <Upload className="mr-1.5 h-3.5 w-3.5" />
555
+ Import
556
+ </Button>
557
+ <Button size="sm" onClick={() => onAddRule(activeTab)}>
558
+ <Plus className="mr-1.5 h-3.5 w-3.5" />
559
+ Add rule
560
+ </Button>
561
+ </div>
562
+ </div>
563
+
564
+ {/* Tabs + content */}
565
+ <Tabs
566
+ defaultValue="standard"
567
+ onValueChange={(v) => setActiveTab(v as "standard" | "handoff")}
568
+ >
569
+ <TabsList className="w-full">
570
+ <TabsTrigger value="standard" className="flex-1">
571
+ Standard Rules
572
+ </TabsTrigger>
573
+ <TabsTrigger value="handoff" className="flex-1">
574
+ Hand-off Rules
575
+ </TabsTrigger>
576
+ </TabsList>
577
+
578
+ <TabsContent value="standard">
579
+ <RuleTabContent
580
+ rules={standardRules}
581
+ description="Standard rules guide AI behavior during response generation. These rules help the AI understand how to respond appropriately."
582
+ onEditRule={onEditRule}
583
+ onDeleteRule={onDeleteRule}
584
+ onToggleRule={onToggleRule}
585
+ onReorderRules={onReorderRules}
586
+ rowsPerPage={rowsPerPage}
587
+ />
588
+ </TabsContent>
589
+
590
+ <TabsContent value="handoff">
591
+ <RuleTabContent
592
+ rules={handoffRules}
593
+ description="Hand-off rules define when the AI should transfer a conversation to a human agent."
594
+ onEditRule={onEditRule}
595
+ onDeleteRule={onDeleteRule}
596
+ onToggleRule={onToggleRule}
597
+ onReorderRules={onReorderRules}
598
+ rowsPerPage={rowsPerPage}
599
+ />
600
+ </TabsContent>
601
+ </Tabs>
602
+ </div>
603
+ );
604
+ }
605
+
606
+ // ---------------------------------------------------------------------------
607
+ // AddEditRuleModal
608
+ // ---------------------------------------------------------------------------
609
+
610
+ export type AddEditRuleModalProps = {
611
+ open: boolean;
612
+ onOpenChange: (open: boolean) => void;
613
+ /** Pass a rule to edit; omit or pass null/undefined for "Add" mode */
614
+ rule?: AiBuilderRuleItem | null;
615
+ /** Pre-select the rule type in Add mode (defaults to "standard") */
616
+ defaultType?: "standard" | "handoff";
617
+ onConfirm: (text: string, type: "standard" | "handoff") => void;
618
+ };
619
+
620
+ export function AddEditRuleModal({
621
+ open,
622
+ onOpenChange,
623
+ rule,
624
+ defaultType = "standard",
625
+ onConfirm,
626
+ }: AddEditRuleModalProps) {
627
+ // Map between internal type key and display label (pattern: value === label)
628
+ const RULE_TYPE_LABELS: Record<"standard" | "handoff", string> = {
629
+ standard: "Standard Rule",
630
+ handoff: "Hand-off Rule",
631
+ };
632
+ const LABEL_TO_TYPE: Record<string, "standard" | "handoff"> = {
633
+ "Standard Rule": "standard",
634
+ "Hand-off Rule": "handoff",
635
+ };
636
+
637
+ const isEdit = !!rule;
638
+ const [draft, setDraft] = React.useState(rule?.text ?? "");
639
+ const [ruleType, setRuleType] = React.useState<"standard" | "handoff">(
640
+ defaultType
641
+ );
642
+
643
+ React.useEffect(() => {
644
+ if (open) {
645
+ setDraft(rule?.text ?? "");
646
+ setRuleType(defaultType);
647
+ }
648
+ }, [open, rule, defaultType]);
649
+
650
+ return (
651
+ <Dialog open={open} onOpenChange={onOpenChange}>
652
+ <DialogContent className="max-w-lg">
653
+ <DialogHeader>
654
+ <DialogTitle>{isEdit ? "Edit Rule" : "Add Rule"}</DialogTitle>
655
+ </DialogHeader>
656
+
657
+ <div className="flex flex-col gap-3 py-2">
658
+ {!isEdit && (
659
+ <div className="flex flex-col gap-1.5">
660
+ <Label htmlFor="rule-type">
661
+ Rule Type <span className="text-destructive">*</span>
662
+ </Label>
663
+ <Select
664
+ value={RULE_TYPE_LABELS[ruleType]}
665
+ onValueChange={(v) => setRuleType(LABEL_TO_TYPE[v])}
666
+ >
667
+ <SelectTrigger id="rule-type" className="w-full">
668
+ <SelectValue />
669
+ </SelectTrigger>
670
+ <SelectContent>
671
+ <SelectItem value="Standard Rule">Standard Rule</SelectItem>
672
+ <SelectItem value="Hand-off Rule">Hand-off Rule</SelectItem>
673
+ </SelectContent>
674
+ </Select>
675
+ </div>
676
+ )}
677
+
678
+ <div className="flex flex-col gap-1.5">
679
+ <Label htmlFor="rule-draft">
680
+ Rule <span className="text-destructive">*</span>
681
+ </Label>
682
+ <Textarea
683
+ id="rule-draft"
684
+ rows={4}
685
+ placeholder="Describe the rule the agent should follow…"
686
+ value={draft}
687
+ onChange={(e) => setDraft(e.target.value)}
688
+ />
689
+ </div>
690
+ </div>
691
+
692
+ <DialogFooter>
693
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
694
+ Cancel
695
+ </Button>
696
+ <Button
697
+ disabled={!draft.trim()}
698
+ onClick={() => {
699
+ onConfirm(draft.trim(), ruleType);
700
+ onOpenChange(false);
701
+ }}
702
+ >
703
+ {isEdit ? "Save" : "Add"}
704
+ </Button>
705
+ </DialogFooter>
706
+ </DialogContent>
707
+ </Dialog>
708
+ );
709
+ }