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