@startsimpli/ui 0.4.6 → 0.4.7

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.
Files changed (69) hide show
  1. package/package.json +2 -1
  2. package/src/components/ActivityTimeline.tsx +173 -0
  3. package/src/components/LogActivityDialog.tsx +303 -0
  4. package/src/components/QuickLogButtons.tsx +32 -0
  5. package/src/components/badge/StageBadge.tsx +31 -0
  6. package/src/components/badge/index.ts +3 -0
  7. package/src/components/command-palette/CommandPalette.tsx +344 -0
  8. package/src/components/command-palette/command-palette-context.tsx +51 -0
  9. package/src/components/command-palette/index.ts +3 -0
  10. package/src/components/compose/compose-header.tsx +72 -0
  11. package/src/components/compose/compose-loading.tsx +13 -0
  12. package/src/components/compose/index.ts +6 -0
  13. package/src/components/compose/save-status-indicator.tsx +57 -0
  14. package/src/components/compose/send-confirmation-dialog.tsx +87 -0
  15. package/src/components/compose/subject-input.tsx +25 -0
  16. package/src/components/compose/useAutoSave.ts +93 -0
  17. package/src/components/dashboard/DashboardGrid.tsx +32 -0
  18. package/src/components/dashboard/DashboardSection.tsx +32 -0
  19. package/src/components/dashboard/MetricCard.tsx +129 -0
  20. package/src/components/dashboard/PeriodSelector.tsx +55 -0
  21. package/src/components/dashboard/SparklineTrend.tsx +102 -0
  22. package/src/components/dashboard/index.ts +14 -0
  23. package/src/components/email-dialogs/index.ts +14 -0
  24. package/src/components/email-dialogs/merge-fields.tsx +196 -0
  25. package/src/components/email-dialogs/preview-dialog.tsx +194 -0
  26. package/src/components/email-dialogs/schedule-dialog.tsx +297 -0
  27. package/src/components/email-dialogs/template-picker.tsx +225 -0
  28. package/src/components/email-dialogs/test-send-dialog.tsx +188 -0
  29. package/src/components/email-editor/add-block-menu.tsx +151 -0
  30. package/src/components/email-editor/block-toolbar.tsx +73 -0
  31. package/src/components/email-editor/blocks/button-block.tsx +44 -0
  32. package/src/components/email-editor/blocks/divider-block.tsx +43 -0
  33. package/src/components/email-editor/blocks/footer-block.tsx +39 -0
  34. package/src/components/email-editor/blocks/header-block.tsx +39 -0
  35. package/src/components/email-editor/blocks/image-block.tsx +61 -0
  36. package/src/components/email-editor/blocks/index.ts +9 -0
  37. package/src/components/email-editor/blocks/metrics-block.tsx +198 -0
  38. package/src/components/email-editor/blocks/social-block.tsx +75 -0
  39. package/src/components/email-editor/blocks/spacer-block.tsx +26 -0
  40. package/src/components/email-editor/blocks/text-block.tsx +75 -0
  41. package/src/components/email-editor/editor-sidebar.tsx +791 -0
  42. package/src/components/email-editor/email-editor.tsx +886 -0
  43. package/src/components/email-editor/index.ts +50 -0
  44. package/src/components/email-editor/renderer/block-renderers.ts +209 -0
  45. package/src/components/email-editor/renderer/email-html-renderer.ts +128 -0
  46. package/src/components/email-editor/types.ts +413 -0
  47. package/src/components/email-editor/utils/defaults.ts +116 -0
  48. package/src/components/email-editor/utils/undo-redo.ts +59 -0
  49. package/src/components/enrichment/EnrichButton.tsx +33 -0
  50. package/src/components/enrichment/EnrichmentProgress.tsx +66 -0
  51. package/src/components/enrichment/QualityBadge.tsx +43 -0
  52. package/src/components/enrichment/index.ts +8 -0
  53. package/src/components/gantt/GanttChart.tsx +25 -25
  54. package/src/components/gantt/types.ts +5 -5
  55. package/src/components/index.ts +46 -0
  56. package/src/components/integrations/ConnectionStatus.tsx +77 -0
  57. package/src/components/integrations/IntegrationCard.tsx +92 -0
  58. package/src/components/integrations/index.ts +5 -0
  59. package/src/components/kanban/KanbanBoard.tsx +103 -0
  60. package/src/components/kanban/index.ts +2 -0
  61. package/src/components/lists/CreateListDialog.tsx +158 -0
  62. package/src/components/lists/ListCard.tsx +77 -0
  63. package/src/components/lists/index.ts +5 -0
  64. package/src/components/pipeline/StageTransitionModal.tsx +146 -0
  65. package/src/components/pipeline/index.ts +2 -0
  66. package/src/components/settings/SettingsCard.tsx +33 -0
  67. package/src/components/settings/SettingsLayout.tsx +28 -0
  68. package/src/components/settings/SettingsNav.tsx +42 -0
  69. package/src/components/settings/index.ts +6 -0
@@ -0,0 +1,194 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ } from '../ui/dialog'
10
+ import { Button } from '../ui/button'
11
+ import { Label } from '../ui/label'
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from '../ui/select'
19
+ import { Tabs, TabsList, TabsTrigger } from '../ui/tabs'
20
+ import { Monitor, Smartphone, Code } from 'lucide-react'
21
+ import { MergeFieldPreview } from './merge-fields'
22
+ import { cn } from '../../utils/cn'
23
+
24
+ export interface PreviewRecipient {
25
+ id: string
26
+ name: string
27
+ label?: string
28
+ /** Sample data for merge field substitution, keyed by merge field token (e.g. "{{recipient.firstName}}") */
29
+ mergeData?: Record<string, string>
30
+ }
31
+
32
+ interface PreviewDialogProps {
33
+ open: boolean
34
+ onOpenChange: (open: boolean) => void
35
+ subject: string
36
+ /**
37
+ * Pre-rendered email body HTML. The component will wrap this in an email
38
+ * shell with proper styling for preview.
39
+ */
40
+ bodyHtml: string
41
+ /** Recipients available for merge field preview */
42
+ recipients?: PreviewRecipient[]
43
+ /** Additional merge field sample data applied on top of recipient data */
44
+ sampleData?: Record<string, string>
45
+ /** Unsubscribe link text. Set to null to hide. */
46
+ unsubscribeText?: string | null
47
+ }
48
+
49
+ // Default sample recipient for preview
50
+ const DEFAULT_RECIPIENT: PreviewRecipient = {
51
+ id: 'sample',
52
+ name: 'John Smith',
53
+ mergeData: {
54
+ '{{recipient.firstName}}': 'John',
55
+ '{{recipient.lastName}}': 'Smith',
56
+ '{{recipient.fullName}}': 'John Smith',
57
+ '{{recipient.email}}': 'john@example.com',
58
+ },
59
+ }
60
+
61
+ export function PreviewDialog({
62
+ open,
63
+ onOpenChange,
64
+ subject,
65
+ bodyHtml,
66
+ recipients = [],
67
+ sampleData,
68
+ unsubscribeText = 'Unsubscribe from these updates',
69
+ }: PreviewDialogProps) {
70
+ const [viewMode, setViewMode] = useState<'desktop' | 'mobile' | 'html'>('desktop')
71
+ const [selectedRecipientId, setSelectedRecipientId] = useState<string>('sample')
72
+
73
+ const previewRecipients = recipients.length > 0 ? recipients : [DEFAULT_RECIPIENT]
74
+ const selectedRecipient = previewRecipients.find(r => r.id === selectedRecipientId) || previewRecipients[0]
75
+
76
+ // Build merge data from selected recipient + any extra sample data
77
+ const mergeDataForPreview: Record<string, string> = {
78
+ ...selectedRecipient.mergeData,
79
+ '{{date.today}}': new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }),
80
+ '{{date.month}}': new Date().toLocaleDateString('en-US', { month: 'long' }),
81
+ '{{date.quarter}}': `Q${Math.ceil((new Date().getMonth() + 1) / 3)} ${new Date().getFullYear()}`,
82
+ '{{date.year}}': new Date().getFullYear().toString(),
83
+ ...sampleData,
84
+ }
85
+
86
+ const previewSubject = MergeFieldPreview({ content: subject, sampleData: mergeDataForPreview })
87
+ const previewBody = MergeFieldPreview({ content: bodyHtml, sampleData: mergeDataForPreview })
88
+
89
+ const fullEmailHtml = `
90
+ <!DOCTYPE html>
91
+ <html>
92
+ <head>
93
+ <meta charset="utf-8">
94
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
95
+ <title>${previewSubject}</title>
96
+ </head>
97
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f3f4f6;">
98
+ <div style="max-width: 600px; margin: 0 auto; padding: 24px;">
99
+ <div style="background: white; border-radius: 8px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
100
+ ${previewBody}
101
+ </div>
102
+ ${unsubscribeText ? `
103
+ <div style="text-align: center; padding: 24px; font-size: 12px; color: #6b7280;">
104
+ <a href="#" style="color: #6b7280;">${unsubscribeText}</a>
105
+ </div>
106
+ ` : ''}
107
+ </div>
108
+ </body>
109
+ </html>
110
+ `.trim()
111
+
112
+ return (
113
+ <Dialog open={open} onOpenChange={onOpenChange}>
114
+ <DialogContent className="max-w-4xl max-h-[90vh] flex flex-col">
115
+ <DialogHeader>
116
+ <DialogTitle>Preview Email</DialogTitle>
117
+ </DialogHeader>
118
+
119
+ <div className="flex items-center justify-between gap-4 pb-4 border-b">
120
+ <div className="flex items-center gap-4">
121
+ <div className="space-y-1">
122
+ <Label className="text-xs text-muted-foreground">Preview as</Label>
123
+ <Select value={selectedRecipientId} onValueChange={setSelectedRecipientId}>
124
+ <SelectTrigger className="w-[200px]">
125
+ <SelectValue />
126
+ </SelectTrigger>
127
+ <SelectContent>
128
+ {previewRecipients.map(r => (
129
+ <SelectItem key={r.id} value={r.id}>
130
+ {r.label || r.name}
131
+ </SelectItem>
132
+ ))}
133
+ </SelectContent>
134
+ </Select>
135
+ </div>
136
+ </div>
137
+
138
+ <Tabs value={viewMode} onValueChange={(v) => setViewMode(v as typeof viewMode)}>
139
+ <TabsList>
140
+ <TabsTrigger value="desktop" className="gap-2">
141
+ <Monitor className="h-4 w-4" />
142
+ Desktop
143
+ </TabsTrigger>
144
+ <TabsTrigger value="mobile" className="gap-2">
145
+ <Smartphone className="h-4 w-4" />
146
+ Mobile
147
+ </TabsTrigger>
148
+ <TabsTrigger value="html" className="gap-2">
149
+ <Code className="h-4 w-4" />
150
+ HTML
151
+ </TabsTrigger>
152
+ </TabsList>
153
+ </Tabs>
154
+ </div>
155
+
156
+ <div className="flex-1 overflow-auto">
157
+ {/* Subject line preview */}
158
+ <div className="p-4 bg-muted/50 rounded-lg mb-4">
159
+ <Label className="text-xs text-muted-foreground">Subject</Label>
160
+ <p className="font-medium mt-1">{previewSubject}</p>
161
+ </div>
162
+
163
+ {viewMode === 'html' ? (
164
+ <div className="bg-muted rounded-lg p-4 overflow-auto">
165
+ <pre className="text-xs whitespace-pre-wrap font-mono">{fullEmailHtml}</pre>
166
+ </div>
167
+ ) : (
168
+ <div
169
+ className={cn(
170
+ 'bg-gray-100 rounded-lg p-4 mx-auto transition-all',
171
+ viewMode === 'mobile' ? 'max-w-[375px]' : 'max-w-full'
172
+ )}
173
+ >
174
+ <div className="bg-white rounded-lg shadow-sm overflow-hidden">
175
+ <iframe
176
+ srcDoc={fullEmailHtml}
177
+ className="w-full border-0"
178
+ style={{ height: viewMode === 'mobile' ? '500px' : '400px' }}
179
+ title="Email preview"
180
+ />
181
+ </div>
182
+ </div>
183
+ )}
184
+ </div>
185
+
186
+ <div className="flex justify-end gap-2 pt-4 border-t">
187
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
188
+ Close
189
+ </Button>
190
+ </div>
191
+ </DialogContent>
192
+ </Dialog>
193
+ )
194
+ }
@@ -0,0 +1,297 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '../ui/dialog'
12
+ import { Button } from '../ui/button'
13
+ import { Label } from '../ui/label'
14
+ import { Calendar } from '../ui/calendar'
15
+ import {
16
+ Popover,
17
+ PopoverContent,
18
+ PopoverTrigger,
19
+ } from '../ui/popover'
20
+ import {
21
+ Select,
22
+ SelectContent,
23
+ SelectItem,
24
+ SelectTrigger,
25
+ SelectValue,
26
+ } from '../ui/select'
27
+ import { Loader2, CalendarIcon, Clock } from 'lucide-react'
28
+ import { useToast } from '../toast'
29
+ import { format, addDays, setHours, setMinutes, isBefore, startOfDay } from 'date-fns'
30
+ import { cn } from '../../utils/cn'
31
+
32
+ interface SummaryItem {
33
+ label: string
34
+ value: string
35
+ }
36
+
37
+ interface ScheduleDialogProps {
38
+ open: boolean
39
+ onOpenChange: (open: boolean) => void
40
+ /** Summary items displayed at the top of the dialog (e.g. subject, recipient count) */
41
+ summaryItems: SummaryItem[]
42
+ /** Called to schedule the message. Receives the ISO date string for the scheduled time. */
43
+ onSchedule: (scheduledAt: string) => Promise<void>
44
+ /** Called to save draft before scheduling when there are unsaved changes */
45
+ onSaveDraft?: () => Promise<void>
46
+ /** Called to prepare recipients (e.g. expand contact tags) before scheduling */
47
+ onPrepareRecipients?: () => Promise<void>
48
+ /** Whether there are unsaved changes that need saving before scheduling */
49
+ hasUnsavedChanges?: boolean
50
+ /** Dialog title. Defaults to "Schedule Send" */
51
+ title?: string
52
+ /** Dialog description. Defaults to "Choose when to send this email" */
53
+ description?: string
54
+ /** Info message shown after confirming schedule time */
55
+ scheduledInfoMessage?: string
56
+ }
57
+
58
+ // Generate time options in 30-minute increments
59
+ const generateTimeOptions = () => {
60
+ const options: { value: string; label: string }[] = []
61
+ for (let hour = 0; hour < 24; hour++) {
62
+ for (const minute of [0, 30]) {
63
+ const h = hour.toString().padStart(2, '0')
64
+ const m = minute.toString().padStart(2, '0')
65
+ const period = hour < 12 ? 'AM' : 'PM'
66
+ const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour
67
+ options.push({
68
+ value: `${h}:${m}`,
69
+ label: `${displayHour}:${m.padStart(2, '0')} ${period}`,
70
+ })
71
+ }
72
+ }
73
+ return options
74
+ }
75
+
76
+ const TIME_OPTIONS = generateTimeOptions()
77
+
78
+ // Quick schedule options
79
+ const QUICK_OPTIONS = [
80
+ { label: 'Tomorrow 9 AM', getValue: () => setHours(setMinutes(addDays(new Date(), 1), 0), 9) },
81
+ { label: 'Tomorrow 2 PM', getValue: () => setHours(setMinutes(addDays(new Date(), 1), 0), 14) },
82
+ { label: 'Monday 9 AM', getValue: () => {
83
+ const today = new Date()
84
+ const daysUntilMonday = (8 - today.getDay()) % 7 || 7
85
+ return setHours(setMinutes(addDays(today, daysUntilMonday), 0), 9)
86
+ }},
87
+ { label: 'Next week', getValue: () => setHours(setMinutes(addDays(new Date(), 7), 0), 9) },
88
+ ]
89
+
90
+ export function ScheduleDialog({
91
+ open,
92
+ onOpenChange,
93
+ summaryItems,
94
+ onSchedule,
95
+ onSaveDraft,
96
+ onPrepareRecipients,
97
+ hasUnsavedChanges = false,
98
+ title = 'Schedule Send',
99
+ description = 'Choose when to send this email',
100
+ scheduledInfoMessage = 'You can cancel or edit this scheduled send from the messages list.',
101
+ }: ScheduleDialogProps) {
102
+ const { toast } = useToast()
103
+ const [scheduling, setScheduling] = useState(false)
104
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(addDays(new Date(), 1))
105
+ const [selectedTime, setSelectedTime] = useState('09:00')
106
+
107
+ const getScheduledDateTime = (): Date | null => {
108
+ if (!selectedDate) return null
109
+ const [hours, minutes] = selectedTime.split(':').map(Number)
110
+ return setMinutes(setHours(selectedDate, hours), minutes)
111
+ }
112
+
113
+ const isValidScheduleTime = (): boolean => {
114
+ const scheduledTime = getScheduledDateTime()
115
+ if (!scheduledTime) return false
116
+ // Must be at least 5 minutes in the future
117
+ return scheduledTime.getTime() > Date.now() + 5 * 60 * 1000
118
+ }
119
+
120
+ const handleQuickSelect = (getValue: () => Date) => {
121
+ const date = getValue()
122
+ setSelectedDate(startOfDay(date))
123
+ const hours = date.getHours().toString().padStart(2, '0')
124
+ const minutes = date.getMinutes().toString().padStart(2, '0')
125
+ setSelectedTime(`${hours}:${minutes}`)
126
+ }
127
+
128
+ const handleSchedule = async () => {
129
+ const scheduledDateTime = getScheduledDateTime()
130
+ if (!scheduledDateTime) {
131
+ toast({ description: 'Please select a date and time', variant: 'destructive' })
132
+ return
133
+ }
134
+
135
+ if (!isValidScheduleTime()) {
136
+ toast({ description: 'Scheduled time must be at least 5 minutes in the future', variant: 'destructive' })
137
+ return
138
+ }
139
+
140
+ setScheduling(true)
141
+ try {
142
+ // Save any pending changes first
143
+ if (hasUnsavedChanges && onSaveDraft) {
144
+ await onSaveDraft()
145
+ }
146
+
147
+ // Prepare recipients if needed
148
+ if (onPrepareRecipients) {
149
+ await onPrepareRecipients()
150
+ }
151
+
152
+ // Delegate scheduling to the caller
153
+ await onSchedule(scheduledDateTime.toISOString())
154
+
155
+ toast({
156
+ description: `Scheduled for ${format(scheduledDateTime, 'PPP')} at ${format(scheduledDateTime, 'p')}`,
157
+ })
158
+ onOpenChange(false)
159
+ } catch (error) {
160
+ console.error('Error scheduling:', error)
161
+ toast({
162
+ description: error instanceof Error ? error.message : 'Failed to schedule',
163
+ variant: 'destructive',
164
+ })
165
+ } finally {
166
+ setScheduling(false)
167
+ }
168
+ }
169
+
170
+ return (
171
+ <Dialog open={open} onOpenChange={onOpenChange}>
172
+ <DialogContent className="sm:max-w-[500px]">
173
+ <DialogHeader>
174
+ <DialogTitle>{title}</DialogTitle>
175
+ <DialogDescription>{description}</DialogDescription>
176
+ </DialogHeader>
177
+
178
+ <div className="space-y-6 py-4">
179
+ {/* Summary */}
180
+ <div className="p-4 bg-muted rounded-lg space-y-2">
181
+ {summaryItems.map((item) => (
182
+ <div key={item.label} className="flex justify-between text-sm">
183
+ <span className="text-muted-foreground">{item.label}:</span>
184
+ <span className="font-medium truncate ml-4">{item.value}</span>
185
+ </div>
186
+ ))}
187
+ </div>
188
+
189
+ {/* Quick options */}
190
+ <div className="space-y-2">
191
+ <Label className="text-sm text-muted-foreground">Quick schedule</Label>
192
+ <div className="grid grid-cols-2 gap-2">
193
+ {QUICK_OPTIONS.map((option) => (
194
+ <Button
195
+ key={option.label}
196
+ variant="outline"
197
+ size="sm"
198
+ onClick={() => handleQuickSelect(option.getValue)}
199
+ className="justify-start"
200
+ >
201
+ {option.label}
202
+ </Button>
203
+ ))}
204
+ </div>
205
+ </div>
206
+
207
+ {/* Date/Time picker */}
208
+ <div className="space-y-4">
209
+ <Label className="text-sm text-muted-foreground">Or pick a specific date and time</Label>
210
+
211
+ <div className="flex gap-4">
212
+ {/* Date picker */}
213
+ <div className="flex-1 space-y-2">
214
+ <Label htmlFor="date">Date</Label>
215
+ <Popover>
216
+ <PopoverTrigger asChild>
217
+ <Button
218
+ variant="outline"
219
+ className={cn(
220
+ 'w-full justify-start text-left font-normal',
221
+ !selectedDate && 'text-muted-foreground'
222
+ )}
223
+ >
224
+ <CalendarIcon className="mr-2 h-4 w-4" />
225
+ {selectedDate ? format(selectedDate, 'PPP') : 'Pick a date'}
226
+ </Button>
227
+ </PopoverTrigger>
228
+ <PopoverContent className="w-auto p-0" align="start">
229
+ <Calendar
230
+ mode="single"
231
+ selected={selectedDate}
232
+ onSelect={setSelectedDate}
233
+ disabled={(date) => isBefore(date, startOfDay(new Date()))}
234
+ initialFocus
235
+ />
236
+ </PopoverContent>
237
+ </Popover>
238
+ </div>
239
+
240
+ {/* Time picker */}
241
+ <div className="w-[140px] space-y-2">
242
+ <Label htmlFor="time">Time</Label>
243
+ <Select value={selectedTime} onValueChange={setSelectedTime}>
244
+ <SelectTrigger>
245
+ <Clock className="mr-2 h-4 w-4" />
246
+ <SelectValue />
247
+ </SelectTrigger>
248
+ <SelectContent className="max-h-[300px]">
249
+ {TIME_OPTIONS.map((option) => (
250
+ <SelectItem key={option.value} value={option.value}>
251
+ {option.label}
252
+ </SelectItem>
253
+ ))}
254
+ </SelectContent>
255
+ </Select>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ {/* Preview scheduled time */}
261
+ {selectedDate && isValidScheduleTime() && (
262
+ <div className="p-3 bg-blue-50 border border-blue-200 rounded-lg text-blue-800 text-sm">
263
+ <p>
264
+ <strong>Scheduled for:</strong>{' '}
265
+ {format(getScheduledDateTime()!, 'PPPP')} at {format(getScheduledDateTime()!, 'p')}
266
+ </p>
267
+ {scheduledInfoMessage && (
268
+ <p className="text-xs mt-1 text-blue-600">
269
+ {scheduledInfoMessage}
270
+ </p>
271
+ )}
272
+ </div>
273
+ )}
274
+ </div>
275
+
276
+ <DialogFooter>
277
+ <Button variant="outline" onClick={() => onOpenChange(false)} disabled={scheduling}>
278
+ Cancel
279
+ </Button>
280
+ <Button onClick={handleSchedule} disabled={scheduling || !isValidScheduleTime()}>
281
+ {scheduling ? (
282
+ <>
283
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
284
+ Scheduling...
285
+ </>
286
+ ) : (
287
+ <>
288
+ <CalendarIcon className="mr-2 h-4 w-4" />
289
+ Schedule Send
290
+ </>
291
+ )}
292
+ </Button>
293
+ </DialogFooter>
294
+ </DialogContent>
295
+ </Dialog>
296
+ )
297
+ }