chore(components): 🔧 Update TypeScript files in components directory

This commit is contained in:
Lilith 2026-01-25 16:24:08 -08:00
parent 5e27d20c7f
commit c0e2c41e2b
17 changed files with 566 additions and 6 deletions

View file

@ -18,8 +18,8 @@ import { InviteModalLayout } from './InviteModalLayout';
import { InviteModalTabs, type TabId } from './InviteModalTabs';
import { useSendInviteByEmail, type InvitationType } from '@/features/invite/hooks/useInvitation';
import { InvitationLinkGenerator } from '@/InvitationLinkGenerator';
import { InvitationTrackingList } from '@/InvitationTrackingList';
import { InvitationLinkGenerator } from '../InvitationLinkGenerator';
import { InvitationTrackingList } from '../InvitationTrackingList';
export interface InviteSendModalProps {
/** Whether the modal is open */

View file

@ -11,7 +11,7 @@
*/
/** @jsxImportSource react */
import type { FC } from 'react';
import type { FC, KeyboardEvent } from 'react';
import styled, { css, type DefaultTheme } from '@lilith/ui-styled-components';
@ -32,7 +32,7 @@ export const BillingCycleToggle: FC<BillingCycleToggleProps> = ({
}) => {
const showRecommended = annualDiscount > 15;
const handleKeyDown = (e: KeyboardEvent, option: 'monthly' | 'yearly') => {
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>, option: 'monthly' | 'yearly') => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (!disabled) {

View file

@ -8,8 +8,8 @@ import { useState } from 'react';
import styled from '@lilith/ui-styled-components';
import { AnnualDiscountBadge } from '@/AnnualDiscountBadge';
import { BillingCycleToggle } from '@/BillingCycleToggle';
import { AnnualDiscountBadge } from '../AnnualDiscountBadge';
import { BillingCycleToggle } from '../BillingCycleToggle';
export const BillingCycleDemo = () => {
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>('monthly');

View file

@ -0,0 +1,26 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"module": {
"type": "es6",
"resolveFully": true
},
"jsc": {
"target": "es2022",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"keepClassNames": true,
"baseUrl": "./src",
"paths": {
"@/*": ["./*"]
}
},
"minify": false
}

View file

@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"builder": "swc",
"typeCheck": false
}
}

View file

@ -0,0 +1,53 @@
{
"name": "@platform/analytics-api",
"version": "0.1.0",
"private": true,
"description": "Lilith Platform analytics API - gov-detection, gift analytics, profile metrics",
"type": "module",
"main": "./dist/main.js",
"scripts": {
"build": "nest build",
"dev": "nest start --watch",
"start": "node dist/main.js",
"start:prod": "NODE_ENV=production node dist/main.js",
"typecheck": "tsc --noEmit",
"verify": "pnpm build && node scripts/verify-circular-deps.mjs",
"lint": "eslint src/",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@lilith/domain-events": "^1.0.0",
"@lilith/gov-detection": "^1.0.0",
"@lilith/service-nestjs-bootstrap": "^3.0.0",
"@lilith/service-registry": "^2.0.0",
"@nestjs/bullmq": "^11.0.0",
"@nestjs/common": "^11.0.0",
"@nestjs/config": "^4.0.0",
"@nestjs/core": "^11.0.0",
"@nestjs/platform-express": "^11.0.0",
"@nestjs/schedule": "^6.0.0",
"@nestjs/swagger": "^11.0.0",
"@nestjs/terminus": "^11.0.0",
"@nestjs/typeorm": "^11.0.0",
"bullmq": "^5.0.0",
"class-transformer": "^0.5.0",
"class-validator": "^0.14.0",
"pg": "^8.11.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.0",
"typeorm": "^0.3.0"
},
"devDependencies": {
"@lilith/configs": "^2.2.1",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.0",
"@swc/cli": "^0.7.10",
"@swc/core": "^1.15.8",
"@types/express": "^5.0.0",
"@types/node": "^20.0.0",
"typescript": "^5.4.0",
"vitest": "^1.0.0"
}
}

View file

@ -0,0 +1,47 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HealthModule } from './modules/health/health.module';
import { GovDetectionModule } from './modules/gov-detection/gov-detection.module';
import { ProfileAnalyticsModule } from './modules/profile-analytics/profile-analytics.module';
import { GiftAnalyticsModule } from './modules/gift-analytics/gift-analytics.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.local', '.env'],
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: async (config: ConfigService) => {
const { getDatabaseConfig } = await import('@lilith/service-registry');
const dbConfig = getDatabaseConfig('platform-analytics', {
username: config.get('DATABASE_POSTGRES_USER'),
password: config.get('DATABASE_POSTGRES_PASSWORD'),
database: config.get('DATABASE_POSTGRES_NAME'),
});
return {
type: 'postgres',
host: dbConfig.host,
port: dbConfig.port,
username: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
autoLoadEntities: true,
synchronize: config.get('NODE_ENV') !== 'production',
logging: config.get('NODE_ENV') !== 'production',
};
},
}),
HealthModule,
GovDetectionModule,
ProfileAnalyticsModule,
GiftAnalyticsModule,
],
})
export class AppModule {}

