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,36 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect, vi } from 'vitest'
3
+ import Onboarding from './Onboarding'
4
+
5
+ vi.mock('react-router-dom', async () => ({
6
+ ...(await vi.importActual('react-router-dom')),
7
+ useNavigate: () => vi.fn(),
8
+ }))
9
+
10
+ vi.mock('../api/client', () => ({
11
+ api: {
12
+ get: vi.fn(() => Promise.resolve({ lists: [] })),
13
+ put: vi.fn(() => Promise.resolve({})),
14
+ },
15
+ }))
16
+
17
+ describe('Onboarding', () => {
18
+ it('renders welcome title', () => {
19
+ render(<Onboarding />)
20
+ expect(screen.getByText('Welcome to AMLIQ')).toBeInTheDocument()
21
+ })
22
+
23
+ it('renders step indicators', () => {
24
+ render(<Onboarding />)
25
+ expect(screen.getByText('Country')).toBeInTheDocument()
26
+ expect(screen.getByText('Lists')).toBeInTheDocument()
27
+ expect(screen.getByText('Threshold')).toBeInTheDocument()
28
+ })
29
+
30
+ it('renders country selection on step 1', () => {
31
+ render(<Onboarding />)
32
+ expect(screen.getByText('Select your country')).toBeInTheDocument()
33
+ expect(screen.getByText('United States')).toBeInTheDocument()
34
+ expect(screen.getByText('Israel')).toBeInTheDocument()
35
+ })
36
+ })
@@ -0,0 +1,60 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { useNavigate } from 'react-router-dom'
4
+ import { api } from '../api/client'
5
+ import { StepIndicator } from './onboarding/StepIndicator'
6
+ import { CountryStep } from './onboarding/CountryStep'
7
+ import { ListsStep } from './onboarding/ListsStep'
8
+ import { ThresholdStep } from './onboarding/ThresholdStep'
9
+
10
+ const COUNTRIES = [
11
+ { code: 'US', label: 'United States' },
12
+ { code: 'IL', label: 'Israel' },
13
+ { code: 'GB', label: 'United Kingdom' },
14
+ { code: 'DE', label: 'Germany' },
15
+ { code: 'CH', label: 'Switzerland' },
16
+ { code: 'JP', label: 'Japan' },
17
+ ]
18
+
19
+ interface ListSuggestion {
20
+ list_id: string; threshold: number; sync_enabled: boolean
21
+ }
22
+
23
+ export default function Onboarding() {
24
+ const { t } = useTranslation('onboarding')
25
+ const navigate = useNavigate()
26
+ const [step, setStep] = useState(1)
27
+ const [country, setCountry] = useState('')
28
+ const [lists, setLists] = useState<ListSuggestion[]>([])
29
+ const [threshold, setThreshold] = useState(70)
30
+ const [saving, setSaving] = useState(false)
31
+
32
+ useEffect(() => {
33
+ if (country) {
34
+ api.get<{ lists: ListSuggestion[] }>(`/onboarding/lists?country=${country}`)
35
+ .then(r => setLists(r.lists ?? []))
36
+ }
37
+ }, [country])
38
+
39
+ const handleFinish = async () => {
40
+ setSaving(true)
41
+ try {
42
+ await api.put('/config', { default_threshold: threshold / 100 })
43
+ navigate('/dashboard')
44
+ } catch { setSaving(false) }
45
+ }
46
+
47
+ return (
48
+ <div className="min-h-screen bg-apple-bg/50 flex items-center justify-center p-lg">
49
+ <div className="max-w-lg w-full space-y-xl">
50
+ <h1 className="sf-title text-white text-center">{t('welcome')}</h1>
51
+ <StepIndicator current={step} />
52
+ {step === 1 && <CountryStep countries={COUNTRIES} value={country}
53
+ onChange={c => { setCountry(c); setStep(2) }} />}
54
+ {step === 2 && <ListsStep lists={lists} onNext={() => setStep(3)} />}
55
+ {step === 3 && <ThresholdStep value={threshold}
56
+ onChange={setThreshold} onFinish={handleFinish} saving={saving} />}
57
+ </div>
58
+ </div>
59
+ )
60
+ }
@@ -0,0 +1,15 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { PEPScreening } from './PEPScreening'
4
+
5
+ describe('PEPScreening', () => {
6
+ it('renders page title', () => {
7
+ render(<PEPScreening />)
8
+ expect(screen.getByRole('heading', { name: /Screen Entity/i })).toBeInTheDocument()
9
+ })
10
+
11
+ it('renders search button', () => {
12
+ render(<PEPScreening />)
13
+ expect(screen.getByRole('button', { name: /Screen Entity/i })).toBeInTheDocument()
14
+ })
15
+ })
@@ -0,0 +1,100 @@
1
+ import { useState } from 'react'
2
+ import { PageHeader } from '../components/layout/PageHeader'
3
+ import { Card } from '../components/ui/Card'
4
+ import { Button } from '../components/ui/Button'
5
+ import { SearchField } from '../components/ui/SearchField'
6
+ import { ScreeningQuotaBanner } from '../components/screening/ScreeningQuotaBanner'
7
+ import { ScreenResults } from '../components/screening/ScreenResults'
8
+ import { LimitReachedBanner } from '../components/screening/LimitReachedBanner'
9
+ import { ScreeningProgress } from '../components/screening/ScreeningProgress'
10
+ import { api, ApiError } from '../api/client'
11
+ import { Badge } from '../components/ui/Badge'
12
+ import type { ScreenResponse } from '../types'
13
+
14
+ interface PEPResult {
15
+ results: Array<{ entity_id: string; position: string; country: string; tier: number }>
16
+ total: number
17
+ }
18
+
19
+ export function PEPScreening() {
20
+ const [query, setQuery] = useState('')
21
+ const [results, setResults] = useState<ScreenResponse | null>(null)
22
+ const [pepResults, setPepResults] = useState<PEPResult | null>(null)
23
+ const [loading, setLoading] = useState(false)
24
+ const [error, setError] = useState<Error | null>(null)
25
+ const isLimitError = error instanceof ApiError && error.status === 402
26
+
27
+ const handleScreen = async () => {
28
+ if (!query.trim()) return
29
+ setLoading(true)
30
+ setError(null)
31
+ setPepResults(null)
32
+ try {
33
+ const [screenData, pepData] = await Promise.allSettled([
34
+ api.post<ScreenResponse>('/screen', { entity_name: query.trim() }),
35
+ api.post<PEPResult>('/pep/screen', { name: query.trim() }),
36
+ ])
37
+ if (screenData.status === 'fulfilled') setResults(screenData.value)
38
+ if (pepData.status === 'fulfilled') setPepResults(pepData.value)
39
+ if (screenData.status === 'rejected') {
40
+ const e = screenData.reason instanceof Error ? screenData.reason : new Error(String(screenData.reason))
41
+ if (e instanceof ApiError && e.status === 402) setError(e)
42
+ }
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err : new Error(String(err)))
45
+ } finally {
46
+ setLoading(false)
47
+ }
48
+ }
49
+
50
+ const pepList = pepResults?.results ?? []
51
+
52
+ return (
53
+ <div>
54
+ <PageHeader
55
+ title="PEP & Sanctions Screening"
56
+ description="Screen against PEP databases and global sanctions lists simultaneously"
57
+ />
58
+ <div className="mb-lg"><ScreeningQuotaBanner /></div>
59
+ <Card className="mb-lg">
60
+ <SearchField
61
+ placeholder="Enter name (e.g. Benjamin Netanyahu, Vladimir Putin)"
62
+ value={query} onChange={setQuery} onSubmit={handleScreen}
63
+ />
64
+ <Button onClick={handleScreen} disabled={loading || !query.trim()} className="w-full mt-lg">
65
+ {loading ? 'Screening...' : 'Screen PEP & Sanctions'}
66
+ </Button>
67
+ </Card>
68
+
69
+ {error && isLimitError && <LimitReachedBanner error={error} />}
70
+ {error && !isLimitError && (
71
+ <Card className="mb-lg"><p role="alert" className="text-apple-red sf-body">{error.message}</p></Card>
72
+ )}
73
+
74
+ {loading && (
75
+ <div className="mt-xxl">
76
+ <ScreeningProgress query={query} />
77
+ </div>
78
+ )}
79
+
80
+ {!loading && pepList.length > 0 && (
81
+ <Card className="mb-lg">
82
+ <h3 className="sf-headline mb-md">PEP Results — {pepList.length} profiles</h3>
83
+ <div className="space-y-sm">
84
+ {pepList.map((p, i) => (
85
+ <div key={i} className="flex items-center justify-between p-md bg-white/[0.03] rounded-apple-md">
86
+ <div>
87
+ <p className="sf-body font-medium">{p.entity_id}</p>
88
+ <p className="sf-caption text-apple-label-tertiary">{p.position} — {p.country}</p>
89
+ </div>
90
+ <Badge color={p.tier <= 2 ? 'red' : 'orange'} size="sm">Tier {p.tier}</Badge>
91
+ </div>
92
+ ))}
93
+ </div>
94
+ </Card>
95
+ )}
96
+
97
+ {!loading && results && <ScreenResults data={results} />}
98
+ </div>
99
+ )
100
+ }
@@ -0,0 +1,81 @@
1
+ import { useState } from 'react'
2
+ import { useSearchParams, Link } from 'react-router-dom'
3
+ import { ArrowLeft, CheckCircle } from 'lucide-react'
4
+ import { api } from '../api/client'
5
+
6
+ export function ResetPassword() {
7
+ const [params] = useSearchParams()
8
+ const token = params.get('token') || ''
9
+ const [password, setPassword] = useState('')
10
+ const [confirm, setConfirm] = useState('')
11
+ const [done, setDone] = useState(false)
12
+ const [loading, setLoading] = useState(false)
13
+ const [error, setError] = useState('')
14
+
15
+ const handleSubmit = async (e: React.FormEvent) => {
16
+ e.preventDefault()
17
+ if (password.length < 8) { setError('Password must be at least 8 characters'); return }
18
+ if (password !== confirm) { setError('Passwords do not match'); return }
19
+ setLoading(true); setError('')
20
+ try {
21
+ await api.post('/auth/reset-password', { token, password })
22
+ setDone(true)
23
+ } catch (err) {
24
+ setError(err instanceof Error ? err.message : 'Reset failed')
25
+ } finally {
26
+ setLoading(false)
27
+ }
28
+ }
29
+
30
+ if (!token) {
31
+ return (
32
+ <div className="min-h-screen bg-apple-bg-primary flex items-center justify-center px-4">
33
+ <div className="text-center">
34
+ <p className="text-apple-red sf-headline">Invalid reset link</p>
35
+ <Link to="/forgot-password" className="text-apple-blue text-sm mt-md inline-block">
36
+ Request a new reset link
37
+ </Link>
38
+ </div>
39
+ </div>
40
+ )
41
+ }
42
+
43
+ return (
44
+ <div className="min-h-screen bg-apple-bg-primary flex items-center justify-center px-4">
45
+ <div className="w-full max-w-sm">
46
+ <Link to="/login"
47
+ className="inline-flex items-center gap-1.5 text-sm text-apple-label-secondary hover:text-white mb-8">
48
+ <ArrowLeft size={16} /> Back to login
49
+ </Link>
50
+
51
+ {done ? (
52
+ <div className="rounded-apple-lg bg-green-500/10 border border-green-500/30 p-6 text-center">
53
+ <CheckCircle size={32} className="text-green-400 mx-auto mb-3" />
54
+ <p className="text-white font-medium mb-1">Password updated</p>
55
+ <Link to="/login" className="text-apple-blue text-sm">Sign in with your new password</Link>
56
+ </div>
57
+ ) : (
58
+ <>
59
+ <h1 className="text-2xl font-bold text-white mb-2">Set new password</h1>
60
+ <p className="text-sm text-apple-label-secondary mb-8">Enter your new password below.</p>
61
+ <form onSubmit={handleSubmit} className="space-y-4">
62
+ <input type="password" required value={password} minLength={8}
63
+ onChange={e => setPassword(e.target.value)}
64
+ placeholder="New password (8+ characters)" className="input-field w-full" autoFocus />
65
+ <input type="password" required value={confirm}
66
+ onChange={e => setConfirm(e.target.value)}
67
+ placeholder="Confirm password" className="input-field w-full" />
68
+ {error && <p role="alert" className="text-apple-red text-sm">{error}</p>}
69
+ <button type="submit" disabled={loading}
70
+ className="w-full py-2.5 rounded-apple-md bg-apple-blue text-white font-medium hover:bg-apple-blue/90 transition-colors cursor-pointer disabled:opacity-50">
71
+ {loading ? 'Updating...' : 'Update Password'}
72
+ </button>
73
+ </form>
74
+ </>
75
+ )}
76
+ </div>
77
+ </div>
78
+ )
79
+ }
80
+
81
+ export default ResetPassword
@@ -0,0 +1,15 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { RiskAssessment } from './RiskAssessment'
4
+
5
+ describe('RiskAssessment', () => {
6
+ it('renders page title', () => {
7
+ render(<RiskAssessment />)
8
+ expect(screen.getByText('Risk Assessment')).toBeInTheDocument()
9
+ })
10
+
11
+ it('renders calculate button', () => {
12
+ render(<RiskAssessment />)
13
+ expect(screen.getByText('Calculate Risk')).toBeInTheDocument()
14
+ })
15
+ })
@@ -0,0 +1,108 @@
1
+ import { useState } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { PageHeader } from '../components/layout/PageHeader'
4
+ import { Card } from '../components/ui/Card'
5
+ import { Button } from '../components/ui/Button'
6
+ import { LoadingSpinner } from '../components/ui/LoadingSpinner'
7
+ import { api } from '../api/client'
8
+
9
+ interface RiskResult {
10
+ composite_score: number
11
+ risk_level: string
12
+ factors: string[]
13
+ breakdown: Record<string, number>
14
+ }
15
+
16
+ export function RiskAssessment() {
17
+ const { t } = useTranslation('compliance')
18
+ const [entityId, setEntityId] = useState('')
19
+ const [country, setCountry] = useState('')
20
+ const [result, setResult] = useState<RiskResult | null>(null)
21
+ const [loading, setLoading] = useState(false)
22
+ const [error, setError] = useState('')
23
+
24
+ const calculate = async () => {
25
+ if (!entityId.trim()) return
26
+ setLoading(true)
27
+ setError('')
28
+ try {
29
+ const d = await api.post<RiskResult>('/risk/score', {
30
+ entity_id: entityId, country,
31
+ sanctions_score: 0, pep_score: 0,
32
+ adverse_media_score: 0, industry_score: 0,
33
+ })
34
+ setResult(d ?? null)
35
+ } catch (err) {
36
+ setError(err instanceof Error ? err.message : 'Calculation failed')
37
+ setResult(null)
38
+ } finally {
39
+ setLoading(false)
40
+ }
41
+ }
42
+
43
+ const levelColor: Record<string, string> = {
44
+ critical: 'text-apple-red', high: 'text-apple-orange',
45
+ medium: 'text-apple-yellow', low: 'text-apple-green',
46
+ }
47
+
48
+ return (
49
+ <div className="max-w-4xl mx-auto">
50
+ <PageHeader title={t('risk.title')} description="Calculate composite risk scores" />
51
+ <Card className="mb-lg">
52
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-lg mb-lg">
53
+ <input placeholder={t('risk.entity_placeholder')} value={entityId}
54
+ aria-label={t('risk.entity_placeholder')}
55
+ onChange={e => setEntityId(e.target.value)} className="input-field" />
56
+ <input placeholder={t('risk.country_placeholder')} value={country}
57
+ aria-label={t('risk.country_placeholder')}
58
+ onChange={e => setCountry(e.target.value)} className="input-field" />
59
+ </div>
60
+ <Button onClick={calculate} disabled={loading || !entityId.trim()} className="w-full">
61
+ {loading ? 'Calculating...' : t('risk.calculate')}
62
+ </Button>
63
+ </Card>
64
+
65
+ {loading && <LoadingSpinner />}
66
+ {error && (
67
+ <Card className="mb-lg">
68
+ <p role="alert" className="text-apple-red sf-body">{error}</p>
69
+ </Card>
70
+ )}
71
+ {result && <RiskResultCard result={result} levelColor={levelColor} t={t} />}
72
+ </div>
73
+ )
74
+ }
75
+
76
+ function RiskResultCard({ result, levelColor, t }: {
77
+ result: RiskResult; levelColor: Record<string, string>; t: (k: string) => string
78
+ }) {
79
+ return (
80
+ <Card>
81
+ <div className="flex items-baseline gap-md mb-lg">
82
+ <span className="text-4xl font-bold">{(result.composite_score * 100).toFixed(0)}</span>
83
+ <span className={`text-lg font-medium ${levelColor[result.risk_level]}`}>
84
+ {result.risk_level.toUpperCase()}
85
+ </span>
86
+ </div>
87
+ {(result.factors ?? []).length > 0 && (
88
+ <div className="mb-lg">
89
+ <p className="sf-caption text-white/60 mb-sm">{t('risk.risk_factors')}</p>
90
+ <div className="flex flex-wrap gap-sm">
91
+ {result.factors.map(f => (
92
+ <span key={f} className="px-2 py-0.5 bg-apple-red/20 text-apple-red text-xs rounded">{f}</span>
93
+ ))}
94
+ </div>
95
+ </div>
96
+ )}
97
+ <div>
98
+ <p className="sf-caption text-white/60 mb-sm">{t('risk.score_breakdown')}</p>
99
+ {Object.entries(result.breakdown).map(([k, v]) => (
100
+ <div key={k} className="flex justify-between text-sm py-sm border-b border-white/[0.06]">
101
+ <span className="capitalize">{k.replace('_', ' ')}</span>
102
+ <span>{(v * 100).toFixed(1)}%</span>
103
+ </div>
104
+ ))}
105
+ </div>
106
+ </Card>
107
+ )
108
+ }
@@ -0,0 +1,74 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import { PageHeader } from '../components/layout/PageHeader';
5
+ import { Button } from '../components/ui/Button';
6
+ import { RefreshCw, ShoppingBag } from 'lucide-react';
7
+ import { useLists } from '../hooks/useLists';
8
+ import { LoadingSpinner } from '../components/ui/LoadingSpinner';
9
+ import { ListCard } from '../components/lists/ListCard';
10
+ import { api } from '../api/client';
11
+
12
+ export function SanctionsLists() {
13
+ const { t } = useTranslation('lists');
14
+ const navigate = useNavigate();
15
+ const { lists, loading, error, refetch, triggerSync } = useLists();
16
+ const [reloading, setReloading] = useState(false);
17
+ const [reloadMsg, setReloadMsg] = useState('');
18
+
19
+ const reloadAll = useCallback(async () => {
20
+ setReloading(true);
21
+ setReloadMsg('');
22
+ try {
23
+ await api.post('/admin/lists/refresh', {});
24
+ setReloadMsg('Reload started. Lists will update in the background.');
25
+ setTimeout(() => refetch(), 3000);
26
+ } catch (err) {
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ setReloadMsg(`Error: ${msg}`);
29
+ } finally {
30
+ setReloading(false);
31
+ }
32
+ }, [refetch]);
33
+
34
+ if (loading) {
35
+ return (
36
+ <div className="flex items-center justify-center h-96">
37
+ <LoadingSpinner />
38
+ </div>
39
+ );
40
+ }
41
+
42
+ const totalEntries = lists.reduce((acc, l) => acc + l.entity_count, 0);
43
+
44
+ return (
45
+ <div>
46
+ <PageHeader
47
+ title={t('title')}
48
+ description={`${lists.length} lists • ${totalEntries.toLocaleString()} ${t('entities')}`}
49
+ />
50
+ <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-sm mb-md">
51
+ <Button variant="secondary" size="sm" onClick={() => navigate('/lists/marketplace')}
52
+ className="flex items-center justify-center gap-xs">
53
+ <ShoppingBag className="w-4 h-4" /> Browse Marketplace
54
+ </Button>
55
+ <Button variant="primary" size="sm" onClick={reloadAll}
56
+ disabled={reloading} className="flex items-center justify-center gap-xs">
57
+ <RefreshCw className={`w-4 h-4 ${reloading ? 'animate-spin' : ''}`} />
58
+ {reloading ? 'Reloading...' : 'Reload All Lists'}
59
+ </Button>
60
+ {reloadMsg && (
61
+ <span className={`sf-caption ${reloadMsg.startsWith('Error') ? 'text-apple-red' : 'text-apple-green'}`}>
62
+ {reloadMsg}
63
+ </span>
64
+ )}
65
+ </div>
66
+ {error && <p className="text-apple-red sf-caption mb-md" role="alert">{error.message}</p>}
67
+ <div className="space-y-md">
68
+ {lists.map((list) => (
69
+ <ListCard key={list.id} list={list} triggerSync={triggerSync} t={t} />
70
+ ))}
71
+ </div>
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,82 @@
1
+ import { render, screen, waitFor } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { describe, it, expect, vi } from 'vitest'
4
+ import { ScreenEntity } from './ScreenEntity'
5
+
6
+ const mockScreen = vi.fn()
7
+ const mockClear = vi.fn()
8
+
9
+ vi.mock('../hooks/useScreening', () => ({
10
+ useScreening: () => ({
11
+ result: null,
12
+ loading: false,
13
+ error: null,
14
+ screen: mockScreen,
15
+ clear: mockClear,
16
+ }),
17
+ }))
18
+
19
+ describe('ScreenEntity', () => {
20
+ it('renders page title', () => {
21
+ render(<ScreenEntity />)
22
+ expect(screen.getByRole('heading', { name: /Screen Entity/i })).toBeInTheDocument()
23
+ })
24
+
25
+ it('renders page description', () => {
26
+ render(<ScreenEntity />)
27
+ expect(screen.getByText('Perform manual entity screening')).toBeInTheDocument()
28
+ })
29
+
30
+ it('renders screening form', () => {
31
+ render(<ScreenEntity />)
32
+ expect(screen.getByPlaceholderText('First Name')).toBeInTheDocument()
33
+ })
34
+
35
+ it('renders screening layers list', () => {
36
+ render(<ScreenEntity />)
37
+ expect(screen.getByText('Screening Layers')).toBeInTheDocument()
38
+ })
39
+
40
+ it('does not show results section initially', () => {
41
+ render(<ScreenEntity />)
42
+ expect(screen.queryByText(/Screening Results/i)).not.toBeInTheDocument()
43
+ })
44
+
45
+ it('renders submit button', () => {
46
+ render(<ScreenEntity />)
47
+ const btn = screen.getByRole('button', { name: /screen entity/i })
48
+ expect(btn).toBeInTheDocument()
49
+ })
50
+
51
+ it('calls screen on form submit', async () => {
52
+ mockScreen.mockClear()
53
+ render(<ScreenEntity />)
54
+ await userEvent.type(screen.getByPlaceholderText('First Name'), 'John')
55
+ await userEvent.type(screen.getByPlaceholderText('Last Name'), 'Doe')
56
+ await userEvent.click(screen.getByRole('button', { name: /screen entity/i }))
57
+ await waitFor(() => {
58
+ expect(mockScreen).toHaveBeenCalledTimes(1)
59
+ })
60
+ })
61
+
62
+ it('passes correct payload for individual screening', async () => {
63
+ mockScreen.mockClear()
64
+ render(<ScreenEntity />)
65
+ await userEvent.type(screen.getByPlaceholderText('First Name'), 'Jane')
66
+ await userEvent.type(screen.getByPlaceholderText('Last Name'), 'Smith')
67
+ await userEvent.click(screen.getByRole('button', { name: /screen entity/i }))
68
+ await waitFor(() => {
69
+ expect(mockScreen).toHaveBeenCalledWith(
70
+ expect.objectContaining({ entity_name: 'Jane Smith', entity_type: 'individual' }),
71
+ )
72
+ })
73
+ })
74
+
75
+ it('allows switching between individual and company types', () => {
76
+ render(<ScreenEntity />)
77
+ const tabs = screen.getAllByRole('button').filter(
78
+ btn => btn.textContent === 'Individual' || btn.textContent === 'Company'
79
+ )
80
+ expect(tabs.length).toBe(2)
81
+ })
82
+ })
@@ -0,0 +1,76 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ScreeningForm } from '../components/screening/ScreeningForm';
5
+ import { ScreeningLayersList } from '../components/screening/ScreeningLayersList';
6
+ import { ScreenResults } from '../components/screening/ScreenResults';
7
+ import { ScreeningQuotaBanner } from '../components/screening/ScreeningQuotaBanner';
8
+ import { LimitReachedBanner } from '../components/screening/LimitReachedBanner';
9
+ import { ScreeningProgress } from '../components/screening/ScreeningProgress';
10
+ import { useScreening } from '../hooks/useScreening';
11
+ import { ApiError } from '../api/client';
12
+ import type { EntityType } from '../types';
13
+
14
+ interface FormData {
15
+ firstName: string; lastName: string; companyName: string; dob: string; nationality: string;
16
+ }
17
+
18
+ export function ScreenEntity() {
19
+ const { t } = useTranslation('screening');
20
+ const navigate = useNavigate();
21
+ const { result, loading, error, screen, clear } = useScreening();
22
+ const [lastQuery, setLastQuery] = useState('');
23
+ const isLimitError = error instanceof ApiError && error.status === 402;
24
+
25
+ const handleSubmit = async (data: FormData, type: EntityType) => {
26
+ const name = type === 'individual' ? `${data.firstName} ${data.lastName}`.trim() : data.companyName;
27
+ setLastQuery(name);
28
+ try {
29
+ await screen({
30
+ entity_name: name, entity_type: type,
31
+ dob: data.dob || undefined, nationality: data.nationality || undefined,
32
+ });
33
+ } catch { /* error captured in useScreening state */ }
34
+ };
35
+
36
+ return (
37
+ <div>
38
+ <div className="text-center mb-xxl">
39
+ <div className="relative inline-flex items-center justify-center w-16 h-16 rounded-full bg-apple-blue/10 mb-lg">
40
+ <div className="absolute inset-0 rounded-full bg-apple-blue/20 blur-xl" />
41
+ <img src="/logo.svg" alt="AMLIQ" className="relative w-10 h-10" />
42
+ </div>
43
+ <h1 className="text-3xl font-bold tracking-tight gradient-text mb-sm">{t('title')}</h1>
44
+ <p className="sf-caption max-w-md mx-auto">{t('description')}</p>
45
+ </div>
46
+
47
+ <div className="max-w-4xl mx-auto mb-lg">
48
+ <ScreeningQuotaBanner />
49
+ </div>
50
+
51
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-lg max-w-4xl mx-auto">
52
+ <div className="lg:col-span-2 glass-panel rounded-apple-lg p-lg glow-blue">
53
+ <ScreeningForm onSubmit={handleSubmit} loading={loading} />
54
+ </div>
55
+ <ScreeningLayersList />
56
+ </div>
57
+
58
+ {error && isLimitError && <LimitReachedBanner error={error} />}
59
+ {error && !isLimitError && (
60
+ <p className="text-apple-red sf-caption mt-md text-center" role="alert">{error.message}</p>
61
+ )}
62
+
63
+ {loading && (
64
+ <div className="mt-xxl">
65
+ <ScreeningProgress query={lastQuery} />
66
+ </div>
67
+ )}
68
+
69
+ {result && (
70
+ <div className="max-w-4xl mx-auto mt-xl">
71
+ <ScreenResults data={result} />
72
+ </div>
73
+ )}
74
+ </div>
75
+ );
76
+ }