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,79 @@
1
+ import { TimelineEvent } from "@/lib/mock-data/alert-data"
2
+ import { CheckCircle, Circle, UserPlus, Edit, XCircle } from "lucide-react"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ interface AlertTimelineProps {
6
+ events: TimelineEvent[]
7
+ }
8
+
9
+ export const AlertTimeline = ({ events }: AlertTimelineProps) => {
10
+ const getIcon = (action: string) => {
11
+ if (action.includes("Created")) return <Circle className="h-4 w-4" />
12
+ if (action.includes("Assigned")) return <UserPlus className="h-4 w-4" />
13
+ if (action.includes("Updated") || action.includes("Modified")) return <Edit className="h-4 w-4" />
14
+ if (action.includes("Closed") || action.includes("Resolved")) return <CheckCircle className="h-4 w-4" />
15
+ if (action.includes("Rejected")) return <XCircle className="h-4 w-4" />
16
+ return <Circle className="h-4 w-4" />
17
+ }
18
+
19
+ const getStatusColor = (status?: string) => {
20
+ switch (status) {
21
+ case "Unassigned":
22
+ return "text-destructive"
23
+ case "Closed":
24
+ return "text-success"
25
+ case "In Progress":
26
+ return "text-warning"
27
+ default:
28
+ return "text-muted-foreground"
29
+ }
30
+ }
31
+
32
+ if (events.length === 0) {
33
+ return (
34
+ <div className="text-center py-8 text-muted-foreground">
35
+ No timeline events yet
36
+ </div>
37
+ )
38
+ }
39
+
40
+ return (
41
+ <div className="space-y-3">
42
+ {events.map((event, index) => (
43
+ <div key={event.id} className="relative pl-6 pb-3">
44
+ {/* Timeline line */}
45
+ {index !== events.length - 1 && (
46
+ <div className="absolute left-[7px] top-5 bottom-0 w-[2px] bg-border" />
47
+ )}
48
+
49
+ {/* Icon */}
50
+ <div className={cn(
51
+ "absolute left-0 top-0 flex-none",
52
+ getStatusColor(event.status)
53
+ )}>
54
+ <div className="h-4 w-4">
55
+ {getIcon(event.action)}
56
+ </div>
57
+ </div>
58
+
59
+ {/* Content */}
60
+ <div className="space-y-0.5">
61
+ <p className="text-xs font-medium text-foreground">{event.action}</p>
62
+ <p className="text-xs text-muted-foreground">by {event.user}</p>
63
+ {event.details && (
64
+ <p className="text-xs text-muted-foreground">{event.details}</p>
65
+ )}
66
+ {event.status && (
67
+ <p className={cn("text-xs font-medium", getStatusColor(event.status))}>
68
+ Status: {event.status}
69
+ </p>
70
+ )}
71
+ <p className="text-xs text-muted-foreground/70 pt-0.5">
72
+ {event.timestamp}
73
+ </p>
74
+ </div>
75
+ </div>
76
+ ))}
77
+ </div>
78
+ )
79
+ }
@@ -0,0 +1,155 @@
1
+ import { useState } from "react"
2
+ import { Alert } from "@/lib/mock-data/alert-data"
3
+ import { FormCard } from "@/components/ui/form-card"
4
+ import { InfoField } from "@/components/ui/info-field"
5
+ import { Badge } from "@/components/ui/badge"
6
+ import { Button } from "@/components/ui/button"
7
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
8
+ import { JsonViewer } from "@/components/ui/json-viewer"
9
+ import { ChevronRight } from "lucide-react"
10
+
11
+ interface ContextSectionProps {
12
+ alert: Alert
13
+ }
14
+
15
+ export const ContextSection = ({ alert }: ContextSectionProps) => {
16
+ const [isDialogOpen, setIsDialogOpen] = useState(false)
17
+
18
+ const renderOFACContext = () => {
19
+ const data = alert.contextData
20
+ if (!data) return null
21
+
22
+ return (
23
+ <div className="space-y-4">
24
+ <div className="grid grid-cols-2 gap-4">
25
+ <InfoField label="Entity Name" value={data.entityName} />
26
+ <InfoField label="Entity Type" value={data.entityType} />
27
+ <InfoField label="Match Score" value={<Badge variant="alert-ofac">{data.matchScore}</Badge>} />
28
+ <InfoField label="List Name" value={data.listName} />
29
+ </div>
30
+
31
+ <div>
32
+ <h4 className="text-sm font-medium mb-3">Flagged Items</h4>
33
+ <div className="overflow-x-auto border rounded-lg">
34
+ <table className="w-full">
35
+ <thead className="border-b bg-muted/50">
36
+ <tr>
37
+ <th className="px-4 py-3 text-left text-sm font-medium">Field</th>
38
+ <th className="px-4 py-3 text-left text-sm font-medium">Value</th>
39
+ <th className="px-4 py-3 text-left text-sm font-medium">Match Score</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ {data.flaggedItems?.map((item: any, index: number) => (
44
+ <tr key={index} className="border-b hover:bg-muted/50">
45
+ <td className="px-4 py-3 text-sm font-medium">{item.field}</td>
46
+ <td className="px-4 py-3 text-sm">{item.value}</td>
47
+ <td className="px-4 py-3">
48
+ <Badge variant={parseInt(item.matchScore) > 90 ? "alert-ofac" : "outline"}>
49
+ {item.matchScore}
50
+ </Badge>
51
+ </td>
52
+ </tr>
53
+ ))}
54
+ </tbody>
55
+ </table>
56
+ </div>
57
+ </div>
58
+
59
+ {alert.contextData && (
60
+ <div>
61
+ <Button
62
+ variant="ghost"
63
+ className="w-full justify-between h-auto py-3 px-4 border rounded-lg hover:bg-muted/50"
64
+ onClick={() => setIsDialogOpen(true)}
65
+ >
66
+ <span className="text-sm font-medium">Detail Result</span>
67
+ <ChevronRight className="h-4 w-4" />
68
+ </Button>
69
+ </div>
70
+ )}
71
+ </div>
72
+ )
73
+ }
74
+
75
+ const renderTransactionContext = () => {
76
+ return (
77
+ <div className="grid grid-cols-2 gap-4">
78
+ <InfoField label="Transaction ID" value="TXN-12345" />
79
+ <InfoField label="Amount" value="$30,000.00" />
80
+ <InfoField label="Transaction Type" value="Wire Transfer" />
81
+ <InfoField label="Date" value="2025-09-30" />
82
+ </div>
83
+ )
84
+ }
85
+
86
+ const renderFileRecordContext = () => {
87
+ return (
88
+ <div className="grid grid-cols-2 gap-4">
89
+ <InfoField label="File Name" value="wire_batch_093025.csv" />
90
+ <InfoField label="Record Count" value="1,234" />
91
+ <InfoField label="Error Count" value="3" />
92
+ <InfoField label="Status" value={<Badge variant="alert-error">Failed</Badge>} />
93
+ </div>
94
+ )
95
+ }
96
+
97
+ const renderProductContext = () => {
98
+ return (
99
+ <div className="grid grid-cols-2 gap-4">
100
+ <InfoField label="Product ID" value="PROD-2787" />
101
+ <InfoField label="Product Name" value="Business Checking Account" />
102
+ <InfoField label="Status" value="Pending Deactivation" />
103
+ <InfoField label="Reason" value="Compliance review required" />
104
+ </div>
105
+ )
106
+ }
107
+
108
+ const getContextTitle = () => {
109
+ switch (alert.contextType) {
110
+ case "Ofac":
111
+ return "OFAC Context"
112
+ case "Transaction":
113
+ return "Transaction Context"
114
+ case "File Record":
115
+ return "File Processing Context"
116
+ case "Product":
117
+ return "Product Context"
118
+ default:
119
+ return "Context Information"
120
+ }
121
+ }
122
+
123
+ const renderContext = () => {
124
+ switch (alert.contextType) {
125
+ case "Ofac":
126
+ return renderOFACContext()
127
+ case "Transaction":
128
+ return renderTransactionContext()
129
+ case "File Record":
130
+ return renderFileRecordContext()
131
+ case "Product":
132
+ return renderProductContext()
133
+ default:
134
+ return <p className="text-sm text-muted-foreground">No additional context available</p>
135
+ }
136
+ }
137
+
138
+
139
+ return (
140
+ <>
141
+ <FormCard title={getContextTitle()}>
142
+ {renderContext()}
143
+ </FormCard>
144
+
145
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
146
+ <DialogContent className="max-w-4xl">
147
+ <DialogHeader>
148
+ <DialogTitle>{getContextTitle()} - Detail Result</DialogTitle>
149
+ </DialogHeader>
150
+ <JsonViewer data={alert.contextData} />
151
+ </DialogContent>
152
+ </Dialog>
153
+ </>
154
+ )
155
+ }
@@ -0,0 +1,341 @@
1
+ import {
2
+ LayoutDashboard, Bell, Briefcase, ArrowLeftRight, FileText, Users, Building2,
3
+ CreditCard, Shield, RefreshCw, DollarSign, Zap, Settings, User, Key,
4
+ Upload, AlertCircle, TrendingUp, Box, BarChart3, FileCheck, Receipt,
5
+ Landmark, Search, Repeat, CheckCircle, ChevronRight
6
+ } from "lucide-react"
7
+ import { NavLink, useLocation } from "react-router-dom"
8
+ import { useState } from "react"
9
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"
10
+ import braidLogo from "@/assets/braid-logo.png"
11
+
12
+ import {
13
+ Sidebar,
14
+ SidebarContent,
15
+ SidebarGroup,
16
+ SidebarGroupContent,
17
+ SidebarGroupLabel,
18
+ SidebarHeader,
19
+ SidebarMenu,
20
+ SidebarMenuButton,
21
+ SidebarMenuItem,
22
+ SidebarMenuBadge,
23
+ SidebarMenuSub,
24
+ SidebarMenuSubItem,
25
+ SidebarMenuSubButton,
26
+ SidebarTrigger,
27
+ useSidebar,
28
+ } from "@/components/ui/sidebar"
29
+
30
+ const navigationItems = [
31
+ {
32
+ title: "Dashboard",
33
+ url: "/dashboard",
34
+ icon: LayoutDashboard,
35
+ },
36
+ {
37
+ title: "Alerts and Cases",
38
+ icon: Bell,
39
+ items: [
40
+ {
41
+ title: "Alerts",
42
+ url: "/alerts",
43
+ icon: Bell,
44
+ badge: "99+"
45
+ },
46
+ {
47
+ title: "Cases",
48
+ url: "/cases",
49
+ icon: Briefcase,
50
+ },
51
+ ],
52
+ },
53
+ {
54
+ title: "Transactions",
55
+ icon: ArrowLeftRight,
56
+ items: [
57
+ {
58
+ title: "Transaction History",
59
+ url: "/transactions/history",
60
+ icon: FileText,
61
+ },
62
+ {
63
+ title: "Transaction Review",
64
+ url: "/transactions/review",
65
+ icon: FileCheck,
66
+ },
67
+ {
68
+ title: "New Transaction",
69
+ url: "/transactions/new",
70
+ icon: Receipt,
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ title: "Statement",
76
+ url: "/statement",
77
+ icon: FileText,
78
+ },
79
+ {
80
+ title: "Individuals",
81
+ url: "/individuals",
82
+ icon: Users,
83
+ },
84
+ {
85
+ title: "Businesses",
86
+ url: "/business",
87
+ icon: Building2,
88
+ },
89
+ {
90
+ title: "Counterparty",
91
+ icon: Briefcase,
92
+ items: [
93
+ {
94
+ title: "Management",
95
+ url: "/counterparty/manage",
96
+ icon: FileCheck,
97
+ },
98
+ {
99
+ title: "Create Counterparty",
100
+ url: "/counterparty/create",
101
+ icon: Receipt,
102
+ },
103
+ {
104
+ title: "Domestic Wire",
105
+ url: "/counterparty/domestic-wire",
106
+ icon: Zap,
107
+ },
108
+ ],
109
+ },
110
+ {
111
+ title: "Accounts",
112
+ url: "/accounts",
113
+ icon: CreditCard,
114
+ },
115
+ {
116
+ title: "Compliance",
117
+ icon: Shield,
118
+ items: [
119
+ {
120
+ title: "OFAC",
121
+ url: "/compliance/ofac",
122
+ icon: Shield,
123
+ },
124
+ {
125
+ title: "314a",
126
+ url: "/compliance/314a",
127
+ icon: FileText,
128
+ },
129
+ {
130
+ title: "Velocity Limits",
131
+ url: "/compliance/velocity",
132
+ icon: TrendingUp,
133
+ },
134
+ ],
135
+ },
136
+ {
137
+ title: "Recon",
138
+ icon: RefreshCw,
139
+ items: [
140
+ {
141
+ title: "File Upload",
142
+ url: "/recon/upload",
143
+ icon: Upload,
144
+ },
145
+ {
146
+ title: "Exception Review",
147
+ url: "/recon/exceptions",
148
+ icon: AlertCircle,
149
+ },
150
+ ],
151
+ },
152
+ {
153
+ title: "ACH",
154
+ icon: Repeat,
155
+ items: [
156
+ {
157
+ title: "Processing",
158
+ url: "/ach/processing",
159
+ icon: FileText,
160
+ },
161
+ {
162
+ title: "Settlement",
163
+ url: "/ach/settlement",
164
+ icon: Landmark,
165
+ },
166
+ {
167
+ title: "Return",
168
+ url: "/ach/return",
169
+ icon: Search,
170
+ },
171
+ {
172
+ title: "NOC",
173
+ url: "/ach/noc",
174
+ icon: RefreshCw,
175
+ },
176
+ ],
177
+ },
178
+ {
179
+ title: "Wire",
180
+ icon: Zap,
181
+ items: [
182
+ {
183
+ title: "Processing",
184
+ url: "/wire/processing",
185
+ icon: FileText,
186
+ },
187
+ {
188
+ title: "Settlement",
189
+ url: "/wire/settlement",
190
+ icon: Landmark,
191
+ },
192
+ ],
193
+ },
194
+ {
195
+ title: "Configurations",
196
+ icon: Settings,
197
+ items: [
198
+ {
199
+ title: "Developers",
200
+ url: "/configurations/developers",
201
+ icon: Box,
202
+ },
203
+ {
204
+ title: "Programs",
205
+ url: "/configurations/programs",
206
+ icon: LayoutDashboard,
207
+ },
208
+ {
209
+ title: "Products",
210
+ url: "/configurations/products",
211
+ icon: FileText,
212
+ },
213
+ ],
214
+ },
215
+ {
216
+ title: "ClearSight",
217
+ url: "/clearsight",
218
+ icon: BarChart3,
219
+ },
220
+ {
221
+ title: "Settings",
222
+ icon: Settings,
223
+ items: [
224
+ {
225
+ title: "User Management",
226
+ url: "/settings/users",
227
+ icon: Users,
228
+ },
229
+ {
230
+ title: "API Key",
231
+ url: "/settings/api-key",
232
+ icon: Key,
233
+ },
234
+ ],
235
+ },
236
+ ]
237
+
238
+ export function AppSidebar() {
239
+ const { state } = useSidebar()
240
+ const location = useLocation()
241
+ const currentPath = location.pathname
242
+
243
+ // Track which menu items are open
244
+ const [openItems, setOpenItems] = useState<string[]>([])
245
+
246
+ const isActive = (path: string) => currentPath === path
247
+
248
+ const toggleItem = (title: string) => {
249
+ setOpenItems(prev =>
250
+ prev.includes(title)
251
+ ? prev.filter(item => item !== title)
252
+ : [...prev, title]
253
+ )
254
+ }
255
+
256
+ return (
257
+ <Sidebar collapsible="icon">
258
+ <SidebarHeader className="border-b border-sidebar-border">
259
+ <div className="flex items-center justify-between gap-2 px-4 py-3">
260
+ {state !== "collapsed" ? (
261
+ <img src={braidLogo} alt="Braid" className="h-8" />
262
+ ) : (
263
+ <img src={braidLogo} alt="Braid" className="h-6" />
264
+ )}
265
+ <SidebarTrigger />
266
+ </div>
267
+ </SidebarHeader>
268
+
269
+ <SidebarContent>
270
+ <SidebarMenu>
271
+ {navigationItems.map((item) => {
272
+ // Check if it's a group with sub-items
273
+ if (item.items) {
274
+ const isOpen = openItems.includes(item.title)
275
+
276
+ return (
277
+ <Collapsible
278
+ key={item.title}
279
+ open={isOpen}
280
+ onOpenChange={() => toggleItem(item.title)}
281
+ >
282
+ <SidebarMenuItem>
283
+ <CollapsibleTrigger asChild>
284
+ <SidebarMenuButton
285
+ tooltip={state === "collapsed" ? item.title : undefined}
286
+ >
287
+ <item.icon className="w-4 h-4" />
288
+ <span>{item.title}</span>
289
+ <ChevronRight
290
+ className={`ml-auto h-4 w-4 transition-transform ${isOpen ? 'rotate-90' : ''}`}
291
+ />
292
+ </SidebarMenuButton>
293
+ </CollapsibleTrigger>
294
+ <CollapsibleContent>
295
+ <SidebarMenuSub>
296
+ {item.items.map((subItem) => (
297
+ <SidebarMenuSubItem key={subItem.title}>
298
+ <SidebarMenuSubButton
299
+ asChild
300
+ isActive={isActive(subItem.url)}
301
+ >
302
+ <NavLink to={subItem.url}>
303
+ <subItem.icon className="w-4 h-4" />
304
+ <span>{subItem.title}</span>
305
+ {subItem.badge && (
306
+ <SidebarMenuBadge className="ml-auto">
307
+ {subItem.badge}
308
+ </SidebarMenuBadge>
309
+ )}
310
+ </NavLink>
311
+ </SidebarMenuSubButton>
312
+ </SidebarMenuSubItem>
313
+ ))}
314
+ </SidebarMenuSub>
315
+ </CollapsibleContent>
316
+ </SidebarMenuItem>
317
+ </Collapsible>
318
+ )
319
+ }
320
+
321
+ // Single item without sub-items
322
+ return (
323
+ <SidebarMenuItem key={item.title}>
324
+ <SidebarMenuButton
325
+ asChild
326
+ isActive={isActive(item.url!)}
327
+ tooltip={state === "collapsed" ? item.title : undefined}
328
+ >
329
+ <NavLink to={item.url!}>
330
+ <item.icon className="w-4 h-4" />
331
+ <span>{item.title}</span>
332
+ </NavLink>
333
+ </SidebarMenuButton>
334
+ </SidebarMenuItem>
335
+ )
336
+ })}
337
+ </SidebarMenu>
338
+ </SidebarContent>
339
+ </Sidebar>
340
+ )
341
+ }
@@ -0,0 +1,78 @@
1
+ import { EditableFormCard } from "@/components/ui/editable-form-card"
2
+ import { InfoField } from "@/components/ui/info-field"
3
+ import { FormProvider } from "@/components/ui/form-provider"
4
+ import { FormInput } from "@/components/ui/form-input"
5
+ import { useFormWithEditState } from "@/hooks/useFormWithEditState"
6
+ import { achTransferSchema, type ACHTransfer } from "@/lib/schemas/banking-schemas"
7
+ import { defaultACHTransfer } from "@/lib/mock-data/banking-data"
8
+
9
+ interface ACHBankCardProps {
10
+ data?: Partial<ACHTransfer>
11
+ onDataChange?: (data: ACHTransfer) => void
12
+ isEditing?: boolean
13
+ onToggleEdit?: () => void
14
+ className?: string
15
+ hideActions?: boolean
16
+ }
17
+
18
+ export const ACHBankCard = ({ data, onDataChange, isEditing, onToggleEdit, className, hideActions }: ACHBankCardProps) => {
19
+ const form = useFormWithEditState<ACHTransfer>({
20
+ schema: achTransferSchema,
21
+ defaultValues: { ...defaultACHTransfer, ...data },
22
+ initialEditing: isEditing,
23
+ onToggleEdit,
24
+ onSave: onDataChange,
25
+ })
26
+
27
+ const editContent = (
28
+ <FormProvider form={form}>
29
+ <div className="space-y-6">
30
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
31
+ <FormInput
32
+ name="bankDetails.gatewayRoutingNumber"
33
+ label="Gateway Routing Number"
34
+ placeholder="Enter gateway routing number"
35
+ pattern="[0-9]{9}"
36
+ maxLength={9}
37
+ hint="9-digit gateway routing number"
38
+ required
39
+ />
40
+
41
+ <FormInput
42
+ name="bankDetails.rdfiNumberQualifier"
43
+ label="RDFI Number Qualifier"
44
+ placeholder="Enter RDFI qualifier"
45
+ maxLength={2}
46
+ hint="2-digit RDFI number qualifier"
47
+ required
48
+ />
49
+ </div>
50
+ </div>
51
+ </FormProvider>
52
+ )
53
+
54
+ const viewContent = (
55
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
56
+ <InfoField label="Gateway Routing Number" value={form.getValues("bankDetails.gatewayRoutingNumber")} layout="horizontal" />
57
+ <InfoField label="RDFI Number Qualifier" value={form.getValues("bankDetails.rdfiNumberQualifier")} layout="horizontal" />
58
+ </div>
59
+ )
60
+
61
+ return (
62
+ <EditableFormCard
63
+ title="ACH Bank Details"
64
+ description="ACH-specific routing and qualification information"
65
+ variant="subtle"
66
+ className={className}
67
+ isEditing={form.isEditing}
68
+ onToggleEdit={form.handleToggleEdit}
69
+ onSave={form.handleSave}
70
+ onCancel={form.handleCancel}
71
+ hideActions={hideActions}
72
+ isFormValid={form.isFormValid}
73
+ isDirty={form.isDirty}
74
+ editContent={editContent}
75
+ viewContent={viewContent}
76
+ />
77
+ )
78
+ }