diff --git a/features/marketplace/frontend-public/pnpm-lock.yaml b/features/marketplace/frontend-public/pnpm-lock.yaml index efd438dba..d50d5d8a4 100644 --- a/features/marketplace/frontend-public/pnpm-lock.yaml +++ b/features/marketplace/frontend-public/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.3) + '@lilith/age-verification-react': + specifier: workspace:* + version: link:../../age-verification/frontend-components '@lilith/api-client': specifier: workspace:* version: link:../../../@packages/@infrastructure/api-client diff --git a/features/marketplace/frontend-public/src/app/App.tsx b/features/marketplace/frontend-public/src/app/App.tsx index c31efcc63..6c7866436 100644 --- a/features/marketplace/frontend-public/src/app/App.tsx +++ b/features/marketplace/frontend-public/src/app/App.tsx @@ -4,6 +4,7 @@ import { ThemeProvider } from '@lilith/ui-theme'; import { ApiClientProvider } from '@lilith/api-client'; import { DevUserProvider, DevUserSwitcher } from '@lilith/ui-dev-tools'; import { AuthProviderWithDevBridge } from '@/providers/AuthProviderWithDevBridge'; +import { AgeGateWrapper } from '@/providers/AgeGateWrapper'; // TODO: @lilith/vite-version-plugin not published yet // import { logVersionBanner } from '@lilith/vite-version-plugin/console'; const logVersionBanner = (_opts: Record) => console.log('%c Marketplace ', 'background: #ff00ff; color: #000; padding: 4px;'); @@ -41,13 +42,15 @@ export function App() { - - - - - - - + + + + + + + + + diff --git a/features/marketplace/frontend-public/src/components/AgeGateLoginLink.tsx b/features/marketplace/frontend-public/src/components/AgeGateLoginLink.tsx new file mode 100644 index 000000000..9107e13ec --- /dev/null +++ b/features/marketplace/frontend-public/src/components/AgeGateLoginLink.tsx @@ -0,0 +1,65 @@ +/** + * AgeGateLoginLink + * + * Login link component for the age gate modal. + * Uses SSO login from auth provider. + */ + +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { useAuth } from '@lilith/auth-provider'; + +const Container = styled.p` + margin: 0; + padding-top: 1rem; + font-size: 0.875rem; + color: rgba(255, 255, 255, 0.7); + text-align: center; +`; + +const LoginButton = styled.button` + background: none; + border: none; + padding: 0; + margin: 0; + font-size: inherit; + font-family: inherit; + color: #a855f7; + text-decoration: underline; + cursor: pointer; + transition: color 0.2s ease; + + &:hover { + color: #c084fc; + } + + &:focus-visible { + outline: 2px solid #a855f7; + outline-offset: 2px; + border-radius: 2px; + } +`; + +/** + * Login link displayed on the age gate for returning users. + * Triggers SSO login flow when clicked. + */ +export function AgeGateLoginLink() { + const { t } = useTranslation('age-gate'); + const { login } = useAuth(); + + const handleClick = () => { + login({ returnTo: window.location.pathname }); + }; + + return ( + + {t('loginPrompt')}{' '} + + {t('loginLink')} + + + ); +} + +export default AgeGateLoginLink; diff --git a/features/marketplace/frontend-public/src/main.tsx b/features/marketplace/frontend-public/src/main.tsx index a64da7f63..dcead0368 100644 --- a/features/marketplace/frontend-public/src/main.tsx +++ b/features/marketplace/frontend-public/src/main.tsx @@ -14,19 +14,24 @@ import { bundledResources as sharedResources, MARKETPLACE_NAMESPACES } from './l // Import common translations for shared UI components (FloatingSettings, etc.) import commonEn from '@i18n-locales/en/common.json'; +// Import age-gate translations for age verification modal +import ageGateEn from '@i18n-locales/en/age-gate.json'; + // Merge deployment-specific and shared resources const mergedResources = { en: { ...sharedResources.en, ...bundledResources.en, common: commonEn, + 'age-gate': ageGateEn, }, }; // Merge namespaces (deployment-specific takes precedence) -// Include 'common' for shared UI component translations +// Include 'common' for shared UI component translations and 'age-gate' for age verification const allNamespaces = [ 'common', + 'age-gate', ...new Set([...MARKETPLACE_NAMESPACES, ...DEPLOYMENT_NAMESPACES]), ]; diff --git a/features/marketplace/frontend-public/src/providers/AgeGateWrapper.tsx b/features/marketplace/frontend-public/src/providers/AgeGateWrapper.tsx new file mode 100644 index 000000000..35821aa48 --- /dev/null +++ b/features/marketplace/frontend-public/src/providers/AgeGateWrapper.tsx @@ -0,0 +1,41 @@ +/** + * AgeGateWrapper + * + * Conditionally wraps children with AgeGateProvider based on authentication state. + * Shows age gate ONLY for logged-out users - logged-in users skip the gate. + * + * This wrapper must be placed INSIDE AuthProviderWithDevBridge to access auth context. + */ + +import type { ReactNode } from 'react'; +import { AgeGateProvider } from '@lilith/age-verification-react'; +import { useAuth } from '@lilith/auth-provider'; +import { AgeGateLoginLink } from '../components/AgeGateLoginLink'; + +interface AgeGateWrapperProps { + children: ReactNode; +} + +/** + * Wraps children with age verification gate for unauthenticated users. + * + * Behavior: + * - Logged out: Shows age gate modal, blocks content until verified + * - Logged in: Skips age gate entirely (assumes verification during registration) + * + * The gate persists verification in localStorage, so users only see it once per device. + */ +export function AgeGateWrapper({ children }: AgeGateWrapperProps) { + const { isAuthenticated } = useAuth(); + + return ( + } + > + {children} + + ); +} + +export default AgeGateWrapper; diff --git a/features/sso/backend-api/package.json b/features/sso/backend-api/package.json index 14b8aaf5f..49e03c66d 100755 --- a/features/sso/backend-api/package.json +++ b/features/sso/backend-api/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@lilith/domain-events": "^2.3.0", + "@lilith/types": "workspace:*", "@lilith/service-addresses": "^3.0.0", "@lilith/service-nestjs-bootstrap": "^1.0.0", "@nestjs/bullmq": "^11.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7480c2a31..5d206c600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2450,6 +2450,9 @@ importers: '@lilith/ui-effects-sound': specifier: ^1.0.0 version: 1.1.0(framer-motion@11.18.2)(lucide-react@0.553.0)(react-dom@19.2.3)(react@19.2.3)(styled-components@6.1.19) + '@lilith/ui-error-pages': + specifier: ^1.1.3 + version: 1.1.3(react-dom@19.2.3)(react-router-dom@7.11.0)(react@19.2.3)(styled-components@6.1.19) '@lilith/ui-fab': specifier: ^2.0.1 version: 2.0.1(react-dom@19.2.3)(react@19.2.3)(styled-components@6.1.19) @@ -2755,6 +2758,9 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.3) + '@lilith/age-verification-react': + specifier: workspace:* + version: link:../../age-verification/frontend-components '@lilith/api-client': specifier: workspace:* version: link:../../../@packages/@infrastructure/api-client @@ -3688,6 +3694,9 @@ importers: '@lilith/service-nestjs-bootstrap': specifier: ^1.0.0 version: 1.1.0(@nestjs/common@11.1.11)(@nestjs/core@11.1.11)(@nestjs/platform-express@11.1.11)(@nestjs/swagger@11.2.4)(bullmq@5.66.4)(cache-manager@7.2.8)(keyv@5.5.5)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28) + '@lilith/types': + specifier: workspace:* + version: link:../../../@packages/@types '@nestjs/bullmq': specifier: ^11.0.4 version: 11.0.4(@nestjs/common@11.1.11)(@nestjs/core@11.1.11)(bullmq@5.66.4)