braid-ui 1.0.98 → 1.0.99

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 (145) hide show
  1. package/README.md +44 -327
  2. package/components.json +20 -0
  3. package/eslint.config.js +29 -0
  4. package/index.html +24 -0
  5. package/package.json +55 -115
  6. package/postcss.config.js +6 -0
  7. package/public/favicon.ico +0 -0
  8. package/public/placeholder.svg +1 -0
  9. package/public/robots.txt +14 -0
  10. package/src/App.css +42 -0
  11. package/src/App.tsx +94 -0
  12. package/src/components/MainLayout.tsx +15 -0
  13. package/src/components/alerts/AlertDocuments.tsx +320 -0
  14. package/src/components/alerts/AlertNotes.tsx +185 -0
  15. package/src/components/alerts/AlertTimeline.tsx +79 -0
  16. package/src/components/alerts/ContextSection.tsx +155 -0
  17. package/src/components/app-sidebar.tsx +341 -0
  18. package/src/components/form-sections/ACHBankCard.tsx +78 -0
  19. package/src/components/form-sections/ACHBasicInfoCard.tsx +100 -0
  20. package/src/components/form-sections/ACHTransferSection.tsx +64 -0
  21. package/src/components/form-sections/AddressForm.tsx +94 -0
  22. package/src/components/form-sections/BankAddressCard.tsx +95 -0
  23. package/src/components/form-sections/BankingDetailsCard.tsx +46 -0
  24. package/src/components/form-sections/BasicInfoCard.tsx +103 -0
  25. package/src/components/form-sections/BasicInfoSection.tsx +34 -0
  26. package/src/components/form-sections/BeneficiaryAddress.tsx +19 -0
  27. package/src/components/form-sections/BeneficiaryCard.tsx +41 -0
  28. package/src/components/form-sections/BeneficiaryDomesticWire.tsx +23 -0
  29. package/src/components/form-sections/BusinessProfileCard.tsx +131 -0
  30. package/src/components/form-sections/BusinessStatusCard.tsx +53 -0
  31. package/src/components/form-sections/ContactInfoCard.tsx +63 -0
  32. package/src/components/form-sections/CounterpartyBasicInfo.tsx +101 -0
  33. package/src/components/form-sections/CounterpartyProfileCard.tsx +104 -0
  34. package/src/components/form-sections/CounterpartyRecordsCard.tsx +41 -0
  35. package/src/components/form-sections/IntermediaryCard.tsx +77 -0
  36. package/src/components/form-sections/IntermediaryFI.tsx +41 -0
  37. package/src/components/form-sections/IntermediaryFIAddress.tsx +14 -0
  38. package/src/components/form-sections/OriginatorCard.tsx +49 -0
  39. package/src/components/form-sections/OriginatorFI.tsx +42 -0
  40. package/src/components/form-sections/OriginatorFIAddress.tsx +14 -0
  41. package/src/components/form-sections/PaymentInformationSection.tsx +163 -0
  42. package/src/components/form-sections/ReceiverCard.tsx +94 -0
  43. package/src/components/form-sections/WireTransferSection.tsx +75 -0
  44. package/src/components/layouts/list-page.tsx +103 -0
  45. package/src/components/transaction/ACHDetailsSection.tsx +95 -0
  46. package/src/components/transaction/WireDetailsSection.tsx +112 -0
  47. package/src/components/ui/account-card.tsx +94 -0
  48. package/src/components/ui/badge.tsx +75 -0
  49. package/src/components/ui/breadcrumb.tsx +78 -0
  50. package/src/components/ui/business-type-badge.tsx +42 -0
  51. package/src/components/ui/button.tsx +56 -0
  52. package/src/components/ui/calendar.tsx +49 -0
  53. package/src/components/ui/card.tsx +223 -0
  54. package/src/components/ui/container.tsx +45 -0
  55. package/src/components/ui/counterparty-type-badge.tsx +53 -0
  56. package/src/components/ui/data-grid.tsx +99 -0
  57. package/src/components/ui/data-table.tsx +152 -0
  58. package/src/components/ui/detail-page-layout.tsx +83 -0
  59. package/src/components/ui/dialog.tsx +120 -0
  60. package/src/components/ui/dropdown-menu.tsx +82 -0
  61. package/src/components/ui/editable-form-card.tsx +106 -0
  62. package/src/components/ui/editable-info-field.tsx +67 -0
  63. package/src/components/ui/enhanced-input.tsx +78 -0
  64. package/src/components/ui/enhanced-select.tsx +101 -0
  65. package/src/components/ui/enhanced-textarea.tsx +64 -0
  66. package/src/components/ui/entity-card.tsx +140 -0
  67. package/src/components/ui/form-card.tsx +40 -0
  68. package/src/components/ui/form-field.tsx +50 -0
  69. package/src/components/ui/form-input.tsx +29 -0
  70. package/src/components/ui/form-provider.tsx +18 -0
  71. package/src/components/ui/form-section.tsx +66 -0
  72. package/src/components/ui/form-select.tsx +35 -0
  73. package/src/components/ui/info-field.tsx +36 -0
  74. package/src/components/ui/json-viewer.tsx +146 -0
  75. package/src/components/ui/label.tsx +24 -0
  76. package/src/components/ui/metric-card.tsx +80 -0
  77. package/src/components/ui/page-layout.tsx +183 -0
  78. package/src/components/ui/popover.tsx +29 -0
  79. package/src/components/ui/responsive-grid.tsx +46 -0
  80. package/src/components/ui/separator.tsx +31 -0
  81. package/src/components/ui/sheet.tsx +140 -0
  82. package/src/components/ui/sidebar.tsx +775 -0
  83. package/src/components/ui/sonner.tsx +29 -0
  84. package/src/components/ui/stack.tsx +77 -0
  85. package/src/components/ui/status-badge.tsx +68 -0
  86. package/src/components/ui/tabs.tsx +52 -0
  87. package/src/components/ui/toast.tsx +127 -0
  88. package/src/components/ui/toaster.tsx +33 -0
  89. package/src/components/ui/tooltip.tsx +28 -0
  90. package/src/components/ui/use-toast.ts +3 -0
  91. package/src/components/ui-kit/dashboard-demo.tsx +156 -0
  92. package/src/components/ui-kit/pattern-library.tsx +248 -0
  93. package/src/components/ui-kit/showcase.tsx +211 -0
  94. package/src/hooks/use-mobile.tsx +19 -0
  95. package/src/hooks/use-toast.ts +191 -0
  96. package/src/hooks/useEditState.ts +70 -0
  97. package/src/hooks/useFormWithEditState.ts +115 -0
  98. package/src/{styles.css → index.css} +10 -4
  99. package/src/lib/constants.ts +25 -0
  100. package/src/lib/mock-data/alert-data.ts +275 -0
  101. package/src/lib/mock-data/banking-data.ts +72 -0
  102. package/src/lib/mock-data/business-data.ts +71 -0
  103. package/src/lib/mock-data/counterparty-data.ts +70 -0
  104. package/src/lib/mock-data/index.ts +5 -0
  105. package/src/lib/mock-data/transaction-data.ts +283 -0
  106. package/src/lib/mock-data/wire-data.ts +103 -0
  107. package/src/lib/mock-data.tsx +180 -0
  108. package/src/lib/schemas/banking-schemas.ts +30 -0
  109. package/src/lib/schemas/business-schemas.ts +36 -0
  110. package/src/lib/schemas/counterparty-schemas.ts +43 -0
  111. package/src/lib/schemas/index.ts +5 -0
  112. package/src/lib/schemas/wire-schemas.ts +44 -0
  113. package/src/lib/utils.ts +6 -0
  114. package/src/main.tsx +10 -0
  115. package/src/pages/Cases.tsx +16 -0
  116. package/src/pages/Dashboard.tsx +16 -0
  117. package/src/pages/NotFound.tsx +27 -0
  118. package/src/pages/TransactionHistory.tsx +532 -0
  119. package/src/pages/UIKit.tsx +51 -0
  120. package/src/pages/alerts/AlertDetail.tsx +193 -0
  121. package/src/pages/alerts/Alerts.tsx +373 -0
  122. package/src/pages/business/Business.tsx +48 -0
  123. package/src/pages/business/Create.tsx +173 -0
  124. package/src/pages/counterparty/Create.tsx +48 -0
  125. package/src/pages/counterparty/DomesticWire.tsx +78 -0
  126. package/src/pages/counterparty/Manage.tsx +79 -0
  127. package/src/pages/transactions/NewTransaction.tsx +527 -0
  128. package/src/pages/transactions/TransactionDetail.tsx +192 -0
  129. package/src/vite-env.d.ts +1 -0
  130. package/tailwind.config.ts +124 -0
  131. package/tsconfig.app.json +30 -0
  132. package/tsconfig.json +19 -0
  133. package/tsconfig.node.json +22 -0
  134. package/vite.config.ts +22 -0
  135. package/dist/css/braid-ui-variables.css +0 -88
  136. package/dist/css/braid-ui.css +0 -4484
  137. package/dist/css/braid-ui.min.css +0 -1
  138. package/dist/index.cjs +0 -4
  139. package/dist/index.cjs.map +0 -1
  140. package/dist/index.d.cts +0 -2429
  141. package/dist/index.d.ts +0 -2429
  142. package/dist/index.js +0 -4
  143. package/dist/index.js.map +0 -1
  144. package/src/styles-only.css +0 -121
  145. /package/{dist/braid-logo-343BOQZ2.png → src/assets/braid-logo.png} +0 -0
