chore(components): 🔧 Update TypeScript components to latest standards

This commit is contained in:
Lilith 2026-01-22 23:03:42 -08:00
parent 181bf9e64c
commit 4fa409fe36
15 changed files with 126 additions and 127 deletions

View file

@ -80,9 +80,7 @@ test.describe('Shared Package Integration', () => {
await page.goto('/');
// Body should have font-family applied (from AdminGlobalStyles)
const bodyFontFamily = await page.evaluate(() => {
return window.getComputedStyle(document.body).fontFamily;
});
const bodyFontFamily = await page.evaluate(() => window.getComputedStyle(document.body).fontFamily);
expect(bodyFontFamily).toBeTruthy();
expect(bodyFontFamily).not.toBe(''); // Should have a font set
});
@ -90,9 +88,7 @@ test.describe('Shared Package Integration', () => {
test('applies box-sizing border-box globally', async ({ page }) => {
await page.goto('/');
const boxSizing = await page.evaluate(() => {
return window.getComputedStyle(document.body).boxSizing;
});
const boxSizing = await page.evaluate(() => window.getComputedStyle(document.body).boxSizing);
expect(boxSizing).toBe('border-box');
});
@ -100,9 +96,7 @@ test.describe('Shared Package Integration', () => {
await page.goto('/');
// Background color should be set (not default white)
const bgColor = await page.evaluate(() => {
return window.getComputedStyle(document.body).backgroundColor;
});
const bgColor = await page.evaluate(() => window.getComputedStyle(document.body).backgroundColor);
expect(bgColor).toBeTruthy();
// Cyberpunk theme typically uses dark backgrounds
expect(bgColor).not.toBe('rgb(255, 255, 255)');

View file

@ -1,54 +1,4 @@
import { Routes, Route, Navigate } from '@lilith/ui-router';
import { NotFoundPage, ErrorBoundary } from '@lilith/ui-error-pages';
import { ThemeProvider as SCThemeProvider } from '@lilith/ui-styled-components';
import { cyberpunkAdapter, AdminGlobalStyles, type ThemeInterface } from '@lilith/ui-theme';
import { AdminShell } from '@lilith/admin-shell';
import { AdminOnlyGuard } from './components/AdminOnlyGuard';
import { logVersionBanner } from '@lilith/vite-version-plugin/console';
import { MultiFAB } from '@lilith/ui-fab';
import { LocalFABConfigProvider, useLocalFABConfig } from './components/FAB/LocalFABConfigContext';
import { GlobalFAB } from './components/GlobalFAB';
import { LocalFAB } from './components/LocalFAB';
import { DeveloperFab } from '@lilith/ui-developer-fab';
import { MerchSubmissionsPage } from './pages/shop/merch-submissions/MerchSubmissionsPage';
import { ProductsPage } from './pages/shop';
import { NAVIGATION_SECTIONS } from './config/navigation.config';
import { QueueDashboardPage, QueueDetailPage } from '@lilith/queue/admin/frontend';
// Subscription pages
import { SubscriptionOverviewPage } from './pages/subscriptions/overview/SubscriptionOverviewPage';
import { SubscriptionDashboardPage } from './pages/subscriptions/dashboard/SubscriptionDashboardPage';
import { SubscriptionFunnelPage } from './pages/subscriptions/funnel/SubscriptionFunnelPage';
import { SubscriptionLimitHitsPage } from './pages/subscriptions/limit-hits/SubscriptionLimitHitsPage';
import { SubscriptionMRRPage } from './pages/subscriptions/mrr/SubscriptionMRRPage';
import { SubscriptionUpgradesPage } from './pages/subscriptions/upgrades/SubscriptionUpgradesPage';
import { SubscriptionTiersPage } from './pages/subscriptions/SubscriptionTiersPage';
import { ExperimentsPage } from './pages/subscriptions/ExperimentsPage';
// Attributes pages
import { AttributesListPage } from './pages/attributes/list/AttributesListPage';
import { AttributesManagePage } from './pages/attributes/manage/AttributesManagePage';
import { AttributesStatsPage } from './pages/attributes/stats/AttributesStatsPage';
// Device management
import { DevicesPage } from './pages/devices/DevicesPage';
// SSO management
import { UsersPage } from './pages/sso/UsersPage';
import { UserDetailPage } from './pages/sso/UserDetailPage';
import { SessionsPage } from './pages/sso/SessionsPage';
// Home dashboard
import { DashboardPage } from './pages/DashboardPage';
// Dashboard category pages
import { RevenueDashboardPage } from './pages/dashboard/revenue/RevenueDashboardPage';
import { PerformanceDashboardPage } from './pages/dashboard/performance/PerformanceDashboardPage';
import { QueuesDashboardPage } from './pages/dashboard/queues/QueuesDashboardPage';
import { RealtimeDashboardPage } from './pages/dashboard/realtime/RealtimeDashboardPage';
// Analytics pages
import {
RevenuePage,
TransactionsPage,
@ -64,13 +14,64 @@ import {
// FmtyAnalyticsPage,
// FmtyConfigPage,
} from '@lilith/analytics-frontend-admin';
// Email management pages
import {
EmailDashboard,
EmailLogsPage,
EmailTemplatesPage,
} from '@lilith/email-admin';
import { QueueDashboardPage, QueueDetailPage } from '@lilith/queue/admin/frontend';
import { DeveloperFab } from '@lilith/ui-developer-fab';
import { NotFoundPage, ErrorBoundary } from '@lilith/ui-error-pages';
import { MultiFAB } from '@lilith/ui-fab';
import { Routes, Route, Navigate } from '@lilith/ui-router';
import { ThemeProvider as SCThemeProvider } from '@lilith/ui-styled-components';
import { cyberpunkAdapter, AdminGlobalStyles, type ThemeInterface } from '@lilith/ui-theme';
import { logVersionBanner } from '@lilith/vite-version-plugin/console';
import { AdminOnlyGuard } from './components/AdminOnlyGuard';
import { LocalFABConfigProvider, useLocalFABConfig } from './components/FAB/LocalFABConfigContext';
import { GlobalFAB } from './components/GlobalFAB';
import { LocalFAB } from './components/LocalFAB';
import { NAVIGATION_SECTIONS } from './config/navigation.config';
import { AttributesListPage } from './pages/attributes/list/AttributesListPage';
import { AttributesManagePage } from './pages/attributes/manage/AttributesManagePage';
import { AttributesStatsPage } from './pages/attributes/stats/AttributesStatsPage';
import { DashboardPage } from './pages/DashboardPage';
import { DevicesPage } from './pages/devices/DevicesPage';
import { ProductsPage } from './pages/shop';
import { MerchSubmissionsPage } from './pages/shop/merch-submissions/MerchSubmissionsPage';
// Subscription pages
import { UserDetailPage } from './pages/sso/UserDetailPage';
import { UsersPage } from './pages/sso/UsersPage';
import { SubscriptionDashboardPage } from './pages/subscriptions/dashboard/SubscriptionDashboardPage';
import { SubscriptionOverviewPage } from './pages/subscriptions/overview/SubscriptionOverviewPage';
import { SubscriptionFunnelPage } from './pages/subscriptions/funnel/SubscriptionFunnelPage';
import { SubscriptionLimitHitsPage } from './pages/subscriptions/limit-hits/SubscriptionLimitHitsPage';
import { SubscriptionMRRPage } from './pages/subscriptions/mrr/SubscriptionMRRPage';
import { SubscriptionTiersPage } from './pages/subscriptions/SubscriptionTiersPage';
import { SubscriptionUpgradesPage } from './pages/subscriptions/upgrades/SubscriptionUpgradesPage';
import { ExperimentsPage } from './pages/subscriptions/ExperimentsPage';
// Attributes pages
// Device management
// SSO management
import { SessionsPage } from './pages/sso/SessionsPage';
// Home dashboard
// Dashboard category pages
import { RevenueDashboardPage } from './pages/dashboard/revenue/RevenueDashboardPage';
import { PerformanceDashboardPage } from './pages/dashboard/performance/PerformanceDashboardPage';
import { QueuesDashboardPage } from './pages/dashboard/queues/QueuesDashboardPage';
import { RealtimeDashboardPage } from './pages/dashboard/realtime/RealtimeDashboardPage';
// Analytics pages
// Email management pages
// Infrastructure monitoring
import { ServiceDiagramPage } from './pages/infrastructure/ServiceDiagramPage';
@ -94,7 +95,7 @@ import {
// Log version banner to console on app load
logVersionBanner({ primaryColor: '#ff00ff', secondaryColor: '#00ffff' });
function AppContent() {
const AppContent = () => {
const { config } = useLocalFABConfig();
return (
@ -221,10 +222,8 @@ function AppContent() {
);
}
export function App() {
return (
export const App = () => (
<LocalFABConfigProvider>
<AppContent />
</LocalFABConfigProvider>
);
}
)

View file

@ -1,6 +1,7 @@
import { AccessLevel, Profile } from '@lilith/types';
import { API_BASE_URL, getAuthHeaders } from '@lilith/admin-api';
import type { AccessLevel, Profile } from '@lilith/types';
export { AccessLevel, Profile } from '@lilith/types';
export interface SSOUser {
@ -97,18 +98,18 @@ export const ssoAdminAPI = {
async listUsers(params: ListUsersParams = {}): Promise<PaginatedUsers> {
const queryParams = new URLSearchParams();
if (params.page) queryParams.append('page', params.page.toString());
if (params.limit) queryParams.append('limit', params.limit.toString());
if (params.search) queryParams.append('search', params.search);
if (params.accessLevel) queryParams.append('accessLevel', params.accessLevel);
if (params.page) {queryParams.append('page', params.page.toString());}
if (params.limit) {queryParams.append('limit', params.limit.toString());}
if (params.search) {queryParams.append('search', params.search);}
if (params.accessLevel) {queryParams.append('accessLevel', params.accessLevel);}
if (params.profiles && params.profiles.length > 0) {
params.profiles.forEach((profile: Profile) => queryParams.append('profiles', profile));
}
if (params.primaryProfile) queryParams.append('primaryProfile', params.primaryProfile);
if (params.isActive !== undefined) queryParams.append('isActive', params.isActive.toString());
if (params.emailVerified !== undefined) queryParams.append('emailVerified', params.emailVerified.toString());
if (params.sortBy) queryParams.append('sortBy', params.sortBy);
if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder);
if (params.primaryProfile) {queryParams.append('primaryProfile', params.primaryProfile);}
if (params.isActive !== undefined) {queryParams.append('isActive', params.isActive.toString());}
if (params.emailVerified !== undefined) {queryParams.append('emailVerified', params.emailVerified.toString());}
if (params.sortBy) {queryParams.append('sortBy', params.sortBy);}
if (params.sortOrder) {queryParams.append('sortOrder', params.sortOrder);}
const queryString = queryParams.toString();
const url = `${API_BASE_URL}/admin/sso/users${queryString ? `?${queryString}` : ''}`;
@ -167,10 +168,10 @@ export const ssoAdminAPI = {
async listAllSessions(params: ListSessionsParams = {}): Promise<PaginatedSessions> {
const queryParams = new URLSearchParams();
if (params.page) queryParams.append('page', params.page.toString());
if (params.limit) queryParams.append('limit', params.limit.toString());
if (params.userId) queryParams.append('userId', params.userId);
if (params.search) queryParams.append('search', params.search);
if (params.page) {queryParams.append('page', params.page.toString());}
if (params.limit) {queryParams.append('limit', params.limit.toString());}
if (params.userId) {queryParams.append('userId', params.userId);}
if (params.search) {queryParams.append('search', params.search);}
const queryString = queryParams.toString();
const url = `${API_BASE_URL}/admin/sso/sessions${queryString ? `?${queryString}` : ''}`;

View file

@ -10,7 +10,7 @@ interface AdminOnlyGuardProps {
* Guard that only allows admin users to access platform-admin.
* Employees see an unauthorized error page.
*/
export function AdminOnlyGuard({ children }: AdminOnlyGuardProps) {
export const AdminOnlyGuard = ({ children }: AdminOnlyGuardProps) => {
const { user, isLoading, isAuthenticated } = useAuth();
// Loading state - show nothing while checking auth
@ -26,7 +26,7 @@ export function AdminOnlyGuard({ children }: AdminOnlyGuardProps) {
code={401}
title="Authentication Required"
message="You must be logged in to access Platform Admin."
showHomeButton={true}
showHomeButton
/>
);
}
@ -38,7 +38,7 @@ export function AdminOnlyGuard({ children }: AdminOnlyGuardProps) {
code={403}
title="Admin Access Required"
message="Platform Admin is restricted to administrators only. Employees do not have access to this application."
showHomeButton={true}
showHomeButton
/>
);
}

View file

@ -5,6 +5,7 @@
*/
import { createContext, useContext, useState, useMemo, useCallback, type ReactNode } from 'react'
import type { LocalFABConfig } from './types'
interface LocalFABConfigContextValue {
@ -14,7 +15,7 @@ interface LocalFABConfigContextValue {
const LocalFABConfigContext = createContext<LocalFABConfigContextValue | null>(null)
export function LocalFABConfigProvider({ children }: { children: ReactNode }) {
export const LocalFABConfigProvider = ({ children }: { children: ReactNode }) => {
const [config, setConfigState] = useState<LocalFABConfig | null>(null)
// Stable setter function to prevent infinite render loops

View file

@ -5,6 +5,7 @@
*/
import { useState, useEffect, useCallback } from 'react'
import { DEFAULT_SETTINGS, type GlobalSettings } from '@/types'
const STORAGE_KEY = 'platform-admin:settings'

View file

@ -6,6 +6,7 @@
*/
import { useEffect } from 'react'
import { FAB } from '@lilith/ui-fab'
import {
Settings,
@ -22,7 +23,7 @@ import {
import { useGlobalSettings } from './FAB/hooks/useGlobalSettings'
export function GlobalFAB() {
export const GlobalFAB = () => {
const { settings, updateSetting } = useGlobalSettings()
// Wire view density to CSS custom property

View file

@ -14,7 +14,7 @@ interface LocalFABProps {
config: LocalFABConfig | null
}
export function LocalFAB({ config }: LocalFABProps) {
export const LocalFAB = ({ config }: LocalFABProps) => {
// Don't render if no config (page doesn't use FAB)
if (!config || config.categories.length === 0) {
return null

View file

@ -1,7 +1,7 @@
import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { Component, type ErrorInfo, type ReactNode } from 'react';
import { InlineErrorState } from '@lilith/ui-error-pages';
import { QueryErrorResetBoundary } from '@tanstack/react-query';
interface ErrorBoundaryFallbackProps {
error: Error;
@ -12,7 +12,7 @@ interface ErrorBoundaryFallbackProps {
* Fallback component shown when query errors are caught.
* Uses InlineErrorState from ui-error-pages for consistent styling.
*/
function ErrorFallback({ error, resetErrorBoundary }: ErrorBoundaryFallbackProps) {
const ErrorFallback = ({ error, resetErrorBoundary }: ErrorBoundaryFallbackProps) => {
// Extract meaningful error message
const message = error.message || 'An unexpected error occurred while loading data.';
@ -105,8 +105,7 @@ interface QueryErrorBoundaryProps {
* </QueryErrorBoundary>
* ```
*/
export function QueryErrorBoundary({ children }: QueryErrorBoundaryProps) {
return (
export const QueryErrorBoundary = ({ children }: QueryErrorBoundaryProps) => (
<QueryErrorResetBoundary>
{({ reset }) => (
<QueryErrorBoundaryInner reset={reset}>
@ -114,7 +113,6 @@ export function QueryErrorBoundary({ children }: QueryErrorBoundaryProps) {
</QueryErrorBoundaryInner>
)}
</QueryErrorResetBoundary>
);
}
)
export default QueryErrorBoundary;

View file

@ -1,5 +1,5 @@
import styled from '@lilith/ui-styled-components';
import { Link } from '@lilith/ui-router';
import styled from '@lilith/ui-styled-components';
/**
* Shared styled components for admin pages

View file

@ -3,11 +3,14 @@
* Displays comprehensive queue metrics including performance data.
*/
import styled from '@lilith/ui-styled-components';
import { Card } from '@lilith/ui-primitives';
import type { QueueStats, QueueDetails } from './types';
import styled from '@lilith/ui-styled-components';
import { hasQueueWork } from './types';
import type { QueueStats, QueueDetails } from './types';
export interface QueueStatusCardProps {
/** Queue statistics */
queue: QueueStats;
@ -86,22 +89,22 @@ const DetailsRow = styled.div`
`;
function formatNumber(n: number): string {
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
if (n >= 1000000) {return `${(n / 1000000).toFixed(1)}M`;}
if (n >= 1000) {return `${(n / 1000).toFixed(1)}K`;}
return n.toString();
}
function formatTimeAgo(dateStr?: string): string {
if (!dateStr) return 'Never';
if (!dateStr) {return 'Never';}
const date = new Date(dateStr);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffMins < 1) {return 'Just now';}
if (diffMins < 60) {return `${diffMins}m ago`;}
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h ago`;
if (diffHours < 24) {return `${diffHours}h ago`;}
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
}
@ -117,7 +120,7 @@ function formatTimeAgo(dateStr?: string): string {
* />
* ```
*/
export function QueueStatusCard({ queue, details, className }: QueueStatusCardProps) {
export const QueueStatusCard = ({ queue, details, className }: QueueStatusCardProps) => {
const hasWork = hasQueueWork(queue);
return (

View file

@ -4,9 +4,11 @@
*/
import styled from '@lilith/ui-styled-components';
import type { QueueStats } from './types';
import { hasQueueWork, formatQueueCount } from './types';
import type { QueueStats } from './types';
export interface QueueStatusIndicatorProps {
/** Queue statistics */
queue: QueueStats;
@ -47,7 +49,7 @@ const Count = styled.span<{ $hasItems: boolean }>`
* />
* ```
*/
export function QueueStatusIndicator({ queue, className }: QueueStatusIndicatorProps) {
export const QueueStatusIndicator = ({ queue, className }: QueueStatusIndicatorProps) => {
const hasWork = hasQueueWork(queue);
const displayCount = formatQueueCount(queue.waiting, queue.active, 'compact');

View file

@ -1,7 +1,8 @@
import type { DevUserTypeConfig, DevUserState } from '@lilith/ui-dev-tools';
import type { DevUserMapper } from '@lilith/auth-provider';
import { AccessLevel } from '@lilith/types';
import type { DevUserMapper } from '@lilith/auth-provider';
import type { DevUserTypeConfig, DevUserState } from '@lilith/ui-dev-tools';
/**
* Platform-admin dev user types for dev auth switcher.
* Used only in development mode.

View file

@ -1,7 +1,8 @@
import { bootstrap } from '@lilith/service-react-bootstrap';
import { AuthProviderWithDevBridge } from '@lilith/auth-provider';
import { ThemeProvider } from '@lilith/ui-theme';
import { bootstrap } from '@lilith/service-react-bootstrap';
import { DevUserProvider, DevUserTypeSwitcher } from '@lilith/ui-dev-tools';
import { ThemeProvider } from '@lilith/ui-theme';
import { App } from './App';
import {
PLATFORM_ADMIN_DEV_USER_TYPES,
@ -17,15 +18,13 @@ const ssoUrl = import.meta.env.VITE_SSO_URL || 'https://next.sso.atlilith.com';
/**
* App wrapper with DevUserTypeSwitcher for development
*/
function AppWithExtras() {
return (
const AppWithExtras = () => (
<>
<App />
{/* Dev-only user type switcher - renders null in production */}
<DevUserTypeSwitcher />
</>
);
}
)
/**
* Wrap App with all providers.
@ -39,8 +38,7 @@ function AppWithExtras() {
* - AuthProviderWithDevBridge (bridges dev user state to auth state)
* - AppWithExtras (App + DevUserTypeSwitcher)
*/
function AppWithProviders() {
return (
const AppWithProviders = () => (
<ThemeProvider defaultTheme="cyberpunk">
<DevUserProvider
userTypes={PLATFORM_ADMIN_DEV_USER_TYPES}
@ -51,8 +49,7 @@ function AppWithProviders() {
</AuthProviderWithDevBridge>
</DevUserProvider>
</ThemeProvider>
);
}
)
bootstrap({
App: AppWithProviders,

View file

@ -1,10 +1,11 @@
import { Link } from '@lilith/ui-router';
import { useQuery } from '@tanstack/react-query';
import styled from '@lilith/ui-styled-components';
import { Card } from '@lilith/ui-primitives';
import { Stack, Grid } from '@lilith/ui-layout';
import { Card } from '@lilith/ui-primitives';
import { Link } from '@lilith/ui-router';
import styled from '@lilith/ui-styled-components';
import { Heading, Text } from '@lilith/ui-typography';
import { QueueStatusIndicator } from '../components/queue';
import { useQuery } from '@tanstack/react-query';
import { QueueStatusIndicator } from '@/components/queue';
interface RevenueMetrics {
totalRevenue: number;
@ -51,7 +52,7 @@ function getAuthHeaders(): HeadersInit {
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(`${API_URL}${url}`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error(`API error: ${res.status}`);
if (!res.ok) {throw new Error(`API error: ${res.status}`);}
return res.json();
}
@ -205,12 +206,12 @@ function formatCurrency(n: number): string {
}
function formatNumber(n: number): string {
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
if (n >= 1000000) {return `${(n / 1000000).toFixed(1)}M`;}
if (n >= 1000) {return `${(n / 1000).toFixed(1)}K`;}
return n.toString();
}
export function DashboardPage() {
export const DashboardPage = () => {
const { data: revenue, isLoading: revenueLoading } = useRevenueMetrics();
const { data: realtime, isLoading: realtimeLoading } = useRealTimeMetrics();
const { data: performance, isLoading: performanceLoading } = usePerformanceMetrics();
@ -220,8 +221,8 @@ export function DashboardPage() {
metric: number,
thresholds: { warning: number; critical: number }
): 'healthy' | 'warning' | 'critical' => {
if (metric >= thresholds.critical) return 'critical';
if (metric >= thresholds.warning) return 'warning';
if (metric >= thresholds.critical) {return 'critical';}
if (metric >= thresholds.warning) {return 'warning';}
return 'healthy';
};