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,45 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { Zap, LayoutDashboard, Code, Code2, FileText } from 'lucide-react'
4
+ import clsx from 'clsx'
5
+ import { ProductType } from '../../types/billing'
6
+
7
+ interface ProductTabsProps {
8
+ activeProduct: ProductType
9
+ onChange: (product: ProductType) => void
10
+ }
11
+
12
+ const productIcons: Record<ProductType, React.ReactNode> = {
13
+ api: <Zap className="w-4 h-4" />,
14
+ dashboard: <LayoutDashboard className="w-4 h-4" />,
15
+ sdk: <Code className="w-4 h-4" />,
16
+ iframe: <Code2 className="w-4 h-4" />,
17
+ dataset: <FileText className="w-4 h-4" />,
18
+ }
19
+
20
+ const productIds: ProductType[] = ['api', 'dashboard', 'sdk', 'iframe', 'dataset']
21
+
22
+ export function ProductTabs({ activeProduct, onChange }: ProductTabsProps) {
23
+ const { t } = useTranslation('marketing')
24
+
25
+ return (
26
+ <div className="flex overflow-x-auto gap-xs px-lg py-md bg-apple-bg/50 rounded-apple-lg border border-white/10">
27
+ {productIds.map(id => (
28
+ <button
29
+ type="button"
30
+ key={id}
31
+ onClick={() => onChange(id)}
32
+ className={clsx(
33
+ 'flex items-center gap-xs px-md py-sm rounded-apple-md whitespace-nowrap transition-all sf-body',
34
+ activeProduct === id
35
+ ? 'bg-apple-blue text-white'
36
+ : 'text-apple-label-secondary hover:bg-white/5'
37
+ )}
38
+ >
39
+ {productIcons[id]}
40
+ <span className="hidden sm:inline">{t(`products.${id}`)}</span>
41
+ </button>
42
+ ))}
43
+ </div>
44
+ )
45
+ }
@@ -0,0 +1,56 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { motion, AnimatePresence } from 'framer-motion'
3
+ import { FadeUp } from './animations'
4
+
5
+ const quotes = [
6
+ "Other platforms give you 200 alerts. AMLIQ gives you 3. And they actually matter.",
7
+ "Less noise. More signal. Finally.",
8
+ "The real luxury isn't automation — it's confidence.",
9
+ "False positives aren't just annoying — they're expensive.",
10
+ "Fewer alerts. Better decisions.",
11
+ "Everyone's building dashboards. AMLIQ is building decisions.",
12
+ ]
13
+
14
+ export default function QuoteRotator() {
15
+ const [idx, setIdx] = useState(0)
16
+
17
+ useEffect(() => {
18
+ const timer = setInterval(() => setIdx(i => (i + 1) % quotes.length), 5000)
19
+ return () => clearInterval(timer)
20
+ }, [])
21
+
22
+ return (
23
+ <section className="py-24 px-4">
24
+ <div className="max-w-3xl mx-auto text-center">
25
+ <FadeUp>
26
+ <p className="text-xs uppercase tracking-[0.2em] text-[#94A3B8] font-semibold mb-10">
27
+ What compliance teams are saying
28
+ </p>
29
+ <div className="h-[120px] sm:h-[100px] flex items-center justify-center">
30
+ <AnimatePresence mode="wait">
31
+ <motion.blockquote key={idx}
32
+ className="text-2xl sm:text-3xl font-semibold text-[#0F172A] leading-snug"
33
+ style={{ letterSpacing: '-0.01em' }}
34
+ initial={{ opacity: 0, y: 16, filter: 'blur(4px)' }}
35
+ animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
36
+ exit={{ opacity: 0, y: -16, filter: 'blur(4px)' }}
37
+ transition={{ duration: 0.5 }}>
38
+ &ldquo;{quotes[idx]}&rdquo;
39
+ </motion.blockquote>
40
+ </AnimatePresence>
41
+ </div>
42
+ <div className="flex justify-center gap-2 mt-10">
43
+ {quotes.map((_, i) => (
44
+ <motion.button key={i} type="button" onClick={() => setIdx(i)}
45
+ className="w-2 h-2 rounded-full cursor-pointer"
46
+ style={{ background: i === idx ? '#00B894' : '#CBD5E1' }}
47
+ whileHover={{ scale: 1.5 }}
48
+ animate={{ scale: i === idx ? 1.3 : 1 }}
49
+ transition={{ type: 'spring', stiffness: 300 }} />
50
+ ))}
51
+ </div>
52
+ </FadeUp>
53
+ </div>
54
+ </section>
55
+ )
56
+ }
@@ -0,0 +1,81 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { motion } from 'framer-motion'
3
+ import { FadeUp } from './animations'
4
+
5
+ const stats = [
6
+ { value: 3000000, suffix: '+', label: 'Entities in Database', format: true },
7
+ { value: 6, suffix: '', label: 'AI Matching Layers', format: false },
8
+ { value: 1, suffix: 'ms', label: 'Screening Latency', format: false, prefix: '<' },
9
+ { value: 86, suffix: '', label: 'Sanctions Lists', format: false },
10
+ ]
11
+
12
+ function AnimatedNumber({ value, suffix, prefix, format }: {
13
+ value: number; suffix: string; prefix?: string; format: boolean
14
+ }) {
15
+ const [current, setCurrent] = useState(0)
16
+ const ref = useRef<HTMLDivElement>(null)
17
+ const started = useRef(false)
18
+
19
+ useEffect(() => {
20
+ const el = ref.current
21
+ if (!el) return
22
+ const obs = new IntersectionObserver(([entry]) => {
23
+ if (entry.isIntersecting && !started.current) {
24
+ started.current = true
25
+ const steps = 50
26
+ let step = 0
27
+ const timer = setInterval(() => {
28
+ step++
29
+ const progress = step / steps
30
+ const eased = 1 - Math.pow(1 - progress, 3)
31
+ setCurrent(value * eased)
32
+ if (step >= steps) clearInterval(timer)
33
+ }, 25)
34
+ }
35
+ }, { threshold: 0.5 })
36
+ obs.observe(el)
37
+ return () => obs.disconnect()
38
+ }, [value])
39
+
40
+ const display = format
41
+ ? Math.floor(current).toLocaleString()
42
+ : Math.floor(current).toString()
43
+
44
+ return (
45
+ <div ref={ref} className="text-center px-6">
46
+ <motion.p
47
+ className="text-4xl sm:text-5xl font-extrabold tracking-tight mb-2"
48
+ style={{
49
+ background: 'linear-gradient(135deg, #0F172A 0%, #334155 100%)',
50
+ WebkitBackgroundClip: 'text',
51
+ WebkitTextFillColor: 'transparent',
52
+ }}>
53
+ {prefix}{display}{suffix}
54
+ </motion.p>
55
+ <div className="h-0.5 w-10 mx-auto rounded-full bg-gradient-to-r from-[#00B894] to-[#6366F1] mb-2" />
56
+ </div>
57
+ )
58
+ }
59
+
60
+ export default function StatsBar() {
61
+ return (
62
+ <section className="py-20 px-4" style={{
63
+ background: 'linear-gradient(180deg, #F8FAFC 0%, #FFFFFF 100%)',
64
+ borderTop: '1px solid rgba(0,0,0,0.04)',
65
+ borderBottom: '1px solid rgba(0,0,0,0.04)',
66
+ }}>
67
+ <div className="max-w-5xl mx-auto">
68
+ <FadeUp>
69
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-10 md:gap-0 md:divide-x md:divide-[#E2E8F0]">
70
+ {stats.map(s => (
71
+ <div key={s.label} className="text-center px-6">
72
+ <AnimatedNumber value={s.value} suffix={s.suffix} prefix={s.prefix} format={s.format} />
73
+ <p className="text-sm text-[#94A3B8] font-medium mt-1">{s.label}</p>
74
+ </div>
75
+ ))}
76
+ </div>
77
+ </FadeUp>
78
+ </div>
79
+ </section>
80
+ )
81
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { Layers, Globe, Zap, ShieldCheck } from 'lucide-react'
4
+
5
+ const statIcons = [
6
+ <Layers size={20} />,
7
+ <Globe size={20} />,
8
+ <Zap size={20} />,
9
+ <ShieldCheck size={20} />,
10
+ ]
11
+
12
+ export default function StatsSection() {
13
+ const { t } = useTranslation('marketing')
14
+
15
+ const stats = [
16
+ { label: t('stats.screened_daily'), value: t('stats.screened_value') },
17
+ { label: t('stats.uptime'), value: t('stats.uptime_value') },
18
+ { label: t('stats.fewer_fp'), value: t('stats.fewer_fp_value') },
19
+ { label: t('stats.latency'), value: t('stats.latency_value') },
20
+ ]
21
+
22
+ return (
23
+ <section className="py-20 px-4">
24
+ <div className="max-w-6xl mx-auto">
25
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
26
+ {stats.map((stat, i) => (
27
+ <div
28
+ key={i}
29
+ className="card-vibrancy p-lg text-center group hover:border-apple-blue/40 transition-all cursor-default"
30
+ >
31
+ <div className="flex justify-center mb-md text-apple-blue">
32
+ {statIcons[i]}
33
+ </div>
34
+ <p className="text-3xl sm:text-4xl font-bold text-white mb-xs tracking-tight">
35
+ {stat.value}
36
+ </p>
37
+ <p className="sf-caption text-apple-label-secondary">{stat.label}</p>
38
+ </div>
39
+ ))}
40
+ </div>
41
+ </div>
42
+ </section>
43
+ )
44
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { Quote } from 'lucide-react'
3
+
4
+ interface TestimonialCardProps {
5
+ quote: string
6
+ author: string
7
+ title: string
8
+ company: string
9
+ }
10
+
11
+ function Initials({ name }: { name: string }) {
12
+ const initials = name.split(' ').map(n => n[0]).join('').slice(0, 2)
13
+ return (
14
+ <div className="w-10 h-10 rounded-full bg-gradient-to-br from-apple-blue to-apple-green flex items-center justify-center text-white text-sm font-bold shrink-0">
15
+ {initials}
16
+ </div>
17
+ )
18
+ }
19
+
20
+ export default function TestimonialCard({ quote, author, title, company }: TestimonialCardProps) {
21
+ return (
22
+ <div className="card-vibrancy p-lg h-full flex flex-col">
23
+ <Quote size={20} className="text-apple-blue/40 mb-md shrink-0" />
24
+
25
+ <p className="sf-body text-apple-label-secondary leading-relaxed flex-1 mb-lg">
26
+ {quote}
27
+ </p>
28
+
29
+ <div className="flex items-center gap-md">
30
+ <Initials name={author} />
31
+ <div>
32
+ <p className="sf-headline text-white">{author}</p>
33
+ <p className="sf-caption text-apple-label-tertiary">{title}, {company}</p>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import TestimonialCard from './TestimonialCard'
4
+
5
+ export default function TestimonialsSection() {
6
+ const { t } = useTranslation('marketing')
7
+ const items = t('testimonials.items', { returnObjects: true }) as Array<{
8
+ quote: string; author: string; title: string; company: string
9
+ }>
10
+
11
+ return (
12
+ <section className="py-20 px-4">
13
+ <div className="max-w-6xl mx-auto">
14
+ <h2 className="text-sf-title font-bold text-white text-center mb-12">
15
+ {t('testimonials.heading')}
16
+ </h2>
17
+ <div className="flex md:grid md:grid-cols-3 gap-6 overflow-x-auto snap-x snap-mandatory pb-4 -mx-4 px-4 md:mx-0 md:px-0 md:overflow-visible">
18
+ {items.map((item, i) => (
19
+ <div key={i} className="min-w-[300px] md:min-w-0 snap-center">
20
+ <TestimonialCard
21
+ quote={item.quote}
22
+ author={item.author}
23
+ title={item.title}
24
+ company={item.company}
25
+ />
26
+ </div>
27
+ ))}
28
+ </div>
29
+ </div>
30
+ </section>
31
+ )
32
+ }
@@ -0,0 +1,56 @@
1
+ import { useRef } from 'react'
2
+ import { motion, useScroll, useTransform, useInView } from 'framer-motion'
3
+ import type { ReactNode } from 'react'
4
+
5
+ export function FadeUp({ children, delay = 0 }: { children: ReactNode; delay?: number }) {
6
+ const ref = useRef(null)
7
+ const isInView = useInView(ref, { once: true, margin: '-60px' })
8
+ return (
9
+ <motion.div ref={ref}
10
+ initial={{ opacity: 0, y: 32 }}
11
+ animate={isInView ? { opacity: 1, y: 0 } : {}}
12
+ transition={{ duration: 0.7, delay, ease: [0.25, 0.4, 0, 1] }}>
13
+ {children}
14
+ </motion.div>
15
+ )
16
+ }
17
+
18
+ export function ScaleIn({ children, delay = 0 }: { children: ReactNode; delay?: number }) {
19
+ const ref = useRef(null)
20
+ const isInView = useInView(ref, { once: true, margin: '-40px' })
21
+ return (
22
+ <motion.div ref={ref}
23
+ initial={{ opacity: 0, scale: 0.92 }}
24
+ animate={isInView ? { opacity: 1, scale: 1 } : {}}
25
+ transition={{ duration: 0.6, delay, ease: [0.25, 0.4, 0, 1] }}>
26
+ {children}
27
+ </motion.div>
28
+ )
29
+ }
30
+
31
+ export function SlideIn({ children, from = 'left', delay = 0 }: {
32
+ children: ReactNode; from?: 'left' | 'right'; delay?: number
33
+ }) {
34
+ const ref = useRef(null)
35
+ const isInView = useInView(ref, { once: true, margin: '-40px' })
36
+ const x = from === 'left' ? -48 : 48
37
+ return (
38
+ <motion.div ref={ref}
39
+ initial={{ opacity: 0, x }}
40
+ animate={isInView ? { opacity: 1, x: 0 } : {}}
41
+ transition={{ duration: 0.7, delay, ease: [0.25, 0.4, 0, 1] }}>
42
+ {children}
43
+ </motion.div>
44
+ )
45
+ }
46
+
47
+ export function Parallax({ children, speed = 0.3 }: { children: ReactNode; speed?: number }) {
48
+ const ref = useRef(null)
49
+ const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] })
50
+ const y = useTransform(scrollYProgress, [0, 1], [0, -80 * speed])
51
+ return (
52
+ <motion.div ref={ref} style={{ y }}>
53
+ {children}
54
+ </motion.div>
55
+ )
56
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ interface Country { code: string; label: string }
5
+ interface Props {
6
+ countries: Country[]
7
+ value: string
8
+ onChange: (code: string) => void
9
+ }
10
+
11
+ export function CountryStep({ countries, value, onChange }: Props) {
12
+ const { t } = useTranslation('onboarding')
13
+
14
+ return (
15
+ <div className="rounded-apple-lg border border-white/10 bg-white/5 p-lg space-y-md">
16
+ <h2 className="sf-body font-medium text-white">{t('country.title')}</h2>
17
+ <p className="sf-caption text-apple-label-secondary">
18
+ {t('country.description')}
19
+ </p>
20
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-sm">
21
+ {countries.map(c => (
22
+ <button
23
+ key={c.code}
24
+ onClick={() => onChange(c.code)}
25
+ className={`p-md rounded-apple-md border text-left transition-colors ${
26
+ value === c.code
27
+ ? 'border-apple-blue bg-apple-blue/10 text-white'
28
+ : 'border-white/10 bg-white/5 text-apple-label-secondary hover:border-white/20'
29
+ }`}
30
+ >
31
+ <span className="sf-body font-medium">{c.label}</span>
32
+ <span className="sf-caption block mt-xs">{c.code}</span>
33
+ </button>
34
+ ))}
35
+ </div>
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { CheckCircle2 } from 'lucide-react'
4
+
5
+ interface ListSuggestion { list_id: string; threshold: number; sync_enabled: boolean }
6
+ interface Props { lists: ListSuggestion[]; onNext: () => void }
7
+
8
+ export function ListsStep({ lists, onNext }: Props) {
9
+ const { t } = useTranslation('onboarding')
10
+
11
+ return (
12
+ <div className="rounded-apple-lg border border-white/10 bg-white/5 p-lg space-y-md">
13
+ <h2 className="sf-body font-medium text-white">{t('lists.title')}</h2>
14
+ <p className="sf-caption text-apple-label-secondary">
15
+ {t('lists.description')}
16
+ </p>
17
+ <div className="space-y-sm">
18
+ {lists.map(l => (
19
+ <div key={l.list_id} className="flex items-center gap-sm p-md rounded-apple-md border border-white/10 bg-white/5">
20
+ <CheckCircle2 className="w-5 h-5 text-green-400 shrink-0" />
21
+ <div className="flex-1">
22
+ <span className="sf-body text-white uppercase">{l.list_id}</span>
23
+ <span className="sf-caption text-apple-label-secondary block">
24
+ {t('lists.threshold')} {(l.threshold * 100).toFixed(0)}%
25
+ </span>
26
+ </div>
27
+ </div>
28
+ ))}
29
+ </div>
30
+ {lists.length === 0 && (
31
+ <p className="sf-caption text-apple-label-secondary text-center py-md">
32
+ {t('lists.loading')}
33
+ </p>
34
+ )}
35
+ <button
36
+ onClick={onNext}
37
+ disabled={lists.length === 0}
38
+ className="w-full py-md bg-apple-blue hover:bg-apple-blue/80 text-white rounded-apple-md sf-body font-medium transition-colors disabled:opacity-50"
39
+ >
40
+ {t('continue')}
41
+ </button>
42
+ </div>
43
+ )
44
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ interface Props { current: number }
5
+
6
+ const STEP_KEYS = ['country', 'lists', 'threshold'] as const
7
+
8
+ export function StepIndicator({ current }: Props) {
9
+ const { t } = useTranslation('onboarding')
10
+
11
+ return (
12
+ <div className="flex items-center justify-center gap-sm">
13
+ {STEP_KEYS.map((key, i) => {
14
+ const stepNum = i + 1
15
+ const active = stepNum <= current
16
+ return (
17
+ <div key={key} className="flex items-center gap-xs">
18
+ <div className={`w-8 h-8 rounded-full flex items-center justify-center sf-caption font-medium ${
19
+ active ? 'bg-apple-blue text-white' : 'bg-white/10 text-apple-label-secondary'
20
+ }`}>
21
+ {stepNum}
22
+ </div>
23
+ <span className={`sf-caption ${active ? 'text-white' : 'text-apple-label-secondary'}`}>
24
+ {t(`steps.${key}`)}
25
+ </span>
26
+ {i < STEP_KEYS.length - 1 && (
27
+ <div className={`w-12 h-px ${active ? 'bg-apple-blue' : 'bg-white/10'}`} />
28
+ )}
29
+ </div>
30
+ )
31
+ })}
32
+ </div>
33
+ )
34
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ interface Props {
5
+ value: number
6
+ onChange: (v: number) => void
7
+ onFinish: () => void
8
+ saving: boolean
9
+ }
10
+
11
+ export function ThresholdStep({ value, onChange, onFinish, saving }: Props) {
12
+ const { t } = useTranslation('onboarding')
13
+
14
+ return (
15
+ <div className="rounded-apple-lg border border-white/10 bg-white/5 p-lg space-y-md">
16
+ <h2 className="sf-body font-medium text-white">{t('threshold.title')}</h2>
17
+ <p className="sf-caption text-apple-label-secondary">
18
+ {t('threshold.description')}
19
+ </p>
20
+ <div className="space-y-sm">
21
+ <div className="flex justify-between sf-caption text-apple-label-secondary">
22
+ <span>{t('threshold.low')}</span>
23
+ <span>{t('threshold.current')} {value}%</span>
24
+ <span>{t('threshold.high')}</span>
25
+ </div>
26
+ <input
27
+ type="range"
28
+ min={50} max={95} step={5} value={value}
29
+ aria-valuetext={`${value}%`}
30
+ aria-label={t('threshold.title')}
31
+ onChange={e => onChange(Number(e.target.value))}
32
+ className="w-full accent-apple-blue"
33
+ />
34
+ <p className="sf-caption text-apple-label-secondary text-center">
35
+ {value < 65 ? t('threshold.hint_more') :
36
+ value > 85 ? t('threshold.hint_fewer') :
37
+ t('threshold.hint_balanced')}
38
+ </p>
39
+ </div>
40
+ <button
41
+ onClick={onFinish}
42
+ disabled={saving}
43
+ className="w-full py-md bg-apple-blue hover:bg-apple-blue/80 text-white rounded-apple-md sf-body font-medium transition-colors disabled:opacity-50"
44
+ >
45
+ {saving ? t('threshold.starting') : t('threshold.start')}
46
+ </button>
47
+ </div>
48
+ )
49
+ }
@@ -0,0 +1,102 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { Key, Trash2 } from 'lucide-react'
4
+ import { PageHeader } from '../../components/layout/PageHeader'
5
+ import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
6
+ import { Badge } from '../../components/ui/Badge'
7
+ import { api } from '../../api/client'
8
+
9
+ interface APIKey {
10
+ id: string
11
+ tenant_id: string
12
+ product: string
13
+ key_prefix: string
14
+ rate_limit: number
15
+ created_at: string
16
+ }
17
+
18
+ export default function PlatformAPIKeys() {
19
+ const { t } = useTranslation('platform')
20
+ const [keys, setKeys] = useState<APIKey[]>([])
21
+ const [loading, setLoading] = useState(true)
22
+
23
+ useEffect(() => {
24
+ api.get<{ api_keys: APIKey[] }>('/platform/keys')
25
+ .then(d => setKeys(d?.api_keys ?? []))
26
+ .catch(() => setKeys([]))
27
+ .finally(() => setLoading(false))
28
+ }, [])
29
+
30
+ const handleRevoke = async (keyId: string) => {
31
+ await api.put(`/platform/keys/${keyId}/revoke`, {})
32
+ setKeys(prev => prev.filter(k => k.id !== keyId))
33
+ }
34
+
35
+ if (loading) return <div className="flex items-center justify-center h-96"><LoadingSpinner /></div>
36
+
37
+ return (
38
+ <div>
39
+ <PageHeader title={t('api_keys.title')} description={`${keys.length} API keys`} />
40
+
41
+ {/* Desktop table */}
42
+ <div className="hidden md:block glass-panel rounded-apple-lg overflow-hidden">
43
+ <table className="w-full text-sm">
44
+ <thead>
45
+ <tr className="text-left text-apple-label-secondary border-b border-white/10">
46
+ <th className="px-lg py-md">{t('api_keys.prefix')}</th>
47
+ <th className="px-lg py-md">{t('api_keys.tenant')}</th>
48
+ <th className="px-lg py-md">{t('api_keys.product')}</th>
49
+ <th className="px-lg py-md">{t('api_keys.rate_limit')}</th>
50
+ <th className="px-lg py-md">{t('api_keys.actions')}</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ {keys.map(k => (
55
+ <tr key={k.id} className="border-b border-white/5 hover:bg-white/5">
56
+ <td className="px-lg py-md font-mono text-xs">{k.key_prefix}...</td>
57
+ <td className="px-lg py-md font-mono text-xs">{k.tenant_id}</td>
58
+ <td className="px-lg py-md"><Badge size="sm" color="blue">{k.product}</Badge></td>
59
+ <td className="px-lg py-md">{k.rate_limit || t('api_keys.unlimited')}</td>
60
+ <td className="px-lg py-md">
61
+ <button onClick={() => handleRevoke(k.id)}
62
+ className="text-apple-red text-xs hover:underline cursor-pointer">
63
+ {t('api_keys.revoke')}
64
+ </button>
65
+ </td>
66
+ </tr>
67
+ ))}
68
+ </tbody>
69
+ </table>
70
+ </div>
71
+
72
+ {/* Mobile cards */}
73
+ <div className="md:hidden space-y-sm">
74
+ {keys.map(k => (
75
+ <div key={k.id} className="glass-panel rounded-apple-lg p-lg">
76
+ <div className="flex items-start justify-between mb-sm">
77
+ <div className="flex items-center gap-sm">
78
+ <Key className="w-4 h-4 text-apple-blue flex-shrink-0" />
79
+ <code className="text-xs font-mono text-white">{k.key_prefix}...</code>
80
+ </div>
81
+ <button onClick={() => handleRevoke(k.id)}
82
+ className="p-xs text-apple-red hover:bg-apple-red/10 rounded-apple-md cursor-pointer">
83
+ <Trash2 className="w-4 h-4" />
84
+ </button>
85
+ </div>
86
+ <div className="flex flex-wrap gap-sm">
87
+ <Badge size="sm" color="blue">{k.product}</Badge>
88
+ <span className="sf-caption text-apple-label-secondary">
89
+ {k.rate_limit ? `${k.rate_limit} req/min` : t('api_keys.unlimited')}
90
+ </span>
91
+ </div>
92
+ <p className="sf-caption text-apple-label-tertiary mt-xs truncate">{k.tenant_id}</p>
93
+ </div>
94
+ ))}
95
+ </div>
96
+
97
+ {keys.length === 0 && (
98
+ <p className="text-center py-xl text-apple-label-tertiary sf-body">No API keys found</p>
99
+ )}
100
+ </div>
101
+ )
102
+ }