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