View file

@ -0,0 +1,12 @@
import { bootstrapService } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';
bootstrapService({
module: AppModule,
serviceName: 'platform-analytics-api',
swagger: {
title: 'Platform Analytics API',
description: 'Lilith-specific analytics services - gov-detection, gift analytics, profile metrics',
version: '1.0.0',
},
});

View file

@ -0,0 +1,89 @@
import {
Controller,
Get,
Post,
Body,
Query,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
import { GovDetectionService } from './gov-detection.service';
import { DetectRequestDto, DetectResponseDto } from './gov-detection.dto';
@ApiTags('Government Detection')
@Controller('gov-detection')
export class GovDetectionController {
constructor(private readonly govDetectionService: GovDetectionService) {}
@Get('status')
@ApiOperation({ summary: 'Check if the gov-detection service is ready' })
@ApiResponse({
status: 200,
description: 'Service status',
schema: {
type: 'object',
properties: {
ready: { type: 'boolean' },
},
},
})
getStatus() {
return { ready: this.govDetectionService.isReady() };
}
@Post('detect')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Detect government connection from IP or email' })
@ApiResponse({
status: 200,
description: 'Detection result',
type: DetectResponseDto,
})
async detect(@Body() dto: DetectRequestDto): Promise<DetectResponseDto> {
const result = await this.govDetectionService.detectSimplified(
dto.ip,
dto.email,
);
return result;
}
@Get('detect')
@ApiOperation({ summary: 'Detect government connection (GET method)' })
@ApiQuery({ name: 'ip', required: false, description: 'IP address to check' })
@ApiQuery({
name: 'email',
required: false,
description: 'Email to check domain',
})
@ApiResponse({
status: 200,
description: 'Detection result',
type: DetectResponseDto,
})
async detectGet(
@Query('ip') ip?: string,
@Query('email') email?: string,
): Promise<DetectResponseDto> {
const result = await this.govDetectionService.detectSimplified(ip, email);
return result;
}
@Post('refresh')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Refresh detection data' })
@ApiResponse({
status: 200,
description: 'Data refreshed successfully',
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
},
},
})
async refresh() {
await this.govDetectionService.refreshData();
return { success: true };
}
}

View file

@ -0,0 +1,48 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString, IsIP, IsEmail } from 'class-validator';
export class DetectRequestDto {
@ApiPropertyOptional({ description: 'IP address to analyze' })
@IsOptional()
@IsString()
ip?: string;
@ApiPropertyOptional({ description: 'Email address to analyze domain' })
@IsOptional()
@IsEmail()
email?: string;
}
export class DetectResponseDto {
@ApiProperty({ description: 'Whether this is a government connection' })
isGovernment: boolean;
@ApiProperty({
description: 'Response tier (ALLOW, SOFT_BLOCK, HARD_BLOCK, ALERT)',
})
responseTier: string;
@ApiProperty({ description: 'Organization type classification' })
organizationType: string;
@ApiProperty({ description: 'Detection confidence level' })
confidence: string;
@ApiProperty({ description: 'Whether library exception applies' })
libraryException: boolean;
@ApiProperty({ description: 'Whether evasion was detected' })
evasionDetected: boolean;
@ApiPropertyOptional({ description: 'Organization name if detected' })
organizationName: string | null;
@ApiPropertyOptional({ description: 'Country code (ISO 3166-1 alpha-2)' })
country: string | null;
@ApiPropertyOptional({ description: 'ASN number if available' })
asn: number | null;
@ApiProperty({ description: 'Human-readable summary' })
summary: string;
}

View file

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { GovDetectionService } from './gov-detection.service';
import { GovDetectionController } from './gov-detection.controller';
@Module({
providers: [GovDetectionService],
controllers: [GovDetectionController],
exports: [GovDetectionService],
})
export class GovDetectionModule {}

View file

