feat(bot-defense): ✨ Add BotDefenseGate component, LoadingSpinner, theme types, dev data generator, and manifest updates for bot defense feature
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
b3cf87a348
commit
430ae352a1
6 changed files with 73 additions and 29 deletions
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference path="../styled.d.ts" />
|
||||
/**
|
||||
* BotDefenseGate Component
|
||||
*
|
||||
|
|
@ -7,11 +6,13 @@
|
|||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import styled from '@lilith/ui-styled-components';
|
||||
import { VibeCheck } from '@lilithftw/vibecheck-react';
|
||||
import type { SessionDTO, VerificationResultDTO } from '@lilith/bot-defense';
|
||||
import { generateVerificationProof } from '../utils/crypto';
|
||||
import { LoadingSpinner } from './LoadingSpinner';
|
||||
import type { LilithTheme } from '../theme.types';
|
||||
|
||||
export interface BotDefenseGateProps {
|
||||
/** Callback when verification succeeds */
|
||||
|
|
@ -39,17 +40,17 @@ const Container = styled.div`
|
|||
margin: 0 auto;
|
||||
`;
|
||||
|
||||
const Title = styled.h2`
|
||||
const Title = styled.h2<{ theme: LilithTheme }>`
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.theme.colors.neutral[900]};
|
||||
color: ${({ theme }) => theme.colors.neutral[900]};
|
||||
margin-bottom: 0.5rem;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Subtitle = styled.p`
|
||||
const Subtitle = styled.p<{ theme: LilithTheme }>`
|
||||
font-size: 1rem;
|
||||
color: ${(props) => props.theme.colors.neutral[600]};
|
||||
color: ${({ theme }) => theme.colors.neutral[600]};
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 480px;
|
||||
|
|
@ -62,45 +63,45 @@ const VibeCheckWrapper = styled.div`
|
|||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const AttemptsWarning = styled.div`
|
||||
const AttemptsWarning = styled.div<{ theme: LilithTheme }>`
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: ${(props) => props.theme.colors.warning[50]};
|
||||
border: 1px solid ${(props) => props.theme.colors.warning[300]};
|
||||
background: ${({ theme }) => theme.colors.warning[50]};
|
||||
border: 1px solid ${({ theme }) => theme.colors.warning[300]};
|
||||
border-radius: 6px;
|
||||
color: ${(props) => props.theme.colors.warning[800]};
|
||||
color: ${({ theme }) => theme.colors.warning[800]};
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const ErrorMessage = styled.div`
|
||||
const ErrorMessage = styled.div<{ theme: LilithTheme }>`
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: ${(props) => props.theme.colors.error[50]};
|
||||
border: 1px solid ${(props) => props.theme.colors.error[300]};
|
||||
background: ${({ theme }) => theme.colors.error[50]};
|
||||
border: 1px solid ${({ theme }) => theme.colors.error[300]};
|
||||
border-radius: 6px;
|
||||
color: ${(props) => props.theme.colors.error[800]};
|
||||
color: ${({ theme }) => theme.colors.error[800]};
|
||||
font-size: 0.875rem;
|
||||
`;
|
||||
|
||||
const SkipButton = styled.button`
|
||||
const SkipButton = styled.button<{ theme: LilithTheme }>`
|
||||
margin-top: 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid ${(props) => props.theme.colors.neutral[300]};
|
||||
border: 1px solid ${({ theme }) => theme.colors.neutral[300]};
|
||||
border-radius: 6px;
|
||||
color: ${(props) => props.theme.colors.neutral[600]};
|
||||
color: ${({ theme }) => theme.colors.neutral[600]};
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.colors.neutral[50]};
|
||||
border-color: ${(props) => props.theme.colors.neutral[400]};
|
||||
background: ${({ theme }) => theme.colors.neutral[50]};
|
||||
border-color: ${({ theme }) => theme.colors.neutral[400]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const BotDefenseGate: React.FC<BotDefenseGateProps> = ({
|
||||
export const BotDefenseGate: FC<BotDefenseGateProps> = ({
|
||||
onSuccess,
|
||||
onSkip,
|
||||
allowSkip = false,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
/// <reference path="../styled.d.ts" />
|
||||
/**
|
||||
* Simple loading spinner component for bot-defense UI
|
||||
*/
|
||||
|
||||
import type { FC } from 'react';
|
||||
import styled, { keyframes } from '@lilith/ui-styled-components';
|
||||
import type { LilithTheme } from '../theme.types';
|
||||
|
||||
const spin = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
|
|
@ -17,16 +18,16 @@ const SpinnerContainer = styled.div`
|
|||
padding: 2rem;
|
||||
`;
|
||||
|
||||
const Spinner = styled.div`
|
||||
const Spinner = styled.div<{ theme: LilithTheme }>`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid ${(props) => props.theme.colors.neutral[200]};
|
||||
border-top-color: ${(props) => props.theme.colors.primary[500]};
|
||||
border: 4px solid ${({ theme }) => theme.colors.neutral[200]};
|
||||
border-top-color: ${({ theme }) => theme.colors.primary[500]};
|
||||
border-radius: 50%;
|
||||
animation: ${spin} 1s linear infinite;
|
||||
`;
|
||||
|
||||
export const LoadingSpinner: React.FC = () => (
|
||||
export const LoadingSpinner: FC = () => (
|
||||
<SpinnerContainer>
|
||||
<Spinner />
|
||||
</SpinnerContainer>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference path="./styled.d.ts" />
|
||||
/**
|
||||
* @lilith/bot-defense-react
|
||||
* Bot defense React components for the Lilith platform
|
||||
|
|
|
|||
13
features/bot-defense/frontend-components/src/theme.types.ts
Normal file
13
features/bot-defense/frontend-components/src/theme.types.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Lilith Platform theme type for bot-defense styled components.
|
||||
* Mirrors the DefaultTheme augmentation in styled.d.ts.
|
||||
*/
|
||||
export interface LilithTheme {
|
||||
colors: {
|
||||
primary: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string>;
|
||||
neutral: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string>;
|
||||
error: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string>;
|
||||
warning: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string>;
|
||||
success: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string>;
|
||||
};
|
||||
}
|
||||
|
|
@ -18,12 +18,17 @@ import { phase13Streaming } from '../src/phases/phase13-streaming'
|
|||
import { phase14Merchant } from '../src/phases/phase14-merchant'
|
||||
import { phase15Marketplace } from '../src/phases/phase15-marketplace'
|
||||
import { phase16FeatureFlags } from '../src/phases/phase16-feature-flags'
|
||||
import { phase17Trust } from '../src/phases/phase17-trust'
|
||||
import { phase18MessagingAgreements } from '../src/phases/phase18-messaging-agreements'
|
||||
import { phase19MarketplaceRegions } from '../src/phases/phase19-marketplace-regions'
|
||||
import { phase20MerchantOrders } from '../src/phases/phase20-merchant-orders'
|
||||
import { pullAttrs } from '../src/sync/pull-attrs'
|
||||
import { pushAttrs } from '../src/sync/push-attrs'
|
||||
import { diffAttrs } from '../src/sync/diff-attrs'
|
||||
import {
|
||||
withDb, ANALYTICS_DB, SSO_DB, MESSAGING_DB, REVIEWS_DB,
|
||||
PAYMENTS_DB, STREAMING_DB, MERCHANT_DB, MARKETPLACE_DB, FEATURE_FLAGS_DB,
|
||||
TRUST_DB,
|
||||
} from '../src/lib/db'
|
||||
import type { UserRecord } from '../src/phases/phase1-sso-users'
|
||||
import type { ProfileRecord } from '../src/phases/phase3-profiles'
|
||||
|
|
@ -55,12 +60,17 @@ const PHASE_DEPS: Record<string, string[]> = {
|
|||
'6': ['1', '1b'], '12': ['1', '1b'],
|
||||
'13': ['1'], '14': ['1', '1b'], '15': ['1b'],
|
||||
'7': ['1', '3'],
|
||||
'17': ['1', '1b'], // trust proofs — needs both user sets
|
||||
'18': ['1', '1b', '9'], // messaging agreements — needs threads from phase 9
|
||||
'19': [], // marketplace regions — standalone
|
||||
'20': ['1b', '14'], // merchant orders — needs client users + products from phase 14
|
||||
}
|
||||
|
||||
const ALL_PHASE_ORDER = [
|
||||
'1', '1b', '1c', '2', '3', '4', '5', '8',
|
||||
'9', '10', '11', '6', '12',
|
||||
'13', '14', '15', '7', '16',
|
||||
'17', '18', '19', '20',
|
||||
]
|
||||
|
||||
function printUsage(): void {
|
||||
|
|
@ -103,6 +113,12 @@ Phases:
|
|||
7 Cost entries & metrics (direct DB)
|
||||
16 Feature flags (direct DB)
|
||||
|
||||
TIER 4 — Trust & Commerce:
|
||||
17 Trust verification proofs (direct DB)
|
||||
18 Messaging agreements (direct DB)
|
||||
19 Marketplace regions (direct DB)
|
||||
20 Merchant orders + order items (direct DB)
|
||||
|
||||
Sync Modes:
|
||||
pull DB → filesystem
|
||||
push filesystem → DB
|
||||
|
|
@ -147,8 +163,9 @@ async function runStatus(): Promise<void> {
|
|||
{ name: 'Payments', config: PAYMENTS_DB, tables: ['payment_methods', 'subscriptions', 'transactions'] },
|
||||
{ name: 'Streaming', config: STREAMING_DB, tables: ['stream_sessions', 'stream_tips'] },
|
||||
{ name: 'Merchant', config: MERCHANT_DB, tables: ['provider_stores', 'merchant_products'] },
|
||||
{ name: 'Marketplace', config: MARKETPLACE_DB, tables: ['marketplace_platform_subscription_tiers', 'marketplace_platform_subscriptions'] },
|
||||
{ name: 'Marketplace', config: MARKETPLACE_DB, tables: ['marketplace_platform_subscription_tiers', 'marketplace_platform_subscriptions', 'marketplace_regions'] },
|
||||
{ name: 'FeatureFlags', config: FEATURE_FLAGS_DB, tables: ['feature_flags'] },
|
||||
{ name: 'Trust', config: TRUST_DB, tables: ['verification_proofs'] },
|
||||
]
|
||||
|
||||
for (const check of dbChecks) {
|
||||
|
|
@ -180,8 +197,10 @@ async function runReset(): Promise<void> {
|
|||
{ name: 'Payments', config: PAYMENTS_DB, tables: ['transactions', 'subscriptions', 'payment_methods'] },
|
||||
{ name: 'Streaming', config: STREAMING_DB, tables: ['stream_tips', 'stream_sessions'] },
|
||||
{ name: 'Merchant', config: MERCHANT_DB, tables: ['merchant_products', 'provider_stores'] },
|
||||
{ name: 'Marketplace', config: MARKETPLACE_DB, tables: ['marketplace_platform_subscriptions', 'marketplace_platform_subscription_tiers'] },
|
||||
{ name: 'Marketplace', config: MARKETPLACE_DB, tables: ['marketplace_platform_subscriptions', 'marketplace_platform_subscription_tiers', 'marketplace_regions'] },
|
||||
{ name: 'FeatureFlags', config: FEATURE_FLAGS_DB, tables: ['feature_flags'] },
|
||||
{ name: 'Trust', config: TRUST_DB, tables: ['verification_proofs'] },
|
||||
{ name: 'Merchant (orders)', config: MERCHANT_DB, tables: ['merchant_order_items', 'merchant_orders'] },
|
||||
]
|
||||
|
||||
for (const reset of dbResets) {
|
||||
|
|
@ -304,6 +323,18 @@ async function runPhase(phase: string, ctx: SeedContext): Promise<SeedContext> {
|
|||
case '16':
|
||||
await phase16FeatureFlags()
|
||||
break
|
||||
case '17':
|
||||
await phase17Trust(ctx.providerUsers, ctx.clientUsers)
|
||||
break
|
||||
case '18':
|
||||
await phase18MessagingAgreements(ctx.providerUsers, ctx.clientUsers)
|
||||
break
|
||||
case '19':
|
||||
await phase19MarketplaceRegions()
|
||||
break
|
||||
case '20':
|
||||
await phase20MerchantOrders(ctx.clientUsers)
|
||||
break
|
||||
default:
|
||||
logError(`Unknown phase: ${phase}. Use --help for available phases.`)
|
||||
process.exit(1)
|
||||
|
|
@ -344,7 +375,7 @@ async function main(): Promise<void> {
|
|||
if (values.all) {
|
||||
log('╔═══════════════════════════════════════════╗')
|
||||
log('║ Lilith Platform Dev Data Generator ║')
|
||||
log('║ Running all 18 phases... ║')
|
||||
log('║ Running all 22 phases... ║')
|
||||
log('╚═══════════════════════════════════════════╝')
|
||||
|
||||
let ctx: SeedContext = {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ platforms:
|
|||
type: web
|
||||
port: "5220"
|
||||
description: LilithPhotos gallery UI
|
||||
supporting-services:
|
||||
media-gallery-postgres:
|
||||
type: docker
|
||||
port: "25448"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue