amliq-dashboard 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (492) hide show
  1. package/.env.example +3 -0
  2. package/.env.production +1 -0
  3. package/Dockerfile +26 -0
  4. package/QUICK_START.md +267 -0
  5. package/README.md +161 -0
  6. package/TESTING_GUIDE.md +322 -0
  7. package/dist/.well-known/ai-plugin.json +15 -0
  8. package/dist/_headers +8 -0
  9. package/dist/_redirects +1 -0
  10. package/dist/assets/APIKeys-BvwFauIs.js +6 -0
  11. package/dist/assets/APIKeys-DaMSBZQL.js +1 -0
  12. package/dist/assets/AdverseMedia-DckXfMzS.js +4 -0
  13. package/dist/assets/AlertDetailPage-Bc2_zwu_.js +1 -0
  14. package/dist/assets/AlertQueue-DFtBiRoM.js +8 -0
  15. package/dist/assets/Analytics-DtruB5lQ.js +1 -0
  16. package/dist/assets/AuditTrail-BK68ePu0.js +1 -0
  17. package/dist/assets/AuthDivider-gRHEt7gx.js +1 -0
  18. package/dist/assets/Badge-B9VFkofy.js +1 -0
  19. package/dist/assets/BatchJobs-Bz6KFnXD.js +6 -0
  20. package/dist/assets/BillingPage-R9nQ5nFb.js +11 -0
  21. package/dist/assets/Card-Cw-T_-oU.js +1 -0
  22. package/dist/assets/CaseDetail-DYs7Zg_y.js +7 -0
  23. package/dist/assets/CaseManagement-B1Q4Dp1Y.js +1 -0
  24. package/dist/assets/ComplianceReport-Dhssqc9T.js +1 -0
  25. package/dist/assets/Configuration-B92kl9T0.js +1 -0
  26. package/dist/assets/CryptoScreening-C7yFlskC.js +1 -0
  27. package/dist/assets/Dashboard-w4-Nhv9q.js +17 -0
  28. package/dist/assets/DataSources-u3-Ri_5_.js +3 -0
  29. package/dist/assets/EDDWorkflow-CFzlEO0e.js +1 -0
  30. package/dist/assets/EmptyState-Do0xJAT9.js +6 -0
  31. package/dist/assets/ForgotPassword-BPYyQFQp.js +1 -0
  32. package/dist/assets/LandingPage-CjaP0zw4.js +41 -0
  33. package/dist/assets/ListsMarketplace-CG6KkT9B.js +6 -0
  34. package/dist/assets/Login-677LEGP1.js +1 -0
  35. package/dist/assets/MFASetup-BxqCPvui.js +1 -0
  36. package/dist/assets/Monitoring-9TN9MK4y.js +1 -0
  37. package/dist/assets/Onboarding-Cf0Y83lX.js +1 -0
  38. package/dist/assets/Operations-CcXwc2xw.js +10 -0
  39. package/dist/assets/Overview-B_Ky0VWV.js +1 -0
  40. package/dist/assets/PEPScreening-pUQjTH9c.js +1 -0
  41. package/dist/assets/PageHeader-BObHFn0i.js +1 -0
  42. package/dist/assets/PrivacyPolicy-DZ2xrKir.js +1 -0
  43. package/dist/assets/ResetPassword-AtxtpIG1.js +1 -0
  44. package/dist/assets/RiskAssessment-Dzuh1Usv.js +1 -0
  45. package/dist/assets/SARForm-D8w2ZGe-.js +1 -0
  46. package/dist/assets/SanctionsLists-CPA3UH2v.js +1 -0
  47. package/dist/assets/ScheduledTasks-Dsjk-6UR.js +1 -0
  48. package/dist/assets/ScreenEntity-D1U0YL7v.js +3 -0
  49. package/dist/assets/ScreeningProgress-BW49PzoB.js +66 -0
  50. package/dist/assets/SearchField-CulFKdiI.js +1 -0
  51. package/dist/assets/Signup-C0FmFOo_.js +1 -0
  52. package/dist/assets/SystemHealth-B_8u4eva.js +1 -0
  53. package/dist/assets/TaskHistory-BCmEM89q.js +3 -0
  54. package/dist/assets/Team-CUo_FlU7.js +1 -0
  55. package/dist/assets/TenantDetail-CgZQHUY8.js +1 -0
  56. package/dist/assets/Tenants-RB9E9ick.js +2 -0
  57. package/dist/assets/TermsOfService-BfL-kndX.js +1 -0
  58. package/dist/assets/TransactionMonitoring-DGh_T22s.js +14 -0
  59. package/dist/assets/TxnScreening-B7cm2-eh.js +1 -0
  60. package/dist/assets/UBOChain-falXAsCo.js +1 -0
  61. package/dist/assets/Users-Doakpg9c.js +1 -0
  62. package/dist/assets/Webhooks-BZpAfaxv.js +1 -0
  63. package/dist/assets/alert-triangle-YCFVm7B_.js +6 -0
  64. package/dist/assets/alerts-COUnTwWY.js +1 -0
  65. package/dist/assets/arrow-left-9ApLqP95.js +6 -0
  66. package/dist/assets/check-VTcvVtIU.js +6 -0
  67. package/dist/assets/check-circle-2-Dn-lG5YQ.js +6 -0
  68. package/dist/assets/code-DIJRWFVv.js +6 -0
  69. package/dist/assets/fingerprint-C0MGVtax.js +6 -0
  70. package/dist/assets/flag-B8_Gwxh6.js +6 -0
  71. package/dist/assets/index-CfdYcqRM.js +289 -0
  72. package/dist/assets/index-HPU_Rhdu.css +1 -0
  73. package/dist/assets/layers-zHz2o4vo.js +6 -0
  74. package/dist/assets/loader-B9_dNihg.js +6 -0
  75. package/dist/assets/mail-BijL2qwp.js +6 -0
  76. package/dist/assets/plus-i0oBIIPS.js +6 -0
  77. package/dist/assets/refresh-cw-CSuAwutt.js +6 -0
  78. package/dist/assets/useAnalytics-Bj8IONcw.js +72 -0
  79. package/dist/assets/x-circle-DLGKvijE.js +6 -0
  80. package/dist/favicon.svg +15 -0
  81. package/dist/index.html +51 -0
  82. package/dist/llms.txt +28 -0
  83. package/dist/logo.png +0 -0
  84. package/dist/logo.svg +17 -0
  85. package/dist/manifest.json +44 -0
  86. package/dist/robots.txt +15 -0
  87. package/dist/schema.json +13 -0
  88. package/dist/sitemap.xml +28 -0
  89. package/dist/sw.js +67 -0
  90. package/e2e/auth-setup.ts +20 -0
  91. package/e2e/billing.spec.ts +50 -0
  92. package/e2e/cases.spec.ts +53 -0
  93. package/e2e/compliance.spec.ts +65 -0
  94. package/e2e/config.spec.ts +56 -0
  95. package/e2e/dashboard.spec.ts +47 -0
  96. package/e2e/fixtures.ts +33 -0
  97. package/e2e/lists.spec.ts +43 -0
  98. package/e2e/login.spec.ts +64 -0
  99. package/e2e/media.spec.ts +48 -0
  100. package/e2e/mocks.ts +51 -0
  101. package/e2e/monitoring.spec.ts +44 -0
  102. package/e2e/navigation.spec.ts +56 -0
  103. package/e2e/onboarding.spec.ts +49 -0
  104. package/e2e/responsive.spec.ts +40 -0
  105. package/e2e/risk.spec.ts +61 -0
  106. package/e2e/screening.spec.ts +76 -0
  107. package/e2e/team.spec.ts +53 -0
  108. package/index.html +50 -0
  109. package/package.json +47 -0
  110. package/playwright.config.ts +35 -0
  111. package/postcss.config.js +6 -0
  112. package/public/.well-known/ai-plugin.json +15 -0
  113. package/public/_headers +8 -0
  114. package/public/_redirects +1 -0
  115. package/public/favicon.svg +15 -0
  116. package/public/llms.txt +28 -0
  117. package/public/logo.png +0 -0
  118. package/public/logo.svg +17 -0
  119. package/public/manifest.json +44 -0
  120. package/public/robots.txt +15 -0
  121. package/public/schema.json +13 -0
  122. package/public/sitemap.xml +28 -0
  123. package/public/sw.js +67 -0
  124. package/scripts/test-runner.sh +152 -0
  125. package/src/App.tsx +48 -0
  126. package/src/api/alerts.ts +19 -0
  127. package/src/api/analytics.ts +6 -0
  128. package/src/api/audit.ts +11 -0
  129. package/src/api/auth.ts +26 -0
  130. package/src/api/billing.ts +54 -0
  131. package/src/api/cases.ts +48 -0
  132. package/src/api/client.ts +50 -0
  133. package/src/api/config.ts +30 -0
  134. package/src/api/edd.ts +25 -0
  135. package/src/api/enforcement.ts +33 -0
  136. package/src/api/lists.ts +24 -0
  137. package/src/api/monitoring.ts +82 -0
  138. package/src/api/pep.ts +30 -0
  139. package/src/api/risk.ts +26 -0
  140. package/src/api/screening.ts +37 -0
  141. package/src/api/team.ts +27 -0
  142. package/src/api/transactions.ts +37 -0
  143. package/src/components/admin/TenantCards.tsx +51 -0
  144. package/src/components/alerts/AlertActions.test.tsx +80 -0
  145. package/src/components/alerts/AlertActions.tsx +68 -0
  146. package/src/components/alerts/AlertCard.test.tsx +86 -0
  147. package/src/components/alerts/AlertCard.tsx +60 -0
  148. package/src/components/alerts/AlertDetailSidebar.tsx +63 -0
  149. package/src/components/alerts/AlertFilters.test.tsx +73 -0
  150. package/src/components/alerts/AlertFilters.tsx +88 -0
  151. package/src/components/alerts/EntityDetailsCard.test.tsx +61 -0
  152. package/src/components/alerts/EntityDetailsCard.tsx +37 -0
  153. package/src/components/alerts/NotesCard.test.tsx +39 -0
  154. package/src/components/alerts/NotesCard.tsx +28 -0
  155. package/src/components/auth/AuthDivider.tsx +18 -0
  156. package/src/components/auth/LoginForm.tsx +62 -0
  157. package/src/components/auth/LoginLeftPanel.tsx +43 -0
  158. package/src/components/auth/PasswordStrength.tsx +26 -0
  159. package/src/components/auth/SignInButtons.tsx +46 -0
  160. package/src/components/auth/SignupLeftPanel.tsx +35 -0
  161. package/src/components/auth/countries.ts +17 -0
  162. package/src/components/charts/AreaChart.tsx +45 -0
  163. package/src/components/charts/BarChart.tsx +48 -0
  164. package/src/components/charts/DonutChart.tsx +47 -0
  165. package/src/components/compliance/CaseActions.tsx +74 -0
  166. package/src/components/compliance/CaseTimeline.tsx +68 -0
  167. package/src/components/compliance/MediaResultCard.tsx +87 -0
  168. package/src/components/compliance/PEPResultCard.tsx +78 -0
  169. package/src/components/config/MatchingModesCard.test.tsx +75 -0
  170. package/src/components/config/MatchingModesCard.tsx +40 -0
  171. package/src/components/config/ScreeningLayersCard.test.tsx +57 -0
  172. package/src/components/config/ScreeningLayersCard.tsx +36 -0
  173. package/src/components/config/ThresholdsCard.test.tsx +79 -0
  174. package/src/components/config/ThresholdsCard.tsx +51 -0
  175. package/src/components/dashboard/ActivityFeed.tsx +93 -0
  176. package/src/components/dashboard/DashboardGreeting.tsx +23 -0
  177. package/src/components/dashboard/DashboardSkeleton.tsx +35 -0
  178. package/src/components/dashboard/QuickActions.tsx +52 -0
  179. package/src/components/dashboard/TopEntitiesTable.tsx +63 -0
  180. package/src/components/data/ComplianceMetrics.test.tsx +32 -0
  181. package/src/components/data/ComplianceMetrics.tsx +40 -0
  182. package/src/components/data/ConfidenceScore.test.tsx +62 -0
  183. package/src/components/data/ConfidenceScore.tsx +27 -0
  184. package/src/components/data/StatCard.test.tsx +72 -0
  185. package/src/components/data/StatCard.tsx +63 -0
  186. package/src/components/data/StatusBadge.test.tsx +63 -0
  187. package/src/components/data/StatusBadge.tsx +39 -0
  188. package/src/components/layout/AppShell.tsx +48 -0
  189. package/src/components/layout/Breadcrumbs.tsx +48 -0
  190. package/src/components/layout/CommandPalette.tsx +81 -0
  191. package/src/components/layout/DashboardLayout.tsx +19 -0
  192. package/src/components/layout/MobileHeader.tsx +30 -0
  193. package/src/components/layout/NavGroup.test.tsx +63 -0
  194. package/src/components/layout/NavGroup.tsx +64 -0
  195. package/src/components/layout/NotificationBell.tsx +89 -0
  196. package/src/components/layout/PageHeader.test.tsx +61 -0
  197. package/src/components/layout/PageHeader.tsx +19 -0
  198. package/src/components/layout/ProtectedRoute.tsx +31 -0
  199. package/src/components/layout/PublicLayout.tsx +17 -0
  200. package/src/components/layout/Sidebar.test.tsx +67 -0
  201. package/src/components/layout/Sidebar.tsx +92 -0
  202. package/src/components/layout/Toolbar.test.tsx +47 -0
  203. package/src/components/layout/Toolbar.tsx +68 -0
  204. package/src/components/layout/navItems.ts +94 -0
  205. package/src/components/lists/ListCard.tsx +78 -0
  206. package/src/components/lists/ListMarketplaceCard.tsx +77 -0
  207. package/src/components/screening/CircularConfidence.tsx +38 -0
  208. package/src/components/screening/LimitReachedBanner.tsx +31 -0
  209. package/src/components/screening/ListSelector.tsx +64 -0
  210. package/src/components/screening/MatchDetailHeader.tsx +64 -0
  211. package/src/components/screening/MatchEntityInfo.tsx +56 -0
  212. package/src/components/screening/MatchEvidenceBars.tsx +65 -0
  213. package/src/components/screening/MatchMetadata.tsx +95 -0
  214. package/src/components/screening/ScreenResults.tsx +49 -0
  215. package/src/components/screening/ScreeningForm.test.tsx +83 -0
  216. package/src/components/screening/ScreeningForm.tsx +100 -0
  217. package/src/components/screening/ScreeningLayersList.test.tsx +33 -0
  218. package/src/components/screening/ScreeningLayersList.tsx +46 -0
  219. package/src/components/screening/ScreeningProgress.tsx +91 -0
  220. package/src/components/screening/ScreeningQuotaBanner.tsx +64 -0
  221. package/src/components/screening/ScreeningResultCard.tsx +47 -0
  222. package/src/components/screening/ScreeningResultRow.tsx +45 -0
  223. package/src/components/screening/ScreeningResults.test.tsx +37 -0
  224. package/src/components/screening/ScreeningResults.tsx +33 -0
  225. package/src/components/screening/ShareResults.tsx +103 -0
  226. package/src/components/screening/ThresholdSlider.tsx +23 -0
  227. package/src/components/transactions/WebhookCTA.tsx +63 -0
  228. package/src/components/ui/Avatar.test.tsx +47 -0
  229. package/src/components/ui/Avatar.tsx +35 -0
  230. package/src/components/ui/Badge.test.tsx +49 -0
  231. package/src/components/ui/Badge.tsx +33 -0
  232. package/src/components/ui/Button.test.tsx +56 -0
  233. package/src/components/ui/Button.tsx +46 -0
  234. package/src/components/ui/Card.test.tsx +61 -0
  235. package/src/components/ui/Card.tsx +29 -0
  236. package/src/components/ui/ConfirmModal.tsx +67 -0
  237. package/src/components/ui/Divider.test.tsx +24 -0
  238. package/src/components/ui/Divider.tsx +5 -0
  239. package/src/components/ui/EmptyState.test.tsx +49 -0
  240. package/src/components/ui/EmptyState.tsx +22 -0
  241. package/src/components/ui/ErrorBoundary.tsx +44 -0
  242. package/src/components/ui/ExportMenu.tsx +71 -0
  243. package/src/components/ui/LanguageSwitcher.tsx +37 -0
  244. package/src/components/ui/LoadingSpinner.test.tsx +41 -0
  245. package/src/components/ui/LoadingSpinner.tsx +21 -0
  246. package/src/components/ui/MetricCard.tsx +63 -0
  247. package/src/components/ui/ScoreRing.tsx +51 -0
  248. package/src/components/ui/SearchField.test.tsx +55 -0
  249. package/src/components/ui/SearchField.tsx +31 -0
  250. package/src/components/ui/SeverityBadge.tsx +57 -0
  251. package/src/components/ui/ThemeToggle.tsx +37 -0
  252. package/src/components/ui/Toast.test.tsx +79 -0
  253. package/src/components/ui/Toast.tsx +75 -0
  254. package/src/components/ui/Toggle.test.tsx +85 -0
  255. package/src/components/ui/Toggle.tsx +46 -0
  256. package/src/context/AuthContext.tsx +71 -0
  257. package/src/data/pepProfiles.ts +76 -0
  258. package/src/data/pepProfilesExtra.ts +58 -0
  259. package/src/hooks/useAlerts.ts +36 -0
  260. package/src/hooks/useAnalytics.ts +23 -0
  261. package/src/hooks/useApi.test.ts +79 -0
  262. package/src/hooks/useApi.ts +33 -0
  263. package/src/hooks/useAudit.ts +28 -0
  264. package/src/hooks/useBilling.ts +38 -0
  265. package/src/hooks/useConfig.ts +35 -0
  266. package/src/hooks/useDebounce.test.ts +84 -0
  267. package/src/hooks/useDebounce.ts +15 -0
  268. package/src/hooks/useDirection.ts +15 -0
  269. package/src/hooks/useLists.ts +34 -0
  270. package/src/hooks/useMediaQuery.test.ts +97 -0
  271. package/src/hooks/useMediaQuery.ts +28 -0
  272. package/src/hooks/useScreening.ts +33 -0
  273. package/src/hooks/useSidebar.ts +18 -0
  274. package/src/hooks/useUsage.ts +27 -0
  275. package/src/i18n/config.ts +33 -0
  276. package/src/i18n/locales/ar/admin.json +19 -0
  277. package/src/i18n/locales/ar/alerts.json +52 -0
  278. package/src/i18n/locales/ar/analytics.json +9 -0
  279. package/src/i18n/locales/ar/audit.json +12 -0
  280. package/src/i18n/locales/ar/auth.json +60 -0
  281. package/src/i18n/locales/ar/batch.json +5 -0
  282. package/src/i18n/locales/ar/billing.json +41 -0
  283. package/src/i18n/locales/ar/common.json +65 -0
  284. package/src/i18n/locales/ar/compliance.json +83 -0
  285. package/src/i18n/locales/ar/config.json +13 -0
  286. package/src/i18n/locales/ar/dashboard.json +19 -0
  287. package/src/i18n/locales/ar/errors.json +9 -0
  288. package/src/i18n/locales/ar/index.ts +29 -0
  289. package/src/i18n/locales/ar/legal.json +10 -0
  290. package/src/i18n/locales/ar/lists.json +6 -0
  291. package/src/i18n/locales/ar/marketing.json +110 -0
  292. package/src/i18n/locales/ar/monitoring.json +15 -0
  293. package/src/i18n/locales/ar/nav.json +35 -0
  294. package/src/i18n/locales/ar/onboarding.json +25 -0
  295. package/src/i18n/locales/ar/platform.json +23 -0
  296. package/src/i18n/locales/ar/screening.json +26 -0
  297. package/src/i18n/locales/ar/team.json +11 -0
  298. package/src/i18n/locales/en/admin.json +21 -0
  299. package/src/i18n/locales/en/alerts.json +52 -0
  300. package/src/i18n/locales/en/analytics.json +9 -0
  301. package/src/i18n/locales/en/audit.json +12 -0
  302. package/src/i18n/locales/en/auth.json +60 -0
  303. package/src/i18n/locales/en/batch.json +5 -0
  304. package/src/i18n/locales/en/billing.json +87 -0
  305. package/src/i18n/locales/en/common.json +65 -0
  306. package/src/i18n/locales/en/compliance.json +83 -0
  307. package/src/i18n/locales/en/config.json +29 -0
  308. package/src/i18n/locales/en/dashboard.json +23 -0
  309. package/src/i18n/locales/en/errors.json +9 -0
  310. package/src/i18n/locales/en/index.ts +29 -0
  311. package/src/i18n/locales/en/legal.json +114 -0
  312. package/src/i18n/locales/en/lists.json +6 -0
  313. package/src/i18n/locales/en/marketing.json +174 -0
  314. package/src/i18n/locales/en/monitoring.json +15 -0
  315. package/src/i18n/locales/en/nav.json +35 -0
  316. package/src/i18n/locales/en/onboarding.json +31 -0
  317. package/src/i18n/locales/en/platform.json +25 -0
  318. package/src/i18n/locales/en/screening.json +26 -0
  319. package/src/i18n/locales/en/team.json +12 -0
  320. package/src/i18n/locales/he/admin.json +19 -0
  321. package/src/i18n/locales/he/alerts.json +52 -0
  322. package/src/i18n/locales/he/analytics.json +9 -0
  323. package/src/i18n/locales/he/audit.json +12 -0
  324. package/src/i18n/locales/he/auth.json +60 -0
  325. package/src/i18n/locales/he/batch.json +5 -0
  326. package/src/i18n/locales/he/billing.json +41 -0
  327. package/src/i18n/locales/he/common.json +65 -0
  328. package/src/i18n/locales/he/compliance.json +83 -0
  329. package/src/i18n/locales/he/config.json +13 -0
  330. package/src/i18n/locales/he/dashboard.json +19 -0
  331. package/src/i18n/locales/he/errors.json +9 -0
  332. package/src/i18n/locales/he/index.ts +29 -0
  333. package/src/i18n/locales/he/legal.json +10 -0
  334. package/src/i18n/locales/he/lists.json +6 -0
  335. package/src/i18n/locales/he/marketing.json +110 -0
  336. package/src/i18n/locales/he/monitoring.json +15 -0
  337. package/src/i18n/locales/he/nav.json +35 -0
  338. package/src/i18n/locales/he/onboarding.json +25 -0
  339. package/src/i18n/locales/he/platform.json +23 -0
  340. package/src/i18n/locales/he/screening.json +26 -0
  341. package/src/i18n/locales/he/team.json +11 -0
  342. package/src/index.css +112 -0
  343. package/src/main.tsx +15 -0
  344. package/src/pages/APIKeys.tsx +120 -0
  345. package/src/pages/AddMonitorModal.tsx +30 -0
  346. package/src/pages/AdverseMedia.test.tsx +18 -0
  347. package/src/pages/AdverseMedia.tsx +89 -0
  348. package/src/pages/AlertDetailPage.tsx +64 -0
  349. package/src/pages/AlertQueue.test.tsx +48 -0
  350. package/src/pages/AlertQueue.tsx +63 -0
  351. package/src/pages/Analytics.tsx +50 -0
  352. package/src/pages/AuditTrail.tsx +64 -0
  353. package/src/pages/BatchJobs.tsx +79 -0
  354. package/src/pages/CaseDetail.tsx +72 -0
  355. package/src/pages/CaseManagement.test.tsx +31 -0
  356. package/src/pages/CaseManagement.tsx +92 -0
  357. package/src/pages/Configuration.test.tsx +96 -0
  358. package/src/pages/Configuration.tsx +123 -0
  359. package/src/pages/CryptoScreening.tsx +109 -0
  360. package/src/pages/Dashboard.test.tsx +51 -0
  361. package/src/pages/Dashboard.tsx +66 -0
  362. package/src/pages/EDDWorkflow.test.tsx +29 -0
  363. package/src/pages/EDDWorkflow.tsx +73 -0
  364. package/src/pages/ForgotPassword.test.tsx +49 -0
  365. package/src/pages/ForgotPassword.tsx +67 -0
  366. package/src/pages/ListsMarketplace.tsx +102 -0
  367. package/src/pages/Login.test.tsx +100 -0
  368. package/src/pages/Login.tsx +57 -0
  369. package/src/pages/MFASetup.tsx +114 -0
  370. package/src/pages/MonitorProfileCard.tsx +27 -0
  371. package/src/pages/Monitoring.tsx +68 -0
  372. package/src/pages/Onboarding.test.tsx +36 -0
  373. package/src/pages/Onboarding.tsx +60 -0
  374. package/src/pages/PEPScreening.test.tsx +15 -0
  375. package/src/pages/PEPScreening.tsx +100 -0
  376. package/src/pages/ResetPassword.tsx +81 -0
  377. package/src/pages/RiskAssessment.test.tsx +15 -0
  378. package/src/pages/RiskAssessment.tsx +108 -0
  379. package/src/pages/SanctionsLists.tsx +74 -0
  380. package/src/pages/ScreenEntity.test.tsx +82 -0
  381. package/src/pages/ScreenEntity.tsx +76 -0
  382. package/src/pages/Signup.test.tsx +98 -0
  383. package/src/pages/Signup.tsx +92 -0
  384. package/src/pages/TaskHistory.tsx +183 -0
  385. package/src/pages/Team.test.tsx +15 -0
  386. package/src/pages/Team.tsx +140 -0
  387. package/src/pages/TransactionMonitoring.test.tsx +18 -0
  388. package/src/pages/TransactionMonitoring.tsx +118 -0
  389. package/src/pages/TxnScreening.tsx +125 -0
  390. package/src/pages/UBOChain.test.tsx +35 -0
  391. package/src/pages/UBOChain.tsx +65 -0
  392. package/src/pages/Webhooks.tsx +137 -0
  393. package/src/pages/admin/DataSources.tsx +230 -0
  394. package/src/pages/admin/Operations.tsx +103 -0
  395. package/src/pages/admin/ScheduledTasks.tsx +155 -0
  396. package/src/pages/admin/SystemHealth.tsx +58 -0
  397. package/src/pages/admin/TenantDetail.tsx +62 -0
  398. package/src/pages/admin/Tenants.test.tsx +20 -0
  399. package/src/pages/admin/Tenants.tsx +63 -0
  400. package/src/pages/admin/opsRunners.ts +81 -0
  401. package/src/pages/admin/opsTerminal.tsx +63 -0
  402. package/src/pages/billing/ActiveSubscriptions.tsx +41 -0
  403. package/src/pages/billing/AddProductModal.tsx +99 -0
  404. package/src/pages/billing/BillingPage.test.tsx +30 -0
  405. package/src/pages/billing/BillingPage.tsx +67 -0
  406. package/src/pages/billing/CurrentPlan.tsx +50 -0
  407. package/src/pages/billing/InvoiceList.tsx +104 -0
  408. package/src/pages/billing/InvoiceRow.tsx +37 -0
  409. package/src/pages/billing/LemonSqueezySetup.tsx +51 -0
  410. package/src/pages/billing/PaymentAlert.tsx +33 -0
  411. package/src/pages/billing/PlanComparison.tsx +53 -0
  412. package/src/pages/billing/ProductUsage.tsx +43 -0
  413. package/src/pages/billing/PromoCodeInput.tsx +58 -0
  414. package/src/pages/billing/SeatManager.tsx +80 -0
  415. package/src/pages/billing/SeatRow.tsx +32 -0
  416. package/src/pages/billing/SubscriptionCard.tsx +73 -0
  417. package/src/pages/billing/UpgradeModal.tsx +53 -0
  418. package/src/pages/billing/UsageHistory.tsx +37 -0
  419. package/src/pages/billing/UsageMeter.tsx +26 -0
  420. package/src/pages/billing/UsageOverview.tsx +38 -0
  421. package/src/pages/legal/PrivacyPolicy.tsx +25 -0
  422. package/src/pages/legal/TermsOfService.tsx +25 -0
  423. package/src/pages/marketing/BundleCallout.tsx +24 -0
  424. package/src/pages/marketing/CTASection.tsx +48 -0
  425. package/src/pages/marketing/CaseStudy.tsx +37 -0
  426. package/src/pages/marketing/ComparisonTable.tsx +66 -0
  427. package/src/pages/marketing/CompetitiveEdge.tsx +55 -0
  428. package/src/pages/marketing/DataCoverage.tsx +54 -0
  429. package/src/pages/marketing/DataRain.tsx +30 -0
  430. package/src/pages/marketing/EnterpriseCTA.tsx +26 -0
  431. package/src/pages/marketing/FAQItem.tsx +27 -0
  432. package/src/pages/marketing/FAQSchema.tsx +43 -0
  433. package/src/pages/marketing/FAQSection.tsx +19 -0
  434. package/src/pages/marketing/FeatureDetail.tsx +19 -0
  435. package/src/pages/marketing/FeaturesGrid.tsx +60 -0
  436. package/src/pages/marketing/FeaturesSpotlight.tsx +68 -0
  437. package/src/pages/marketing/FooterSection.tsx +92 -0
  438. package/src/pages/marketing/GradientOrbs.tsx +26 -0
  439. package/src/pages/marketing/HeroSearch.tsx +113 -0
  440. package/src/pages/marketing/HeroSection.tsx +72 -0
  441. package/src/pages/marketing/LandingPage.tsx +45 -0
  442. package/src/pages/marketing/LogoCloud.tsx +31 -0
  443. package/src/pages/marketing/LogoMarquee.tsx +45 -0
  444. package/src/pages/marketing/MarketingNav.tsx +80 -0
  445. package/src/pages/marketing/MatchingLayers.tsx +78 -0
  446. package/src/pages/marketing/MobileMenu.tsx +45 -0
  447. package/src/pages/marketing/PricingCard.tsx +57 -0
  448. package/src/pages/marketing/PricingFeatureRow.tsx +16 -0
  449. package/src/pages/marketing/PricingSection.tsx +103 -0
  450. package/src/pages/marketing/PricingToggle.tsx +40 -0
  451. package/src/pages/marketing/ProductPricingCards.tsx +23 -0
  452. package/src/pages/marketing/ProductShowcase.tsx +81 -0
  453. package/src/pages/marketing/ProductTabs.tsx +45 -0
  454. package/src/pages/marketing/QuoteRotator.tsx +56 -0
  455. package/src/pages/marketing/StatsBar.tsx +81 -0
  456. package/src/pages/marketing/StatsSection.tsx +44 -0
  457. package/src/pages/marketing/TestimonialCard.tsx +38 -0
  458. package/src/pages/marketing/TestimonialsSection.tsx +32 -0
  459. package/src/pages/marketing/animations.tsx +56 -0
  460. package/src/pages/onboarding/CountryStep.tsx +38 -0
  461. package/src/pages/onboarding/ListsStep.tsx +44 -0
  462. package/src/pages/onboarding/StepIndicator.tsx +34 -0
  463. package/src/pages/onboarding/ThresholdStep.tsx +49 -0
  464. package/src/pages/platform/APIKeys.tsx +102 -0
  465. package/src/pages/platform/Overview.tsx +58 -0
  466. package/src/pages/platform/Users.tsx +110 -0
  467. package/src/pages/reporting/ComplianceReport.tsx +99 -0
  468. package/src/pages/reporting/SARForm.tsx +99 -0
  469. package/src/routes/appRoutes.tsx +60 -0
  470. package/src/routes/compliance.tsx +28 -0
  471. package/src/routes/lazyCompliance.ts +34 -0
  472. package/src/routes/lazyPages.ts +35 -0
  473. package/src/routes/lazyPlatform.ts +5 -0
  474. package/src/routes/platform.tsx +15 -0
  475. package/src/styles/effects.css +76 -0
  476. package/src/test/setup.ts +25 -0
  477. package/src/test/utils.tsx +49 -0
  478. package/src/types/alert.ts +31 -0
  479. package/src/types/analytics.ts +16 -0
  480. package/src/types/audit.ts +11 -0
  481. package/src/types/billing.ts +60 -0
  482. package/src/types/common.ts +15 -0
  483. package/src/types/config.ts +19 -0
  484. package/src/types/entity.ts +34 -0
  485. package/src/types/index.ts +8 -0
  486. package/src/types/list.ts +15 -0
  487. package/src/types/screening.ts +32 -0
  488. package/src/vite-env.d.ts +1 -0
  489. package/tailwind.config.js +65 -0
  490. package/tsconfig.json +22 -0
  491. package/vite.config.ts +11 -0
  492. package/vitest.config.ts +19 -0
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Badge } from '../ui/Badge';
4
+
5
+ interface Entity {
6
+ name: string;
7
+ alerts: number;
8
+ risk: string;
9
+ }
10
+
11
+ interface Props {
12
+ entities: Entity[];
13
+ }
14
+
15
+ const riskColor: Record<string, 'red' | 'orange' | 'green' | 'blue'> = {
16
+ critical: 'red', high: 'orange', medium: 'orange', low: 'green',
17
+ };
18
+
19
+ const riskGlow: Record<string, string> = {
20
+ critical: 'shadow-[0_0_10px_rgba(255,69,58,0.3)]',
21
+ high: 'shadow-[0_0_10px_rgba(255,159,10,0.3)]',
22
+ medium: 'shadow-[0_0_8px_rgba(255,159,10,0.2)]',
23
+ low: 'shadow-[0_0_8px_rgba(48,209,88,0.2)]',
24
+ };
25
+
26
+ export function TopEntitiesTable({ entities }: Props) {
27
+ const { t } = useTranslation('dashboard');
28
+
29
+ return (
30
+ <div className="card-vibrancy p-xl">
31
+ <h3 className="sf-headline mb-lg">{t('top_entities')}</h3>
32
+ <div className="space-y-sm">
33
+ {entities.map((entity, i) => (
34
+ <div
35
+ key={entity.name}
36
+ className="flex items-center justify-between p-md rounded-apple-md bg-white/[0.03]
37
+ hover:bg-white/[0.06] hover:shadow-[0_0_20px_rgba(255,255,255,0.03)]
38
+ transition-all cursor-default group"
39
+ >
40
+ <div className="flex items-center gap-md">
41
+ <span className="text-[13px] font-bold text-transparent bg-clip-text bg-gradient-to-b from-white to-white/40 w-6 text-center font-mono">
42
+ {String(i + 1).padStart(2, '0')}
43
+ </span>
44
+ <div>
45
+ <p className="text-[14px] font-medium text-white group-hover:text-apple-blue transition-colors">
46
+ {entity.name}
47
+ </p>
48
+ <p className="text-[11px] text-apple-label-tertiary">
49
+ {entity.alerts} {t('alerts')}
50
+ </p>
51
+ </div>
52
+ </div>
53
+ <div className={riskGlow[(entity.risk ?? '').toLowerCase()] ?? ''}>
54
+ <Badge size="sm" color={riskColor[(entity.risk ?? '').toLowerCase()] ?? 'blue'}>
55
+ {entity.risk ?? 'Unknown'}
56
+ </Badge>
57
+ </div>
58
+ </div>
59
+ ))}
60
+ </div>
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,32 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
3
+ import { ComplianceMetrics } from './ComplianceMetrics'
4
+
5
+ vi.mock('../../api/client', () => ({
6
+ api: {
7
+ get: vi.fn(() => Promise.resolve({
8
+ openCases: 5,
9
+ activeMonitors: 3,
10
+ highRiskEntities: 2,
11
+ pendingEDD: 1,
12
+ unreviewedMedia: 4,
13
+ txnAlerts: 7,
14
+ })),
15
+ },
16
+ }))
17
+
18
+ describe('ComplianceMetrics', () => {
19
+ it('renders section title', async () => {
20
+ render(<ComplianceMetrics />)
21
+ const title = await screen.findByText('Compliance Overview')
22
+ expect(title).toBeInTheDocument()
23
+ })
24
+
25
+ it('renders stat cards', async () => {
26
+ render(<ComplianceMetrics />)
27
+ const card = await screen.findByText('Open Cases')
28
+ expect(card).toBeInTheDocument()
29
+ expect(screen.getByText('Active Monitors')).toBeInTheDocument()
30
+ expect(screen.getByText('Txn Alerts')).toBeInTheDocument()
31
+ })
32
+ })
@@ -0,0 +1,40 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { StatCard } from './StatCard';
4
+ import { api } from '../../api/client';
5
+
6
+ interface ComplianceStats {
7
+ openCases: number;
8
+ activeMonitors: number;
9
+ highRiskEntities: number;
10
+ pendingEDD: number;
11
+ unreviewedMedia: number;
12
+ txnAlerts: number;
13
+ }
14
+
15
+ export function ComplianceMetrics() {
16
+ const { t } = useTranslation('dashboard');
17
+ const [stats, setStats] = useState<ComplianceStats | null>(null);
18
+
19
+ useEffect(() => {
20
+ api.get<ComplianceStats>('/dashboard/compliance')
21
+ .then((data) => setStats(data ?? null))
22
+ .catch(() => setStats(null));
23
+ }, []);
24
+
25
+ if (!stats) return null;
26
+
27
+ return (
28
+ <div>
29
+ <h3 className="sf-headline mb-lg">{t('compliance_overview')}</h3>
30
+ <div className="grid grid-cols-2 md:grid-cols-3 gap-lg">
31
+ <StatCard title={t('open_cases')} value={stats.openCases} color="orange" />
32
+ <StatCard title={t('active_monitors')} value={stats.activeMonitors} />
33
+ <StatCard title={t('high_risk')} value={stats.highRiskEntities} color="red" />
34
+ <StatCard title={t('pending_edd')} value={stats.pendingEDD} color="orange" />
35
+ <StatCard title={t('unreviewed_media')} value={stats.unreviewedMedia} />
36
+ <StatCard title={t('txn_alerts')} value={stats.txnAlerts} color="red" />
37
+ </div>
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,62 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { ConfidenceScore } from './ConfidenceScore'
4
+
5
+ describe('ConfidenceScore', () => {
6
+ it('displays score as percentage', () => {
7
+ render(<ConfidenceScore score={75} />)
8
+ expect(screen.getByText('75%')).toBeInTheDocument()
9
+ })
10
+
11
+ it('rounds score to nearest integer', () => {
12
+ render(<ConfidenceScore score={75.6} />)
13
+ expect(screen.getByText('76%')).toBeInTheDocument()
14
+ })
15
+
16
+ it('renders high score with red color', () => {
17
+ render(<ConfidenceScore score={85} />)
18
+ const element = screen.getByText('85%')
19
+ expect(element).toHaveClass('text-apple-red', 'bg-apple-red/20')
20
+ })
21
+
22
+ it('renders medium score with orange color', () => {
23
+ render(<ConfidenceScore score={65} />)
24
+ const element = screen.getByText('65%')
25
+ expect(element).toHaveClass('text-apple-orange', 'bg-apple-orange/20')
26
+ })
27
+
28
+ it('renders low score with green color', () => {
29
+ render(<ConfidenceScore score={50} />)
30
+ const element = screen.getByText('50%')
31
+ expect(element).toHaveClass('text-apple-green', 'bg-apple-green/20')
32
+ })
33
+
34
+ it('uses boundary score 80 for red threshold', () => {
35
+ render(<ConfidenceScore score={80} />)
36
+ expect(screen.getByText('80%')).toHaveClass('text-apple-red', 'bg-apple-red/20')
37
+ })
38
+
39
+ it('uses boundary score 60 for orange threshold', () => {
40
+ render(<ConfidenceScore score={60} />)
41
+ expect(screen.getByText('60%')).toHaveClass('text-apple-orange', 'bg-apple-orange/20')
42
+ })
43
+
44
+ it('renders different sizes', () => {
45
+ const { rerender } = render(<ConfidenceScore score={75} size="sm" />)
46
+ let element = screen.getByText('75%')
47
+ expect(element).toHaveClass('text-xs', 'px-md', 'py-xs')
48
+
49
+ rerender(<ConfidenceScore score={75} size="md" />)
50
+ element = screen.getByText('75%')
51
+ expect(element).toHaveClass('text-sm', 'px-lg', 'py-md')
52
+
53
+ rerender(<ConfidenceScore score={75} size="lg" />)
54
+ element = screen.getByText('75%')
55
+ expect(element).toHaveClass('text-base', 'px-xl', 'py-lg')
56
+ })
57
+
58
+ it('has rounded-full class', () => {
59
+ render(<ConfidenceScore score={50} />)
60
+ expect(screen.getByText('50%')).toHaveClass('rounded-full', 'font-semibold')
61
+ })
62
+ })
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+
4
+ interface ConfidenceScoreProps {
5
+ score: number;
6
+ size?: 'sm' | 'md' | 'lg';
7
+ }
8
+
9
+ export function ConfidenceScore({ score, size = 'md' }: ConfidenceScoreProps) {
10
+ const getColor = (s: number) => {
11
+ if (s >= 80) return 'text-apple-red bg-apple-red/20';
12
+ if (s >= 60) return 'text-apple-orange bg-apple-orange/20';
13
+ return 'text-apple-green bg-apple-green/20';
14
+ };
15
+
16
+ const sizes = {
17
+ sm: 'text-xs px-md py-xs',
18
+ md: 'text-sm px-lg py-md',
19
+ lg: 'text-base px-xl py-lg',
20
+ };
21
+
22
+ return (
23
+ <div className={clsx('rounded-full font-semibold inline-block', getColor(score), sizes[size])}>
24
+ {score.toFixed(0)}%
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,72 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { StatCard } from './StatCard'
4
+
5
+ describe('StatCard', () => {
6
+ it('renders title and value', () => {
7
+ render(<StatCard title="Total Alerts" value={42} />)
8
+ expect(screen.getByText('Total Alerts')).toBeInTheDocument()
9
+ expect(screen.getByText('42')).toBeInTheDocument()
10
+ })
11
+
12
+ it('renders string value', () => {
13
+ render(<StatCard title="Status" value="Active" />)
14
+ expect(screen.getByText('Active')).toBeInTheDocument()
15
+ })
16
+
17
+ it('renders positive trend with TrendingUp icon', () => {
18
+ const { container } = render(
19
+ <StatCard title="Growth" value={100} trend={12} />
20
+ )
21
+ expect(screen.getByText('12%')).toBeInTheDocument()
22
+ expect(container.querySelector('svg')).toBeInTheDocument()
23
+ })
24
+
25
+ it('renders negative trend with TrendingDown icon', () => {
26
+ render(<StatCard title="Decline" value={50} trend={-5} />)
27
+ expect(screen.getByText('5%')).toBeInTheDocument()
28
+ })
29
+
30
+ it('displays absolute trend value', () => {
31
+ render(<StatCard title="Test" value={100} trend={-25} />)
32
+ expect(screen.getByText('25%')).toBeInTheDocument()
33
+ })
34
+
35
+ it('renders description when provided', () => {
36
+ render(
37
+ <StatCard
38
+ title="Resolution Time"
39
+ value="2.5h"
40
+ description="Average time in hours"
41
+ />
42
+ )
43
+ expect(screen.getByText('Average time in hours')).toBeInTheDocument()
44
+ })
45
+
46
+ it('renders different color variants', () => {
47
+ const { rerender } = render(
48
+ <StatCard title="Growth" value={10} trend={5} color="green" />
49
+ )
50
+ let trendDiv = screen.getByText('5%').parentElement
51
+ expect(trendDiv?.className).toContain('text-apple-green')
52
+
53
+ rerender(<StatCard title="Decline" value={20} trend={-8} color="red" />)
54
+ trendDiv = screen.getByText('8%').parentElement
55
+ expect(trendDiv?.className).toContain('text-apple-red')
56
+
57
+ rerender(<StatCard title="Change" value={30} trend={3} color="orange" />)
58
+ trendDiv = screen.getByText('3%').parentElement
59
+ expect(trendDiv?.className).toContain('text-apple-orange')
60
+ })
61
+
62
+ it('applies default blue color', () => {
63
+ render(<StatCard title="Default" value={5} trend={2} />)
64
+ const trendDiv = screen.getByText('2%').parentElement
65
+ expect(trendDiv?.className).toContain('text-apple-blue')
66
+ })
67
+
68
+ it('does not render trend when undefined', () => {
69
+ render(<StatCard title="No Trend" value={100} />)
70
+ expect(screen.queryByText('%')).not.toBeInTheDocument()
71
+ })
72
+ })
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+ import { TrendingUp, TrendingDown } from 'lucide-react';
4
+
5
+ interface StatCardProps {
6
+ title: string;
7
+ value: string | number;
8
+ trend?: number;
9
+ description?: string;
10
+ color?: 'blue' | 'green' | 'red' | 'orange';
11
+ }
12
+
13
+ const glows: Record<string, string> = {
14
+ blue: 'shadow-[0_0_32px_rgba(10,132,255,0.15)]',
15
+ green: 'shadow-[0_0_32px_rgba(48,209,88,0.15)]',
16
+ orange: 'shadow-[0_0_32px_rgba(255,159,10,0.15)]',
17
+ red: 'shadow-[0_0_32px_rgba(255,69,58,0.15)]',
18
+ };
19
+
20
+ const trendGlow: Record<string, string> = {
21
+ up: 'text-apple-green drop-shadow-[0_0_6px_rgba(48,209,88,0.6)]',
22
+ down: 'text-apple-red drop-shadow-[0_0_6px_rgba(255,69,58,0.6)]',
23
+ };
24
+
25
+ const iconBg: Record<string, string> = {
26
+ blue: 'bg-apple-blue/10', green: 'bg-apple-green/10',
27
+ orange: 'bg-apple-orange/10', red: 'bg-apple-red/10',
28
+ };
29
+
30
+ export function StatCard({ title, value, trend, description, color = 'blue' }: StatCardProps) {
31
+ const trendDir = trend !== undefined ? (trend > 0 ? 'up' : 'down') : null;
32
+
33
+ return (
34
+ <div className={clsx(
35
+ 'card-vibrancy p-xl rounded-apple-lg transition-all hover:scale-[1.02]',
36
+ glows[color],
37
+ )}>
38
+ <div className="flex items-center justify-between mb-md">
39
+ <p className="sf-caption tracking-wide uppercase text-[11px]">{title}</p>
40
+ <div className={clsx(
41
+ 'w-8 h-8 rounded-full flex items-center justify-center animate-pulse',
42
+ iconBg[color],
43
+ )}>
44
+ <div className={clsx('w-2 h-2 rounded-full', `bg-apple-${color}`)} />
45
+ </div>
46
+ </div>
47
+ <h3 className="text-4xl font-extrabold tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 mb-sm">
48
+ {value}
49
+ </h3>
50
+ <div className="flex items-center justify-between">
51
+ {description && <p className="sf-caption">{description}</p>}
52
+ {trendDir && (
53
+ <div className={clsx('flex items-center gap-xs font-bold text-sm', trendGlow[trendDir])}>
54
+ {trendDir === 'up'
55
+ ? <TrendingUp className="w-5 h-5" />
56
+ : <TrendingDown className="w-5 h-5" />}
57
+ <span>{Math.abs(trend!)}%</span>
58
+ </div>
59
+ )}
60
+ </div>
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,63 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { StatusBadge } from './StatusBadge'
4
+
5
+ describe('StatusBadge', () => {
6
+ it('renders status badge with correct color for open', () => {
7
+ render(<StatusBadge status="open" type="status" />)
8
+ expect(screen.getByText('Open')).toBeInTheDocument()
9
+ expect(screen.getByText('Open')).toHaveClass('bg-apple-blue/20', 'text-apple-blue')
10
+ })
11
+
12
+ it('renders status badge with correct color for investigating', () => {
13
+ render(<StatusBadge status="investigating" type="status" />)
14
+ expect(screen.getByText('Investigating')).toBeInTheDocument()
15
+ expect(screen.getByText('Investigating')).toHaveClass('bg-apple-orange/20', 'text-apple-orange')
16
+ })
17
+
18
+ it('renders status badge with correct color for resolved', () => {
19
+ render(<StatusBadge status="resolved" type="status" />)
20
+ expect(screen.getByText('Resolved')).toBeInTheDocument()
21
+ expect(screen.getByText('Resolved')).toHaveClass('bg-apple-green/20', 'text-apple-green')
22
+ })
23
+
24
+ it('renders status badge with correct color for archived', () => {
25
+ render(<StatusBadge status="archived" type="status" />)
26
+ expect(screen.getByText('Archived')).toBeInTheDocument()
27
+ expect(screen.getByText('Archived')).toHaveClass('bg-white/10', 'text-white')
28
+ })
29
+
30
+ it('renders priority badge with correct color for critical', () => {
31
+ render(<StatusBadge priority="critical" type="priority" />)
32
+ expect(screen.getByText('CRITICAL')).toBeInTheDocument()
33
+ expect(screen.getByText('CRITICAL')).toHaveClass('bg-apple-red/20', 'text-apple-red')
34
+ })
35
+
36
+ it('renders priority badge with correct color for high', () => {
37
+ render(<StatusBadge priority="high" type="priority" />)
38
+ expect(screen.getByText('HIGH')).toBeInTheDocument()
39
+ expect(screen.getByText('HIGH')).toHaveClass('bg-apple-orange/20', 'text-apple-orange')
40
+ })
41
+
42
+ it('renders priority badge with correct color for medium', () => {
43
+ render(<StatusBadge priority="medium" type="priority" />)
44
+ expect(screen.getByText('MEDIUM')).toBeInTheDocument()
45
+ expect(screen.getByText('MEDIUM')).toHaveClass('bg-apple-orange/20', 'text-apple-orange')
46
+ })
47
+
48
+ it('renders priority badge with correct color for low', () => {
49
+ render(<StatusBadge priority="low" type="priority" />)
50
+ expect(screen.getByText('LOW')).toBeInTheDocument()
51
+ expect(screen.getByText('LOW')).toHaveClass('bg-white/10', 'text-white')
52
+ })
53
+
54
+ it('returns null when no status or priority provided', () => {
55
+ const { container } = render(<StatusBadge />)
56
+ expect(container.firstChild).toBeNull()
57
+ })
58
+
59
+ it('returns null when type does not match prop', () => {
60
+ const { container } = render(<StatusBadge status="open" type="priority" />)
61
+ expect(container.firstChild).toBeNull()
62
+ })
63
+ })
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Badge } from '../ui/Badge';
3
+ import type { AlertStatus, AlertPriority } from '../../types';
4
+
5
+ interface StatusBadgeProps {
6
+ status?: AlertStatus;
7
+ priority?: AlertPriority;
8
+ type?: 'status' | 'priority';
9
+ }
10
+
11
+ export function StatusBadge({ status, priority, type = 'status' }: StatusBadgeProps) {
12
+ if (type === 'status' && status) {
13
+ const colors: Record<AlertStatus, any> = {
14
+ open: 'blue',
15
+ investigating: 'orange',
16
+ resolved: 'green',
17
+ archived: 'gray',
18
+ };
19
+ const labels: Record<AlertStatus, string> = {
20
+ open: 'Open',
21
+ investigating: 'Investigating',
22
+ resolved: 'Resolved',
23
+ archived: 'Archived',
24
+ };
25
+ return <Badge color={colors[status]}>{labels[status]}</Badge>;
26
+ }
27
+
28
+ if (type === 'priority' && priority) {
29
+ const colors: Record<AlertPriority, any> = {
30
+ critical: 'red',
31
+ high: 'orange',
32
+ medium: 'orange',
33
+ low: 'gray',
34
+ };
35
+ return <Badge color={colors[priority]}>{priority.toUpperCase()}</Badge>;
36
+ }
37
+
38
+ return null;
39
+ }
@@ -0,0 +1,48 @@
1
+ import React, { useRef, useCallback } from 'react';
2
+ import { Sidebar } from './Sidebar';
3
+ import { Toolbar } from './Toolbar';
4
+ import { Breadcrumbs } from './Breadcrumbs';
5
+ import { useSidebar } from '../../hooks/useSidebar';
6
+
7
+ interface AppShellProps {
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ export function AppShell({ children }: AppShellProps) {
12
+ const { isOpen, toggle, close, open } = useSidebar();
13
+ const touchStart = useRef(0);
14
+
15
+ const onTouchStart = useCallback((e: React.TouchEvent) => {
16
+ touchStart.current = e.touches[0].clientX;
17
+ }, []);
18
+
19
+ const onTouchEnd = useCallback((e: React.TouchEvent) => {
20
+ const dx = e.changedTouches[0].clientX - touchStart.current;
21
+ if (dx > 80 && touchStart.current < 40) open();
22
+ if (dx < -80 && isOpen) close();
23
+ }, [isOpen, open, close]);
24
+
25
+ return (
26
+ <div className="flex h-screen bg-apple-bg"
27
+ onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>
28
+ <a href="#main-content" className="skip-link">Skip to main content</a>
29
+ <Sidebar isOpen={isOpen} onClose={close} />
30
+ <div className="flex-1 flex flex-col overflow-hidden relative">
31
+ <div
32
+ className="absolute inset-0 pointer-events-none opacity-40"
33
+ style={{
34
+ background: 'radial-gradient(ellipse 60% 40% at 50% 0%, rgba(10,132,255,0.04) 0%, transparent 70%)',
35
+ }}
36
+ aria-hidden="true"
37
+ />
38
+ <Toolbar onMenuClick={toggle} />
39
+ <Breadcrumbs />
40
+ <main id="main-content" className="flex-1 overflow-y-auto relative" tabIndex={-1}>
41
+ <div className="p-lg md:p-xxl max-w-7xl mx-auto min-h-[60vh]">
42
+ {children}
43
+ </div>
44
+ </main>
45
+ </div>
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react'
2
+ import { Link, useLocation } from 'react-router-dom'
3
+ import { ChevronRight, Home } from 'lucide-react'
4
+
5
+ const labelMap: Record<string, string> = {
6
+ dashboard: 'Dashboard', alerts: 'Alerts', screen: 'Screen',
7
+ monitoring: 'Monitoring', batch: 'Batch Jobs', compliance: 'Compliance',
8
+ cases: 'Cases', risk: 'Risk', pep: 'PEP', media: 'Adverse Media',
9
+ txn: 'Transactions', crypto: 'Crypto', analytics: 'Analytics',
10
+ audit: 'Audit Log', lists: 'Lists', marketplace: 'Marketplace',
11
+ config: 'Configuration', billing: 'Billing', team: 'Team',
12
+ admin: 'Admin', tenants: 'Tenants', health: 'Health',
13
+ keys: 'API Keys', webhooks: 'Webhooks', platform: 'Platform',
14
+ users: 'Users', overview: 'Overview',
15
+ 'data-sources': 'Data Sources', 'txn-screen': 'Txn Screen',
16
+ }
17
+
18
+ export function Breadcrumbs() {
19
+ const location = useLocation()
20
+ const parts = location.pathname.split('/').filter(Boolean)
21
+
22
+ if (parts.length <= 1) return null
23
+
24
+ return (
25
+ <nav aria-label="Breadcrumb" className="flex items-center gap-xs px-lg py-sm text-[12px]">
26
+ <Link to="/dashboard" className="text-apple-label-tertiary hover:text-white transition-colors">
27
+ <Home className="w-3.5 h-3.5" />
28
+ </Link>
29
+ {parts.map((part, i) => {
30
+ const path = '/' + parts.slice(0, i + 1).join('/')
31
+ const isLast = i === parts.length - 1
32
+ const label = labelMap[part] || part
33
+ return (
34
+ <React.Fragment key={path}>
35
+ <ChevronRight className="w-3 h-3 text-white/20" />
36
+ {isLast ? (
37
+ <span className="text-white/60">{label}</span>
38
+ ) : (
39
+ <Link to={path} className="text-apple-label-tertiary hover:text-white transition-colors">
40
+ {label}
41
+ </Link>
42
+ )}
43
+ </React.Fragment>
44
+ )
45
+ })}
46
+ </nav>
47
+ )
48
+ }
@@ -0,0 +1,81 @@
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import { Search } from 'lucide-react'
4
+ import { navSections, canAccess } from './navItems'
5
+ import { useAuth } from '../../context/AuthContext'
6
+
7
+ interface Props {
8
+ open: boolean
9
+ onClose: () => void
10
+ }
11
+
12
+ export function CommandPalette({ open, onClose }: Props) {
13
+ const [query, setQuery] = useState('')
14
+ const inputRef = useRef<HTMLInputElement>(null!)
15
+ const navigate = useNavigate()
16
+ const { user } = useAuth()
17
+ const role = user?.role ?? 'viewer'
18
+
19
+ useEffect(() => {
20
+ if (open) {
21
+ setQuery('')
22
+ setTimeout(() => inputRef.current?.focus(), 50)
23
+ }
24
+ }, [open])
25
+
26
+ useEffect(() => {
27
+ const handler = (e: KeyboardEvent) => {
28
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
29
+ e.preventDefault()
30
+ open ? onClose() : onClose() // toggle handled by parent
31
+ }
32
+ if (e.key === 'Escape' && open) onClose()
33
+ }
34
+ document.addEventListener('keydown', handler)
35
+ return () => document.removeEventListener('keydown', handler)
36
+ }, [open, onClose])
37
+
38
+ if (!open) return null
39
+
40
+ const allItems = navSections
41
+ .filter((s) => canAccess(role, s.minRole))
42
+ .flatMap((s) => s.items.filter((i) => canAccess(role, i.minRole)))
43
+
44
+ const filtered = query
45
+ ? allItems.filter((i) => i.label.toLowerCase().includes(query.toLowerCase()))
46
+ : allItems
47
+
48
+ const go = (path: string) => {
49
+ navigate(path)
50
+ onClose()
51
+ }
52
+
53
+ return (
54
+ <div className="fixed inset-0 z-[100] flex items-start justify-center pt-[20vh]"
55
+ onClick={onClose}>
56
+ <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
57
+ <div className="relative w-full max-w-lg bg-[#1a1a24] border border-white/10 rounded-apple-lg shadow-2xl overflow-hidden"
58
+ onClick={(e) => e.stopPropagation()}>
59
+ <div className="flex items-center gap-md px-lg border-b border-white/[0.06]">
60
+ <Search className="w-4 h-4 text-apple-label-tertiary" />
61
+ <input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)}
62
+ placeholder="Search pages..." className="flex-1 py-lg bg-transparent text-white text-sm outline-none" />
63
+ <kbd className="text-[10px] text-apple-label-tertiary bg-white/5 px-1.5 py-0.5 rounded">ESC</kbd>
64
+ </div>
65
+ <div className="max-h-64 overflow-y-auto py-sm">
66
+ {filtered.map(({ icon: Icon, label, path }) => (
67
+ <button key={path} onClick={() => go(path)}
68
+ className="w-full flex items-center gap-md px-lg py-md hover:bg-white/5 text-left cursor-pointer transition-colors">
69
+ <Icon className="w-4 h-4 text-apple-label-secondary" />
70
+ <span className="text-sm text-white">{label}</span>
71
+ <span className="ml-auto text-[11px] text-apple-label-tertiary">{path}</span>
72
+ </button>
73
+ ))}
74
+ {filtered.length === 0 && (
75
+ <p className="px-lg py-md text-sm text-apple-label-tertiary">No results</p>
76
+ )}
77
+ </div>
78
+ </div>
79
+ </div>
80
+ )
81
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+
3
+ interface DashboardLayoutProps {
4
+ children: React.ReactNode
5
+ sidebar?: React.ReactNode
6
+ }
7
+
8
+ export default function DashboardLayout({ children, sidebar }: DashboardLayoutProps) {
9
+ return (
10
+ <div className="flex h-screen bg-apple-bg">
11
+ {sidebar && (
12
+ <aside className="hidden md:block w-64 border-r border-white/10">{sidebar}</aside>
13
+ )}
14
+ <div className="flex-1 flex flex-col overflow-hidden">
15
+ <main className="flex-1 overflow-auto">{children}</main>
16
+ </div>
17
+ </div>
18
+ )
19
+ }