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,103 @@
1
+ import { useState } from 'react'
2
+ import { Share2, MessageCircle, Linkedin, Mail, Copy, Check } from 'lucide-react'
3
+ import type { ScreenResponse } from '../../types'
4
+
5
+ interface Props {
6
+ data: ScreenResponse
7
+ }
8
+
9
+ function buildSummary(data: ScreenResponse): string {
10
+ const matches = data.matches ?? []
11
+ if (matches.length === 0) {
12
+ return `AMLIQ Screening Report\n\nEntity: ${data.query}\nResult: CLEAR - No sanctions matches found\nProcessing: ${data.processing_time_ms ?? 0}ms\n\nScreened by AMLIQ - AI-Enhanced Sanctions Screening\nhttps://amliq.finance`
13
+ }
14
+ const top = matches.slice(0, 3)
15
+ const lines = top.map(
16
+ (m) => `- ${m.entity_name} (${m.list_id}) — ${(m.confidence * 100).toFixed(0)}% confidence`,
17
+ )
18
+ return `AMLIQ Screening Report\n\nEntity: ${data.query}\nMatches: ${data.total_matches ?? matches.length}\n${lines.join('\n')}\nProcessing: ${data.processing_time_ms ?? 0}ms\n\nScreened by AMLIQ - AI-Enhanced Sanctions Screening\nhttps://amliq.finance`
19
+ }
20
+
21
+ function buildSubject(data: ScreenResponse): string {
22
+ const count = data.total_matches ?? data.matches?.length ?? 0
23
+ return count === 0
24
+ ? `AMLIQ: ${data.query} — CLEAR`
25
+ : `AMLIQ: ${data.query} — ${count} match${count > 1 ? 'es' : ''} found`
26
+ }
27
+
28
+ export function ShareResults({ data }: Props) {
29
+ const [copied, setCopied] = useState(false)
30
+ const [open, setOpen] = useState(false)
31
+
32
+ const summary = buildSummary(data)
33
+ const subject = buildSubject(data)
34
+ const encoded = encodeURIComponent(summary)
35
+ const encodedSubject = encodeURIComponent(subject)
36
+
37
+ const copyToClipboard = () => {
38
+ navigator.clipboard.writeText(summary)
39
+ setCopied(true)
40
+ setTimeout(() => setCopied(false), 2000)
41
+ }
42
+
43
+ const channels = [
44
+ {
45
+ name: 'WhatsApp',
46
+ icon: MessageCircle,
47
+ color: 'hover:bg-[#25D366]/20 hover:text-[#25D366]',
48
+ href: `https://wa.me/?text=${encoded}`,
49
+ },
50
+ {
51
+ name: 'LinkedIn',
52
+ icon: Linkedin,
53
+ color: 'hover:bg-[#0A66C2]/20 hover:text-[#0A66C2]',
54
+ href: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent('https://amliq.finance')}&summary=${encoded}`,
55
+ },
56
+ {
57
+ name: 'Email',
58
+ icon: Mail,
59
+ color: 'hover:bg-apple-blue/20 hover:text-apple-blue',
60
+ href: `mailto:?subject=${encodedSubject}&body=${encoded}`,
61
+ },
62
+ ]
63
+
64
+ return (
65
+ <div className="relative">
66
+ <button
67
+ onClick={() => setOpen(!open)}
68
+ className="flex items-center gap-xs px-md py-sm rounded-apple-md border border-white/10 hover:bg-white/5 text-apple-label-secondary hover:text-white transition-colors cursor-pointer text-sm"
69
+ >
70
+ <Share2 className="w-4 h-4" />
71
+ Share
72
+ </button>
73
+
74
+ {open && (
75
+ <div className="absolute right-0 top-full mt-sm z-50 w-56 rounded-apple-lg border border-white/10 bg-apple-bg-secondary shadow-xl p-sm">
76
+ <p className="text-xs text-apple-label-tertiary px-md py-xs mb-xs">
77
+ Share screening results
78
+ </p>
79
+ {channels.map((ch) => (
80
+ <a
81
+ key={ch.name}
82
+ href={ch.href}
83
+ target="_blank"
84
+ rel="noopener noreferrer"
85
+ onClick={() => setOpen(false)}
86
+ className={`flex items-center gap-md px-md py-sm rounded-apple-md text-sm text-apple-label-secondary transition-colors ${ch.color}`}
87
+ >
88
+ <ch.icon className="w-4 h-4" />
89
+ {ch.name}
90
+ </a>
91
+ ))}
92
+ <button
93
+ onClick={copyToClipboard}
94
+ className="flex items-center gap-md px-md py-sm rounded-apple-md text-sm text-apple-label-secondary hover:bg-white/10 hover:text-white transition-colors w-full cursor-pointer"
95
+ >
96
+ {copied ? <Check className="w-4 h-4 text-apple-green" /> : <Copy className="w-4 h-4" />}
97
+ {copied ? 'Copied!' : 'Copy to clipboard'}
98
+ </button>
99
+ </div>
100
+ )}
101
+ </div>
102
+ )
103
+ }
@@ -0,0 +1,23 @@
1
+ interface ThresholdSliderProps {
2
+ value: number
3
+ onChange: (v: number) => void
4
+ }
5
+
6
+ export function ThresholdSlider({ value, onChange }: ThresholdSliderProps) {
7
+ return (
8
+ <div className="mt-lg">
9
+ <label className="sf-caption text-white/60 flex justify-between">
10
+ <span>Confidence Threshold</span>
11
+ <span className="font-semibold text-white">{value}%</span>
12
+ </label>
13
+ <input
14
+ type="range"
15
+ min={0}
16
+ max={100}
17
+ value={value}
18
+ onChange={(e) => onChange(Number(e.target.value))}
19
+ className="w-full mt-sm accent-apple-blue"
20
+ />
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,63 @@
1
+ import { useNavigate } from 'react-router-dom'
2
+ import { Webhook, ArrowRight, Zap, Shield, Code } from 'lucide-react'
3
+
4
+ const STEPS = [
5
+ { icon: Code, title: 'Get your API key', desc: 'Generate a key from Settings > API Keys' },
6
+ { icon: Webhook, title: 'Configure webhook', desc: 'Point your payment system to our endpoint' },
7
+ { icon: Zap, title: 'Screen in real-time', desc: 'Every transaction is screened automatically' },
8
+ ] as const
9
+
10
+ export function WebhookCTA() {
11
+ const navigate = useNavigate()
12
+
13
+ return (
14
+ <div className="rounded-apple-lg border border-white/[0.08] bg-gradient-to-br from-apple-blue/[0.06] to-transparent p-xl text-center">
15
+ <div className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-apple-blue/10 mb-lg">
16
+ <Shield className="w-7 h-7 text-apple-blue" />
17
+ </div>
18
+ <h3 className="text-lg font-semibold text-white mb-sm">
19
+ Connect your payment system
20
+ </h3>
21
+ <p className="sf-caption max-w-md mx-auto mb-xl">
22
+ Attach a webhook to screen every transaction in real-time against
23
+ global sanctions lists. Get alerts within milliseconds.
24
+ </p>
25
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-lg mb-xl max-w-2xl mx-auto">
26
+ {STEPS.map((step, i) => (
27
+ <div key={i} className="flex flex-col items-center gap-sm">
28
+ <div className="flex items-center justify-center w-10 h-10 rounded-apple-md bg-white/[0.06]">
29
+ <step.icon className="w-5 h-5 text-apple-blue" />
30
+ </div>
31
+ <p className="text-sm font-medium text-white">{step.title}</p>
32
+ <p className="text-xs text-apple-label-secondary">{step.desc}</p>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ <div className="flex flex-col sm:flex-row items-center justify-center gap-md">
37
+ <button
38
+ onClick={() => navigate('/webhooks')}
39
+ className="flex items-center gap-sm px-xl py-md bg-apple-blue hover:bg-apple-blue/80
40
+ text-white text-sm font-semibold rounded-apple-md transition-colors cursor-pointer"
41
+ >
42
+ <Webhook className="w-4 h-4" />
43
+ Set Up Webhook
44
+ <ArrowRight className="w-4 h-4" />
45
+ </button>
46
+ <button
47
+ onClick={() => navigate('/api-keys')}
48
+ className="flex items-center gap-sm px-xl py-md border border-white/10
49
+ hover:bg-white/5 text-white text-sm font-medium rounded-apple-md
50
+ transition-colors cursor-pointer"
51
+ >
52
+ <Code className="w-4 h-4" />
53
+ Get API Key
54
+ </button>
55
+ </div>
56
+ <div className="mt-lg">
57
+ <code className="text-xs text-apple-label-tertiary bg-white/[0.04] px-md py-sm rounded-apple">
58
+ POST https://api.amliq.finance/api/v1/transactions/screen
59
+ </code>
60
+ </div>
61
+ </div>
62
+ )
63
+ }
@@ -0,0 +1,47 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Avatar } from './Avatar'
4
+
5
+ describe('Avatar', () => {
6
+ it('renders initials from name', () => {
7
+ render(<Avatar name="John Doe" />)
8
+ expect(screen.getByText('JD')).toBeInTheDocument()
9
+ })
10
+
11
+ it('handles single name', () => {
12
+ render(<Avatar name="Prince" />)
13
+ expect(screen.getByText('P')).toBeInTheDocument()
14
+ })
15
+
16
+ it('handles multiple names and takes first two initials', () => {
17
+ render(<Avatar name="John Michael Doe" />)
18
+ expect(screen.getByText('JM')).toBeInTheDocument()
19
+ })
20
+
21
+ it('converts initials to uppercase', () => {
22
+ render(<Avatar name="alice bob" />)
23
+ expect(screen.getByText('AB')).toBeInTheDocument()
24
+ })
25
+
26
+ it('renders different sizes', () => {
27
+ const { rerender } = render(<Avatar name="Test User" size="sm" />)
28
+ expect(screen.getByText('TU')).toHaveClass('w-6', 'h-6', 'text-xs')
29
+
30
+ rerender(<Avatar name="Test User" size="md" />)
31
+ expect(screen.getByText('TU')).toHaveClass('w-8', 'h-8', 'text-sm')
32
+
33
+ rerender(<Avatar name="Test User" size="lg" />)
34
+ expect(screen.getByText('TU')).toHaveClass('w-10', 'h-10', 'text-base')
35
+ })
36
+
37
+ it('applies custom className', () => {
38
+ render(<Avatar name="Test User" className="custom-class" />)
39
+ expect(screen.getByText('TU')).toHaveClass('custom-class')
40
+ })
41
+
42
+ it('has base styling', () => {
43
+ render(<Avatar name="Test User" />)
44
+ const element = screen.getByText('TU')
45
+ expect(element).toHaveClass('rounded-full', 'bg-apple-blue', 'flex', 'items-center', 'justify-center', 'font-semibold', 'text-white')
46
+ })
47
+ })
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+
4
+ interface AvatarProps {
5
+ name: string;
6
+ size?: 'sm' | 'md' | 'lg';
7
+ className?: string;
8
+ }
9
+
10
+ export function Avatar({ name, size = 'md', className }: AvatarProps) {
11
+ const initials = name
12
+ .split(' ')
13
+ .map((n) => n[0])
14
+ .join('')
15
+ .toUpperCase()
16
+ .slice(0, 2);
17
+
18
+ const sizes = {
19
+ sm: 'w-6 h-6 text-xs',
20
+ md: 'w-8 h-8 text-sm',
21
+ lg: 'w-10 h-10 text-base',
22
+ };
23
+
24
+ return (
25
+ <div
26
+ className={clsx(
27
+ 'rounded-full bg-apple-blue flex items-center justify-center font-semibold text-white',
28
+ sizes[size],
29
+ className
30
+ )}
31
+ >
32
+ {initials}
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,49 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Badge } from './Badge'
4
+
5
+ describe('Badge', () => {
6
+ it('renders badge with text', () => {
7
+ render(<Badge>Test Badge</Badge>)
8
+ expect(screen.getByText('Test Badge')).toBeInTheDocument()
9
+ })
10
+
11
+ it('renders different color variants', () => {
12
+ const { rerender } = render(<Badge color="green">Green</Badge>)
13
+ expect(screen.getByText('Green')).toHaveClass('bg-apple-green/20', 'text-apple-green')
14
+
15
+ rerender(<Badge color="red">Red</Badge>)
16
+ expect(screen.getByText('Red')).toHaveClass('bg-apple-red/20', 'text-apple-red')
17
+
18
+ rerender(<Badge color="orange">Orange</Badge>)
19
+ expect(screen.getByText('Orange')).toHaveClass('bg-apple-orange/20', 'text-apple-orange')
20
+
21
+ rerender(<Badge color="blue">Blue</Badge>)
22
+ expect(screen.getByText('Blue')).toHaveClass('bg-apple-blue/20', 'text-apple-blue')
23
+
24
+ rerender(<Badge color="purple">Purple</Badge>)
25
+ expect(screen.getByText('Purple')).toHaveClass('bg-purple-500/20', 'text-purple-400')
26
+
27
+ rerender(<Badge color="gray">Gray</Badge>)
28
+ expect(screen.getByText('Gray')).toHaveClass('bg-white/10', 'text-white')
29
+ })
30
+
31
+ it('renders different sizes', () => {
32
+ const { rerender } = render(<Badge size="sm">Small</Badge>)
33
+ expect(screen.getByText('Small')).toHaveClass('px-md', 'py-xs', 'text-sf-caption')
34
+
35
+ rerender(<Badge size="md">Medium</Badge>)
36
+ expect(screen.getByText('Medium')).toHaveClass('px-lg', 'py-sm', 'text-sf-body')
37
+ })
38
+
39
+ it('applies default color and size', () => {
40
+ render(<Badge>Default</Badge>)
41
+ expect(screen.getByText('Default')).toHaveClass('bg-apple-blue/20', 'text-apple-blue')
42
+ expect(screen.getByText('Default')).toHaveClass('px-lg', 'py-sm')
43
+ })
44
+
45
+ it('has rounded-full class', () => {
46
+ render(<Badge>Pill</Badge>)
47
+ expect(screen.getByText('Pill')).toHaveClass('rounded-full')
48
+ })
49
+ })
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+
4
+ interface BadgeProps {
5
+ children: React.ReactNode;
6
+ color?: 'green' | 'red' | 'orange' | 'blue' | 'purple' | 'gray';
7
+ size?: 'sm' | 'md';
8
+ }
9
+
10
+ const colors = {
11
+ green: 'bg-apple-green/15 text-apple-green shadow-[0_0_12px_rgba(48,209,88,0.1)]',
12
+ red: 'bg-apple-red/15 text-apple-red shadow-[0_0_12px_rgba(255,69,58,0.1)]',
13
+ orange: 'bg-apple-orange/15 text-apple-orange shadow-[0_0_12px_rgba(255,159,10,0.1)]',
14
+ blue: 'bg-apple-blue/15 text-apple-blue shadow-[0_0_12px_rgba(10,132,255,0.1)]',
15
+ purple: 'bg-purple-500/15 text-purple-400 shadow-[0_0_12px_rgba(168,85,247,0.1)]',
16
+ gray: 'bg-white/[0.06] text-white/80',
17
+ };
18
+
19
+ const sizes = {
20
+ sm: 'px-md py-xs text-sf-caption font-semibold',
21
+ md: 'px-lg py-sm text-sf-body font-semibold',
22
+ };
23
+
24
+ export function Badge({ children, color = 'blue', size = 'md' }: BadgeProps) {
25
+ return (
26
+ <span className={clsx(
27
+ 'rounded-full inline-block border border-white/[0.04]',
28
+ colors[color], sizes[size],
29
+ )}>
30
+ {children}
31
+ </span>
32
+ );
33
+ }
@@ -0,0 +1,56 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { describe, it, expect, vi } from 'vitest'
4
+ import { Button } from './Button'
5
+
6
+ describe('Button', () => {
7
+ it('renders button with text', () => {
8
+ render(<Button>Click me</Button>)
9
+ expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument()
10
+ })
11
+
12
+ it('renders different variants', () => {
13
+ const { rerender } = render(<Button variant="primary">Primary</Button>)
14
+ expect(screen.getByRole('button')).toHaveClass('bg-apple-blue')
15
+
16
+ rerender(<Button variant="secondary">Secondary</Button>)
17
+ expect(screen.getByRole('button')).toHaveClass('bg-apple-bg-tertiary')
18
+
19
+ rerender(<Button variant="destructive">Destructive</Button>)
20
+ expect(screen.getByRole('button')).toHaveClass('bg-apple-red')
21
+ })
22
+
23
+ it('renders different sizes', () => {
24
+ const { rerender } = render(<Button size="sm">Small</Button>)
25
+ expect(screen.getByRole('button')).toHaveClass('px-md', 'py-sm')
26
+
27
+ rerender(<Button size="md">Medium</Button>)
28
+ expect(screen.getByRole('button')).toHaveClass('px-lg', 'py-md')
29
+
30
+ rerender(<Button size="lg">Large</Button>)
31
+ expect(screen.getByRole('button')).toHaveClass('px-xl', 'py-lg')
32
+ })
33
+
34
+ it('handles click events', async () => {
35
+ const handler = vi.fn()
36
+ render(<Button onClick={handler}>Click</Button>)
37
+ await userEvent.click(screen.getByRole('button'))
38
+ expect(handler).toHaveBeenCalledOnce()
39
+ })
40
+
41
+ it('disables button when disabled prop is true', () => {
42
+ render(<Button disabled>Disabled</Button>)
43
+ expect(screen.getByRole('button')).toBeDisabled()
44
+ expect(screen.getByRole('button')).toHaveClass('disabled:opacity-50')
45
+ })
46
+
47
+ it('respects type prop', () => {
48
+ render(<Button type="submit">Submit</Button>)
49
+ expect(screen.getByRole('button')).toHaveAttribute('type', 'submit')
50
+ })
51
+
52
+ it('applies custom className', () => {
53
+ render(<Button className="custom-class">Button</Button>)
54
+ expect(screen.getByRole('button')).toHaveClass('custom-class')
55
+ })
56
+ })
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+
4
+ interface ButtonProps {
5
+ children: React.ReactNode;
6
+ variant?: 'primary' | 'secondary' | 'destructive';
7
+ size?: 'sm' | 'md' | 'lg';
8
+ disabled?: boolean;
9
+ onClick?: () => void;
10
+ className?: string;
11
+ type?: 'button' | 'submit';
12
+ }
13
+
14
+ const baseClasses = [
15
+ 'font-semibold rounded-apple-md cursor-pointer transition-all duration-200',
16
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
17
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-apple-blue',
18
+ ].join(' ');
19
+
20
+ const variants = {
21
+ primary: 'bg-gradient-to-br from-[#0A84FF] to-[#0066CC] text-white hover:shadow-[0_0_20px_rgba(10,132,255,0.3)]',
22
+ secondary: 'bg-apple-bg-tertiary text-white border border-white/[0.08] hover:bg-white/[0.06] hover:border-white/[0.12]',
23
+ destructive: 'bg-gradient-to-br from-[#FF453A] to-[#CC362E] text-white hover:shadow-[0_0_20px_rgba(255,69,58,0.3)]',
24
+ };
25
+
26
+ const sizes = {
27
+ sm: 'px-md py-sm text-sf-caption',
28
+ md: 'px-lg py-md text-sf-body',
29
+ lg: 'px-xl py-lg text-sf-headline',
30
+ };
31
+
32
+ export function Button({
33
+ children, variant = 'primary', size = 'md',
34
+ disabled, onClick, className, type = 'button',
35
+ }: ButtonProps) {
36
+ return (
37
+ <button
38
+ type={type}
39
+ disabled={disabled}
40
+ onClick={onClick}
41
+ className={clsx(baseClasses, variants[variant], sizes[size], className)}
42
+ >
43
+ {children}
44
+ </button>
45
+ );
46
+ }
@@ -0,0 +1,61 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { describe, it, expect, vi } from 'vitest'
4
+ import { Card } from './Card'
5
+
6
+ describe('Card', () => {
7
+ it('renders children content', () => {
8
+ render(
9
+ <Card>
10
+ <p>Card content</p>
11
+ </Card>
12
+ )
13
+ expect(screen.getByText('Card content')).toBeInTheDocument()
14
+ })
15
+
16
+ it('applies base styling classes', () => {
17
+ const { container } = render(<Card>Test</Card>)
18
+ const card = container.querySelector('div[class*="bg-apple-bg-secondary"]')
19
+ expect(card?.className).toContain('bg-apple-bg-secondary')
20
+ expect(card?.className).toContain('rounded-apple-lg')
21
+ })
22
+
23
+ it('handles click events when onClick provided', async () => {
24
+ const handler = vi.fn()
25
+ render(<Card onClick={handler}>Clickable</Card>)
26
+ await userEvent.click(screen.getByText('Clickable'))
27
+ expect(handler).toHaveBeenCalledOnce()
28
+ })
29
+
30
+ it('applies hover styles when hover prop is true', () => {
31
+ const { container } = render(<Card hover>Hoverable</Card>)
32
+ const card = container.querySelector('div[class*="cursor-pointer"]')
33
+ expect(card?.className).toContain('cursor-pointer')
34
+ })
35
+
36
+ it('does not apply hover styles when hover prop is false', () => {
37
+ render(<Card hover={false}>Not Hoverable</Card>)
38
+ const card = screen.getByText('Not Hoverable').parentElement
39
+ expect(card).not.toHaveClass('hover:bg-apple-bg-secondary')
40
+ })
41
+
42
+ it('applies custom className', () => {
43
+ const { container } = render(<Card className="custom-class">Test</Card>)
44
+ const card = container.querySelector('div[class*="custom-class"]')
45
+ expect(card?.className).toContain('custom-class')
46
+ })
47
+
48
+ it('combines all props correctly', async () => {
49
+ const handler = vi.fn()
50
+ const { container } = render(
51
+ <Card hover onClick={handler} className="extra">
52
+ Combined
53
+ </Card>
54
+ )
55
+ const card = container.querySelector('button[class*="extra"]')
56
+ expect(card?.className).toContain('extra')
57
+ expect(card?.className).toContain('cursor-pointer')
58
+ await userEvent.click(card!)
59
+ expect(handler).toHaveBeenCalledOnce()
60
+ })
61
+ })
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+
4
+ interface CardProps {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ onClick?: () => void;
8
+ hover?: boolean;
9
+ }
10
+
11
+ export function Card({ children, className, onClick, hover }: CardProps) {
12
+ const classes = clsx(
13
+ 'rounded-[16px] border border-white/[0.06] p-lg transition-all duration-200',
14
+ 'bg-[rgba(18,18,26,0.8)] backdrop-blur-[24px] backdrop-saturate-[180%]',
15
+ 'shadow-[0_0_0_1px_rgba(255,255,255,0.03),0_4px_24px_rgba(0,0,0,0.4)]',
16
+ hover && 'cursor-pointer hover:border-white/[0.12] hover:shadow-[0_0_0_1px_rgba(10,132,255,0.1),0_8px_32px_rgba(0,0,0,0.5)]',
17
+ className
18
+ );
19
+
20
+ if (onClick) {
21
+ return (
22
+ <button type="button" onClick={onClick} className={clsx(classes, 'text-left w-full')}>
23
+ {children}
24
+ </button>
25
+ );
26
+ }
27
+
28
+ return <div className={classes}>{children}</div>;
29
+ }
@@ -0,0 +1,67 @@
1
+ import React from 'react'
2
+ import clsx from 'clsx'
3
+
4
+ interface ConfirmModalProps {
5
+ open: boolean
6
+ title: string
7
+ message: string
8
+ confirmLabel?: string
9
+ cancelLabel?: string
10
+ variant?: 'destructive' | 'primary'
11
+ onConfirm: () => void
12
+ onCancel: () => void
13
+ }
14
+
15
+ export function ConfirmModal({
16
+ open, title, message,
17
+ confirmLabel = 'Confirm', cancelLabel = 'Cancel',
18
+ variant = 'primary', onConfirm, onCancel,
19
+ }: ConfirmModalProps) {
20
+ if (!open) return null
21
+
22
+ const accentBar = variant === 'destructive'
23
+ ? 'bg-gradient-to-r from-red-500 to-rose-500'
24
+ : 'bg-gradient-to-r from-[#0A84FF] to-[#6366F1]'
25
+
26
+ const confirmBtn = variant === 'destructive'
27
+ ? 'bg-gradient-to-br from-red-500 to-rose-600 text-white hover:shadow-[0_0_20px_rgba(239,68,68,0.3)]'
28
+ : 'bg-gradient-to-br from-[#0A84FF] to-[#6366F1] text-white hover:shadow-[0_0_20px_rgba(10,132,255,0.3)]'
29
+
30
+ return (
31
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4"
32
+ onClick={onCancel}>
33
+ <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
34
+ <div className="relative w-full max-w-md rounded-2xl overflow-hidden
35
+ bg-[rgba(18,18,26,0.95)] backdrop-blur-xl border border-white/[0.08]
36
+ shadow-[0_20px_60px_rgba(0,0,0,0.5)]"
37
+ onClick={e => e.stopPropagation()}>
38
+ {/* accent bar */}
39
+ <div className={clsx('h-1', accentBar)} />
40
+
41
+ {/* body */}
42
+ <div className="p-6">
43
+ <h3 className="text-lg font-bold text-white mb-2">{title}</h3>
44
+ <p className="text-sm text-apple-label-secondary leading-relaxed">{message}</p>
45
+ </div>
46
+
47
+ {/* footer */}
48
+ <div className="flex gap-3 p-4 border-t border-white/[0.06] bg-white/[0.02]">
49
+ <button type="button" onClick={onCancel}
50
+ className="flex-1 py-2.5 rounded-xl text-sm font-semibold
51
+ text-white/70 border border-white/[0.08] hover:bg-white/[0.04]
52
+ transition-all cursor-pointer active:scale-[0.97]">
53
+ {cancelLabel}
54
+ </button>
55
+ <button type="button" onClick={onConfirm}
56
+ className={clsx(
57
+ 'flex-1 py-2.5 rounded-xl text-sm font-semibold',
58
+ 'transition-all cursor-pointer active:scale-[0.97]',
59
+ confirmBtn,
60
+ )}>
61
+ {confirmLabel}
62
+ </button>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1,24 @@
1
+ import { render } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Divider } from './Divider'
4
+
5
+ describe('Divider', () => {
6
+ it('renders divider element', () => {
7
+ const { container } = render(<Divider />)
8
+ const divider = container.querySelector('div[class*="h-px"]')
9
+ expect(divider).toBeInTheDocument()
10
+ })
11
+
12
+ it('has correct styling', () => {
13
+ const { container } = render(<Divider />)
14
+ const divider = container.querySelector('div[class*="h-px"]')
15
+ expect(divider).toHaveClass('h-px', 'bg-apple-separator', 'my-lg')
16
+ })
17
+
18
+ it('creates a visual separator', () => {
19
+ const { container } = render(<Divider />)
20
+ const divider = container.querySelector('div[class*="h-px"]')
21
+ expect(divider?.className).toContain('h-px')
22
+ expect(divider?.className).toContain('bg-apple-separator')
23
+ })
24
+ })
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+
3
+ export function Divider() {
4
+ return <div className="h-px bg-apple-separator my-lg" />;
5
+ }