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,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LayoutDashboard, AlertCircle, Search, Settings,
|
|
3
|
+
BarChart3, FileText, Eye, Package, CreditCard,
|
|
4
|
+
Briefcase, Shield, UserCheck, Globe, Zap,
|
|
5
|
+
Users, Activity, Server, ShoppingBag, List,
|
|
6
|
+
Key, Webhook, Wallet, Database, Play, Clock,
|
|
7
|
+
} from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
export interface NavSection {
|
|
10
|
+
title: string;
|
|
11
|
+
items: NavItem[];
|
|
12
|
+
minRole?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NavItem {
|
|
16
|
+
icon: typeof LayoutDashboard;
|
|
17
|
+
label: string;
|
|
18
|
+
path: string;
|
|
19
|
+
minRole?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ROLE_RANK: Record<string, number> = {
|
|
23
|
+
viewer: 0, auditor: 1, analyst: 2, admin: 3, superadmin: 4,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function canAccess(userRole: string, minRole?: string): boolean {
|
|
27
|
+
if (!minRole) return true;
|
|
28
|
+
return (ROLE_RANK[userRole] ?? 0) >= (ROLE_RANK[minRole] ?? 99);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const mainNav: NavSection = {
|
|
32
|
+
title: 'Main',
|
|
33
|
+
items: [
|
|
34
|
+
{ icon: LayoutDashboard, label: 'Dashboard', path: '/dashboard' },
|
|
35
|
+
{ icon: AlertCircle, label: 'Alerts', path: '/alerts' },
|
|
36
|
+
{ icon: Search, label: 'Screen', path: '/screen' },
|
|
37
|
+
{ icon: Eye, label: 'Monitoring', path: '/monitoring' },
|
|
38
|
+
{ icon: Package, label: 'Batch Jobs', path: '/batch' },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const complianceNav: NavSection = {
|
|
43
|
+
title: 'Compliance',
|
|
44
|
+
items: [
|
|
45
|
+
{ icon: Briefcase, label: 'Cases', path: '/compliance/cases' },
|
|
46
|
+
{ icon: Shield, label: 'Risk Assessment', path: '/compliance/risk' },
|
|
47
|
+
{ icon: UserCheck, label: 'PEP Screening', path: '/compliance/pep' },
|
|
48
|
+
{ icon: Globe, label: 'Adverse Media', path: '/compliance/media' },
|
|
49
|
+
{ icon: Zap, label: 'Transactions', path: '/compliance/txn' },
|
|
50
|
+
{ icon: Wallet, label: 'Crypto Screening', path: '/compliance/crypto' },
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const systemNav: NavSection = {
|
|
55
|
+
title: 'System',
|
|
56
|
+
items: [
|
|
57
|
+
{ icon: BarChart3, label: 'Analytics', path: '/analytics' },
|
|
58
|
+
{ icon: FileText, label: 'Audit Log', path: '/audit', minRole: 'auditor' },
|
|
59
|
+
{ icon: List, label: 'Sanctions Lists', path: '/lists' },
|
|
60
|
+
{ icon: ShoppingBag, label: 'Marketplace', path: '/lists/marketplace' },
|
|
61
|
+
{ icon: Key, label: 'API Keys', path: '/keys', minRole: 'admin' },
|
|
62
|
+
{ icon: Webhook, label: 'Webhooks', path: '/webhooks', minRole: 'admin' },
|
|
63
|
+
{ icon: Clock, label: 'Task History', path: '/tasks' },
|
|
64
|
+
{ icon: Settings, label: 'Configuration', path: '/config', minRole: 'admin' },
|
|
65
|
+
{ icon: CreditCard, label: 'Billing', path: '/billing', minRole: 'admin' },
|
|
66
|
+
{ icon: Users, label: 'Team', path: '/team', minRole: 'admin' },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const adminNav: NavSection = {
|
|
71
|
+
title: 'Admin',
|
|
72
|
+
minRole: 'admin',
|
|
73
|
+
items: [
|
|
74
|
+
{ icon: Server, label: 'Tenants', path: '/admin/tenants' },
|
|
75
|
+
{ icon: Activity, label: 'System Health', path: '/admin/health' },
|
|
76
|
+
{ icon: Database, label: 'Data Sources', path: '/admin/data-sources' },
|
|
77
|
+
{ icon: Play, label: 'Operations', path: '/admin/operations' },
|
|
78
|
+
{ icon: Clock, label: 'Scheduled Tasks', path: '/admin/tasks' },
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const platformNav: NavSection = {
|
|
83
|
+
title: 'Platform',
|
|
84
|
+
minRole: 'admin',
|
|
85
|
+
items: [
|
|
86
|
+
{ icon: Server, label: 'Overview', path: '/platform/overview' },
|
|
87
|
+
{ icon: Users, label: 'Users', path: '/platform/users' },
|
|
88
|
+
{ icon: Activity, label: 'API Keys', path: '/platform/keys' },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const navSections: NavSection[] = [
|
|
93
|
+
mainNav, complianceNav, systemNav, adminNav, platformNav,
|
|
94
|
+
];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '../ui/Card';
|
|
3
|
+
import { Button } from '../ui/Button';
|
|
4
|
+
import { Badge } from '../ui/Badge';
|
|
5
|
+
import { RefreshCw, Shield } from 'lucide-react';
|
|
6
|
+
import type { ListMeta } from '../../api/lists';
|
|
7
|
+
import type { TFunction } from 'i18next';
|
|
8
|
+
|
|
9
|
+
const NAMES: Record<string, string> = {
|
|
10
|
+
'ofac-sdn': 'OFAC SDN', 'ofac': 'OFAC SDN',
|
|
11
|
+
'opensanctions_default': 'OpenSanctions Global',
|
|
12
|
+
'un': 'UN Security Council', 'un-consolidated': 'UN Consolidated',
|
|
13
|
+
'eu_fsf': 'EU Financial Sanctions', 'eu-sanctions': 'EU Sanctions',
|
|
14
|
+
'uk_ofsi': 'UK OFSI', 'gb-fcdo': 'UK FCDO',
|
|
15
|
+
'ch-seco': 'Swiss SECO', 'il-nbctf': 'Israel NBCTF',
|
|
16
|
+
'il-mod-terrorists': 'Israeli MoD Terror List',
|
|
17
|
+
'icij_offshore': 'ICIJ Offshore Leaks',
|
|
18
|
+
'gleif_lei': 'GLEIF LEI Registry',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface ListCardProps {
|
|
22
|
+
list: ListMeta;
|
|
23
|
+
triggerSync: (id: string) => void;
|
|
24
|
+
t: TFunction;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ListCard({ list, triggerSync, t }: ListCardProps) {
|
|
28
|
+
const name = NAMES[list.id] ??
|
|
29
|
+
list.id.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Card>
|
|
33
|
+
<div className="flex items-start justify-between mb-md">
|
|
34
|
+
<div className="flex items-center gap-md">
|
|
35
|
+
<div className="flex h-9 w-9 items-center justify-center rounded-apple-md bg-apple-blue/10">
|
|
36
|
+
<Shield className="w-4 h-4 text-apple-blue" />
|
|
37
|
+
</div>
|
|
38
|
+
<div>
|
|
39
|
+
<h3 className="sf-headline">{name}</h3>
|
|
40
|
+
<p className="sf-caption text-apple-label-tertiary mt-xs">{list.id}</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<Badge size="sm" color={list.sync_enabled ? 'green' : 'gray'}>
|
|
44
|
+
{list.sync_enabled ? 'Active' : 'Disabled'}
|
|
45
|
+
</Badge>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-md">
|
|
48
|
+
<div>
|
|
49
|
+
<p className="sf-caption text-apple-label-tertiary">{t('entities')}</p>
|
|
50
|
+
<p className="sf-body font-semibold">
|
|
51
|
+
{list.entity_count.toLocaleString()}
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<p className="sf-caption text-apple-label-tertiary">{t('threshold')}</p>
|
|
56
|
+
<p className="sf-body font-semibold">
|
|
57
|
+
{(list.threshold * 100).toFixed(0)}%
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div>
|
|
61
|
+
<p className="sf-caption text-apple-label-tertiary">{t('last_synced')}</p>
|
|
62
|
+
<p className="sf-body">
|
|
63
|
+
{list.last_synced && list.last_synced > 0
|
|
64
|
+
? new Date(list.last_synced * 1000).toLocaleDateString()
|
|
65
|
+
: 'Never'}
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="flex items-end justify-end">
|
|
69
|
+
<Button variant="secondary" size="sm"
|
|
70
|
+
onClick={() => triggerSync(list.id)}
|
|
71
|
+
className="flex items-center gap-xs">
|
|
72
|
+
<RefreshCw className="w-3 h-3" /> Sync
|
|
73
|
+
</Button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</Card>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Shield, Globe, Flag, Check, Plus, Clock, RefreshCw, AlertTriangle, Scale } from 'lucide-react';
|
|
3
|
+
import { Badge } from '../ui/Badge';
|
|
4
|
+
|
|
5
|
+
interface MarketplaceList {
|
|
6
|
+
id: string; name: string; description: string; region: string;
|
|
7
|
+
category: string; entity_count: number; update_frequency: string;
|
|
8
|
+
last_synced: string; enabled: boolean; tier: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
list: MarketplaceList;
|
|
13
|
+
onToggle: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function categoryIcon(cat: string) {
|
|
17
|
+
if (cat === 'pep') return Globe;
|
|
18
|
+
if (cat === 'law_enforcement') return AlertTriangle;
|
|
19
|
+
if (cat === 'regulatory') return Scale;
|
|
20
|
+
if (cat === 'country') return Flag;
|
|
21
|
+
return Shield;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const regionColor: Record<string, 'blue' | 'green' | 'orange' | 'purple' | 'gray'> = {
|
|
25
|
+
Global: 'blue', Americas: 'green', Europe: 'purple',
|
|
26
|
+
'Middle East': 'orange', 'Asia-Pacific': 'blue', Africa: 'green',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function ListMarketplaceCard({ list, onToggle }: Props) {
|
|
30
|
+
const Icon = categoryIcon(list.category);
|
|
31
|
+
const synced = list.last_synced
|
|
32
|
+
? new Date(list.last_synced).toLocaleDateString()
|
|
33
|
+
: 'Not synced';
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="card-vibrancy flex flex-col justify-between p-lg hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)] transition-all duration-200">
|
|
37
|
+
<div>
|
|
38
|
+
<div className="flex items-start justify-between mb-md">
|
|
39
|
+
<div className="flex items-center gap-md">
|
|
40
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-apple-md bg-apple-bg-tertiary">
|
|
41
|
+
<Icon className="w-5 h-5 text-apple-blue" aria-hidden="true" />
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<h3 className="sf-headline">{list.name}</h3>
|
|
45
|
+
<Badge size="sm" color={regionColor[list.region] ?? 'gray'}>{list.region}</Badge>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
{list.enabled && <span className="badge-green animate-pulse">• Active</span>}
|
|
49
|
+
</div>
|
|
50
|
+
<p className="sf-caption line-clamp-2 mb-md">{list.description}</p>
|
|
51
|
+
<div className="grid grid-cols-2 gap-sm mb-md">
|
|
52
|
+
<div className="flex items-center gap-xs sf-caption">
|
|
53
|
+
<Shield className="w-3 h-3" aria-hidden="true" />
|
|
54
|
+
{list.entity_count > 0 ? list.entity_count.toLocaleString() : '—'} entities
|
|
55
|
+
</div>
|
|
56
|
+
<div className="flex items-center gap-xs sf-caption">
|
|
57
|
+
<RefreshCw className="w-3 h-3" aria-hidden="true" />
|
|
58
|
+
{list.update_frequency}
|
|
59
|
+
</div>
|
|
60
|
+
<div className="flex items-center gap-xs sf-caption col-span-2">
|
|
61
|
+
<Clock className="w-3 h-3" aria-hidden="true" />
|
|
62
|
+
{synced}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<button onClick={onToggle}
|
|
67
|
+
aria-label={list.enabled ? `Disable ${list.name}` : `Enable ${list.name}`}
|
|
68
|
+
className={`w-full flex items-center justify-center gap-xs rounded-apple-md py-sm text-[13px] font-semibold transition-all cursor-pointer min-h-[36px] ${
|
|
69
|
+
list.enabled
|
|
70
|
+
? 'bg-apple-green/15 text-apple-green hover:bg-apple-red/15 hover:text-apple-red'
|
|
71
|
+
: 'bg-apple-blue text-white hover:bg-blue-600'
|
|
72
|
+
}`}>
|
|
73
|
+
{list.enabled ? <><Check className="w-3 h-3" /> Enabled</> : <><Plus className="w-3 h-3" /> Enable</>}
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
score: number;
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function CircularConfidence({ score, size = 56 }: Props) {
|
|
9
|
+
const radius = (size - 6) / 2;
|
|
10
|
+
const circumference = 2 * Math.PI * radius;
|
|
11
|
+
const offset = circumference - (score / 100) * circumference;
|
|
12
|
+
|
|
13
|
+
const strokeColor = score >= 80 ? '#FF453A'
|
|
14
|
+
: score >= 60 ? '#FF9F0A'
|
|
15
|
+
: '#30D158';
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="relative flex-shrink-0" style={{ width: size, height: size }}>
|
|
19
|
+
<svg width={size} height={size} className="-rotate-90">
|
|
20
|
+
<circle
|
|
21
|
+
cx={size / 2} cy={size / 2} r={radius}
|
|
22
|
+
fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth={3}
|
|
23
|
+
/>
|
|
24
|
+
<circle
|
|
25
|
+
cx={size / 2} cy={size / 2} r={radius}
|
|
26
|
+
fill="none" stroke={strokeColor} strokeWidth={3}
|
|
27
|
+
strokeLinecap="round"
|
|
28
|
+
strokeDasharray={circumference}
|
|
29
|
+
strokeDashoffset={offset}
|
|
30
|
+
className="transition-all duration-700"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
33
|
+
<span className="absolute inset-0 flex items-center justify-center text-[12px] font-bold text-white">
|
|
34
|
+
{score.toFixed(0)}%
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useNavigate } from 'react-router-dom'
|
|
2
|
+
import { ArrowUpCircle } from 'lucide-react'
|
|
3
|
+
import { ApiError } from '../../api/client'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
error: Error
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function LimitReachedBanner({ error }: Props) {
|
|
10
|
+
const navigate = useNavigate()
|
|
11
|
+
const code = error instanceof ApiError ? error.code : ''
|
|
12
|
+
const isFreeTier = code === 'FREE_TIER_EXHAUSTED'
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="max-w-md mx-auto mt-lg p-lg rounded-apple-lg border border-amber-500/20 bg-amber-500/5 text-center" role="alert">
|
|
16
|
+
<ArrowUpCircle className="w-8 h-8 text-amber-400 mx-auto mb-sm" />
|
|
17
|
+
<p className="text-white font-semibold mb-xs">
|
|
18
|
+
{isFreeTier ? 'Free tier limit reached' : 'Screening limit reached'}
|
|
19
|
+
</p>
|
|
20
|
+
<p className="sf-caption text-apple-label-secondary mb-md">
|
|
21
|
+
{isFreeTier
|
|
22
|
+
? 'You\'ve used all 100 free screenings this month. Subscribe to unlock more.'
|
|
23
|
+
: 'You\'ve used all screenings on your current plan. Upgrade to continue.'}
|
|
24
|
+
</p>
|
|
25
|
+
<button type="button" onClick={() => navigate('/billing')}
|
|
26
|
+
className="px-6 py-2 bg-apple-blue hover:bg-apple-blue/90 text-white text-sm font-semibold rounded-apple transition-colors cursor-pointer">
|
|
27
|
+
{isFreeTier ? 'View Plans' : 'Upgrade Plan'}
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Card } from '../ui/Card'
|
|
3
|
+
import { Button } from '../ui/Button'
|
|
4
|
+
import { Badge } from '../ui/Badge'
|
|
5
|
+
|
|
6
|
+
const LISTS = [
|
|
7
|
+
{ id: 'OFAC', label: 'OFAC SDN', color: 'red' as const },
|
|
8
|
+
{ id: 'EU', label: 'EU Consolidated', color: 'blue' as const },
|
|
9
|
+
{ id: 'UN', label: 'UN Consolidated', color: 'blue' as const },
|
|
10
|
+
{ id: 'UKOFSI', label: 'UK OFSI', color: 'purple' as const },
|
|
11
|
+
{ id: 'SECO', label: 'SECO', color: 'orange' as const },
|
|
12
|
+
{ id: 'IsraeliMoD', label: 'Israeli MoD', color: 'orange' as const },
|
|
13
|
+
{ id: 'SDFM', label: 'SDFM Ukraine', color: 'orange' as const },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
interface ListSelectorProps {
|
|
17
|
+
selected: string[]
|
|
18
|
+
onChange: (lists: string[]) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ListSelector({ selected, onChange }: ListSelectorProps) {
|
|
22
|
+
const toggle = (id: string) => {
|
|
23
|
+
onChange(
|
|
24
|
+
selected.includes(id)
|
|
25
|
+
? selected.filter((s) => s !== id)
|
|
26
|
+
: [...selected, id],
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const allSelected = selected.length === LISTS.length
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Card>
|
|
34
|
+
<div className="flex items-center justify-between mb-md">
|
|
35
|
+
<h3 className="sf-headline">Sanctions Lists</h3>
|
|
36
|
+
<Button
|
|
37
|
+
size="sm"
|
|
38
|
+
variant="secondary"
|
|
39
|
+
onClick={() => onChange(allSelected ? [] : LISTS.map((l) => l.id))}
|
|
40
|
+
>
|
|
41
|
+
{allSelected ? 'Clear All' : 'Select All'}
|
|
42
|
+
</Button>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="space-y-sm">
|
|
45
|
+
{LISTS.map((list) => (
|
|
46
|
+
<label
|
|
47
|
+
key={list.id}
|
|
48
|
+
className="flex items-center gap-md cursor-pointer py-xs"
|
|
49
|
+
>
|
|
50
|
+
<input
|
|
51
|
+
type="checkbox"
|
|
52
|
+
checked={selected.includes(list.id)}
|
|
53
|
+
onChange={() => toggle(list.id)}
|
|
54
|
+
className="accent-apple-blue w-4 h-4"
|
|
55
|
+
/>
|
|
56
|
+
<Badge color={list.color} size="sm">
|
|
57
|
+
{list.label}
|
|
58
|
+
</Badge>
|
|
59
|
+
</label>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
</Card>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Badge } from '../ui/Badge'
|
|
2
|
+
import { ConfidenceScore } from '../data/ConfidenceScore'
|
|
3
|
+
import type { ScreenMatch } from '../../types'
|
|
4
|
+
|
|
5
|
+
const listColors: Record<string, 'red' | 'blue' | 'purple' | 'orange' | 'green' | 'gray'> = {
|
|
6
|
+
OFAC: 'red',
|
|
7
|
+
EU: 'blue',
|
|
8
|
+
UN: 'purple',
|
|
9
|
+
UKOFSI: 'orange',
|
|
10
|
+
SECO: 'green',
|
|
11
|
+
SDFM: 'gray',
|
|
12
|
+
NBCTF: 'gray',
|
|
13
|
+
IsraeliMoD: 'gray',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getListColor(listId: string): 'red' | 'blue' | 'purple' | 'orange' | 'green' | 'gray' {
|
|
17
|
+
const upper = listId.toUpperCase()
|
|
18
|
+
for (const [key, color] of Object.entries(listColors)) {
|
|
19
|
+
if (upper.startsWith(key.toUpperCase())) return color
|
|
20
|
+
}
|
|
21
|
+
return 'gray'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const typeIcons: Record<string, string> = {
|
|
25
|
+
Individual: '\u{1F464}',
|
|
26
|
+
Company: '\u{1F3E2}',
|
|
27
|
+
Vessel: '\u{1F6A2}',
|
|
28
|
+
Aircraft: '\u{2708}\u{FE0F}',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
match: ScreenMatch
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function MatchDetailHeader({ match }: Props) {
|
|
36
|
+
const pct = Math.round(match.confidence * 100)
|
|
37
|
+
const entityType = match.type ?? match.entity_type ?? 'Individual'
|
|
38
|
+
const icon = typeIcons[entityType] ?? '\u{1F4CB}'
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex items-start justify-between gap-md">
|
|
42
|
+
<div className="flex-1 min-w-0">
|
|
43
|
+
<h3 className="sf-headline text-lg truncate">{match.entity_name}</h3>
|
|
44
|
+
<div className="flex flex-wrap gap-sm mt-sm">
|
|
45
|
+
<Badge color="gray" size="sm">
|
|
46
|
+
{icon} {entityType}
|
|
47
|
+
</Badge>
|
|
48
|
+
<Badge color={getListColor(match.list_id)} size="sm">
|
|
49
|
+
{match.list_id}
|
|
50
|
+
</Badge>
|
|
51
|
+
{match.disposition && (
|
|
52
|
+
<Badge
|
|
53
|
+
color={match.disposition === 'AutoEscalate' ? 'red' : 'orange'}
|
|
54
|
+
size="sm"
|
|
55
|
+
>
|
|
56
|
+
{match.disposition}
|
|
57
|
+
</Badge>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<ConfidenceScore score={pct} size="md" />
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Badge } from '../ui/Badge'
|
|
2
|
+
import type { ScreenMatch } from '../../types'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
match: ScreenMatch
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function InfoRow({ label, value }: { label: string; value: string }) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex items-baseline gap-sm">
|
|
11
|
+
<span className="sf-caption text-white/40 w-24 shrink-0">{label}</span>
|
|
12
|
+
<span className="sf-body text-white/90 truncate">{value}</span>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function MatchEntityInfo({ match }: Props) {
|
|
18
|
+
const hasGiven = match.given_name && match.given_name.trim()
|
|
19
|
+
const hasFamily = match.family_name && match.family_name.trim()
|
|
20
|
+
const hasDob = match.date_of_birth && match.date_of_birth.trim()
|
|
21
|
+
const hasNationalities = match.nationalities && match.nationalities.length > 0
|
|
22
|
+
const originalScript = match.original_script
|
|
23
|
+
?? (match.metadata?.original_script as string | undefined)
|
|
24
|
+
const hasOriginal = originalScript && originalScript.trim()
|
|
25
|
+
|
|
26
|
+
const hasAnyInfo = hasGiven || hasFamily || hasDob || hasNationalities || hasOriginal
|
|
27
|
+
if (!hasAnyInfo) return null
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-xs">
|
|
31
|
+
{(hasGiven || hasFamily) && (
|
|
32
|
+
<InfoRow
|
|
33
|
+
label="Name"
|
|
34
|
+
value={[match.given_name, match.family_name].filter(Boolean).join(' ')}
|
|
35
|
+
/>
|
|
36
|
+
)}
|
|
37
|
+
{hasDob && <InfoRow label="Date of Birth" value={match.date_of_birth!} />}
|
|
38
|
+
{hasOriginal && (
|
|
39
|
+
<div className="flex items-baseline gap-sm">
|
|
40
|
+
<span className="sf-caption text-white/40 w-24 shrink-0">Script</span>
|
|
41
|
+
<span className="sf-body text-white/50 italic truncate">
|
|
42
|
+
{originalScript}
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
{hasNationalities && (
|
|
47
|
+
<div className="flex items-center gap-sm flex-wrap">
|
|
48
|
+
<span className="sf-caption text-white/40 w-24 shrink-0">Nationality</span>
|
|
49
|
+
{match.nationalities!.map((nat) => (
|
|
50
|
+
<Badge key={nat} color="gray" size="sm">{nat}</Badge>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { LayerEvidence } from '../../types'
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
layers: LayerEvidence[]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function barColor(pct: number): string {
|
|
8
|
+
if (pct >= 80) return 'bg-apple-red'
|
|
9
|
+
if (pct >= 50) return 'bg-apple-orange'
|
|
10
|
+
return 'bg-apple-green'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function gradientColor(pct: number): string {
|
|
14
|
+
if (pct >= 80) return 'from-apple-red to-apple-orange'
|
|
15
|
+
if (pct >= 50) return 'from-apple-orange to-apple-yellow'
|
|
16
|
+
return 'from-apple-green to-apple-teal'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function EvidenceBar({ layer }: { layer: LayerEvidence }) {
|
|
20
|
+
const pct = Math.round(layer.score * 100)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<div className="flex items-center justify-between mb-xs">
|
|
25
|
+
<span className="sf-caption font-semibold text-white/80">
|
|
26
|
+
{layer.layer}
|
|
27
|
+
</span>
|
|
28
|
+
<span className="sf-caption text-white/50">
|
|
29
|
+
{layer.algorithm} — {pct}%
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="h-1.5 bg-white/[0.06] rounded-full overflow-hidden">
|
|
33
|
+
<div
|
|
34
|
+
className={`h-full rounded-full bg-gradient-to-r ${gradientColor(pct)} transition-all duration-500`}
|
|
35
|
+
style={{ width: `${pct}%` }}
|
|
36
|
+
role="progressbar"
|
|
37
|
+
aria-valuenow={pct}
|
|
38
|
+
aria-valuemin={0}
|
|
39
|
+
aria-valuemax={100}
|
|
40
|
+
aria-label={`${layer.layer} score`}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
{layer.matched && (
|
|
44
|
+
<p className="sf-caption text-white/30 mt-xs truncate">
|
|
45
|
+
Matched: “{layer.matched}”
|
|
46
|
+
</p>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function MatchEvidenceBars({ layers }: Props) {
|
|
53
|
+
if (!layers || layers.length === 0) return null
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="space-y-md">
|
|
57
|
+
<h4 className="sf-caption font-semibold text-white/50 uppercase tracking-wider">
|
|
58
|
+
Evidence Layers
|
|
59
|
+
</h4>
|
|
60
|
+
{layers.map((layer, i) => (
|
|
61
|
+
<EvidenceBar key={`${layer.layer}-${i}`} layer={layer} />
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Badge } from '../ui/Badge'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
metadata?: Record<string, unknown>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function MetadataRow({ label, value }: { label: string; value: string }) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex items-baseline gap-sm py-xs">
|
|
11
|
+
<span className="sf-caption text-white/40 w-32 shrink-0 capitalize">
|
|
12
|
+
{label.replace(/_/g, ' ')}
|
|
13
|
+
</span>
|
|
14
|
+
<span className="sf-caption text-white/70 break-words min-w-0">
|
|
15
|
+
{value}
|
|
16
|
+
</span>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderValue(value: unknown): string {
|
|
22
|
+
if (value === null || value === undefined) return '-'
|
|
23
|
+
if (Array.isArray(value)) return value.join(', ')
|
|
24
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
25
|
+
return String(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractArrayField(metadata: Record<string, unknown>, key: string): string[] {
|
|
29
|
+
const val = metadata[key]
|
|
30
|
+
if (Array.isArray(val)) return val.map(String)
|
|
31
|
+
if (typeof val === 'string' && val.trim()) return [val]
|
|
32
|
+
return []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function MatchMetadata({ metadata }: Props) {
|
|
36
|
+
const [expanded, setExpanded] = useState(false)
|
|
37
|
+
if (!metadata || Object.keys(metadata).length === 0) return null
|
|
38
|
+
|
|
39
|
+
const aliases = extractArrayField(metadata, 'aliases')
|
|
40
|
+
const programs = extractArrayField(metadata, 'programs')
|
|
41
|
+
const addresses = extractArrayField(metadata, 'addresses')
|
|
42
|
+
const ids = extractArrayField(metadata, 'identifiers')
|
|
43
|
+
|
|
44
|
+
const otherKeys = Object.keys(metadata).filter(
|
|
45
|
+
(k) => !['aliases', 'programs', 'addresses', 'identifiers', 'original_script'].includes(k)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const hasContent = aliases.length > 0 || programs.length > 0 ||
|
|
49
|
+
addresses.length > 0 || ids.length > 0 || otherKeys.length > 0
|
|
50
|
+
|
|
51
|
+
if (!hasContent) return null
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
onClick={() => setExpanded(!expanded)}
|
|
58
|
+
className="w-full flex items-center justify-between cursor-pointer group"
|
|
59
|
+
>
|
|
60
|
+
<h4 className="sf-caption font-semibold text-white/50 uppercase tracking-wider">
|
|
61
|
+
Metadata
|
|
62
|
+
</h4>
|
|
63
|
+
<span className="sf-caption text-white/30 group-hover:text-white/50 transition-colors">
|
|
64
|
+
{expanded ? 'Hide' : 'Show'}
|
|
65
|
+
</span>
|
|
66
|
+
</button>
|
|
67
|
+
|
|
68
|
+
{expanded && (
|
|
69
|
+
<div className="mt-md space-y-sm animate-fade-in">
|
|
70
|
+
{aliases.length > 0 && (
|
|
71
|
+
<div className="flex flex-wrap items-center gap-sm">
|
|
72
|
+
<span className="sf-caption text-white/40 w-32 shrink-0">Aliases</span>
|
|
73
|
+
{aliases.map((a) => <Badge key={a} color="gray" size="sm">{a}</Badge>)}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
{programs.length > 0 && (
|
|
77
|
+
<div className="flex flex-wrap items-center gap-sm">
|
|
78
|
+
<span className="sf-caption text-white/40 w-32 shrink-0">Programs</span>
|
|
79
|
+
{programs.map((p) => <Badge key={p} color="blue" size="sm">{p}</Badge>)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
{addresses.length > 0 && addresses.map((a, i) => (
|
|
83
|
+
<MetadataRow key={`addr-${i}`} label="Address" value={a} />
|
|
84
|
+
))}
|
|
85
|
+
{ids.length > 0 && ids.map((id, i) => (
|
|
86
|
+
<MetadataRow key={`id-${i}`} label="ID" value={id} />
|
|
87
|
+
))}
|
|
88
|
+
{otherKeys.map((k) => (
|
|
89
|
+
<MetadataRow key={k} label={k} value={renderValue(metadata[k])} />
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|