@@ -0,0 +1,527 @@
1
+ import { useState } from "react"
2
+ import { useNavigate } from "react-router-dom"
3
+ import { PageLayout } from "@/components/ui/page-layout"
4
+ import { FormCard } from "@/components/ui/form-card"
5
+ import { Button } from "@/components/ui/button"
6
+ import { FormProvider } from "@/components/ui/form-provider"
7
+ import { FormSelect } from "@/components/ui/form-select"
8
+ import { FormInput } from "@/components/ui/form-input"
9
+ import { InfoField } from "@/components/ui/info-field"
10
+ import { DataGrid } from "@/components/ui/data-grid"
11
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"
12
+ import { useForm } from "react-hook-form"
13
+ import { zodResolver } from "@hookform/resolvers/zod"
14
+ import { z } from "zod"
15
+ import { ArrowLeftRight, Settings, Building2, Zap, Search, CheckCircle2, XCircle, AlertCircle, Edit } from "lucide-react"
16
+ import { toast } from "sonner"
17
+ import { cn } from "@/lib/utils"
18
+
19
+ const newTransactionSchema = z.object({
20
+ transactionType: z.string().min(1, "Transaction type is required"),
21
+ accountNumber: z.string().min(1, "Account number is required"),
22
+ counterpartyName: z.string().min(1, "Counterparty name is required"),
23
+ amount: z.string().min(1, "Amount is required"),
24
+ currency: z.string().min(1, "Currency is required"),
25
+ description: z.string().optional(),
26
+ })
27
+
28
+ type NewTransactionForm = z.infer<typeof newTransactionSchema>
29
+
30
+ const TRANSACTION_TYPES = [
31
+ { value: "transfer", label: "Transfer", icon: ArrowLeftRight },
32
+ { value: "adjustment", label: "Adjustment", icon: Settings },
33
+ { value: "ach", label: "ACH", icon: Building2 },
34
+ { value: "wire", label: "Wire", icon: Zap },
35
+ ]
36
+
37
+ const CURRENCY_OPTIONS = [
38
+ { value: "USD", label: "USD - US Dollar" },
39
+ { value: "EUR", label: "EUR - Euro" },
40
+ { value: "GBP", label: "GBP - British Pound" },
41
+ { value: "JPY", label: "JPY - Japanese Yen" },
42
+ { value: "CAD", label: "CAD - Canadian Dollar" },
43
+ { value: "AUD", label: "AUD - Australian Dollar" },
44
+ { value: "CHF", label: "CHF - Swiss Franc" },
45
+ { value: "CNY", label: "CNY - Chinese Yuan" },
46
+ ]
47
+
48
+ // Mock account data - would come from API in real app
49
+ const mockAccountData = {
50
+ accountNumber: "****1234",
51
+ accountName: "Business Checking",
52
+ accountType: "Checking",
53
+ balance: "$125,450.00",
54
+ customerName: "Acme Corporation",
55
+ customerId: "CUST-001",
56
+ customerType: "Business",
57
+ }
58
+
59
+ // Mock counterparty data - would come from API in real app
60
+ const mockCounterpartyData = {
61
+ counterpartyName: "Global Tech Solutions Inc.",
62
+ counterpartyId: "CP-5678",
63
+ counterpartyType: "Business",
64
+ status: "Active",
65
+ taxId: "98-7654321",
66
+ primaryContact: "Sarah Johnson",
67
+ contactEmail: "sarah.johnson@globaltech.com",
68
+ contactPhone: "+1 (555) 987-6543",
69
+ address: "456 Innovation Drive, San Francisco, CA 94105",
70
+ }
71
+
72
+ export default function NewTransaction() {
73
+ const navigate = useNavigate()
74
+ const [accountLookedUp, setAccountLookedUp] = useState(false)
75
+ const [accountData, setAccountData] = useState<typeof mockAccountData | null>(null)
76
+ const [counterpartyLookedUp, setCounterpartyLookedUp] = useState(false)
77
+ const [counterpartyData, setCounterpartyData] = useState<typeof mockCounterpartyData | null>(null)
78
+ const [confirmationOpen, setConfirmationOpen] = useState(false)
79
+ const [submissionStatus, setSubmissionStatus] = useState<"success" | "error" | null>(null)
80
+ const [errorMessage, setErrorMessage] = useState("")
81
+ const [transactionId, setTransactionId] = useState("")
82
+
83
+ const form = useForm<NewTransactionForm>({
84
+ resolver: zodResolver(newTransactionSchema),
85
+ defaultValues: {
86
+ transactionType: "",
87
+ accountNumber: "",
88
+ counterpartyName: "",
89
+ amount: "",
90
+ currency: "USD",
91
+ description: "",
92
+ },
93
+ })
94
+
95
+ const transactionType = form.watch("transactionType")
96
+ const accountNumber = form.watch("accountNumber")
97
+ const counterpartyName = form.watch("counterpartyName")
98
+ const amount = form.watch("amount")
99
+
100
+ const handleAccountLookup = () => {
101
+ const accNum = form.getValues("accountNumber")
102
+ if (!accNum) {
103
+ toast.error("Please enter an account number")
104
+ return
105
+ }
106
+ // Simulate account lookup
107
+ setAccountData(mockAccountData)
108
+ setAccountLookedUp(true)
109
+ toast.success("Account found")
110
+ }
111
+
112
+ const handleCounterpartyLookup = () => {
113
+ const cpName = form.getValues("counterpartyName")
114
+ if (!cpName) {
115
+ toast.error("Please enter a counterparty name")
116
+ return
117
+ }
118
+ // Simulate counterparty lookup
119
+ setCounterpartyData(mockCounterpartyData)
120
+ setCounterpartyLookedUp(true)
121
+ toast.success("Counterparty found")
122
+ }
123
+
124
+ const handleEditAccount = () => {
125
+ setAccountLookedUp(false)
126
+ setAccountData(null)
127
+ // Reset dependent sections
128
+ form.setValue("transactionType", "")
129
+ setCounterpartyLookedUp(false)
130
+ setCounterpartyData(null)
131
+ form.setValue("counterpartyName", "")
132
+ form.setValue("amount", "")
133
+ form.setValue("description", "")
134
+ }
135
+
136
+ const handleEditCounterparty = () => {
137
+ setCounterpartyLookedUp(false)
138
+ setCounterpartyData(null)
139
+ // Reset dependent sections
140
+ form.setValue("amount", "")
141
+ form.setValue("description", "")
142
+ }
143
+
144
+ const handleSubmit = () => {
145
+ const data = form.getValues()
146
+
147
+ if (!data.transactionType || !data.accountNumber || !data.counterpartyName || !data.amount) {
148
+ toast.error("Please complete all required fields")
149
+ return
150
+ }
151
+
152
+ if (!accountLookedUp || !counterpartyLookedUp) {
153
+ toast.error("Please lookup both account and counterparty")
154
+ return
155
+ }
156
+
157
+ // Simulate transaction processing with random success/failure
158
+ const amount = parseFloat(data.amount)
159
+ const balance = 125450 // Current balance from mock data
160
+
161
+ // Simulate different error scenarios
162
+ const hasError = Math.random() > 0.7 // 30% chance of error for demo
163
+
164
+ if (hasError) {
165
+ const errorScenarios = [
166
+ "Insufficient funds. Current balance: $125,450.00, Required: $" + amount.toFixed(2),
167
+ "Transaction limit exceeded. Daily limit: $50,000.00",
168
+ "Counterparty account is temporarily unavailable. Please try again later.",
169
+ "Invalid routing number for the selected transaction type."
170
+ ]
171
+ setSubmissionStatus("error")
172
+ setErrorMessage(errorScenarios[Math.floor(Math.random() * errorScenarios.length)])
173
+ setConfirmationOpen(true)
174
+ } else {
175
+ // Success scenario
176
+ const txId = "TXN-" + Math.random().toString(36).substr(2, 9).toUpperCase()
177
+ setTransactionId(txId)
178
+ setSubmissionStatus("success")
179
+ setConfirmationOpen(true)
180
+ }
181
+ }
182
+
183
+ const handleConfirmationClose = () => {
184
+ setConfirmationOpen(false)
185
+ if (submissionStatus === "success") {
186
+ navigate("/transactions/history")
187
+ }
188
+ }
189
+
190
+ const handleCancel = () => {
191
+ navigate("/dashboard")
192
+ }
193
+
194
+ const accountDataGrid = accountData ? [
195
+ {
196
+ title: "Account Information",
197
+ items: [
198
+ { label: "Account Number", value: accountData.accountNumber },
199
+ { label: "Account Name", value: accountData.accountName },
200
+ { label: "Account Type", value: accountData.accountType },
201
+ { label: "Balance", value: accountData.balance },
202
+ { label: "Customer Name", value: accountData.customerName },
203
+ { label: "Customer ID", value: accountData.customerId },
204
+ ]
205
+ }
206
+ ] : []
207
+
208
+ const counterpartyDataGrid = counterpartyData ? [
209
+ {
210
+ title: "Counterparty Information",
211
+ items: [
212
+ { label: "Counterparty Name", value: counterpartyData.counterpartyName },
213
+ { label: "Counterparty ID", value: counterpartyData.counterpartyId },
214
+ { label: "Type", value: counterpartyData.counterpartyType },
215
+ { label: "Status", value: counterpartyData.status },
216
+ { label: "Tax ID", value: counterpartyData.taxId },
217
+ { label: "Primary Contact", value: counterpartyData.primaryContact },
218
+ { label: "Contact Email", value: counterpartyData.contactEmail },
219
+ { label: "Contact Phone", value: counterpartyData.contactPhone },
220
+ { label: "Address", value: counterpartyData.address },
221
+ ]
222
+ }
223
+ ] : []
224
+
225
+ const reviewData = [
226
+ {
227
+ title: "Transaction Summary",
228
+ items: [
229
+ { label: "Transaction Type", value: TRANSACTION_TYPES.find(t => t.value === transactionType)?.label || "-" },
230
+ { label: "Account Number", value: accountNumber || "-" },
231
+ { label: "Counterparty", value: counterpartyName || "-" },
232
+ { label: "Amount", value: amount ? `$${amount}` : "-" },
233
+ { label: "Description", value: form.watch("description") || "N/A" },
234
+ ]
235
+ }
236
+ ]
237
+
238
+ return (
239
+ <PageLayout
240
+ title="New Transaction"
241
+ description="Complete the form below to initiate a new transaction"
242
+ >
243
+ <div className="max-w-4xl mx-auto space-y-6">
244
+ <FormProvider form={form}>
245
+ <form onSubmit={(e) => e.preventDefault()}>
246
+ {/* Account Lookup */}
247
+ {!accountLookedUp ? (
248
+ <FormCard
249
+ title="Account Lookup"
250
+ variant="default"
251
+ >
252
+ <div className="space-y-4">
253
+ <FormInput
254
+ name="accountNumber"
255
+ label="Account Number"
256
+ placeholder="Enter account number"
257
+ />
258
+ <Button
259
+ onClick={handleAccountLookup}
260
+ className="w-full sm:w-auto"
261
+ disabled={!accountNumber}
262
+ >
263
+ <Search className="h-4 w-4 mr-2" />
264
+ Lookup Account
265
+ </Button>
266
+ </div>
267
+ </FormCard>
268
+ ) : (
269
+ /* Account Information (Read-only) */
270
+ <FormCard
271
+ title="Account Information"
272
+ variant="subtle"
273
+ headerActions={
274
+ <div className="flex items-center gap-2">
275
+ <CheckCircle2 className="h-5 w-5 text-success" />
276
+ <Button
277
+ variant="ghost"
278
+ size="sm"
279
+ onClick={handleEditAccount}
280
+ >
281
+ <Edit className="h-4 w-4 mr-1" />
282
+ Edit
283
+ </Button>
284
+ </div>
285
+ }
286
+ >
287
+ <DataGrid data={accountDataGrid} columns={2} />
288
+ </FormCard>
289
+ )}
290
+
291
+ {/* Transaction Type Selection */}
292
+ <FormCard
293
+ title="Transaction Type"
294
+ variant="default"
295
+ className={cn(!accountLookedUp && "opacity-50 pointer-events-none")}
296
+ >
297
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-4">
298
+ {TRANSACTION_TYPES.map((type) => {
299
+ const Icon = type.icon
300
+ const isSelected = transactionType === type.value
301
+ return (
302
+ <button
303
+ key={type.value}
304
+ type="button"
305
+ onClick={() => form.setValue("transactionType", type.value)}
306
+ disabled={!accountLookedUp}
307
+ className={cn(
308
+ "flex flex-col items-center justify-center p-4 lg:p-3 rounded-lg border-2 transition-all",
309
+ "hover:border-primary/50 hover:shadow-md",
310
+ isSelected
311
+ ? "border-primary bg-primary/5 shadow-sm"
312
+ : "border-border bg-card"
313
+ )}
314
+ >
315
+ <Icon className={cn(
316
+ "h-6 w-6 lg:h-5 lg:w-5 mb-2",
317
+ isSelected ? "text-primary" : "text-muted-foreground"
318
+ )} />
319
+ <span className={cn(
320
+ "text-sm font-medium",
321
+ isSelected ? "text-primary" : "text-foreground"
322
+ )}>
323
+ {type.label}
324
+ </span>
325
+ </button>
326
+ )
327
+ })}
328
+ </div>
329
+ </FormCard>
330
+
331
+ {/* Counterparty Lookup */}
332
+ {!counterpartyLookedUp ? (
333
+ <FormCard
334
+ title="Counterparty Lookup"
335
+ variant="default"
336
+ className={cn(!transactionType && "opacity-50 pointer-events-none")}
337
+ >
338
+ <div className="space-y-4">
339
+ <FormInput
340
+ name="counterpartyName"
341
+ label="Counterparty Name"
342
+ placeholder="Enter counterparty name"
343
+ disabled={!transactionType}
344
+ />
345
+ <Button
346
+ onClick={handleCounterpartyLookup}
347
+ className="w-full sm:w-auto"
348
+ disabled={!transactionType || !counterpartyName}
349
+ >
350
+ <Search className="h-4 w-4 mr-2" />
351
+ Lookup Counterparty
352
+ </Button>
353
+ </div>
354
+ </FormCard>
355
+ ) : (
356
+ /* Counterparty Information (Read-only) */
357
+ <FormCard
358
+ title="Counterparty Information"
359
+ variant="subtle"
360
+ headerActions={
361
+ <div className="flex items-center gap-2">
362
+ <CheckCircle2 className="h-5 w-5 text-success" />
363
+ <Button
364
+ variant="ghost"
365
+ size="sm"
366
+ onClick={handleEditCounterparty}
367
+ >
368
+ <Edit className="h-4 w-4 mr-1" />
369
+ Edit
370
+ </Button>
371
+ </div>
372
+ }
373
+ >
374
+ <DataGrid data={counterpartyDataGrid} columns={2} />
375
+ </FormCard>
376
+ )}
377
+
378
+ {/* Transaction Details */}
379
+ <FormCard
380
+ title="Transaction Details"
381
+ variant="default"
382
+ className={cn(!counterpartyLookedUp && "opacity-50 pointer-events-none")}
383
+ >
384
+ <div className="space-y-4">
385
+ <div className="grid grid-cols-1 sm:grid-cols-[1fr_auto] gap-4">
386
+ <FormInput
387
+ name="amount"
388
+ label="Amount"
389
+ placeholder="0.00"
390
+ type="number"
391
+ disabled={!counterpartyLookedUp}
392
+ />
393
+ <FormSelect
394
+ name="currency"
395
+ label="Currency"
396
+ options={CURRENCY_OPTIONS}
397
+ disabled={!counterpartyLookedUp}
398
+ />
399
+ </div>
400
+ <FormInput
401
+ name="description"
402
+ label="Description (Optional)"
403
+ placeholder="Enter transaction description"
404
+ disabled={!counterpartyLookedUp}
405
+ />
406
+ </div>
407
+ </FormCard>
408
+
409
+ {/* Review Summary */}
410
+ <FormCard
411
+ title="Review & Submit"
412
+ variant="default"
413
+ >
414
+ <div className="space-y-6">
415
+ <DataGrid data={reviewData} columns={2} />
416
+
417
+ <div className="flex gap-3 justify-end pt-4 border-t border-border">
418
+ <Button
419
+ type="button"
420
+ variant="outline"
421
+ onClick={handleCancel}
422
+ className="w-32"
423
+ >
424
+ Cancel
425
+ </Button>
426
+ <Button
427
+ type="button"
428
+ onClick={handleSubmit}
429
+ className="w-48"
430
+ disabled={!transactionType || !accountNumber || !counterpartyName || !amount}
431
+ >
432
+ <CheckCircle2 className="h-4 w-4 mr-2" />
433
+ Submit Transaction
434
+ </Button>
435
+ </div>
436
+ </div>
437
+ </FormCard>
438
+ </form>
439
+ </FormProvider>
440
+
441
+ {/* Confirmation Dialog */}
442
+ <Dialog open={confirmationOpen} onOpenChange={setConfirmationOpen}>
443
+ <DialogContent className="sm:max-w-md">
444
+ <DialogHeader>
445
+ <div className="flex items-center gap-3 mb-2">
446
+ {submissionStatus === "success" ? (
447
+ <div className="h-12 w-12 rounded-full bg-success/10 flex items-center justify-center">
448
+ <CheckCircle2 className="h-6 w-6 text-success" />
449
+ </div>
450
+ ) : (
451
+ <div className="h-12 w-12 rounded-full bg-destructive/10 flex items-center justify-center">
452
+ <XCircle className="h-6 w-6 text-destructive" />
453
+ </div>
454
+ )}
455
+ <DialogTitle className="text-xl">
456
+ {submissionStatus === "success" ? "Transaction Successful" : "Transaction Failed"}
457
+ </DialogTitle>
458
+ </div>
459
+ <DialogDescription className="text-left">
460
+ {submissionStatus === "success" ? (
461
+ <div className="space-y-4 pt-2">
462
+ <p className="text-foreground">Your transaction has been successfully submitted and is being processed.</p>
463
+ <div className="bg-muted/50 rounded-lg p-4 space-y-2">
464
+ <div className="flex justify-between text-sm">
465
+ <span className="text-muted-foreground">Transaction ID:</span>
466
+ <span className="font-mono font-medium">{transactionId}</span>
467
+ </div>
468
+ <div className="flex justify-between text-sm">
469
+ <span className="text-muted-foreground">Amount:</span>
470
+ <span className="font-medium">${amount}</span>
471
+ </div>
472
+ <div className="flex justify-between text-sm">
473
+ <span className="text-muted-foreground">Type:</span>
474
+ <span className="font-medium">{TRANSACTION_TYPES.find(t => t.value === transactionType)?.label}</span>
475
+ </div>
476
+ <div className="flex justify-between text-sm">
477
+ <span className="text-muted-foreground">Counterparty:</span>
478
+ <span className="font-medium">{counterpartyName}</span>
479
+ </div>
480
+ </div>
481
+ <div className="flex items-start gap-2 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg">
482
+ <AlertCircle className="h-5 w-5 text-blue-500 mt-0.5 flex-shrink-0" />
483
+ <p className="text-sm text-blue-700 dark:text-blue-300">
484
+ You will receive a confirmation email once the transaction is completed.
485
+ </p>
486
+ </div>
487
+ </div>
488
+ ) : (
489
+ <div className="space-y-4 pt-2">
490
+ <div className="flex items-start gap-2 p-4 bg-destructive/10 border border-destructive/20 rounded-lg">
491
+ <XCircle className="h-5 w-5 text-destructive mt-0.5 flex-shrink-0" />
492
+ <div className="space-y-1">
493
+ <p className="text-sm font-medium text-destructive">Error Processing Transaction</p>
494
+ <p className="text-sm text-muted-foreground">{errorMessage}</p>
495
+ </div>
496
+ </div>
497
+ <p className="text-sm text-muted-foreground">
498
+ Please review the error message above and try again. If the problem persists, contact support.
499
+ </p>
500
+ </div>
501
+ )}
502
+ </DialogDescription>
503
+ </DialogHeader>
504
+ <DialogFooter className="sm:justify-end gap-2">
505
+ {submissionStatus === "error" && (
506
+ <Button
507
+ type="button"
508
+ variant="outline"
509
+ onClick={() => setConfirmationOpen(false)}
510
+ >
511
+ Try Again
512
+ </Button>
513
+ )}
514
+ <Button
515
+ type="button"
516
+ onClick={handleConfirmationClose}
517
+ variant={submissionStatus === "success" ? "default" : "outline"}
518
+ >
519
+ {submissionStatus === "success" ? "View Transactions" : "Close"}
520
+ </Button>
521
+ </DialogFooter>
522
+ </DialogContent>
523
+ </Dialog>
524
+ </div>
525
+ </PageLayout>
526
+ )
527
+ }