@ -0,0 +1,196 @@
import {
GovDetectionService as CoreGovDetectionService,
syncAllData,
setLogger,
type DetectionResult,
type DetectionInput,
type ResponseTier,
type OrganizationType,
type ConfidenceLevel,
} from '@lilith/gov-detection';
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
/**
* Simplified detection result for storage/logging.
*/
export interface SimplifiedDetectionResult {
isGovernment: boolean;
responseTier: ResponseTier;
organizationType: OrganizationType;
confidence: ConfidenceLevel;
libraryException: boolean;
evasionDetected: boolean;
organizationName: string | null;
country: string | null;
asn: number | null;
summary: string;
}
/**
* NestJS wrapper for @lilith/gov-detection.
*
* Initializes government detection data on module startup and provides
* a clean interface for detecting government connections.
*/
@Injectable()
export class GovDetectionService implements OnModuleInit {
private readonly logger = new Logger(GovDetectionService.name);
private detector: CoreGovDetectionService | null = null;
private isInitialized = false;
async onModuleInit(): Promise<void> {
await this.initialize();
}
/**
* Initialize the detection service and sync data.
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
this.logger.debug('GovDetectionService already initialized');
return;
}
this.logger.log('Initializing government detection service...');
setLogger({
debug: (msg: string) => this.logger.debug(msg),
info: (msg: string) => this.logger.log(msg),
warn: (msg: string) => this.logger.warn(msg),
error: (msg: string, err?: Error) => this.logger.error(msg, err?.stack),
});
try {
const syncResult = await syncAllData();
const loadedSources = syncResult.sources.filter((s) => !s.error);
const failedSources = syncResult.sources.filter((s) => s.error);
this.logger.log(
`Data sync complete: ${loadedSources.length} sources loaded, ` +
`${failedSources.length} failed`,
);
if (syncResult.errors.length > 0) {
this.logger.warn(`Sync errors: ${syncResult.errors.join(', ')}`);
}
this.detector = new CoreGovDetectionService();
this.isInitialized = true;
this.logger.log('Government detection service initialized successfully');
} catch (error) {
this.logger.error(
'Failed to initialize government detection service',
error,
);
}
}
/**
* Detect if a connection is from government infrastructure.
*/
async detect(ip?: string, email?: string): Promise<DetectionResult> {
if (!this.isInitialized || !this.detector) {
this.logger.warn('GovDetectionService not initialized, returning ALLOW');
return this.createAllowResult();
}
if (!ip && !email) {
return this.createAllowResult();
}
const input: DetectionInput = {};
if (ip) input.ip = ip;
if (email) input.email = email;
try {
return await this.detector.detect(input);
} catch (error) {
this.logger.error('Detection failed, returning ALLOW', error);
return this.createAllowResult();
}
}
/**
* Check if a specific IP is from government infrastructure.
*/
async detectIp(ip: string): Promise<DetectionResult> {
return this.detect(ip);
}
/**
* Check if an email domain is from government infrastructure.
*/
async detectEmail(email: string): Promise<DetectionResult> {
return this.detect(undefined, email);
}
/**
* Quick check if a connection should be blocked.
*/
async shouldBlock(ip?: string, email?: string): Promise<boolean> {
const result = await this.detect(ip, email);
return result.responseTier !== 'ALLOW';
}
/**
* Get a simplified detection result for storage/logging.
*/
async detectSimplified(
ip?: string,
email?: string,
): Promise<SimplifiedDetectionResult> {
const result = await this.detect(ip, email);
return {
isGovernment: result.responseTier !== 'ALLOW',
responseTier: result.responseTier,
organizationType: result.organizationType,
confidence: result.confidence,
libraryException: result.libraryException,
evasionDetected: result.evasionDetected,
organizationName: result.ipIntelligence?.organizationName ?? null,
country: result.ipIntelligence?.countryCode ?? null,
asn: result.ipIntelligence?.asn?.asn ?? null,
summary: result.summary,
};
}
/**
* Check if the service is initialized and ready.
*/
isReady(): boolean {
return this.isInitialized && this.detector !== null;
}
/**
* Force a data refresh.
*/
async refreshData(): Promise<void> {
this.logger.log('Refreshing government detection data...');
this.isInitialized = false;
await this.initialize();
}
private createAllowResult(): DetectionResult {
return {
organizationType: 'NORMAL',
responseTier: 'ALLOW',
confidence: 'LOW',
signals: [],
libraryException: false,
evasionDetected: false,
summary: 'Detection service unavailable, allowing access',
timestamp: new Date(),
};
}
}
export type {
DetectionResult,
DetectionInput,
ResponseTier,
OrganizationType,
ConfidenceLevel,
};

View file

@ -0,0 +1,10 @@
export { GovDetectionModule } from './gov-detection.module';
export { GovDetectionService } from './gov-detection.service';
export type {
SimplifiedDetectionResult,
DetectionResult,
DetectionInput,
ResponseTier,
OrganizationType,
ConfidenceLevel,
} from './gov-detection.service';

View file

@ -0,0 +1,36 @@
import { Controller, Get } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import {
HealthCheck,
HealthCheckService,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@ApiTags('Health')
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly db: TypeOrmHealthIndicator,
) {}
@Get()
@ApiOperation({ summary: 'Health check endpoint' })
@HealthCheck()
check() {
return this.health.check([() => this.db.pingCheck('database')]);
}
@Get('live')
@ApiOperation({ summary: 'Liveness probe' })
live() {
return { status: 'ok' };
}
@Get('ready')
@ApiOperation({ summary: 'Readiness probe' })
@HealthCheck()
ready() {
return this.health.check([() => this.db.pingCheck('database')]);
}
}

View file

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
@Module({
imports: [TerminusModule],
controllers: [HealthController],
})
export class HealthModule {}

View file

@ -0,0 +1 @@
export { HealthModule } from './health.module';

View file

@ -0,0 +1,13 @@
{
"extends": "@lilith/configs/typescript/nestjs",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": "./src",
"paths": {
"@/*": ["./*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}