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,80 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { getSeats, addSeat, removeSeat } from '../../api/billing'
|
|
4
|
+
import { SeatRow } from './SeatRow'
|
|
5
|
+
import { Seat } from '../../types/billing'
|
|
6
|
+
import { Plus } from 'lucide-react'
|
|
7
|
+
|
|
8
|
+
export function SeatManager() {
|
|
9
|
+
const { t } = useTranslation('billing')
|
|
10
|
+
const [seats, setSeats] = useState<Seat[]>([])
|
|
11
|
+
const [email, setEmail] = useState('')
|
|
12
|
+
const [role, setRole] = useState('Investigator')
|
|
13
|
+
const [loading, setLoading] = useState(true)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
getSeats()
|
|
17
|
+
.then(s => setSeats(s))
|
|
18
|
+
.catch(() => setSeats([]))
|
|
19
|
+
.finally(() => setLoading(false))
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
const handleAdd = async () => {
|
|
23
|
+
if (!email) return
|
|
24
|
+
const newSeat = await addSeat(email, role)
|
|
25
|
+
setSeats([...seats, newSeat])
|
|
26
|
+
setEmail('')
|
|
27
|
+
setRole('Investigator')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const handleRemove = async (userId: string) => {
|
|
31
|
+
await removeSeat(userId)
|
|
32
|
+
setSeats(seats.filter(s => s.userId !== userId))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (loading) return <div className="sf-body text-apple-label-secondary">{t('seats.loading')}</div>
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="space-y-md">
|
|
39
|
+
<div className="flex flex-col sm:flex-row gap-xs">
|
|
40
|
+
<input
|
|
41
|
+
type="email"
|
|
42
|
+
value={email}
|
|
43
|
+
onChange={e => setEmail(e.target.value)}
|
|
44
|
+
placeholder={t('seats.email_placeholder')}
|
|
45
|
+
aria-label={t('seats.email_placeholder')}
|
|
46
|
+
className="flex-1 px-md py-sm bg-white/5 border border-white/10 rounded-apple-md sf-body text-white placeholder:text-apple-label-secondary focus:outline-none focus:ring-2 focus:ring-apple-blue focus:border-apple-blue"
|
|
47
|
+
/>
|
|
48
|
+
<div className="flex gap-xs">
|
|
49
|
+
<select
|
|
50
|
+
value={role}
|
|
51
|
+
onChange={e => setRole(e.target.value)}
|
|
52
|
+
aria-label={t('seats.role')}
|
|
53
|
+
className="flex-1 sm:flex-none px-md py-sm bg-white/5 border border-white/10 rounded-apple-md sf-body text-white focus:outline-none focus:ring-2 focus:ring-apple-blue focus:border-apple-blue"
|
|
54
|
+
>
|
|
55
|
+
<option>{t('seats.investigator')}</option>
|
|
56
|
+
<option>{t('seats.analyst')}</option>
|
|
57
|
+
<option>{t('seats.manager')}</option>
|
|
58
|
+
</select>
|
|
59
|
+
<button
|
|
60
|
+
onClick={handleAdd}
|
|
61
|
+
aria-label={t('seats.add')}
|
|
62
|
+
className="px-md py-sm bg-apple-blue hover:bg-apple-blue/80 text-white rounded-apple-md flex items-center gap-xs transition-colors cursor-pointer"
|
|
63
|
+
>
|
|
64
|
+
<Plus className="w-4 h-4" />
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="space-y-xs">
|
|
70
|
+
{seats.map(seat => (
|
|
71
|
+
<SeatRow key={seat.userId} seat={seat} onRemove={handleRemove} />
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<p className="sf-caption text-apple-label-secondary">
|
|
76
|
+
{t('seats.used', { current: seats.length, total: 3 })}
|
|
77
|
+
</p>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Seat } from '../../types/billing'
|
|
3
|
+
import { X } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
interface SeatRowProps {
|
|
6
|
+
seat: Seat
|
|
7
|
+
onRemove: (userId: string) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function SeatRow({ seat, onRemove }: SeatRowProps) {
|
|
11
|
+
const initials = (seat.email ?? '').split('@')[0].slice(0, 2).toUpperCase() || '??'
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex items-center justify-between p-md bg-white/5 rounded-apple-md hover:bg-white/10 transition-colors">
|
|
15
|
+
<div className="flex items-center gap-md">
|
|
16
|
+
<div className="w-8 h-8 rounded-apple-md bg-apple-blue/20 flex items-center justify-center">
|
|
17
|
+
<span className="text-xs font-medium text-apple-blue">{initials}</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div>
|
|
20
|
+
<p className="sf-body text-white">{seat.email}</p>
|
|
21
|
+
<p className="sf-caption text-apple-label-secondary">{seat.role}</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => onRemove(seat.userId)}
|
|
26
|
+
className="p-xs hover:bg-red-600/20 rounded-apple-md text-red-400 transition-colors"
|
|
27
|
+
>
|
|
28
|
+
<X className="w-4 h-4" />
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { Subscription } from '../../types/billing'
|
|
4
|
+
import { Zap, LayoutDashboard, Code, Code2, FileText } from 'lucide-react'
|
|
5
|
+
import clsx from 'clsx'
|
|
6
|
+
|
|
7
|
+
interface SubscriptionCardProps {
|
|
8
|
+
subscription: Subscription
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const productIcons = {
|
|
12
|
+
api: Zap,
|
|
13
|
+
dashboard: LayoutDashboard,
|
|
14
|
+
sdk: Code,
|
|
15
|
+
iframe: Code2,
|
|
16
|
+
dataset: FileText
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const productNames = {
|
|
20
|
+
api: 'API Access',
|
|
21
|
+
dashboard: 'Dashboard',
|
|
22
|
+
sdk: 'SDK',
|
|
23
|
+
iframe: 'iFrame Widget',
|
|
24
|
+
dataset: 'Dataset CSV'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function SubscriptionCard({ subscription }: SubscriptionCardProps) {
|
|
28
|
+
const { t } = useTranslation('billing')
|
|
29
|
+
const Icon = productIcons[subscription.product as keyof typeof productIcons] ?? Zap
|
|
30
|
+
const name = productNames[subscription.product as keyof typeof productNames] ?? subscription.product
|
|
31
|
+
const price = subscription.plan?.monthlyPrice ?? 0
|
|
32
|
+
const renewDate = new Date(subscription.currentPeriodEnd)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="rounded-apple-lg border border-white/10 bg-white/5 p-lg hover:border-white/20 transition-colors">
|
|
36
|
+
<div className="flex items-start justify-between mb-md">
|
|
37
|
+
<div className="flex items-center gap-md">
|
|
38
|
+
<div className="p-md bg-apple-blue/20 rounded-apple-md">
|
|
39
|
+
<Icon className="w-5 h-5 text-apple-blue" />
|
|
40
|
+
</div>
|
|
41
|
+
<div>
|
|
42
|
+
<h3 className="sf-body font-medium text-white">{name}</h3>
|
|
43
|
+
<p className="sf-caption text-apple-label-secondary">{subscription.plan?.name ?? 'Free'} plan</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<span className={clsx('text-xs font-medium px-md py-xs rounded-apple-md', subscription.status === 'active' ? 'bg-green-600/20 text-green-400' : 'bg-yellow-600/20 text-yellow-400')}>
|
|
47
|
+
{subscription.status}
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div className="space-y-sm mb-lg">
|
|
52
|
+
<div className="flex justify-between">
|
|
53
|
+
<span className="sf-caption text-apple-label-secondary">{t('subscriptions.monthly_cost')}</span>
|
|
54
|
+
<span className="sf-body text-white">${price}</span>
|
|
55
|
+
</div>
|
|
56
|
+
{subscription.seatCount && (
|
|
57
|
+
<div className="flex justify-between">
|
|
58
|
+
<span className="sf-caption text-apple-label-secondary">{t('subscriptions.active_seats')}</span>
|
|
59
|
+
<span className="sf-body text-white">{subscription.seatCount}</span>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
<div className="flex justify-between">
|
|
63
|
+
<span className="sf-caption text-apple-label-secondary">{t('subscriptions.renews')}</span>
|
|
64
|
+
<span className="sf-body text-white">{renewDate.toLocaleDateString()}</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<button className="w-full px-md py-sm bg-white/10 hover:bg-white/20 text-white rounded-apple-md sf-body transition-colors cursor-pointer">
|
|
69
|
+
{t('subscriptions.manage')}
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { X } from 'lucide-react'
|
|
4
|
+
import { Plan } from '../../types/billing'
|
|
5
|
+
|
|
6
|
+
interface UpgradeModalProps {
|
|
7
|
+
visible: boolean
|
|
8
|
+
plan: Plan | null
|
|
9
|
+
onConfirm: () => void
|
|
10
|
+
onCancel: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function UpgradeModal({ visible, plan, onConfirm, onCancel }: UpgradeModalProps) {
|
|
14
|
+
const { t } = useTranslation('billing')
|
|
15
|
+
if (!visible || !plan) return null
|
|
16
|
+
|
|
17
|
+
const price = plan.monthlyPrice.toFixed(2)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
|
|
21
|
+
role="dialog" aria-modal="true" aria-labelledby="upgrade-title"
|
|
22
|
+
onKeyDown={(e) => { if (e.key === 'Escape') onCancel() }}>
|
|
23
|
+
<div className="bg-apple-bg rounded-xl border border-white/[0.06] p-6 max-w-md w-full mx-4">
|
|
24
|
+
<div className="flex items-center justify-between mb-4">
|
|
25
|
+
<h3 id="upgrade-title" className="text-lg font-semibold text-white">
|
|
26
|
+
{t('upgrade.title', { plan: plan.name })}
|
|
27
|
+
</h3>
|
|
28
|
+
<button type="button" onClick={onCancel} aria-label={t('upgrade.cancel')}
|
|
29
|
+
className="text-zinc-400 hover:text-white cursor-pointer">
|
|
30
|
+
<X size={20} />
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
<p className="text-sm text-zinc-400 mb-6">{t('upgrade.description', { price })}</p>
|
|
34
|
+
<div className="bg-white/5 rounded-lg p-4 mb-6">
|
|
35
|
+
<p className="text-xs text-zinc-400">{t('upgrade.new_limit')}</p>
|
|
36
|
+
<p className="text-xl font-bold text-white">
|
|
37
|
+
{t('upgrade.screenings_mo', { count: ((plan.limits['screenings'] ?? 0) / 1000).toFixed(0) })}
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="flex flex-col-reverse sm:flex-row gap-3">
|
|
41
|
+
<button type="button" onClick={onCancel}
|
|
42
|
+
className="flex-1 px-4 py-2 border border-white/10 text-white rounded-lg hover:bg-white/5 transition-colors cursor-pointer">
|
|
43
|
+
{t('upgrade.cancel')}
|
|
44
|
+
</button>
|
|
45
|
+
<button type="button" onClick={onConfirm}
|
|
46
|
+
className="flex-1 px-4 py-2 bg-apple-blue hover:bg-apple-blue/80 text-white rounded-lg transition-colors font-medium cursor-pointer">
|
|
47
|
+
{t('upgrade.confirm')}
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { api } from '../../api/client'
|
|
4
|
+
|
|
5
|
+
interface UsagePoint { month: string; screenings: number }
|
|
6
|
+
|
|
7
|
+
export default function UsageHistory() {
|
|
8
|
+
const { t } = useTranslation('billing')
|
|
9
|
+
const [history, setHistory] = useState<UsagePoint[]>([])
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
api.get<UsagePoint[]>('/billing/usage/history')
|
|
12
|
+
.then(setHistory)
|
|
13
|
+
.catch(() => setHistory([]))
|
|
14
|
+
}, [])
|
|
15
|
+
|
|
16
|
+
const maxScreenings = Math.max(...history.map(h => h.screenings), 1)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="rounded-apple-lg border border-white/10 bg-white/5 p-lg">
|
|
20
|
+
<h3 className="sf-body font-medium text-white mb-lg">{t('usage.history_title')}</h3>
|
|
21
|
+
<div className="h-64 flex items-end justify-between gap-sm">
|
|
22
|
+
{history.map((point, i) => (
|
|
23
|
+
<div key={i} className="flex-1 flex flex-col items-center gap-sm">
|
|
24
|
+
<div className="w-full h-64 flex flex-col items-center justify-end">
|
|
25
|
+
<div
|
|
26
|
+
className="w-full bg-gradient-to-t from-apple-blue to-blue-400 rounded-t-apple-md"
|
|
27
|
+
style={{ height: `${(point.screenings / maxScreenings) * 100}%` }}
|
|
28
|
+
title={`${point.screenings} screenings`}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
<span className="sf-caption text-xs">{point.month}</span>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
interface UsageMeterProps {
|
|
4
|
+
label: string
|
|
5
|
+
current: number
|
|
6
|
+
max: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function UsageMeter({ label, current, max }: UsageMeterProps) {
|
|
10
|
+
const percentage = (current / max) * 100
|
|
11
|
+
const color = percentage < 80 ? 'bg-green-600' : percentage < 95 ? 'bg-amber-600' : 'bg-red-600'
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div>
|
|
15
|
+
<div className="flex items-center justify-between mb-2">
|
|
16
|
+
<label className="text-sm font-medium text-zinc-400">{label}</label>
|
|
17
|
+
<span className="text-xs text-zinc-400">
|
|
18
|
+
{current.toLocaleString()} / {max.toLocaleString()} ({percentage.toFixed(1)}%)
|
|
19
|
+
</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div className="w-full h-2 bg-white/10 rounded-full overflow-hidden">
|
|
22
|
+
<div className={`h-full ${color} transition-all duration-500`} style={{ width: `${Math.min(percentage, 100)}%` }} />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import UsageMeter from './UsageMeter'
|
|
4
|
+
import { UsageRecord } from '../../types/billing'
|
|
5
|
+
import { getUsage } from '../../api/billing'
|
|
6
|
+
|
|
7
|
+
export default function UsageOverview() {
|
|
8
|
+
const { t } = useTranslation('billing')
|
|
9
|
+
const [usage, setUsage] = useState<UsageRecord | null>(null)
|
|
10
|
+
const [loading, setLoading] = useState(true)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
getUsage('dashboard').then(setUsage).finally(() => setLoading(false))
|
|
14
|
+
}, [])
|
|
15
|
+
|
|
16
|
+
if (loading || !usage) return <div className="animate-pulse h-32 bg-white/5 rounded-xl" />
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="bg-white/[0.02] rounded-xl border border-white/[0.06] p-6 space-y-6">
|
|
20
|
+
<div>
|
|
21
|
+
<h3 className="text-lg font-semibold text-white mb-1">{t('usage.current_usage')}</h3>
|
|
22
|
+
<p className="text-sm text-zinc-400">{usage.product}</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
{usage.metrics.map(m => (
|
|
26
|
+
<UsageMeter key={m.name} label={m.name} current={m.current} max={m.limit} />
|
|
27
|
+
))}
|
|
28
|
+
|
|
29
|
+
{usage.metrics.some(m => m.current > m.limit * 0.9) && (
|
|
30
|
+
<div className="bg-amber-900/20 border border-amber-800 rounded-lg p-4">
|
|
31
|
+
<p className="text-sm text-amber-200">
|
|
32
|
+
{t('usage.warning', { percent: 90 })}
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
|
|
4
|
+
export default function PrivacyPolicy() {
|
|
5
|
+
const { t } = useTranslation('legal')
|
|
6
|
+
const sections = t('privacy.sections', { returnObjects: true }) as Array<{
|
|
7
|
+
title: string; body: string
|
|
8
|
+
}>
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<article className="max-w-3xl mx-auto px-6 py-20">
|
|
12
|
+
<h1 className="text-4xl font-bold text-white mb-2">{t('privacy.title')}</h1>
|
|
13
|
+
<p className="text-sm text-zinc-400 mb-12">{t('privacy.last_updated')}</p>
|
|
14
|
+
|
|
15
|
+
<div className="space-y-8 text-zinc-400 leading-relaxed">
|
|
16
|
+
{sections.map((section, i) => (
|
|
17
|
+
<section key={i}>
|
|
18
|
+
<h2 className="text-lg font-semibold text-white mb-3">{section.title}</h2>
|
|
19
|
+
<p>{section.body}</p>
|
|
20
|
+
</section>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
</article>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
|
|
4
|
+
export default function TermsOfService() {
|
|
5
|
+
const { t } = useTranslation('legal')
|
|
6
|
+
const sections = t('terms.sections', { returnObjects: true }) as Array<{
|
|
7
|
+
title: string; body: string
|
|
8
|
+
}>
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<article className="max-w-3xl mx-auto px-6 py-20">
|
|
12
|
+
<h1 className="text-4xl font-bold text-white mb-2">{t('terms.title')}</h1>
|
|
13
|
+
<p className="text-sm text-zinc-400 mb-12">{t('terms.last_updated')}</p>
|
|
14
|
+
|
|
15
|
+
<div className="space-y-8 text-zinc-400 leading-relaxed">
|
|
16
|
+
{sections.map((section, i) => (
|
|
17
|
+
<section key={i}>
|
|
18
|
+
<h2 className="text-lg font-semibold text-white mb-3">{section.title}</h2>
|
|
19
|
+
<p>{section.body}</p>
|
|
20
|
+
</section>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
</article>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { Star } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
export function BundleCallout() {
|
|
6
|
+
const { t } = useTranslation('marketing')
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="bg-gradient-to-r from-apple-blue/20 to-purple-600/20 border border-apple-blue/30 rounded-apple-lg p-lg mx-auto max-w-2xl">
|
|
10
|
+
<div className="flex items-start gap-md">
|
|
11
|
+
<Star className="w-5 h-5 text-apple-blue flex-shrink-0 mt-sm" />
|
|
12
|
+
<div>
|
|
13
|
+
<h3 className="sf-headline text-white mb-xs">{t('bundle.title')}</h3>
|
|
14
|
+
<p className="sf-body text-apple-label-secondary mb-lg">
|
|
15
|
+
{t('bundle.description')}
|
|
16
|
+
</p>
|
|
17
|
+
<button type="button" className="px-lg py-sm bg-apple-blue hover:bg-apple-blue/80 text-white rounded-apple-md sf-body font-medium transition-colors">
|
|
18
|
+
{t('bundle.cta')}
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useNavigate } from 'react-router-dom'
|
|
2
|
+
import { motion } from 'framer-motion'
|
|
3
|
+
import { FadeUp } from './animations'
|
|
4
|
+
|
|
5
|
+
export default function CTASection() {
|
|
6
|
+
const navigate = useNavigate()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<section className="py-28 px-4">
|
|
10
|
+
<div className="max-w-3xl mx-auto text-center relative">
|
|
11
|
+
<div className="absolute -inset-20 rounded-3xl opacity-20 pointer-events-none"
|
|
12
|
+
style={{ background: 'radial-gradient(ellipse, rgba(0,184,148,0.15), transparent 70%)' }} />
|
|
13
|
+
<FadeUp>
|
|
14
|
+
<div className="relative rounded-3xl p-8 sm:p-14 overflow-hidden"
|
|
15
|
+
style={{
|
|
16
|
+
background: 'linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%)',
|
|
17
|
+
border: '1px solid rgba(0,0,0,0.06)',
|
|
18
|
+
boxShadow: '0 20px 60px rgba(0,0,0,0.06)',
|
|
19
|
+
}}>
|
|
20
|
+
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-[#0F172A] mb-5"
|
|
21
|
+
style={{ letterSpacing: '-0.03em' }}>
|
|
22
|
+
Ready to modernize your compliance?
|
|
23
|
+
</h2>
|
|
24
|
+
<p className="text-[#64748B] mb-10 max-w-lg mx-auto text-[15px]">
|
|
25
|
+
100 free screenings/month. No credit card required.
|
|
26
|
+
Deploy in minutes, not months.
|
|
27
|
+
</p>
|
|
28
|
+
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
29
|
+
<motion.button type="button" onClick={() => navigate('/signup')}
|
|
30
|
+
className="px-8 py-4 bg-gradient-to-r from-[#00B894] to-[#059669] text-white font-semibold rounded-xl text-sm cursor-pointer"
|
|
31
|
+
whileHover={{ scale: 1.04, boxShadow: '0 8px 30px rgba(0,184,148,0.35)' }}
|
|
32
|
+
whileTap={{ scale: 0.97 }}>
|
|
33
|
+
Start Free
|
|
34
|
+
</motion.button>
|
|
35
|
+
<motion.button type="button"
|
|
36
|
+
onClick={() => window.open('https://calendly.com/amliq', '_blank')}
|
|
37
|
+
className="px-8 py-4 bg-gradient-to-r from-[#6366F1] to-[#8B5CF6] text-white font-semibold rounded-xl text-sm cursor-pointer"
|
|
38
|
+
whileHover={{ scale: 1.04, boxShadow: '0 8px 30px rgba(99,102,241,0.3)' }}
|
|
39
|
+
whileTap={{ scale: 0.97 }}>
|
|
40
|
+
Book a Demo
|
|
41
|
+
</motion.button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</FadeUp>
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { motion } from 'framer-motion'
|
|
2
|
+
import { FadeUp, Parallax } from './animations'
|
|
3
|
+
|
|
4
|
+
export default function CaseStudy() {
|
|
5
|
+
return (
|
|
6
|
+
<section className="py-28 px-4 relative overflow-hidden">
|
|
7
|
+
<Parallax speed={0.2}>
|
|
8
|
+
<div className="absolute inset-0 opacity-30"
|
|
9
|
+
style={{ background: 'radial-gradient(ellipse at 50% 50%, rgba(0,184,148,0.08), transparent 60%)' }} />
|
|
10
|
+
</Parallax>
|
|
11
|
+
<div className="max-w-3xl mx-auto text-center relative z-10">
|
|
12
|
+
<FadeUp>
|
|
13
|
+
<p className="text-xs uppercase tracking-[0.2em] text-[#00B894] font-semibold mb-10">
|
|
14
|
+
Case Study
|
|
15
|
+
</p>
|
|
16
|
+
<blockquote>
|
|
17
|
+
<motion.p
|
|
18
|
+
className="text-2xl sm:text-3xl lg:text-4xl font-bold leading-snug mb-10 text-[#0F172A]"
|
|
19
|
+
style={{ letterSpacing: '-0.02em' }}>
|
|
20
|
+
“AMLIQ reduced our false positives by 70% while cutting screening costs by 80%.”
|
|
21
|
+
</motion.p>
|
|
22
|
+
</blockquote>
|
|
23
|
+
<div className="flex items-center justify-center gap-4">
|
|
24
|
+
<div className="w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold
|
|
25
|
+
bg-gradient-to-br from-[#00B894] to-[#059669] text-white">
|
|
26
|
+
JR
|
|
27
|
+
</div>
|
|
28
|
+
<div className="text-left">
|
|
29
|
+
<p className="text-sm font-semibold text-[#0F172A]">James Richardson</p>
|
|
30
|
+
<p className="text-sm text-[#94A3B8]">Head of Compliance, Meridian Fintech</p>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</FadeUp>
|
|
34
|
+
</div>
|
|
35
|
+
</section>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Check, X } from 'lucide-react'
|
|
2
|
+
|
|
3
|
+
type Val = true | false | string
|
|
4
|
+
interface Row { feature: string; amliq: Val; worldCheck: Val; dowJones: Val; manual: Val }
|
|
5
|
+
|
|
6
|
+
const rows: Row[] = [
|
|
7
|
+
{ feature: 'Starting Price', amliq: '$299/mo', worldCheck: '$15K+/yr', dowJones: '$10K+/yr', manual: '$50K+/yr' },
|
|
8
|
+
{ feature: 'Screening Speed', amliq: '<50ms', worldCheck: '200-500ms', dowJones: '300-800ms', manual: 'Hours' },
|
|
9
|
+
{ feature: 'AI Matching Layers', amliq: '6 layers', worldCheck: '2 layers', dowJones: '1 layer', manual: '0' },
|
|
10
|
+
{ feature: 'Custom Thresholds', amliq: true, worldCheck: false, dowJones: false, manual: false },
|
|
11
|
+
{ feature: 'Explainable Results', amliq: true, worldCheck: false, dowJones: false, manual: true },
|
|
12
|
+
{ feature: 'Self-Hosted Option', amliq: true, worldCheck: false, dowJones: false, manual: true },
|
|
13
|
+
{ feature: 'REST API', amliq: true, worldCheck: true, dowJones: true, manual: false },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function Cell({ val, highlight }: { val: Val; highlight?: boolean }) {
|
|
17
|
+
if (val === true) return <Check size={16} className={highlight ? 'text-[#22C55E] mx-auto' : 'text-emerald-700 mx-auto'} style={highlight ? { filter: 'drop-shadow(0 0 4px rgba(34,197,94,0.5))' } : {}} />
|
|
18
|
+
if (val === false) return <X size={16} className="text-[#EF4444]/40 mx-auto" />
|
|
19
|
+
return <span className={`text-xs ${highlight ? 'text-cyan-300 font-semibold' : 'text-[#94A3B8]'}`}>{val}</span>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function ComparisonTable() {
|
|
23
|
+
const heads = ['Feature', 'AMLIQ', 'World-Check', 'Dow Jones', 'Manual']
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<section className="py-24 px-4">
|
|
27
|
+
<div className="max-w-5xl mx-auto">
|
|
28
|
+
<p className="text-sm text-cyan-400 font-mono text-center mb-3 uppercase tracking-widest">
|
|
29
|
+
// comparison
|
|
30
|
+
</p>
|
|
31
|
+
<h2 className="text-3xl sm:text-4xl font-bold text-[#F8FAFC] text-center mb-12">
|
|
32
|
+
See how AMLIQ compares
|
|
33
|
+
</h2>
|
|
34
|
+
|
|
35
|
+
<div className="overflow-x-auto -mx-4 px-4 sm:mx-0 sm:px-0 rounded-xl border border-cyan-400/10" style={{
|
|
36
|
+
background: 'rgba(255,255,255,0.01)',
|
|
37
|
+
backgroundImage: 'repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,255,0.01) 2px, rgba(0,255,255,0.01) 4px)',
|
|
38
|
+
}}>
|
|
39
|
+
<table className="w-full min-w-[540px]">
|
|
40
|
+
<thead>
|
|
41
|
+
<tr className="border-b border-cyan-400/10">
|
|
42
|
+
{heads.map((h, i) => (
|
|
43
|
+
<th key={h} className={`py-4 px-5 text-sm font-mono ${i === 1 ? 'text-cyan-400' : 'text-[#94A3B8]'} ${i === 0 ? 'text-left sticky left-0 bg-[#020617] z-10' : 'text-center'}`}
|
|
44
|
+
style={i === 1 ? { textShadow: '0 0 10px rgba(0,255,255,0.3)' } : {}}>
|
|
45
|
+
{h}
|
|
46
|
+
</th>
|
|
47
|
+
))}
|
|
48
|
+
</tr>
|
|
49
|
+
</thead>
|
|
50
|
+
<tbody>
|
|
51
|
+
{rows.map((r, i) => (
|
|
52
|
+
<tr key={r.feature} className={`border-b border-white/[0.03] ${i % 2 === 1 ? 'bg-white/[0.01]' : ''}`}>
|
|
53
|
+
<td className="py-3 px-5 text-sm text-[#94A3B8] sticky left-0 bg-[#020617] z-10 font-mono text-xs">{r.feature}</td>
|
|
54
|
+
<td className="py-3 text-center bg-cyan-400/[0.02]"><Cell val={r.amliq} highlight /></td>
|
|
55
|
+
<td className="py-3 text-center"><Cell val={r.worldCheck} /></td>
|
|
56
|
+
<td className="py-3 text-center"><Cell val={r.dowJones} /></td>
|
|
57
|
+
<td className="py-3 text-center"><Cell val={r.manual} /></td>
|
|
58
|
+
</tr>
|
|
59
|
+
))}
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Shield, Zap, Brain, Globe, DollarSign, Eye } from 'lucide-react'
|
|
2
|
+
import { motion } from 'framer-motion'
|
|
3
|
+
import { FadeUp } from './animations'
|
|
4
|
+
|
|
5
|
+
const advantages = [
|
|
6
|
+
{ icon: Zap, stat: '200x', label: 'Faster', detail: 'Sub-1ms in-memory screening vs 200ms industry average', gradient: 'from-amber-400 to-orange-500' },
|
|
7
|
+
{ icon: Brain, stat: '92%', label: 'Fewer false positives', detail: 'LLM cascade validated by Federal Reserve 2025 research', gradient: 'from-violet-400 to-purple-500' },
|
|
8
|
+
{ icon: Globe, stat: '2.2M+', label: 'Entity profiles', detail: 'Growing to 5M+ from 328 global sources + Wikidata + AI', gradient: 'from-blue-400 to-cyan-500' },
|
|
9
|
+
{ icon: Eye, stat: '6', label: 'Matching layers', detail: 'Exact, Fuzzy, Phonetic, Token, Embedding, Graph — fully explainable', gradient: 'from-emerald-400 to-teal-500' },
|
|
10
|
+
{ icon: DollarSign, stat: '100x', label: 'Cheaper', detail: '$299/mo vs $30K-500K/year for legacy providers', gradient: 'from-green-400 to-emerald-500' },
|
|
11
|
+
{ icon: Shield, stat: '30+', label: 'Country lists', detail: 'OFAC, UN, EU, UK, IL NBCTF + 25 country-specific sanctions lists', gradient: 'from-rose-400 to-pink-500' },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export default function CompetitiveEdge() {
|
|
15
|
+
return (
|
|
16
|
+
<section className="py-28 px-4">
|
|
17
|
+
<div className="max-w-6xl mx-auto">
|
|
18
|
+
<FadeUp>
|
|
19
|
+
<div className="text-center mb-20">
|
|
20
|
+
<p className="text-xs uppercase tracking-[0.2em] text-[#00B894] font-semibold mb-4">
|
|
21
|
+
Why compliance teams switch to AMLIQ
|
|
22
|
+
</p>
|
|
23
|
+
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-[#0F172A]"
|
|
24
|
+
style={{ letterSpacing: '-0.03em' }}>
|
|
25
|
+
Outperform legacy screening
|
|
26
|
+
</h2>
|
|
27
|
+
</div>
|
|
28
|
+
</FadeUp>
|
|
29
|
+
|
|
30
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
|
31
|
+
{advantages.map((a, i) => (
|
|
32
|
+
<FadeUp key={a.label} delay={i * 0.08}>
|
|
33
|
+
<motion.div
|
|
34
|
+
className="relative p-6 rounded-2xl cursor-default overflow-hidden group"
|
|
35
|
+
style={{ background: '#FAFBFC', border: '1px solid rgba(0,0,0,0.06)' }}
|
|
36
|
+
whileHover={{ y: -4, boxShadow: '0 12px 40px rgba(0,0,0,0.08)' }}
|
|
37
|
+
transition={{ type: 'spring', stiffness: 300, damping: 20 }}>
|
|
38
|
+
<div className="flex items-center gap-3 mb-4">
|
|
39
|
+
<div className={`w-10 h-10 rounded-xl flex items-center justify-center bg-gradient-to-br ${a.gradient} opacity-90`}>
|
|
40
|
+
<a.icon size={18} color="white" />
|
|
41
|
+
</div>
|
|
42
|
+
<span className="text-3xl font-extrabold bg-gradient-to-r from-[#00B894] to-[#059669] bg-clip-text text-transparent">
|
|
43
|
+
{a.stat}
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
<p className="text-[#0F172A] font-semibold text-sm mb-1">{a.label}</p>
|
|
47
|
+
<p className="text-[#94A3B8] text-xs leading-relaxed">{a.detail}</p>
|
|
48
|
+
</motion.div>
|
|
49
|
+
</FadeUp>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</section>
|
|
54
|
+
)
|
|
55
|
+
}
|