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.
- package/.env.example +3 -0
- package/.env.production +1 -0
- package/Dockerfile +26 -0
- package/QUICK_START.md +267 -0
- package/README.md +161 -0
- package/TESTING_GUIDE.md +322 -0
- package/dist/.well-known/ai-plugin.json +15 -0
- package/dist/_headers +8 -0
- package/dist/_redirects +1 -0
- package/dist/assets/APIKeys-BvwFauIs.js +6 -0
- package/dist/assets/APIKeys-DaMSBZQL.js +1 -0
- package/dist/assets/AdverseMedia-DckXfMzS.js +4 -0
- package/dist/assets/AlertDetailPage-Bc2_zwu_.js +1 -0
- package/dist/assets/AlertQueue-DFtBiRoM.js +8 -0
- package/dist/assets/Analytics-DtruB5lQ.js +1 -0
- package/dist/assets/AuditTrail-BK68ePu0.js +1 -0
- package/dist/assets/AuthDivider-gRHEt7gx.js +1 -0
- package/dist/assets/Badge-B9VFkofy.js +1 -0
- package/dist/assets/BatchJobs-Bz6KFnXD.js +6 -0
- package/dist/assets/BillingPage-R9nQ5nFb.js +11 -0
- package/dist/assets/Card-Cw-T_-oU.js +1 -0
- package/dist/assets/CaseDetail-DYs7Zg_y.js +7 -0
- package/dist/assets/CaseManagement-B1Q4Dp1Y.js +1 -0
- package/dist/assets/ComplianceReport-Dhssqc9T.js +1 -0
- package/dist/assets/Configuration-B92kl9T0.js +1 -0
- package/dist/assets/CryptoScreening-C7yFlskC.js +1 -0
- package/dist/assets/Dashboard-w4-Nhv9q.js +17 -0
- package/dist/assets/DataSources-u3-Ri_5_.js +3 -0
- package/dist/assets/EDDWorkflow-CFzlEO0e.js +1 -0
- package/dist/assets/EmptyState-Do0xJAT9.js +6 -0
- package/dist/assets/ForgotPassword-BPYyQFQp.js +1 -0
- package/dist/assets/LandingPage-CjaP0zw4.js +41 -0
- package/dist/assets/ListsMarketplace-CG6KkT9B.js +6 -0
- package/dist/assets/Login-677LEGP1.js +1 -0
- package/dist/assets/MFASetup-BxqCPvui.js +1 -0
- package/dist/assets/Monitoring-9TN9MK4y.js +1 -0
- package/dist/assets/Onboarding-Cf0Y83lX.js +1 -0
- package/dist/assets/Operations-CcXwc2xw.js +10 -0
- package/dist/assets/Overview-B_Ky0VWV.js +1 -0
- package/dist/assets/PEPScreening-pUQjTH9c.js +1 -0
- package/dist/assets/PageHeader-BObHFn0i.js +1 -0
- package/dist/assets/PrivacyPolicy-DZ2xrKir.js +1 -0
- package/dist/assets/ResetPassword-AtxtpIG1.js +1 -0
- package/dist/assets/RiskAssessment-Dzuh1Usv.js +1 -0
- package/dist/assets/SARForm-D8w2ZGe-.js +1 -0
- package/dist/assets/SanctionsLists-CPA3UH2v.js +1 -0
- package/dist/assets/ScheduledTasks-Dsjk-6UR.js +1 -0
- package/dist/assets/ScreenEntity-D1U0YL7v.js +3 -0
- package/dist/assets/ScreeningProgress-BW49PzoB.js +66 -0
- package/dist/assets/SearchField-CulFKdiI.js +1 -0
- package/dist/assets/Signup-C0FmFOo_.js +1 -0
- package/dist/assets/SystemHealth-B_8u4eva.js +1 -0
- package/dist/assets/TaskHistory-BCmEM89q.js +3 -0
- package/dist/assets/Team-CUo_FlU7.js +1 -0
- package/dist/assets/TenantDetail-CgZQHUY8.js +1 -0
- package/dist/assets/Tenants-RB9E9ick.js +2 -0
- package/dist/assets/TermsOfService-BfL-kndX.js +1 -0
- package/dist/assets/TransactionMonitoring-DGh_T22s.js +14 -0
- package/dist/assets/TxnScreening-B7cm2-eh.js +1 -0
- package/dist/assets/UBOChain-falXAsCo.js +1 -0
- package/dist/assets/Users-Doakpg9c.js +1 -0
- package/dist/assets/Webhooks-BZpAfaxv.js +1 -0
- package/dist/assets/alert-triangle-YCFVm7B_.js +6 -0
- package/dist/assets/alerts-COUnTwWY.js +1 -0
- package/dist/assets/arrow-left-9ApLqP95.js +6 -0
- package/dist/assets/check-VTcvVtIU.js +6 -0
- package/dist/assets/check-circle-2-Dn-lG5YQ.js +6 -0
- package/dist/assets/code-DIJRWFVv.js +6 -0
- package/dist/assets/fingerprint-C0MGVtax.js +6 -0
- package/dist/assets/flag-B8_Gwxh6.js +6 -0
- package/dist/assets/index-CfdYcqRM.js +289 -0
- package/dist/assets/index-HPU_Rhdu.css +1 -0
- package/dist/assets/layers-zHz2o4vo.js +6 -0
- package/dist/assets/loader-B9_dNihg.js +6 -0
- package/dist/assets/mail-BijL2qwp.js +6 -0
- package/dist/assets/plus-i0oBIIPS.js +6 -0
- package/dist/assets/refresh-cw-CSuAwutt.js +6 -0
- package/dist/assets/useAnalytics-Bj8IONcw.js +72 -0
- package/dist/assets/x-circle-DLGKvijE.js +6 -0
- package/dist/favicon.svg +15 -0
- package/dist/index.html +51 -0
- package/dist/llms.txt +28 -0
- package/dist/logo.png +0 -0
- package/dist/logo.svg +17 -0
- package/dist/manifest.json +44 -0
- package/dist/robots.txt +15 -0
- package/dist/schema.json +13 -0
- package/dist/sitemap.xml +28 -0
- package/dist/sw.js +67 -0
- package/e2e/auth-setup.ts +20 -0
- package/e2e/billing.spec.ts +50 -0
- package/e2e/cases.spec.ts +53 -0
- package/e2e/compliance.spec.ts +65 -0
- package/e2e/config.spec.ts +56 -0
- package/e2e/dashboard.spec.ts +47 -0
- package/e2e/fixtures.ts +33 -0
- package/e2e/lists.spec.ts +43 -0
- package/e2e/login.spec.ts +64 -0
- package/e2e/media.spec.ts +48 -0
- package/e2e/mocks.ts +51 -0
- package/e2e/monitoring.spec.ts +44 -0
- package/e2e/navigation.spec.ts +56 -0
- package/e2e/onboarding.spec.ts +49 -0
- package/e2e/responsive.spec.ts +40 -0
- package/e2e/risk.spec.ts +61 -0
- package/e2e/screening.spec.ts +76 -0
- package/e2e/team.spec.ts +53 -0
- package/index.html +50 -0
- package/package.json +47 -0
- package/playwright.config.ts +35 -0
- package/postcss.config.js +6 -0
- package/public/.well-known/ai-plugin.json +15 -0
- package/public/_headers +8 -0
- package/public/_redirects +1 -0
- package/public/favicon.svg +15 -0
- package/public/llms.txt +28 -0
- package/public/logo.png +0 -0
- package/public/logo.svg +17 -0
- package/public/manifest.json +44 -0
- package/public/robots.txt +15 -0
- package/public/schema.json +13 -0
- package/public/sitemap.xml +28 -0
- package/public/sw.js +67 -0
- package/scripts/test-runner.sh +152 -0
- package/src/App.tsx +48 -0
- package/src/api/alerts.ts +19 -0
- package/src/api/analytics.ts +6 -0
- package/src/api/audit.ts +11 -0
- package/src/api/auth.ts +26 -0
- package/src/api/billing.ts +54 -0
- package/src/api/cases.ts +48 -0
- package/src/api/client.ts +50 -0
- package/src/api/config.ts +30 -0
- package/src/api/edd.ts +25 -0
- package/src/api/enforcement.ts +33 -0
- package/src/api/lists.ts +24 -0
- package/src/api/monitoring.ts +82 -0
- package/src/api/pep.ts +30 -0
- package/src/api/risk.ts +26 -0
- package/src/api/screening.ts +37 -0
- package/src/api/team.ts +27 -0
- package/src/api/transactions.ts +37 -0
- package/src/components/admin/TenantCards.tsx +51 -0
- package/src/components/alerts/AlertActions.test.tsx +80 -0
- package/src/components/alerts/AlertActions.tsx +68 -0
- package/src/components/alerts/AlertCard.test.tsx +86 -0
- package/src/components/alerts/AlertCard.tsx +60 -0
- package/src/components/alerts/AlertDetailSidebar.tsx +63 -0
- package/src/components/alerts/AlertFilters.test.tsx +73 -0
- package/src/components/alerts/AlertFilters.tsx +88 -0
- package/src/components/alerts/EntityDetailsCard.test.tsx +61 -0
- package/src/components/alerts/EntityDetailsCard.tsx +37 -0
- package/src/components/alerts/NotesCard.test.tsx +39 -0
- package/src/components/alerts/NotesCard.tsx +28 -0
- package/src/components/auth/AuthDivider.tsx +18 -0
- package/src/components/auth/LoginForm.tsx +62 -0
- package/src/components/auth/LoginLeftPanel.tsx +43 -0
- package/src/components/auth/PasswordStrength.tsx +26 -0
- package/src/components/auth/SignInButtons.tsx +46 -0
- package/src/components/auth/SignupLeftPanel.tsx +35 -0
- package/src/components/auth/countries.ts +17 -0
- package/src/components/charts/AreaChart.tsx +45 -0
- package/src/components/charts/BarChart.tsx +48 -0
- package/src/components/charts/DonutChart.tsx +47 -0
- package/src/components/compliance/CaseActions.tsx +74 -0
- package/src/components/compliance/CaseTimeline.tsx +68 -0
- package/src/components/compliance/MediaResultCard.tsx +87 -0
- package/src/components/compliance/PEPResultCard.tsx +78 -0
- package/src/components/config/MatchingModesCard.test.tsx +75 -0
- package/src/components/config/MatchingModesCard.tsx +40 -0
- package/src/components/config/ScreeningLayersCard.test.tsx +57 -0
- package/src/components/config/ScreeningLayersCard.tsx +36 -0
- package/src/components/config/ThresholdsCard.test.tsx +79 -0
- package/src/components/config/ThresholdsCard.tsx +51 -0
- package/src/components/dashboard/ActivityFeed.tsx +93 -0
- package/src/components/dashboard/DashboardGreeting.tsx +23 -0
- package/src/components/dashboard/DashboardSkeleton.tsx +35 -0
- package/src/components/dashboard/QuickActions.tsx +52 -0
- package/src/components/dashboard/TopEntitiesTable.tsx +63 -0
- package/src/components/data/ComplianceMetrics.test.tsx +32 -0
- package/src/components/data/ComplianceMetrics.tsx +40 -0
- package/src/components/data/ConfidenceScore.test.tsx +62 -0
- package/src/components/data/ConfidenceScore.tsx +27 -0
- package/src/components/data/StatCard.test.tsx +72 -0
- package/src/components/data/StatCard.tsx +63 -0
- package/src/components/data/StatusBadge.test.tsx +63 -0
- package/src/components/data/StatusBadge.tsx +39 -0
- package/src/components/layout/AppShell.tsx +48 -0
- package/src/components/layout/Breadcrumbs.tsx +48 -0
- package/src/components/layout/CommandPalette.tsx +81 -0
- package/src/components/layout/DashboardLayout.tsx +19 -0
- package/src/components/layout/MobileHeader.tsx +30 -0
- package/src/components/layout/NavGroup.test.tsx +63 -0
- package/src/components/layout/NavGroup.tsx +64 -0
- package/src/components/layout/NotificationBell.tsx +89 -0
- package/src/components/layout/PageHeader.test.tsx +61 -0
- package/src/components/layout/PageHeader.tsx +19 -0
- package/src/components/layout/ProtectedRoute.tsx +31 -0
- package/src/components/layout/PublicLayout.tsx +17 -0
- package/src/components/layout/Sidebar.test.tsx +67 -0
- package/src/components/layout/Sidebar.tsx +92 -0
- package/src/components/layout/Toolbar.test.tsx +47 -0
- package/src/components/layout/Toolbar.tsx +68 -0
- package/src/components/layout/navItems.ts +94 -0
- package/src/components/lists/ListCard.tsx +78 -0
- package/src/components/lists/ListMarketplaceCard.tsx +77 -0
- package/src/components/screening/CircularConfidence.tsx +38 -0
- package/src/components/screening/LimitReachedBanner.tsx +31 -0
- package/src/components/screening/ListSelector.tsx +64 -0
- package/src/components/screening/MatchDetailHeader.tsx +64 -0
- package/src/components/screening/MatchEntityInfo.tsx +56 -0
- package/src/components/screening/MatchEvidenceBars.tsx +65 -0
- package/src/components/screening/MatchMetadata.tsx +95 -0
- package/src/components/screening/ScreenResults.tsx +49 -0
- package/src/components/screening/ScreeningForm.test.tsx +83 -0
- package/src/components/screening/ScreeningForm.tsx +100 -0
- package/src/components/screening/ScreeningLayersList.test.tsx +33 -0
- package/src/components/screening/ScreeningLayersList.tsx +46 -0
- package/src/components/screening/ScreeningProgress.tsx +91 -0
- package/src/components/screening/ScreeningQuotaBanner.tsx +64 -0
- package/src/components/screening/ScreeningResultCard.tsx +47 -0
- package/src/components/screening/ScreeningResultRow.tsx +45 -0
- package/src/components/screening/ScreeningResults.test.tsx +37 -0
- package/src/components/screening/ScreeningResults.tsx +33 -0
- package/src/components/screening/ShareResults.tsx +103 -0
- package/src/components/screening/ThresholdSlider.tsx +23 -0
- package/src/components/transactions/WebhookCTA.tsx +63 -0
- package/src/components/ui/Avatar.test.tsx +47 -0
- package/src/components/ui/Avatar.tsx +35 -0
- package/src/components/ui/Badge.test.tsx +49 -0
- package/src/components/ui/Badge.tsx +33 -0
- package/src/components/ui/Button.test.tsx +56 -0
- package/src/components/ui/Button.tsx +46 -0
- package/src/components/ui/Card.test.tsx +61 -0
- package/src/components/ui/Card.tsx +29 -0
- package/src/components/ui/ConfirmModal.tsx +67 -0
- package/src/components/ui/Divider.test.tsx +24 -0
- package/src/components/ui/Divider.tsx +5 -0
- package/src/components/ui/EmptyState.test.tsx +49 -0
- package/src/components/ui/EmptyState.tsx +22 -0
- package/src/components/ui/ErrorBoundary.tsx +44 -0
- package/src/components/ui/ExportMenu.tsx +71 -0
- package/src/components/ui/LanguageSwitcher.tsx +37 -0
- package/src/components/ui/LoadingSpinner.test.tsx +41 -0
- package/src/components/ui/LoadingSpinner.tsx +21 -0
- package/src/components/ui/MetricCard.tsx +63 -0
- package/src/components/ui/ScoreRing.tsx +51 -0
- package/src/components/ui/SearchField.test.tsx +55 -0
- package/src/components/ui/SearchField.tsx +31 -0
- package/src/components/ui/SeverityBadge.tsx +57 -0
- package/src/components/ui/ThemeToggle.tsx +37 -0
- package/src/components/ui/Toast.test.tsx +79 -0
- package/src/components/ui/Toast.tsx +75 -0
- package/src/components/ui/Toggle.test.tsx +85 -0
- package/src/components/ui/Toggle.tsx +46 -0
- package/src/context/AuthContext.tsx +71 -0
- package/src/data/pepProfiles.ts +76 -0
- package/src/data/pepProfilesExtra.ts +58 -0
- package/src/hooks/useAlerts.ts +36 -0
- package/src/hooks/useAnalytics.ts +23 -0
- package/src/hooks/useApi.test.ts +79 -0
- package/src/hooks/useApi.ts +33 -0
- package/src/hooks/useAudit.ts +28 -0
- package/src/hooks/useBilling.ts +38 -0
- package/src/hooks/useConfig.ts +35 -0
- package/src/hooks/useDebounce.test.ts +84 -0
- package/src/hooks/useDebounce.ts +15 -0
- package/src/hooks/useDirection.ts +15 -0
- package/src/hooks/useLists.ts +34 -0
- package/src/hooks/useMediaQuery.test.ts +97 -0
- package/src/hooks/useMediaQuery.ts +28 -0
- package/src/hooks/useScreening.ts +33 -0
- package/src/hooks/useSidebar.ts +18 -0
- package/src/hooks/useUsage.ts +27 -0
- package/src/i18n/config.ts +33 -0
- package/src/i18n/locales/ar/admin.json +19 -0
- package/src/i18n/locales/ar/alerts.json +52 -0
- package/src/i18n/locales/ar/analytics.json +9 -0
- package/src/i18n/locales/ar/audit.json +12 -0
- package/src/i18n/locales/ar/auth.json +60 -0
- package/src/i18n/locales/ar/batch.json +5 -0
- package/src/i18n/locales/ar/billing.json +41 -0
- package/src/i18n/locales/ar/common.json +65 -0
- package/src/i18n/locales/ar/compliance.json +83 -0
- package/src/i18n/locales/ar/config.json +13 -0
- package/src/i18n/locales/ar/dashboard.json +19 -0
- package/src/i18n/locales/ar/errors.json +9 -0
- package/src/i18n/locales/ar/index.ts +29 -0
- package/src/i18n/locales/ar/legal.json +10 -0
- package/src/i18n/locales/ar/lists.json +6 -0
- package/src/i18n/locales/ar/marketing.json +110 -0
- package/src/i18n/locales/ar/monitoring.json +15 -0
- package/src/i18n/locales/ar/nav.json +35 -0
- package/src/i18n/locales/ar/onboarding.json +25 -0
- package/src/i18n/locales/ar/platform.json +23 -0
- package/src/i18n/locales/ar/screening.json +26 -0
- package/src/i18n/locales/ar/team.json +11 -0
- package/src/i18n/locales/en/admin.json +21 -0
- package/src/i18n/locales/en/alerts.json +52 -0
- package/src/i18n/locales/en/analytics.json +9 -0
- package/src/i18n/locales/en/audit.json +12 -0
- package/src/i18n/locales/en/auth.json +60 -0
- package/src/i18n/locales/en/batch.json +5 -0
- package/src/i18n/locales/en/billing.json +87 -0
- package/src/i18n/locales/en/common.json +65 -0
- package/src/i18n/locales/en/compliance.json +83 -0
- package/src/i18n/locales/en/config.json +29 -0
- package/src/i18n/locales/en/dashboard.json +23 -0
- package/src/i18n/locales/en/errors.json +9 -0
- package/src/i18n/locales/en/index.ts +29 -0
- package/src/i18n/locales/en/legal.json +114 -0
- package/src/i18n/locales/en/lists.json +6 -0
- package/src/i18n/locales/en/marketing.json +174 -0
- package/src/i18n/locales/en/monitoring.json +15 -0
- package/src/i18n/locales/en/nav.json +35 -0
- package/src/i18n/locales/en/onboarding.json +31 -0
- package/src/i18n/locales/en/platform.json +25 -0
- package/src/i18n/locales/en/screening.json +26 -0
- package/src/i18n/locales/en/team.json +12 -0
- package/src/i18n/locales/he/admin.json +19 -0
- package/src/i18n/locales/he/alerts.json +52 -0
- package/src/i18n/locales/he/analytics.json +9 -0
- package/src/i18n/locales/he/audit.json +12 -0
- package/src/i18n/locales/he/auth.json +60 -0
- package/src/i18n/locales/he/batch.json +5 -0
- package/src/i18n/locales/he/billing.json +41 -0
- package/src/i18n/locales/he/common.json +65 -0
- package/src/i18n/locales/he/compliance.json +83 -0
- package/src/i18n/locales/he/config.json +13 -0
- package/src/i18n/locales/he/dashboard.json +19 -0
- package/src/i18n/locales/he/errors.json +9 -0
- package/src/i18n/locales/he/index.ts +29 -0
- package/src/i18n/locales/he/legal.json +10 -0
- package/src/i18n/locales/he/lists.json +6 -0
- package/src/i18n/locales/he/marketing.json +110 -0
- package/src/i18n/locales/he/monitoring.json +15 -0
- package/src/i18n/locales/he/nav.json +35 -0
- package/src/i18n/locales/he/onboarding.json +25 -0
- package/src/i18n/locales/he/platform.json +23 -0
- package/src/i18n/locales/he/screening.json +26 -0
- package/src/i18n/locales/he/team.json +11 -0
- package/src/index.css +112 -0
- package/src/main.tsx +15 -0
- package/src/pages/APIKeys.tsx +120 -0
- package/src/pages/AddMonitorModal.tsx +30 -0
- package/src/pages/AdverseMedia.test.tsx +18 -0
- package/src/pages/AdverseMedia.tsx +89 -0
- package/src/pages/AlertDetailPage.tsx +64 -0
- package/src/pages/AlertQueue.test.tsx +48 -0
- package/src/pages/AlertQueue.tsx +63 -0
- package/src/pages/Analytics.tsx +50 -0
- package/src/pages/AuditTrail.tsx +64 -0
- package/src/pages/BatchJobs.tsx +79 -0
- package/src/pages/CaseDetail.tsx +72 -0
- package/src/pages/CaseManagement.test.tsx +31 -0
- package/src/pages/CaseManagement.tsx +92 -0
- package/src/pages/Configuration.test.tsx +96 -0
- package/src/pages/Configuration.tsx +123 -0
- package/src/pages/CryptoScreening.tsx +109 -0
- package/src/pages/Dashboard.test.tsx +51 -0
- package/src/pages/Dashboard.tsx +66 -0
- package/src/pages/EDDWorkflow.test.tsx +29 -0
- package/src/pages/EDDWorkflow.tsx +73 -0
- package/src/pages/ForgotPassword.test.tsx +49 -0
- package/src/pages/ForgotPassword.tsx +67 -0
- package/src/pages/ListsMarketplace.tsx +102 -0
- package/src/pages/Login.test.tsx +100 -0
- package/src/pages/Login.tsx +57 -0
- package/src/pages/MFASetup.tsx +114 -0
- package/src/pages/MonitorProfileCard.tsx +27 -0
- package/src/pages/Monitoring.tsx +68 -0
- package/src/pages/Onboarding.test.tsx +36 -0
- package/src/pages/Onboarding.tsx +60 -0
- package/src/pages/PEPScreening.test.tsx +15 -0
- package/src/pages/PEPScreening.tsx +100 -0
- package/src/pages/ResetPassword.tsx +81 -0
- package/src/pages/RiskAssessment.test.tsx +15 -0
- package/src/pages/RiskAssessment.tsx +108 -0
- package/src/pages/SanctionsLists.tsx +74 -0
- package/src/pages/ScreenEntity.test.tsx +82 -0
- package/src/pages/ScreenEntity.tsx +76 -0
- package/src/pages/Signup.test.tsx +98 -0
- package/src/pages/Signup.tsx +92 -0
- package/src/pages/TaskHistory.tsx +183 -0
- package/src/pages/Team.test.tsx +15 -0
- package/src/pages/Team.tsx +140 -0
- package/src/pages/TransactionMonitoring.test.tsx +18 -0
- package/src/pages/TransactionMonitoring.tsx +118 -0
- package/src/pages/TxnScreening.tsx +125 -0
- package/src/pages/UBOChain.test.tsx +35 -0
- package/src/pages/UBOChain.tsx +65 -0
- package/src/pages/Webhooks.tsx +137 -0
- package/src/pages/admin/DataSources.tsx +230 -0
- package/src/pages/admin/Operations.tsx +103 -0
- package/src/pages/admin/ScheduledTasks.tsx +155 -0
- package/src/pages/admin/SystemHealth.tsx +58 -0
- package/src/pages/admin/TenantDetail.tsx +62 -0
- package/src/pages/admin/Tenants.test.tsx +20 -0
- package/src/pages/admin/Tenants.tsx +63 -0
- package/src/pages/admin/opsRunners.ts +81 -0
- package/src/pages/admin/opsTerminal.tsx +63 -0
- package/src/pages/billing/ActiveSubscriptions.tsx +41 -0
- package/src/pages/billing/AddProductModal.tsx +99 -0
- package/src/pages/billing/BillingPage.test.tsx +30 -0
- package/src/pages/billing/BillingPage.tsx +67 -0
- package/src/pages/billing/CurrentPlan.tsx +50 -0
- package/src/pages/billing/InvoiceList.tsx +104 -0
- package/src/pages/billing/InvoiceRow.tsx +37 -0
- package/src/pages/billing/LemonSqueezySetup.tsx +51 -0
- package/src/pages/billing/PaymentAlert.tsx +33 -0
- package/src/pages/billing/PlanComparison.tsx +53 -0
- package/src/pages/billing/ProductUsage.tsx +43 -0
- package/src/pages/billing/PromoCodeInput.tsx +58 -0
- package/src/pages/billing/SeatManager.tsx +80 -0
- package/src/pages/billing/SeatRow.tsx +32 -0
- package/src/pages/billing/SubscriptionCard.tsx +73 -0
- package/src/pages/billing/UpgradeModal.tsx +53 -0
- package/src/pages/billing/UsageHistory.tsx +37 -0
- package/src/pages/billing/UsageMeter.tsx +26 -0
- package/src/pages/billing/UsageOverview.tsx +38 -0
- package/src/pages/legal/PrivacyPolicy.tsx +25 -0
- package/src/pages/legal/TermsOfService.tsx +25 -0
- package/src/pages/marketing/BundleCallout.tsx +24 -0
- package/src/pages/marketing/CTASection.tsx +48 -0
- package/src/pages/marketing/CaseStudy.tsx +37 -0
- package/src/pages/marketing/ComparisonTable.tsx +66 -0
- package/src/pages/marketing/CompetitiveEdge.tsx +55 -0
- package/src/pages/marketing/DataCoverage.tsx +54 -0
- package/src/pages/marketing/DataRain.tsx +30 -0
- package/src/pages/marketing/EnterpriseCTA.tsx +26 -0
- package/src/pages/marketing/FAQItem.tsx +27 -0
- package/src/pages/marketing/FAQSchema.tsx +43 -0
- package/src/pages/marketing/FAQSection.tsx +19 -0
- package/src/pages/marketing/FeatureDetail.tsx +19 -0
- package/src/pages/marketing/FeaturesGrid.tsx +60 -0
- package/src/pages/marketing/FeaturesSpotlight.tsx +68 -0
- package/src/pages/marketing/FooterSection.tsx +92 -0
- package/src/pages/marketing/GradientOrbs.tsx +26 -0
- package/src/pages/marketing/HeroSearch.tsx +113 -0
- package/src/pages/marketing/HeroSection.tsx +72 -0
- package/src/pages/marketing/LandingPage.tsx +45 -0
- package/src/pages/marketing/LogoCloud.tsx +31 -0
- package/src/pages/marketing/LogoMarquee.tsx +45 -0
- package/src/pages/marketing/MarketingNav.tsx +80 -0
- package/src/pages/marketing/MatchingLayers.tsx +78 -0
- package/src/pages/marketing/MobileMenu.tsx +45 -0
- package/src/pages/marketing/PricingCard.tsx +57 -0
- package/src/pages/marketing/PricingFeatureRow.tsx +16 -0
- package/src/pages/marketing/PricingSection.tsx +103 -0
- package/src/pages/marketing/PricingToggle.tsx +40 -0
- package/src/pages/marketing/ProductPricingCards.tsx +23 -0
- package/src/pages/marketing/ProductShowcase.tsx +81 -0
- package/src/pages/marketing/ProductTabs.tsx +45 -0
- package/src/pages/marketing/QuoteRotator.tsx +56 -0
- package/src/pages/marketing/StatsBar.tsx +81 -0
- package/src/pages/marketing/StatsSection.tsx +44 -0
- package/src/pages/marketing/TestimonialCard.tsx +38 -0
- package/src/pages/marketing/TestimonialsSection.tsx +32 -0
- package/src/pages/marketing/animations.tsx +56 -0
- package/src/pages/onboarding/CountryStep.tsx +38 -0
- package/src/pages/onboarding/ListsStep.tsx +44 -0
- package/src/pages/onboarding/StepIndicator.tsx +34 -0
- package/src/pages/onboarding/ThresholdStep.tsx +49 -0
- package/src/pages/platform/APIKeys.tsx +102 -0
- package/src/pages/platform/Overview.tsx +58 -0
- package/src/pages/platform/Users.tsx +110 -0
- package/src/pages/reporting/ComplianceReport.tsx +99 -0
- package/src/pages/reporting/SARForm.tsx +99 -0
- package/src/routes/appRoutes.tsx +60 -0
- package/src/routes/compliance.tsx +28 -0
- package/src/routes/lazyCompliance.ts +34 -0
- package/src/routes/lazyPages.ts +35 -0
- package/src/routes/lazyPlatform.ts +5 -0
- package/src/routes/platform.tsx +15 -0
- package/src/styles/effects.css +76 -0
- package/src/test/setup.ts +25 -0
- package/src/test/utils.tsx +49 -0
- package/src/types/alert.ts +31 -0
- package/src/types/analytics.ts +16 -0
- package/src/types/audit.ts +11 -0
- package/src/types/billing.ts +60 -0
- package/src/types/common.ts +15 -0
- package/src/types/config.ts +19 -0
- package/src/types/entity.ts +34 -0
- package/src/types/index.ts +8 -0
- package/src/types/list.ts +15 -0
- package/src/types/screening.ts +32 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +65 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +11 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Shield, Globe, Calendar, AlertTriangle } from 'lucide-react'
|
|
3
|
+
import { Card } from '../ui/Card'
|
|
4
|
+
import { Badge } from '../ui/Badge'
|
|
5
|
+
import type { PEPProfile } from '../../data/pepProfiles'
|
|
6
|
+
|
|
7
|
+
const tierColors: Record<string, string> = {
|
|
8
|
+
Tier1: 'red', Tier2: 'orange', Tier3: 'blue', Tier4: 'green',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function PEPResultCard({ profile }: { profile: PEPProfile }) {
|
|
12
|
+
const tierColor = tierColors[profile.tier] || 'gray'
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Card className="space-y-lg">
|
|
16
|
+
<div className="flex items-start justify-between">
|
|
17
|
+
<div className="flex items-center gap-md">
|
|
18
|
+
<div className="w-10 h-10 rounded-full bg-apple-red/20 flex items-center justify-center">
|
|
19
|
+
<Shield className="w-5 h-5 text-apple-red" />
|
|
20
|
+
</div>
|
|
21
|
+
<div>
|
|
22
|
+
<h3 className="sf-headline">{profile.name}</h3>
|
|
23
|
+
<p className="sf-caption">{profile.id}</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div className="flex items-center gap-sm">
|
|
27
|
+
<Badge color={tierColor as 'red' | 'orange' | 'blue' | 'green'}>{profile.tier}</Badge>
|
|
28
|
+
{profile.isActive ? (
|
|
29
|
+
<Badge color="red">Active</Badge>
|
|
30
|
+
) : (
|
|
31
|
+
<Badge color="green">Inactive</Badge>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-lg">
|
|
37
|
+
<InfoItem icon={Shield} label="Position" value={profile.position} />
|
|
38
|
+
<InfoItem icon={Globe} label="Country" value={profile.country} />
|
|
39
|
+
<InfoItem icon={Calendar} label="Since" value={profile.startDate} />
|
|
40
|
+
<InfoItem icon={AlertTriangle} label="Risk Weight" value={`${(profile.riskWeight * 100).toFixed(0)}%`} />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
{profile.aliases.length > 0 && (
|
|
44
|
+
<div>
|
|
45
|
+
<p className="sf-caption mb-sm">Aliases</p>
|
|
46
|
+
<div className="flex flex-wrap gap-sm">
|
|
47
|
+
{profile.aliases.map(a => (
|
|
48
|
+
<span key={a} className="px-md py-xs bg-apple-bg-tertiary rounded-full text-xs">{a}</span>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
{profile.sanctions.length > 0 && (
|
|
55
|
+
<div>
|
|
56
|
+
<p className="sf-caption mb-sm">Sanctions Lists</p>
|
|
57
|
+
<div className="flex flex-wrap gap-sm">
|
|
58
|
+
{profile.sanctions.map(s => (
|
|
59
|
+
<Badge key={s} color="red">{s}</Badge>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</Card>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function InfoItem({ icon: Icon, label, value }: { icon: typeof Shield; label: string; value: string }) {
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
<div className="flex items-center gap-xs mb-xs">
|
|
72
|
+
<Icon className="w-3 h-3 text-apple-label-secondary" />
|
|
73
|
+
<p className="text-xs text-apple-label-secondary">{label}</p>
|
|
74
|
+
</div>
|
|
75
|
+
<p className="sf-body">{value}</p>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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 { MatchingModesCard } from './MatchingModesCard'
|
|
5
|
+
|
|
6
|
+
describe('MatchingModesCard', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
strictMatching: true,
|
|
9
|
+
autoAlert: false,
|
|
10
|
+
onChange: vi.fn(),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
it('renders card title', () => {
|
|
14
|
+
render(<MatchingModesCard {...defaultProps} />)
|
|
15
|
+
expect(screen.getByText('Matching Modes')).toBeInTheDocument()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('renders strict matching toggle', () => {
|
|
19
|
+
render(<MatchingModesCard {...defaultProps} />)
|
|
20
|
+
expect(screen.getByText('Strict Matching Mode')).toBeInTheDocument()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('renders auto alert toggle', () => {
|
|
24
|
+
render(<MatchingModesCard {...defaultProps} />)
|
|
25
|
+
expect(screen.getByText('Automatic Alerting')).toBeInTheDocument()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('displays strict matching description', () => {
|
|
29
|
+
render(<MatchingModesCard {...defaultProps} />)
|
|
30
|
+
expect(screen.getByText('Require exact matches on key identifiers')).toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('displays auto alert description', () => {
|
|
34
|
+
render(<MatchingModesCard {...defaultProps} />)
|
|
35
|
+
expect(screen.getByText('Automatically create alerts for high-confidence matches')).toBeInTheDocument()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('calls onChange when strict matching toggle clicked', async () => {
|
|
39
|
+
const handler = vi.fn()
|
|
40
|
+
render(<MatchingModesCard {...defaultProps} onChange={handler} />)
|
|
41
|
+
const label = screen.getByText('Strict Matching Mode').closest('label')
|
|
42
|
+
const toggle = label?.querySelector('div')
|
|
43
|
+
await userEvent.click(toggle!)
|
|
44
|
+
expect(handler).toHaveBeenCalledWith('strictMatching', false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('calls onChange when auto alert toggle clicked', async () => {
|
|
48
|
+
const handler = vi.fn()
|
|
49
|
+
render(<MatchingModesCard {...defaultProps} onChange={handler} />)
|
|
50
|
+
const label = screen.getByText('Automatic Alerting').closest('label')
|
|
51
|
+
const toggle = label?.querySelector('div')
|
|
52
|
+
await userEvent.click(toggle!)
|
|
53
|
+
expect(handler).toHaveBeenCalledWith('autoAlert', true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('reflects current mode states', () => {
|
|
57
|
+
const { rerender } = render(
|
|
58
|
+
<MatchingModesCard
|
|
59
|
+
strictMatching={true}
|
|
60
|
+
autoAlert={false}
|
|
61
|
+
onChange={vi.fn()}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
expect(screen.getByText('Strict Matching Mode')).toBeInTheDocument()
|
|
65
|
+
|
|
66
|
+
rerender(
|
|
67
|
+
<MatchingModesCard
|
|
68
|
+
strictMatching={false}
|
|
69
|
+
autoAlert={true}
|
|
70
|
+
onChange={vi.fn()}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
expect(screen.getByText('Automatic Alerting')).toBeInTheDocument()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Card } from '../ui/Card';
|
|
4
|
+
import { Toggle } from '../ui/Toggle';
|
|
5
|
+
import { Divider } from '../ui/Divider';
|
|
6
|
+
|
|
7
|
+
interface ModesProps {
|
|
8
|
+
strictMatching: boolean;
|
|
9
|
+
autoAlert: boolean;
|
|
10
|
+
onChange: (key: string, value: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function MatchingModesCard({ strictMatching, autoAlert, onChange }: ModesProps) {
|
|
14
|
+
const { t } = useTranslation('config');
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Card>
|
|
18
|
+
<h3 className="sf-headline mb-lg">{t('modes.title')}</h3>
|
|
19
|
+
<div className="space-y-md">
|
|
20
|
+
<div>
|
|
21
|
+
<Toggle
|
|
22
|
+
checked={strictMatching}
|
|
23
|
+
onChange={(v) => onChange('strictMatching', v)}
|
|
24
|
+
label={t('modes.strict')}
|
|
25
|
+
/>
|
|
26
|
+
<p className="sf-caption mt-sm">{t('modes.strict_desc')}</p>
|
|
27
|
+
</div>
|
|
28
|
+
<Divider />
|
|
29
|
+
<div>
|
|
30
|
+
<Toggle
|
|
31
|
+
checked={autoAlert}
|
|
32
|
+
onChange={(v) => onChange('autoAlert', v)}
|
|
33
|
+
label={t('modes.auto_alert')}
|
|
34
|
+
/>
|
|
35
|
+
<p className="sf-caption mt-sm">{t('modes.auto_alert_desc')}</p>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</Card>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react'
|
|
2
|
+
import userEvent from '@testing-library/user-event'
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
import { ScreeningLayersCard } from './ScreeningLayersCard'
|
|
5
|
+
|
|
6
|
+
describe('ScreeningLayersCard', () => {
|
|
7
|
+
it('renders card title', () => {
|
|
8
|
+
render(<ScreeningLayersCard />)
|
|
9
|
+
expect(screen.getByText('Enabled Screening Layers')).toBeInTheDocument()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders all screening layers', () => {
|
|
13
|
+
render(<ScreeningLayersCard />)
|
|
14
|
+
expect(screen.getByText(/OFAC Specially Designated Nationals/)).toBeInTheDocument()
|
|
15
|
+
expect(screen.getByText(/OFAC Consolidated Non-SDN/)).toBeInTheDocument()
|
|
16
|
+
expect(screen.getByText(/EU Consolidated Sanctions List/)).toBeInTheDocument()
|
|
17
|
+
expect(screen.getByText(/UN Security Council/)).toBeInTheDocument()
|
|
18
|
+
expect(screen.getByText(/UK Sanctions List/)).toBeInTheDocument()
|
|
19
|
+
expect(screen.getByText(/Canada UNSC/)).toBeInTheDocument()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('enables all layers by default', () => {
|
|
23
|
+
const { container } = render(<ScreeningLayersCard />)
|
|
24
|
+
const toggles = container.querySelectorAll('div[class*="bg-apple-green"]')
|
|
25
|
+
expect(toggles.length).toBeGreaterThan(0)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('toggles layer on and off', async () => {
|
|
29
|
+
render(<ScreeningLayersCard />)
|
|
30
|
+
const label = screen.getByText(/OFAC Specially Designated Nationals/).closest('label')
|
|
31
|
+
const toggleDiv = label?.querySelector('div')
|
|
32
|
+
await userEvent.click(toggleDiv!)
|
|
33
|
+
await userEvent.click(toggleDiv!)
|
|
34
|
+
expect(screen.getByText(/OFAC Specially Designated Nationals/)).toBeInTheDocument()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('renders correct number of toggles', () => {
|
|
38
|
+
render(<ScreeningLayersCard />)
|
|
39
|
+
const labels = screen.getAllByText(/OFAC|EU|UN|UK|Canada/)
|
|
40
|
+
expect(labels.length).toBe(6)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('allows toggling individual layers independently', async () => {
|
|
44
|
+
render(<ScreeningLayersCard />)
|
|
45
|
+
const ofacLabel = screen.getByText(/OFAC Specially Designated Nationals/).closest('label')
|
|
46
|
+
const euLabel = screen.getByText(/EU Consolidated Sanctions List/).closest('label')
|
|
47
|
+
|
|
48
|
+
const ofacToggle = ofacLabel?.querySelector('div')
|
|
49
|
+
const euToggle = euLabel?.querySelector('div')
|
|
50
|
+
|
|
51
|
+
await userEvent.click(ofacToggle!)
|
|
52
|
+
await userEvent.click(euToggle!)
|
|
53
|
+
|
|
54
|
+
expect(ofacLabel).toBeInTheDocument()
|
|
55
|
+
expect(euLabel).toBeInTheDocument()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Card } from '../ui/Card';
|
|
4
|
+
import { Toggle } from '../ui/Toggle';
|
|
5
|
+
|
|
6
|
+
const layers = [
|
|
7
|
+
'OFAC Specially Designated Nationals',
|
|
8
|
+
'OFAC Consolidated Non-SDN',
|
|
9
|
+
'EU Consolidated Sanctions List',
|
|
10
|
+
'UN Security Council',
|
|
11
|
+
'UK Sanctions List',
|
|
12
|
+
'Canada UNSC',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function ScreeningLayersCard() {
|
|
16
|
+
const { t } = useTranslation('config');
|
|
17
|
+
const [enabled, setEnabled] = React.useState<Record<string, boolean>>(
|
|
18
|
+
layers.reduce((acc, layer) => ({ ...acc, [layer]: true }), {})
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Card>
|
|
23
|
+
<h3 className="sf-headline mb-lg">{t('layers.title')}</h3>
|
|
24
|
+
<div className="space-y-md">
|
|
25
|
+
{layers.map((layer) => (
|
|
26
|
+
<Toggle
|
|
27
|
+
key={layer}
|
|
28
|
+
checked={enabled[layer]}
|
|
29
|
+
onChange={(v) => setEnabled({ ...enabled, [layer]: v })}
|
|
30
|
+
label={layer}
|
|
31
|
+
/>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</Card>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import userEvent from '@testing-library/user-event'
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
4
|
+
import { ThresholdsCard } from './ThresholdsCard'
|
|
5
|
+
|
|
6
|
+
describe('ThresholdsCard', () => {
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
fuzzyThreshold: 50,
|
|
9
|
+
alertThreshold: 75,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
it('renders card title', () => {
|
|
13
|
+
render(<ThresholdsCard config={defaultConfig} onChange={vi.fn()} />)
|
|
14
|
+
expect(screen.getByText('Thresholds')).toBeInTheDocument()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('renders fuzzy match threshold slider', () => {
|
|
18
|
+
render(<ThresholdsCard config={defaultConfig} onChange={vi.fn()} />)
|
|
19
|
+
expect(screen.getByText('Fuzzy Match Threshold')).toBeInTheDocument()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('renders alert threshold slider', () => {
|
|
23
|
+
render(<ThresholdsCard config={defaultConfig} onChange={vi.fn()} />)
|
|
24
|
+
expect(screen.getByText('Auto-Alert Threshold')).toBeInTheDocument()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('displays current fuzzy threshold value', () => {
|
|
28
|
+
render(<ThresholdsCard config={defaultConfig} onChange={vi.fn()} />)
|
|
29
|
+
expect(screen.getByText('50%')).toBeInTheDocument()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('displays current alert threshold value', () => {
|
|
33
|
+
render(<ThresholdsCard config={defaultConfig} onChange={vi.fn()} />)
|
|
34
|
+
const percentages = screen.getAllByText(/\d+%/)
|
|
35
|
+
expect(percentages.some(el => el.textContent === '75%')).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('updates fuzzy threshold on slider change', () => {
|
|
39
|
+
const handler = vi.fn()
|
|
40
|
+
const { container } = render(
|
|
41
|
+
<ThresholdsCard config={defaultConfig} onChange={handler} />
|
|
42
|
+
)
|
|
43
|
+
const sliders = container.querySelectorAll('input[type="range"]')
|
|
44
|
+
const fuzzySlider = sliders[0] as HTMLInputElement
|
|
45
|
+
|
|
46
|
+
fireEvent.change(fuzzySlider, { target: { value: '70' } })
|
|
47
|
+
|
|
48
|
+
expect(handler).toHaveBeenCalled()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('updates alert threshold on slider change', () => {
|
|
52
|
+
const handler = vi.fn()
|
|
53
|
+
const { container } = render(
|
|
54
|
+
<ThresholdsCard config={defaultConfig} onChange={handler} />
|
|
55
|
+
)
|
|
56
|
+
const sliders = container.querySelectorAll('input[type="range"]')
|
|
57
|
+
const alertSlider = sliders[1] as HTMLInputElement
|
|
58
|
+
|
|
59
|
+
fireEvent.change(alertSlider, { target: { value: '85' } })
|
|
60
|
+
|
|
61
|
+
expect(handler).toHaveBeenCalled()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('renders slider descriptions', () => {
|
|
65
|
+
render(<ThresholdsCard config={defaultConfig} onChange={vi.fn()} />)
|
|
66
|
+
expect(screen.getByText('Minimum confidence for potential matches')).toBeInTheDocument()
|
|
67
|
+
expect(screen.getByText('Automatically create alerts at this confidence level')).toBeInTheDocument()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('sliders accept 0-100 range', async () => {
|
|
71
|
+
const handler = vi.fn()
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<ThresholdsCard config={defaultConfig} onChange={handler} />
|
|
74
|
+
)
|
|
75
|
+
const sliders = container.querySelectorAll('input[type="range"]')
|
|
76
|
+
expect(sliders[0]).toHaveAttribute('min', '0')
|
|
77
|
+
expect(sliders[0]).toHaveAttribute('max', '100')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Card } from '../ui/Card';
|
|
4
|
+
import { Divider } from '../ui/Divider';
|
|
5
|
+
|
|
6
|
+
interface Config {
|
|
7
|
+
fuzzyThreshold: number;
|
|
8
|
+
alertThreshold: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ThresholdsCardProps {
|
|
12
|
+
config: Config;
|
|
13
|
+
onChange: (config: Config) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ThresholdsCard({ config, onChange }: ThresholdsCardProps) {
|
|
17
|
+
const { t } = useTranslation('config');
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Card>
|
|
21
|
+
<h3 className="sf-headline mb-lg">{t('thresholds.title')}</h3>
|
|
22
|
+
<div className="space-y-lg">
|
|
23
|
+
<div>
|
|
24
|
+
<div className="flex items-center justify-between mb-md">
|
|
25
|
+
<label className="sf-body">{t('thresholds.fuzzy_match')}</label>
|
|
26
|
+
<span className="sf-headline">{config.fuzzyThreshold}%</span>
|
|
27
|
+
</div>
|
|
28
|
+
<input type="range" min="0" max="100" value={config.fuzzyThreshold}
|
|
29
|
+
aria-label={t('thresholds.fuzzy_match')}
|
|
30
|
+
aria-valuetext={`${config.fuzzyThreshold}%`}
|
|
31
|
+
onChange={(e) => onChange({ ...config, fuzzyThreshold: parseInt(e.target.value) })}
|
|
32
|
+
className="w-full h-2 bg-apple-bg-tertiary rounded-full appearance-none cursor-pointer accent-apple-blue" />
|
|
33
|
+
<p className="sf-caption mt-sm">{t('thresholds.fuzzy_match_desc')}</p>
|
|
34
|
+
</div>
|
|
35
|
+
<Divider />
|
|
36
|
+
<div>
|
|
37
|
+
<div className="flex items-center justify-between mb-md">
|
|
38
|
+
<label className="sf-body">{t('thresholds.auto_alert')}</label>
|
|
39
|
+
<span className="sf-headline">{config.alertThreshold}%</span>
|
|
40
|
+
</div>
|
|
41
|
+
<input type="range" min="0" max="100" value={config.alertThreshold}
|
|
42
|
+
aria-label={t('thresholds.auto_alert')}
|
|
43
|
+
aria-valuetext={`${config.alertThreshold}%`}
|
|
44
|
+
onChange={(e) => onChange({ ...config, alertThreshold: parseInt(e.target.value) })}
|
|
45
|
+
className="w-full h-2 bg-apple-bg-tertiary rounded-full appearance-none cursor-pointer accent-apple-blue" />
|
|
46
|
+
<p className="sf-caption mt-sm">{t('thresholds.auto_alert_desc')}</p>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</Card>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { CheckCircle, AlertCircle, Search, Clock, Shield } from 'lucide-react';
|
|
4
|
+
import { api } from '../../api/client';
|
|
5
|
+
|
|
6
|
+
interface AuditEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
action: string;
|
|
9
|
+
entity_name?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
created_at: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const icons: Record<string, typeof CheckCircle> = {
|
|
15
|
+
screening: Search, alert_resolved: CheckCircle,
|
|
16
|
+
alert_escalated: AlertCircle, alert_created: Clock,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const dotColors: Record<string, string> = {
|
|
20
|
+
screening: 'bg-apple-blue shadow-[0_0_8px_rgba(10,132,255,0.6)]',
|
|
21
|
+
alert_resolved: 'bg-apple-green shadow-[0_0_8px_rgba(48,209,88,0.6)]',
|
|
22
|
+
alert_escalated: 'bg-apple-orange shadow-[0_0_8px_rgba(255,159,10,0.6)]',
|
|
23
|
+
alert_created: 'bg-apple-label-tertiary',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const iconColors: Record<string, string> = {
|
|
27
|
+
screening: 'text-apple-blue', alert_resolved: 'text-apple-green',
|
|
28
|
+
alert_escalated: 'text-apple-orange', alert_created: 'text-apple-label-tertiary',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function timeAgo(dateStr: string): string {
|
|
32
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
33
|
+
const mins = Math.floor(diff / 60000);
|
|
34
|
+
if (mins < 1) return 'just now';
|
|
35
|
+
if (mins < 60) return `${mins}m ago`;
|
|
36
|
+
const hrs = Math.floor(mins / 60);
|
|
37
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
38
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ActivityFeed() {
|
|
42
|
+
const { t } = useTranslation('dashboard');
|
|
43
|
+
const [entries, setEntries] = useState<AuditEntry[]>([]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
api.get<{ entries: AuditEntry[] }>('/audit?limit=8')
|
|
47
|
+
.then(d => setEntries(d?.entries ?? []))
|
|
48
|
+
.catch(() => setEntries([]))
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const hasEntries = entries.length > 0;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="card-vibrancy p-xl h-full">
|
|
55
|
+
<h3 className="sf-headline mb-lg">{t('recent_activity') || 'Recent Activity'}</h3>
|
|
56
|
+
{!hasEntries && (
|
|
57
|
+
<div className="flex flex-col items-center justify-center py-xl text-center">
|
|
58
|
+
<Shield className="w-8 h-8 text-apple-label-tertiary mb-md" />
|
|
59
|
+
<p className="sf-caption text-apple-label-tertiary">
|
|
60
|
+
No activity yet. Start screening to see results here.
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
{hasEntries && (
|
|
65
|
+
<div className="relative ml-2">
|
|
66
|
+
<div className="absolute left-[5px] top-1 bottom-1 w-px bg-gradient-to-b from-white/20 via-white/10 to-transparent" />
|
|
67
|
+
<div className="space-y-lg">
|
|
68
|
+
{entries.map((item) => {
|
|
69
|
+
const type = item.action ?? 'screening';
|
|
70
|
+
const Icon = icons[type] ?? Search;
|
|
71
|
+
return (
|
|
72
|
+
<div key={item.id}
|
|
73
|
+
className="relative flex items-start gap-md pl-6 py-sm rounded-apple-md
|
|
74
|
+
hover:bg-white/[0.04] transition-all cursor-default">
|
|
75
|
+
<div className={`absolute left-0 top-[10px] w-[11px] h-[11px] rounded-full ${dotColors[type] ?? dotColors.screening}`} />
|
|
76
|
+
<Icon className={`w-4 h-4 mt-0.5 flex-shrink-0 ${iconColors[type] ?? iconColors.screening}`} />
|
|
77
|
+
<div className="flex-1 min-w-0">
|
|
78
|
+
<p className="text-[13px] text-white truncate">
|
|
79
|
+
{item.entity_name ?? item.description ?? item.action}
|
|
80
|
+
</p>
|
|
81
|
+
<p className="text-[11px] text-apple-label-tertiary/60 mt-px">
|
|
82
|
+
{item.action} · {timeAgo(item.created_at)}
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAuth } from '../../context/AuthContext';
|
|
3
|
+
|
|
4
|
+
export function DashboardGreeting() {
|
|
5
|
+
const { user } = useAuth();
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const hour = now.getHours();
|
|
8
|
+
const greeting = hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening';
|
|
9
|
+
const dateStr = now.toLocaleDateString('en-US', {
|
|
10
|
+
weekday: 'long', month: 'long', day: 'numeric',
|
|
11
|
+
});
|
|
12
|
+
const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
13
|
+
const name = user?.email?.split('@')[0];
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="mb-xxl">
|
|
17
|
+
<h1 className="text-3xl font-bold tracking-tight text-white mb-xs">
|
|
18
|
+
{name ? `${greeting}, ${name}` : 'Welcome back'}
|
|
19
|
+
</h1>
|
|
20
|
+
<p className="sf-caption">{dateStr} · {timeStr}</p>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
function SkeletonCard() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="card-vibrancy p-xl">
|
|
6
|
+
<div className="skeleton h-3 w-20 mb-md" />
|
|
7
|
+
<div className="skeleton h-8 w-24 mb-sm" />
|
|
8
|
+
<div className="skeleton h-3 w-16" />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function DashboardSkeleton() {
|
|
14
|
+
return (
|
|
15
|
+
<div>
|
|
16
|
+
<div className="mb-xxl">
|
|
17
|
+
<div className="skeleton h-8 w-56 mb-sm" />
|
|
18
|
+
<div className="skeleton h-3 w-40" />
|
|
19
|
+
</div>
|
|
20
|
+
<div className="grid grid-cols-1 md:grid-cols-4 gap-lg mb-xxl">
|
|
21
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
22
|
+
<SkeletonCard key={i} />
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-lg">
|
|
26
|
+
<div className="lg:col-span-2 card-vibrancy p-xl">
|
|
27
|
+
<div className="skeleton h-48 w-full" />
|
|
28
|
+
</div>
|
|
29
|
+
<div className="card-vibrancy p-xl">
|
|
30
|
+
<div className="skeleton h-48 w-full" />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { Search, UserCheck, Wallet, FileText, Package, Zap } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
const actions = [
|
|
6
|
+
{
|
|
7
|
+
icon: Search, label: 'Screen Entity', description: 'Run 6-layer sanctions screening',
|
|
8
|
+
path: '/screen', color: 'from-apple-blue/20 to-apple-blue/5',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
icon: UserCheck, label: 'PEP Check', description: 'Screen politically exposed persons',
|
|
12
|
+
path: '/compliance/pep', color: 'from-apple-green/20 to-apple-green/5',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
icon: Wallet, label: 'Crypto Scan', description: 'Screen wallet addresses',
|
|
16
|
+
path: '/compliance/crypto', color: 'from-apple-orange/20 to-apple-orange/5',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
icon: Zap, label: 'Transaction Screen', description: 'Screen payment sender & receiver',
|
|
20
|
+
path: '/compliance/txn', color: 'from-apple-purple/20 to-apple-purple/5',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
icon: FileText, label: 'Freetext Scan', description: 'Paste a document, extract & screen names',
|
|
24
|
+
path: '/screen', color: 'from-apple-teal/20 to-apple-teal/5',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
icon: Package, label: 'Batch Jobs', description: 'Screen customer lists in bulk',
|
|
28
|
+
path: '/batch', color: 'from-apple-indigo/20 to-apple-indigo/5',
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export function QuickActions() {
|
|
33
|
+
const navigate = useNavigate()
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-md">
|
|
37
|
+
{actions.map(({ icon: Icon, label, description, path, color }) => (
|
|
38
|
+
<button key={path + label} onClick={() => navigate(path)}
|
|
39
|
+
className={`text-left p-lg rounded-apple-lg bg-gradient-to-br ${color}
|
|
40
|
+
border border-white/[0.06] hover:border-white/[0.12]
|
|
41
|
+
transition-all cursor-pointer group`}>
|
|
42
|
+
<div className="w-10 h-10 rounded-apple-md bg-white/[0.06] flex items-center justify-center mb-md
|
|
43
|
+
group-hover:bg-white/[0.1] transition-colors">
|
|
44
|
+
<Icon className="w-5 h-5 text-white/80" />
|
|
45
|
+
</div>
|
|
46
|
+
<p className="sf-headline text-sm mb-xs">{label}</p>
|
|
47
|
+
<p className="sf-caption text-white/50 text-xs">{description}</p>
|
|
48
|
+
</button>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|