diff --git a/frontends/nextjs/package.json b/frontends/nextjs/package.json index cbbadd3c8..8f88b04a9 100644 --- a/frontends/nextjs/package.json +++ b/frontends/nextjs/package.json @@ -8,7 +8,7 @@ }, "scripts": { "dev": "next dev", - "build": "next build", + "build": "next build --webpack", "start": "next start", "kill": "fuser -k 3000/tcp", "typecheck": "tsc --noEmit", diff --git a/frontends/nextjs/src/_legacy/App.tsx b/frontends/nextjs/src/_legacy/App.tsx index 900f6690f..ce1e6c8dc 100644 --- a/frontends/nextjs/src/_legacy/App.tsx +++ b/frontends/nextjs/src/_legacy/App.tsx @@ -8,7 +8,7 @@ import { useState, useEffect } from 'react' import { Toaster } from '@/components/ui/sonner' -import { Button } from '@/components/ui/button' +import { Button } from '@/components/ui' // Import level-specific UI components import { Level1 } from '@/components/Level1' import { Level2 } from '@/components/Level2' diff --git a/frontends/nextjs/src/components/AuditLogViewer.tsx b/frontends/nextjs/src/components/AuditLogViewer.tsx index 64c82bd3b..b0166357a 100644 --- a/frontends/nextjs/src/components/AuditLogViewer.tsx +++ b/frontends/nextjs/src/components/AuditLogViewer.tsx @@ -1,8 +1,8 @@ import { useState, useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' -import { ScrollArea } from '@/components/ui/scroll-area' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Badge } from '@/components/ui' +import { ScrollArea } from '@/components/ui' import { SecureDatabase, SecurityContext, AuditLog } from '@/lib/secure-db-layer' import { ShieldCheck, Clock, User, ChartLine, Warning } from '@phosphor-icons/react' import type { User as UserType } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/Builder.tsx b/frontends/nextjs/src/components/Builder.tsx index fec129f9e..fbda8f3ec 100644 --- a/frontends/nextjs/src/components/Builder.tsx +++ b/frontends/nextjs/src/components/Builder.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { useKV } from '@github/spark/hooks' -import { Button } from '@/components/ui/button' +import { Button } from '@/components/ui' import { ComponentCatalog } from './ComponentCatalog' import { Canvas } from './Canvas' import { PropertyInspector } from './PropertyInspector' diff --git a/frontends/nextjs/src/components/CodeEditor.tsx b/frontends/nextjs/src/components/CodeEditor.tsx index e5d39a663..2cf484e97 100644 --- a/frontends/nextjs/src/components/CodeEditor.tsx +++ b/frontends/nextjs/src/components/CodeEditor.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' import Editor from '@monaco-editor/react' import { FloppyDisk, X, ShieldCheck, Warning } from '@phosphor-icons/react' import { securityScanner, type SecurityScanResult } from '@/lib/security-scanner' diff --git a/frontends/nextjs/src/components/ComponentConfigDialog.tsx b/frontends/nextjs/src/components/ComponentConfigDialog.tsx index b3778b372..ce0bf9857 100644 --- a/frontends/nextjs/src/components/ComponentConfigDialog.tsx +++ b/frontends/nextjs/src/components/ComponentConfigDialog.tsx @@ -1,14 +1,14 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Switch } from '@/components/ui/switch' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { ScrollArea } from '@/components/ui/scroll-area' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Textarea } from '@/components/ui' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Switch } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { ScrollArea } from '@/components/ui' import { Database, ComponentNode, ComponentConfig } from '@/lib/database' import { componentCatalog } from '@/lib/component-catalog' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx b/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx index e07a173ba..40b72d26b 100644 --- a/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx +++ b/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Badge } from '@/components/ui/badge' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Separator } from '@/components/ui/separator' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Badge } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Separator } from '@/components/ui' import { Tree, Plus, diff --git a/frontends/nextjs/src/components/CssClassBuilder.tsx b/frontends/nextjs/src/components/CssClassBuilder.tsx index 6805303d8..8e2eaadf6 100644 --- a/frontends/nextjs/src/components/CssClassBuilder.tsx +++ b/frontends/nextjs/src/components/CssClassBuilder.tsx @@ -1,11 +1,11 @@ import { useState, useEffect, useMemo } from 'react' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Badge } from '@/components/ui/badge' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { Badge } from '@/components/ui' import { Database } from '@/lib/database' import { Plus, X, FloppyDisk } from '@phosphor-icons/react' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/CssClassManager.tsx b/frontends/nextjs/src/components/CssClassManager.tsx index 5de268cf8..bca95f177 100644 --- a/frontends/nextjs/src/components/CssClassManager.tsx +++ b/frontends/nextjs/src/components/CssClassManager.tsx @@ -1,12 +1,12 @@ import { useState, useEffect } from 'react' -import { Card } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Badge } from '@/components/ui/badge' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Separator } from '@/components/ui/separator' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' +import { Card } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Badge } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Separator } from '@/components/ui' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' import { Database, CssCategory } from '@/lib/database' import { Plus, X, Pencil, Trash, FloppyDisk } from '@phosphor-icons/react' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/DBALDemo.tsx b/frontends/nextjs/src/components/DBALDemo.tsx index ee92da564..af4b1dcea 100644 --- a/frontends/nextjs/src/components/DBALDemo.tsx +++ b/frontends/nextjs/src/components/DBALDemo.tsx @@ -6,12 +6,12 @@ */ import { useState } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { Badge } from '@/components/ui' import { toast } from 'sonner' import { useKVStore, useBlobStorage, useCachedData } from '@/hooks/useDBAL' diff --git a/frontends/nextjs/src/components/DatabaseManager.tsx b/frontends/nextjs/src/components/DatabaseManager.tsx index e5195146d..929daf83a 100644 --- a/frontends/nextjs/src/components/DatabaseManager.tsx +++ b/frontends/nextjs/src/components/DatabaseManager.tsx @@ -1,9 +1,9 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { ScrollArea } from '@/components/ui/scroll-area' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { ScrollArea } from '@/components/ui' import { Database, DB_KEYS } from '@/lib/database' import { toast } from 'sonner' import { diff --git a/frontends/nextjs/src/components/DropdownConfigManager.tsx b/frontends/nextjs/src/components/DropdownConfigManager.tsx index 0146d3f94..254d0132e 100644 --- a/frontends/nextjs/src/components/DropdownConfigManager.tsx +++ b/frontends/nextjs/src/components/DropdownConfigManager.tsx @@ -1,12 +1,12 @@ import { useState, useEffect } from 'react' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Card } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Card } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Separator } from '@/components/ui' import { Database } from '@/lib/database' import { Plus, X, FloppyDisk, Trash, Pencil } from '@phosphor-icons/react' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/FieldRenderer.tsx b/frontends/nextjs/src/components/FieldRenderer.tsx index 5489e66ab..b6518701e 100644 --- a/frontends/nextjs/src/components/FieldRenderer.tsx +++ b/frontends/nextjs/src/components/FieldRenderer.tsx @@ -1,8 +1,8 @@ -import { Input } from '@/components/ui/input' -import { Textarea } from '@/components/ui/textarea' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Switch } from '@/components/ui/switch' -import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui' +import { Textarea } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Switch } from '@/components/ui' +import { Label } from '@/components/ui' import type { FieldSchema, SchemaConfig } from '@/lib/schema-types' import { getFieldLabel, getHelpText, findModel, getModelLabel } from '@/lib/schema-utils' import { useKV } from '@github/spark/hooks' diff --git a/frontends/nextjs/src/components/GenericPage.tsx b/frontends/nextjs/src/components/GenericPage.tsx index 6f2906d18..97cc43f68 100644 --- a/frontends/nextjs/src/components/GenericPage.tsx +++ b/frontends/nextjs/src/components/GenericPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { RenderComponent } from '@/components/RenderComponent' import { SignOut, House, List, X } from '@phosphor-icons/react' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/GitHubActionsFetcher.refactored.tsx b/frontends/nextjs/src/components/GitHubActionsFetcher.refactored.tsx index c39ab6afb..48441334c 100644 --- a/frontends/nextjs/src/components/GitHubActionsFetcher.refactored.tsx +++ b/frontends/nextjs/src/components/GitHubActionsFetcher.refactored.tsx @@ -1,14 +1,14 @@ import { useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { Skeleton } from '@/components/ui/skeleton' -import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui' +import { Skeleton } from '@/components/ui' +import { Badge } from '@/components/ui' import { ArrowClockwise, Info, Warning } from '@phosphor-icons/react' import { useGitHubFetcher } from '@/hooks/useGitHubFetcher' import { useAutoRefresh } from '@/hooks/useAutoRefresh' import { WorkflowRunCard } from '@/components/WorkflowRunCard' -import { ScrollArea } from '@/components/ui/scroll-area' +import { ScrollArea } from '@/components/ui' /** * Refactored GitHub Actions Fetcher Component diff --git a/frontends/nextjs/src/components/GitHubActionsFetcher.tsx b/frontends/nextjs/src/components/GitHubActionsFetcher.tsx index cfa0885d4..5a354980b 100644 --- a/frontends/nextjs/src/components/GitHubActionsFetcher.tsx +++ b/frontends/nextjs/src/components/GitHubActionsFetcher.tsx @@ -1,14 +1,14 @@ import { useState, useEffect, useMemo } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { Skeleton } from '@/components/ui/skeleton' -import { Badge } from '@/components/ui/badge' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui' +import { Skeleton } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { CheckCircle, XCircle, ArrowClockwise, ArrowSquareOut, Info, Warning, TrendUp, TrendDown, Robot, Download, FileText } from '@phosphor-icons/react' import { toast } from 'sonner' import { Octokit } from 'octokit' -import { ScrollArea } from '@/components/ui/scroll-area' +import { ScrollArea } from '@/components/ui' interface WorkflowRun { id: number diff --git a/frontends/nextjs/src/components/GodCredentialsSettings.tsx b/frontends/nextjs/src/components/GodCredentialsSettings.tsx index 8575f69fb..6574e2dbf 100644 --- a/frontends/nextjs/src/components/GodCredentialsSettings.tsx +++ b/frontends/nextjs/src/components/GodCredentialsSettings.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' import { Database } from '@/lib/database' import { toast } from 'sonner' import { Clock, Key, WarningCircle, CheckCircle } from '@phosphor-icons/react' diff --git a/frontends/nextjs/src/components/IRCWebchat.tsx b/frontends/nextjs/src/components/IRCWebchat.tsx index 799314d7e..746af5e67 100644 --- a/frontends/nextjs/src/components/IRCWebchat.tsx +++ b/frontends/nextjs/src/components/IRCWebchat.tsx @@ -1,9 +1,9 @@ import { useState, useEffect, useRef } from 'react' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Button } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Badge } from '@/components/ui' import { PaperPlaneTilt, Users, SignOut, Gear } from '@phosphor-icons/react' import { useKV } from '@github/spark/hooks' import type { User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/IRCWebchatDeclarative.tsx b/frontends/nextjs/src/components/IRCWebchatDeclarative.tsx index 387c797b5..5269cd42d 100644 --- a/frontends/nextjs/src/components/IRCWebchatDeclarative.tsx +++ b/frontends/nextjs/src/components/IRCWebchatDeclarative.tsx @@ -1,9 +1,9 @@ import { useState, useEffect, useRef } from 'react' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Button } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Badge } from '@/components/ui' import { PaperPlaneTilt, Users, SignOut, Gear } from '@phosphor-icons/react' import { useKV } from '@github/spark/hooks' import type { User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/JsonEditor.tsx b/frontends/nextjs/src/components/JsonEditor.tsx index 220932b1f..cae15d8c6 100644 --- a/frontends/nextjs/src/components/JsonEditor.tsx +++ b/frontends/nextjs/src/components/JsonEditor.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' +import { Button } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' import { FloppyDisk, X, Warning, ShieldCheck } from '@phosphor-icons/react' import Editor from '@monaco-editor/react' import { securityScanner, type SecurityScanResult } from '@/lib/security-scanner' diff --git a/frontends/nextjs/src/components/Level1.tsx b/frontends/nextjs/src/components/Level1.tsx index 8a0a68376..a76ab6b0e 100644 --- a/frontends/nextjs/src/components/Level1.tsx +++ b/frontends/nextjs/src/components/Level1.tsx @@ -17,7 +17,6 @@ */ import { useState, useEffect } from 'react' -import { Database } from '@/lib/database' import { getScrambledPassword } from '@/lib/auth' import { NavigationBar } from './level1/NavigationBar' import { GodCredentialsBanner } from './level1/GodCredentialsBanner' @@ -25,7 +24,7 @@ import { HeroSection } from './level1/HeroSection' import { FeaturesSection } from './level1/FeaturesSection' import { ContactSection } from './level1/ContactSection' import { AppFooter } from './shared/AppFooter' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { GitHubActionsFetcher } from './GitHubActionsFetcher' // Props for Level1 component @@ -58,42 +57,56 @@ export function Level1({ onNavigate }: Level1Props) { // Initialize component state on mount useEffect(() => { + let interval: ReturnType | undefined + const checkCredentials = async () => { - // Check if god credentials should be displayed - const shouldShow = await Database.shouldShowGodCredentials() - setShowGodCredentials(shouldShow) - - // Get supergod account if exists - const superGod = await Database.getSuperGod() - const firstLoginFlags = await Database.getFirstLoginFlags() - setShowSuperGodCredentials(superGod !== null && firstLoginFlags['supergod'] === true) - - // Update timer for god credentials expiry - if (shouldShow) { - const expiry = await Database.getGodCredentialsExpiry() - const updateTimer = () => { - const now = Date.now() - const diff = expiry - now - - // Hide credentials when expired - if (diff <= 0) { - setShowGodCredentials(false) - setTimeRemaining('') - } else { + try { + const { Database } = await import('@/lib/database') + + // Check if god credentials should be displayed + const shouldShow = await Database.shouldShowGodCredentials() + setShowGodCredentials(shouldShow) + + // Get supergod account if exists + const superGod = await Database.getSuperGod() + const firstLoginFlags = await Database.getFirstLoginFlags() + setShowSuperGodCredentials(superGod !== null && firstLoginFlags['supergod'] === true) + + // Update timer for god credentials expiry + if (shouldShow) { + const expiry = await Database.getGodCredentialsExpiry() + const updateTimer = () => { + const now = Date.now() + const diff = expiry - now + + // Hide credentials when expired + if (diff <= 0) { + setShowGodCredentials(false) + setTimeRemaining('') + return + } + // Display remaining time in minutes and seconds const minutes = Math.floor(diff / 60000) const seconds = Math.floor((diff % 60000) / 1000) setTimeRemaining(`${minutes}m ${seconds}s`) } + + updateTimer() + interval = setInterval(updateTimer, 1000) } - - updateTimer() - const interval = setInterval(updateTimer, 1000) - return () => clearInterval(interval) + } catch { + setShowGodCredentials(false) + setShowSuperGodCredentials(false) + setTimeRemaining('') } } - - checkCredentials() + + void checkCredentials() + + return () => { + if (interval) clearInterval(interval) + } }, []) const handleCopyPassword = async () => { diff --git a/frontends/nextjs/src/components/Level2.tsx b/frontends/nextjs/src/components/Level2.tsx index c3400e115..f7b97d650 100644 --- a/frontends/nextjs/src/components/Level2.tsx +++ b/frontends/nextjs/src/components/Level2.tsx @@ -1,10 +1,10 @@ "use client" import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Textarea } from '@/components/ui/textarea' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Textarea } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { User, ChatCircle } from '@phosphor-icons/react' import { toast } from 'sonner' import { Database, hashPassword } from '@/lib/database' diff --git a/frontends/nextjs/src/components/Level3.tsx b/frontends/nextjs/src/components/Level3.tsx index dea262965..d7e100e44 100644 --- a/frontends/nextjs/src/components/Level3.tsx +++ b/frontends/nextjs/src/components/Level3.tsx @@ -1,10 +1,10 @@ "use client" import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Badge } from '@/components/ui' import { Table, TableBody, @@ -12,15 +12,15 @@ import { TableHead, TableHeader, TableRow, -} from '@/components/ui/table' +} from '@/components/ui' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +} from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { MagnifyingGlass, Plus, PencilSimple, Trash, Users, ChatCircle } from '@phosphor-icons/react' import { toast } from 'sonner' import { Database } from '@/lib/database' diff --git a/frontends/nextjs/src/components/Level5.tsx b/frontends/nextjs/src/components/Level5.tsx index 6785b7283..ba00e1735 100644 --- a/frontends/nextjs/src/components/Level5.tsx +++ b/frontends/nextjs/src/components/Level5.tsx @@ -1,10 +1,10 @@ "use client" import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { Dialog, DialogContent, @@ -12,7 +12,7 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' +} from '@/components/ui' import { AlertDialog, AlertDialogAction, @@ -22,7 +22,7 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from '@/components/ui/alert-dialog' +} from '@/components/ui' import { Crown, Buildings, Users, ArrowsLeftRight, Eye, Camera } from '@phosphor-icons/react' import { toast } from 'sonner' import { Level5Header } from './level5/Level5Header' diff --git a/frontends/nextjs/src/components/Login.tsx b/frontends/nextjs/src/components/Login.tsx index 3a6ec603a..36651fc2a 100644 --- a/frontends/nextjs/src/components/Login.tsx +++ b/frontends/nextjs/src/components/Login.tsx @@ -1,8 +1,8 @@ import { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' import { Lock } from '@phosphor-icons/react' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/LuaEditor.tsx b/frontends/nextjs/src/components/LuaEditor.tsx index 976922cd7..e9d90c24f 100644 --- a/frontends/nextjs/src/components/LuaEditor.tsx +++ b/frontends/nextjs/src/components/LuaEditor.tsx @@ -1,16 +1,16 @@ import { useState, useEffect, useRef } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Badge } from '@/components/ui' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from '@/components/ui/select' +} from '@/components/ui' import { Plus, Trash, Play, CheckCircle, XCircle, FileCode, ArrowsOut, BookOpen, ShieldCheck } from '@phosphor-icons/react' import { toast } from 'sonner' import { createLuaEngine, type LuaExecutionResult } from '@/lib/lua-engine' @@ -19,7 +19,7 @@ import type { LuaScript } from '@/lib/level-types' import Editor from '@monaco-editor/react' import { useMonaco } from '@monaco-editor/react' import { LuaSnippetLibrary } from '@/components/LuaSnippetLibrary' -import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet' +import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui' import { securityScanner, type SecurityScanResult } from '@/lib/security-scanner' import { SecurityWarningDialog } from '@/components/SecurityWarningDialog' diff --git a/frontends/nextjs/src/components/LuaSnippetLibrary.tsx b/frontends/nextjs/src/components/LuaSnippetLibrary.tsx index 456fe7656..52290dcec 100644 --- a/frontends/nextjs/src/components/LuaSnippetLibrary.tsx +++ b/frontends/nextjs/src/components/LuaSnippetLibrary.tsx @@ -1,18 +1,18 @@ import { useState } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Separator } from '@/components/ui/separator' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Button } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Separator } from '@/components/ui' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' +} from '@/components/ui' import { MagnifyingGlass, Copy, diff --git a/frontends/nextjs/src/components/ModelListView.tsx b/frontends/nextjs/src/components/ModelListView.tsx index 0c19617ba..f107f1c9e 100644 --- a/frontends/nextjs/src/components/ModelListView.tsx +++ b/frontends/nextjs/src/components/ModelListView.tsx @@ -1,10 +1,10 @@ import { useState, useMemo } from 'react' import { useKV } from '@github/spark/hooks' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Badge } from '@/components/ui/badge' -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' import type { ModelSchema, SchemaConfig } from '@/lib/schema-types' import { getRecordsKey, getFieldLabel, sortRecords, filterRecords, findModel } from '@/lib/schema-utils' import { RecordForm } from './RecordForm' diff --git a/frontends/nextjs/src/components/NerdModeIDE.tsx b/frontends/nextjs/src/components/NerdModeIDE.tsx index 1cb2cd1f6..4f9d44c60 100644 --- a/frontends/nextjs/src/components/NerdModeIDE.tsx +++ b/frontends/nextjs/src/components/NerdModeIDE.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { Badge } from '@/components/ui' import { Dialog, DialogContent, @@ -12,16 +12,16 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' +} from '@/components/ui' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from '@/components/ui/select' -import { Label } from '@/components/ui/label' -import { Separator } from '@/components/ui/separator' +} from '@/components/ui' +import { Label } from '@/components/ui' +import { Separator } from '@/components/ui' import Editor from '@monaco-editor/react' import { File, diff --git a/frontends/nextjs/src/components/NotificationSummaryCard.tsx b/frontends/nextjs/src/components/NotificationSummaryCard.tsx index e506e61d1..fa8a4c336 100644 --- a/frontends/nextjs/src/components/NotificationSummaryCard.tsx +++ b/frontends/nextjs/src/components/NotificationSummaryCard.tsx @@ -1,7 +1,7 @@ import { cn } from '@/lib/utils' -import { Badge } from '@/components/ui/badge' -import { Card } from '@/components/ui/card' -import { Separator } from '@/components/ui/separator' +import { Badge } from '@/components/ui' +import { Card } from '@/components/ui' +import { Separator } from '@/components/ui' export type NotificationSeverity = 'info' | 'warning' | 'critical' diff --git a/frontends/nextjs/src/components/PackageImportExport.tsx b/frontends/nextjs/src/components/PackageImportExport.tsx index 4a9c3a80a..baac31e85 100644 --- a/frontends/nextjs/src/components/PackageImportExport.tsx +++ b/frontends/nextjs/src/components/PackageImportExport.tsx @@ -1,13 +1,13 @@ import { useState, useRef } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Label } from '@/components/ui/label' -import { Input } from '@/components/ui/input' -import { Textarea } from '@/components/ui/textarea' -import { Checkbox } from '@/components/ui/checkbox' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Separator } from '@/components/ui/separator' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui' +import { Label } from '@/components/ui' +import { Input } from '@/components/ui' +import { Textarea } from '@/components/ui' +import { Checkbox } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Separator } from '@/components/ui' import { toast } from 'sonner' import { Database } from '@/lib/database' import { exportPackageAsZip, importPackageFromZip, downloadZip, exportDatabaseSnapshot } from '@/lib/package-export' diff --git a/frontends/nextjs/src/components/PackageManager.tsx b/frontends/nextjs/src/components/PackageManager.tsx index 636ae7866..65e73fecb 100644 --- a/frontends/nextjs/src/components/PackageManager.tsx +++ b/frontends/nextjs/src/components/PackageManager.tsx @@ -1,13 +1,13 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Badge } from '@/components/ui/badge' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Separator } from '@/components/ui/separator' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Separator } from '@/components/ui' import { toast } from 'sonner' import { Database } from '@/lib/database' import { PACKAGE_CATALOG } from '@/lib/package-catalog' diff --git a/frontends/nextjs/src/components/PageRoutesManager.tsx b/frontends/nextjs/src/components/PageRoutesManager.tsx index 22345bc40..9459495c4 100644 --- a/frontends/nextjs/src/components/PageRoutesManager.tsx +++ b/frontends/nextjs/src/components/PageRoutesManager.tsx @@ -1,17 +1,17 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' -import { Badge } from '@/components/ui/badge' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui' import { Plus, Pencil, Trash, Eye, LockKey } from '@phosphor-icons/react' import { Database } from '@/lib/database' import { toast } from 'sonner' import type { PageConfig, UserRole, AppLevel } from '@/lib/level-types' -import { Switch } from '@/components/ui/switch' +import { Switch } from '@/components/ui' export function PageRoutesManager() { const [pages, setPages] = useState([]) diff --git a/frontends/nextjs/src/components/PasswordChangeDialog.tsx b/frontends/nextjs/src/components/PasswordChangeDialog.tsx index 95587bd72..28b7354a0 100644 --- a/frontends/nextjs/src/components/PasswordChangeDialog.tsx +++ b/frontends/nextjs/src/components/PasswordChangeDialog.tsx @@ -1,9 +1,9 @@ import { useState } from 'react' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' import { Warning, Eye, EyeSlash } from '@phosphor-icons/react' interface PasswordChangeDialogProps { diff --git a/frontends/nextjs/src/components/PropertyInspector.tsx b/frontends/nextjs/src/components/PropertyInspector.tsx index e4a96b5db..801ac2f4c 100644 --- a/frontends/nextjs/src/components/PropertyInspector.tsx +++ b/frontends/nextjs/src/components/PropertyInspector.tsx @@ -1,11 +1,11 @@ import { useState, useEffect } from 'react' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Button } from '@/components/ui/button' -import { Separator } from '@/components/ui/separator' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { ScrollArea } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Button } from '@/components/ui' +import { Separator } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import type { ComponentInstance } from '@/lib/builder-types' import { componentCatalog } from '@/lib/component-catalog' import { Code, PaintBrush, Trash, Palette } from '@phosphor-icons/react' diff --git a/frontends/nextjs/src/components/QuickGuide.tsx b/frontends/nextjs/src/components/QuickGuide.tsx index f21c739eb..5291fc8a6 100644 --- a/frontends/nextjs/src/components/QuickGuide.tsx +++ b/frontends/nextjs/src/components/QuickGuide.tsx @@ -1,8 +1,8 @@ -import { Card } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' +import { Card } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui' import { Palette, ListDashes, Code, Sparkle, Package, Terminal } from '@phosphor-icons/react' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Alert, AlertDescription } from '@/components/ui' export function QuickGuide() { return ( diff --git a/frontends/nextjs/src/components/README.md b/frontends/nextjs/src/components/README.md index 6891b9478..ce27e24e2 100644 --- a/frontends/nextjs/src/components/README.md +++ b/frontends/nextjs/src/components/README.md @@ -236,7 +236,7 @@ import { ComponentCatalog } from '@/components/ComponentCatalog' ### New Way (recommended) ```typescript -import { Button } from '@/components/atoms' +import { Button } from '@/components/ui' import { ComponentCatalog } from '@/components/organisms' ``` @@ -271,7 +271,7 @@ export function FormField({ label, error, ...props }: FormFieldProps) { // Organism handling data fetching and display import { useKV } from '@github/spark/hooks' import { Button } from '@/components/atoms' -import { Card } from '@/components/ui/card' +import { Card } from '@/components/ui' export function UserList() { const [users] = useKV('users', []) diff --git a/frontends/nextjs/src/components/RecordForm.tsx b/frontends/nextjs/src/components/RecordForm.tsx index dbe76ba7f..e6fb53191 100644 --- a/frontends/nextjs/src/components/RecordForm.tsx +++ b/frontends/nextjs/src/components/RecordForm.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { ScrollArea } from '@/components/ui/scroll-area' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' +import { Button } from '@/components/ui' +import { ScrollArea } from '@/components/ui' import type { ModelSchema, SchemaConfig } from '@/lib/schema-types' import { validateRecord, createEmptyRecord } from '@/lib/schema-utils' import { FieldRenderer } from './FieldRenderer' diff --git a/frontends/nextjs/src/components/RenderComponent.tsx b/frontends/nextjs/src/components/RenderComponent.tsx index 40f270f0b..2cf420926 100644 --- a/frontends/nextjs/src/components/RenderComponent.tsx +++ b/frontends/nextjs/src/components/RenderComponent.tsx @@ -1,18 +1,18 @@ import type { ComponentInstance } from '@/lib/builder-types' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Textarea } from '@/components/ui/textarea' -import { Label } from '@/components/ui/label' -import { Badge } from '@/components/ui/badge' -import { Card } from '@/components/ui/card' -import { Switch } from '@/components/ui/switch' -import { Checkbox } from '@/components/ui/checkbox' -import { Separator } from '@/components/ui/separator' -import { Alert } from '@/components/ui/alert' -import { Progress } from '@/components/ui/progress' -import { Slider } from '@/components/ui/slider' -import { Avatar, AvatarFallback } from '@/components/ui/avatar' -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Textarea } from '@/components/ui' +import { Label } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Card } from '@/components/ui' +import { Switch } from '@/components/ui' +import { Checkbox } from '@/components/ui' +import { Separator } from '@/components/ui' +import { Alert } from '@/components/ui' +import { Progress } from '@/components/ui' +import { Slider } from '@/components/ui' +import { Avatar, AvatarFallback } from '@/components/ui' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui' import { IRCWebchatDeclarative } from '@/components/IRCWebchatDeclarative' import { NotificationSummaryCard } from '@/components/NotificationSummaryCard' import type { User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/SMTPConfigEditor.tsx b/frontends/nextjs/src/components/SMTPConfigEditor.tsx index dcce32e44..a2d312311 100644 --- a/frontends/nextjs/src/components/SMTPConfigEditor.tsx +++ b/frontends/nextjs/src/components/SMTPConfigEditor.tsx @@ -1,9 +1,9 @@ import { useState, useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Label } from '@/components/ui/label' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { Switch } from '@/components/ui/switch' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Label } from '@/components/ui' +import { Input } from '@/components/ui' +import { Button } from '@/components/ui' +import { Switch } from '@/components/ui' import { Envelope, FloppyDisk, PaperPlaneTilt } from '@phosphor-icons/react' import { toast } from 'sonner' import { Database } from '@/lib/database' diff --git a/frontends/nextjs/src/components/SchemaEditor.tsx b/frontends/nextjs/src/components/SchemaEditor.tsx index 48974a800..1523dfe95 100644 --- a/frontends/nextjs/src/components/SchemaEditor.tsx +++ b/frontends/nextjs/src/components/SchemaEditor.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' +import { Button } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' import type { SchemaConfig } from '@/lib/schema-types' import { FloppyDisk, X, Warning } from '@phosphor-icons/react' import Editor from '@monaco-editor/react' diff --git a/frontends/nextjs/src/components/SchemaEditorLevel4.tsx b/frontends/nextjs/src/components/SchemaEditorLevel4.tsx index 3fe4718e8..f6a56a682 100644 --- a/frontends/nextjs/src/components/SchemaEditorLevel4.tsx +++ b/frontends/nextjs/src/components/SchemaEditorLevel4.tsx @@ -1,16 +1,16 @@ import { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from '@/components/ui/select' -import { Switch } from '@/components/ui/switch' +} from '@/components/ui' +import { Switch } from '@/components/ui' import { Plus, Trash } from '@phosphor-icons/react' import { toast } from 'sonner' import type { ModelSchema, FieldSchema, FieldType } from '@/lib/schema-types' diff --git a/frontends/nextjs/src/components/SecurityWarningDialog.tsx b/frontends/nextjs/src/components/SecurityWarningDialog.tsx index 9e616cddd..672b77456 100644 --- a/frontends/nextjs/src/components/SecurityWarningDialog.tsx +++ b/frontends/nextjs/src/components/SecurityWarningDialog.tsx @@ -6,12 +6,12 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Alert, AlertDescription } from '@/components/ui/alert' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' +} from '@/components/ui' +import { Button } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Separator } from '@/components/ui' import { ShieldWarning, Warning, Info, CheckCircle } from '@phosphor-icons/react' import type { SecurityScanResult, SecurityIssue } from '@/lib/security-scanner' import { getSeverityColor, getSeverityIcon } from '@/lib/security-scanner' diff --git a/frontends/nextjs/src/components/ThemeEditor.tsx b/frontends/nextjs/src/components/ThemeEditor.tsx index d40f40b7d..03c56b878 100644 --- a/frontends/nextjs/src/components/ThemeEditor.tsx +++ b/frontends/nextjs/src/components/ThemeEditor.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Label } from '@/components/ui/label' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Switch } from '@/components/ui/switch' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Label } from '@/components/ui' +import { Input } from '@/components/ui' +import { Button } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import { Switch } from '@/components/ui' import { Palette, Sun, Moon, FloppyDisk, ArrowCounterClockwise } from '@phosphor-icons/react' import { toast } from 'sonner' import { useKV } from '@github/spark/hooks' diff --git a/frontends/nextjs/src/components/UnifiedLogin.tsx b/frontends/nextjs/src/components/UnifiedLogin.tsx index c4bff2dcf..92ef9830c 100644 --- a/frontends/nextjs/src/components/UnifiedLogin.tsx +++ b/frontends/nextjs/src/components/UnifiedLogin.tsx @@ -1,14 +1,14 @@ import { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { SignIn, UserPlus, ArrowLeft, Envelope } from '@phosphor-icons/react' import { toast } from 'sonner' import { Database, hashPassword } from '@/lib/database' import { generateScrambledPassword, simulateEmailSend } from '@/lib/password-utils' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Alert, AlertDescription } from '@/components/ui' interface UnifiedLoginProps { onLogin: (credentials: { username: string; password: string }) => void diff --git a/frontends/nextjs/src/components/UserManagement.tsx b/frontends/nextjs/src/components/UserManagement.tsx index b24651c01..1f7991e0f 100644 --- a/frontends/nextjs/src/components/UserManagement.tsx +++ b/frontends/nextjs/src/components/UserManagement.tsx @@ -1,14 +1,14 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' -import { Badge } from '@/components/ui/badge' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' -import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Textarea } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui' +import { Avatar, AvatarFallback } from '@/components/ui' import { Plus, Pencil, Trash, UserCircle } from '@phosphor-icons/react' import { Database, hashPassword } from '@/lib/database' import { toast } from 'sonner' diff --git a/frontends/nextjs/src/components/examples/ContactForm.example.tsx b/frontends/nextjs/src/components/examples/ContactForm.example.tsx index 0874301c4..89e27f6ee 100644 --- a/frontends/nextjs/src/components/examples/ContactForm.example.tsx +++ b/frontends/nextjs/src/components/examples/ContactForm.example.tsx @@ -2,8 +2,8 @@ // This file serves as a reference for building well-documented components import { useState, useCallback } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui' +import { Input } from '@/components/ui' import { useAuth } from '@/hooks' /** diff --git a/frontends/nextjs/src/components/level1/ContactSection.tsx b/frontends/nextjs/src/components/level1/ContactSection.tsx index c498bb2e6..14b66b20a 100644 --- a/frontends/nextjs/src/components/level1/ContactSection.tsx +++ b/frontends/nextjs/src/components/level1/ContactSection.tsx @@ -1,5 +1,5 @@ -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' -import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui' +import { Button } from '@/components/ui' export function ContactSection() { return ( diff --git a/frontends/nextjs/src/components/level1/FeaturesSection.tsx b/frontends/nextjs/src/components/level1/FeaturesSection.tsx index 0c3d53f9c..22780f05c 100644 --- a/frontends/nextjs/src/components/level1/FeaturesSection.tsx +++ b/frontends/nextjs/src/components/level1/FeaturesSection.tsx @@ -1,4 +1,4 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' export function FeaturesSection() { return ( diff --git a/frontends/nextjs/src/components/level1/GodCredentialsBanner.tsx b/frontends/nextjs/src/components/level1/GodCredentialsBanner.tsx index 7fb7c8716..0dec52403 100644 --- a/frontends/nextjs/src/components/level1/GodCredentialsBanner.tsx +++ b/frontends/nextjs/src/components/level1/GodCredentialsBanner.tsx @@ -1,5 +1,5 @@ -import { Alert, AlertDescription } from '@/components/ui/alert' -import { Button } from '@/components/ui/button' +import { Alert, AlertDescription } from '@/components/ui' +import { Button } from '@/components/ui' import { Warning, Eye, EyeSlash, Copy, Check } from '@phosphor-icons/react' interface GodCredentialsBannerProps { diff --git a/frontends/nextjs/src/components/level1/HeroSection.tsx b/frontends/nextjs/src/components/level1/HeroSection.tsx index 620ec9d27..dfacdde2d 100644 --- a/frontends/nextjs/src/components/level1/HeroSection.tsx +++ b/frontends/nextjs/src/components/level1/HeroSection.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { Button } from '@/components/ui' interface HeroSectionProps { onNavigate: (level: number) => void diff --git a/frontends/nextjs/src/components/level1/NavigationBar.tsx b/frontends/nextjs/src/components/level1/NavigationBar.tsx index 98377b2c9..a5120eb66 100644 --- a/frontends/nextjs/src/components/level1/NavigationBar.tsx +++ b/frontends/nextjs/src/components/level1/NavigationBar.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { Button } from '@/components/ui' import { List, X, User, ShieldCheck } from '@phosphor-icons/react' interface NavigationBarProps { diff --git a/frontends/nextjs/src/components/level2/CommentsList.tsx b/frontends/nextjs/src/components/level2/CommentsList.tsx index f1f88c371..39a33b2ee 100644 --- a/frontends/nextjs/src/components/level2/CommentsList.tsx +++ b/frontends/nextjs/src/components/level2/CommentsList.tsx @@ -1,6 +1,6 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Avatar, AvatarFallback } from '@/components/ui' import { Trash } from '@phosphor-icons/react' import type { Comment, User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/level2/ProfileCard.tsx b/frontends/nextjs/src/components/level2/ProfileCard.tsx index 0dad0987f..114ea1ae6 100644 --- a/frontends/nextjs/src/components/level2/ProfileCard.tsx +++ b/frontends/nextjs/src/components/level2/ProfileCard.tsx @@ -1,9 +1,9 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Avatar, AvatarFallback } from '@/components/ui/avatar' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { Button } from '@/components/ui' +import { Avatar, AvatarFallback } from '@/components/ui' +import { Input } from '@/components/ui' +import { Label } from '@/components/ui' +import { Textarea } from '@/components/ui' import { Envelope } from '@phosphor-icons/react' import type { User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/level4/Level4Header.tsx b/frontends/nextjs/src/components/level4/Level4Header.tsx index e52bb41ac..59ecc7fd0 100644 --- a/frontends/nextjs/src/components/level4/Level4Header.tsx +++ b/frontends/nextjs/src/components/level4/Level4Header.tsx @@ -1,11 +1,11 @@ -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui' +import { Badge } from '@/components/ui' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' +} from '@/components/ui' import { SignOut, Eye, House, Download, Upload, Terminal } from '@phosphor-icons/react' interface Level4HeaderProps { diff --git a/frontends/nextjs/src/components/level4/Level4Tabs.tsx b/frontends/nextjs/src/components/level4/Level4Tabs.tsx index 938d4fe75..1b5a5f121 100644 --- a/frontends/nextjs/src/components/level4/Level4Tabs.tsx +++ b/frontends/nextjs/src/components/level4/Level4Tabs.tsx @@ -1,4 +1,4 @@ -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' import { Database as DatabaseIcon, Lightning, Code, BookOpen, HardDrives, MapTrifold, Tree, Users, Gear, Palette, ListDashes, Sparkle, Package } from '@phosphor-icons/react' import { SchemaEditorLevel4 } from '@/components/SchemaEditorLevel4' import { WorkflowEditor } from '@/components/WorkflowEditor' diff --git a/frontends/nextjs/src/components/level5/GodUsersTab.tsx b/frontends/nextjs/src/components/level5/GodUsersTab.tsx index f9000912d..ad711fbb9 100644 --- a/frontends/nextjs/src/components/level5/GodUsersTab.tsx +++ b/frontends/nextjs/src/components/level5/GodUsersTab.tsx @@ -1,6 +1,6 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Badge } from '@/components/ui' import { Shield, Users } from '@phosphor-icons/react' import type { User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/level5/Level5Header.tsx b/frontends/nextjs/src/components/level5/Level5Header.tsx index c28718cf8..dfb9cd457 100644 --- a/frontends/nextjs/src/components/level5/Level5Header.tsx +++ b/frontends/nextjs/src/components/level5/Level5Header.tsx @@ -1,5 +1,5 @@ -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui' +import { Badge } from '@/components/ui' import { Crown, SignOut, Terminal } from '@phosphor-icons/react' interface Level5HeaderProps { diff --git a/frontends/nextjs/src/components/level5/PowerTransferTab.tsx b/frontends/nextjs/src/components/level5/PowerTransferTab.tsx index 614a474bc..227e6b339 100644 --- a/frontends/nextjs/src/components/level5/PowerTransferTab.tsx +++ b/frontends/nextjs/src/components/level5/PowerTransferTab.tsx @@ -1,10 +1,10 @@ import { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' -import { Alert, AlertDescription } from '@/components/ui/alert' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Badge } from '@/components/ui' +import { Separator } from '@/components/ui' +import { Alert, AlertDescription } from '@/components/ui' import { Crown, ArrowsLeftRight } from '@phosphor-icons/react' import type { User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/level5/PreviewTab.tsx b/frontends/nextjs/src/components/level5/PreviewTab.tsx index 51a25e432..a67dad057 100644 --- a/frontends/nextjs/src/components/level5/PreviewTab.tsx +++ b/frontends/nextjs/src/components/level5/PreviewTab.tsx @@ -1,5 +1,5 @@ -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' import { Eye } from '@phosphor-icons/react' interface PreviewTabProps { diff --git a/frontends/nextjs/src/components/level5/TenantsTab.tsx b/frontends/nextjs/src/components/level5/TenantsTab.tsx index 17dfcd9af..bf779ef54 100644 --- a/frontends/nextjs/src/components/level5/TenantsTab.tsx +++ b/frontends/nextjs/src/components/level5/TenantsTab.tsx @@ -1,7 +1,7 @@ -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' +import { ScrollArea } from '@/components/ui' +import { Badge } from '@/components/ui' import { Buildings, House } from '@phosphor-icons/react' import type { Tenant, User } from '@/lib/level-types' diff --git a/frontends/nextjs/src/components/shared/AppHeader.tsx b/frontends/nextjs/src/components/shared/AppHeader.tsx index b4e0aca7f..a57919666 100644 --- a/frontends/nextjs/src/components/shared/AppHeader.tsx +++ b/frontends/nextjs/src/components/shared/AppHeader.tsx @@ -1,6 +1,6 @@ -import { Button } from '@/components/ui/button' -import { Avatar, AvatarFallback } from '@/components/ui/avatar' -import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui' +import { Avatar, AvatarFallback } from '@/components/ui' +import { Badge } from '@/components/ui' import { SignOut, House } from '@phosphor-icons/react' interface AppHeaderProps { diff --git a/frontends/nextjs/src/components/ui/README.md b/frontends/nextjs/src/components/ui/README.md index 590819f37..49877a478 100644 --- a/frontends/nextjs/src/components/ui/README.md +++ b/frontends/nextjs/src/components/ui/README.md @@ -29,10 +29,10 @@ ui/ ## Usage ```tsx -// Import from the main index +// Prefer the main barrel import import { Button, Card, Table, Dialog } from '@/components/ui' -// Or import from specific category +// Category imports are available when you want stricter layering import { Button, Input } from '@/components/ui/atoms' import { Card, Select } from '@/components/ui/molecules' import { Table, Form } from '@/components/ui/organisms' @@ -79,8 +79,8 @@ import styles from './MyComponent.module.scss' ## Legacy Files -⚠️ **Files in the root of this directory (like `button.tsx`, `dialog.tsx`, etc.) are LEGACY** -and will be removed. Only use components from: +⚠️ **Root wrapper files (like `button.tsx`, `dialog.tsx`, etc.) are legacy** and exist for +backward compatibility. Avoid `@/components/ui/` imports and use: - `atoms/` - `molecules/` - `organisms/` diff --git a/frontends/nextjs/src/components/ui/accordion.ts b/frontends/nextjs/src/components/ui/accordion.ts index 2918b49a2..66914ad83 100644 --- a/frontends/nextjs/src/components/ui/accordion.ts +++ b/frontends/nextjs/src/components/ui/accordion.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './molecules/Accordion' diff --git a/frontends/nextjs/src/components/ui/alert-dialog.ts b/frontends/nextjs/src/components/ui/alert-dialog.ts index b207d437a..45441b8a9 100644 --- a/frontends/nextjs/src/components/ui/alert-dialog.ts +++ b/frontends/nextjs/src/components/ui/alert-dialog.ts @@ -1,5 +1,4 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { AlertDialog, AlertDialogTrigger, diff --git a/frontends/nextjs/src/components/ui/alert.ts b/frontends/nextjs/src/components/ui/alert.ts index bc9b35ab7..206023482 100644 --- a/frontends/nextjs/src/components/ui/alert.ts +++ b/frontends/nextjs/src/components/ui/alert.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Alert, AlertTitle, AlertDescription, type AlertVariant, type AlertProps } from './molecules/Alert' diff --git a/frontends/nextjs/src/components/ui/avatar.ts b/frontends/nextjs/src/components/ui/avatar.ts index 1decb73a4..284c880ed 100644 --- a/frontends/nextjs/src/components/ui/avatar.ts +++ b/frontends/nextjs/src/components/ui/avatar.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Avatar, AvatarImage, AvatarFallback, type AvatarProps } from './atoms/Avatar' diff --git a/frontends/nextjs/src/components/ui/badge.ts b/frontends/nextjs/src/components/ui/badge.ts index 1b5dc9ccf..1fa13dc76 100644 --- a/frontends/nextjs/src/components/ui/badge.ts +++ b/frontends/nextjs/src/components/ui/badge.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Badge, type BadgeProps, type BadgeVariant } from './atoms/Badge' diff --git a/frontends/nextjs/src/components/ui/button.ts b/frontends/nextjs/src/components/ui/button.ts index fe61d0f70..bd69e4248 100644 --- a/frontends/nextjs/src/components/ui/button.ts +++ b/frontends/nextjs/src/components/ui/button.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from './atoms/Button' diff --git a/frontends/nextjs/src/components/ui/card.ts b/frontends/nextjs/src/components/ui/card.ts index 9005bafc1..331fd6d3f 100644 --- a/frontends/nextjs/src/components/ui/card.ts +++ b/frontends/nextjs/src/components/ui/card.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './molecules/Card' diff --git a/frontends/nextjs/src/components/ui/checkbox.ts b/frontends/nextjs/src/components/ui/checkbox.ts index 074417571..77857c5a6 100644 --- a/frontends/nextjs/src/components/ui/checkbox.ts +++ b/frontends/nextjs/src/components/ui/checkbox.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Checkbox, type CheckboxProps } from './atoms/Checkbox' diff --git a/frontends/nextjs/src/components/ui/dialog.ts b/frontends/nextjs/src/components/ui/dialog.ts index a6e66eaf2..1ff6f9c9c 100644 --- a/frontends/nextjs/src/components/ui/dialog.ts +++ b/frontends/nextjs/src/components/ui/dialog.ts @@ -1,5 +1,4 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Dialog, DialogClose, diff --git a/frontends/nextjs/src/components/ui/dropdown-menu.ts b/frontends/nextjs/src/components/ui/dropdown-menu.ts index fd0d4b50f..99a3d8e99 100644 --- a/frontends/nextjs/src/components/ui/dropdown-menu.ts +++ b/frontends/nextjs/src/components/ui/dropdown-menu.ts @@ -1,5 +1,4 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { DropdownMenu, DropdownMenuTrigger, diff --git a/frontends/nextjs/src/components/ui/index.test.ts b/frontends/nextjs/src/components/ui/index.test.ts new file mode 100644 index 000000000..158f417ee --- /dev/null +++ b/frontends/nextjs/src/components/ui/index.test.ts @@ -0,0 +1,13 @@ +import { describe, it, expect } from 'vitest' +import { Button, Card, Dialog, Select, Table, Tabs } from '@/components/ui' + +describe('ui barrel exports', () => { + it('exposes common UI components', () => { + expect(Button).toBeDefined() + expect(Card).toBeDefined() + expect(Dialog).toBeDefined() + expect(Select).toBeDefined() + expect(Table).toBeDefined() + expect(Tabs).toBeDefined() + }) +}) diff --git a/frontends/nextjs/src/components/ui/input.ts b/frontends/nextjs/src/components/ui/input.ts index 3a6ad63af..7f7a0b872 100644 --- a/frontends/nextjs/src/components/ui/input.ts +++ b/frontends/nextjs/src/components/ui/input.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Input, type InputProps } from './atoms/Input' diff --git a/frontends/nextjs/src/components/ui/label.ts b/frontends/nextjs/src/components/ui/label.ts index 6c5501e29..2b4dbfd19 100644 --- a/frontends/nextjs/src/components/ui/label.ts +++ b/frontends/nextjs/src/components/ui/label.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Label, type LabelProps } from './atoms/Label' diff --git a/frontends/nextjs/src/components/ui/progress.ts b/frontends/nextjs/src/components/ui/progress.ts index c70075226..c16961c85 100644 --- a/frontends/nextjs/src/components/ui/progress.ts +++ b/frontends/nextjs/src/components/ui/progress.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Progress, type ProgressProps } from './atoms/Progress' diff --git a/frontends/nextjs/src/components/ui/radio-group.ts b/frontends/nextjs/src/components/ui/radio-group.ts index 940ad3347..cbc991767 100644 --- a/frontends/nextjs/src/components/ui/radio-group.ts +++ b/frontends/nextjs/src/components/ui/radio-group.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { RadioGroup, RadioGroupItem } from './molecules/RadioGroup' diff --git a/frontends/nextjs/src/components/ui/scroll-area.ts b/frontends/nextjs/src/components/ui/scroll-area.ts index ffbec9bc9..634608829 100644 --- a/frontends/nextjs/src/components/ui/scroll-area.ts +++ b/frontends/nextjs/src/components/ui/scroll-area.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { ScrollArea, ScrollBar, type ScrollAreaProps } from './atoms/ScrollArea' diff --git a/frontends/nextjs/src/components/ui/select.ts b/frontends/nextjs/src/components/ui/select.ts index 775ac9cf0..51d5a5034 100644 --- a/frontends/nextjs/src/components/ui/select.ts +++ b/frontends/nextjs/src/components/ui/select.ts @@ -1,5 +1,4 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Select, SelectContent, diff --git a/frontends/nextjs/src/components/ui/separator.ts b/frontends/nextjs/src/components/ui/separator.ts index cf886811d..d90361a2e 100644 --- a/frontends/nextjs/src/components/ui/separator.ts +++ b/frontends/nextjs/src/components/ui/separator.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Separator, type SeparatorProps } from './atoms/Separator' diff --git a/frontends/nextjs/src/components/ui/sheet.ts b/frontends/nextjs/src/components/ui/sheet.ts index 79688c2a2..70d57f5de 100644 --- a/frontends/nextjs/src/components/ui/sheet.ts +++ b/frontends/nextjs/src/components/ui/sheet.ts @@ -1,5 +1,4 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Sheet, SheetTrigger, diff --git a/frontends/nextjs/src/components/ui/skeleton.ts b/frontends/nextjs/src/components/ui/skeleton.ts index 402c737ad..f38eba5f7 100644 --- a/frontends/nextjs/src/components/ui/skeleton.ts +++ b/frontends/nextjs/src/components/ui/skeleton.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Skeleton, type SkeletonProps } from './atoms/Skeleton' diff --git a/frontends/nextjs/src/components/ui/slider.ts b/frontends/nextjs/src/components/ui/slider.ts index 24657a30d..ee91f7bf8 100644 --- a/frontends/nextjs/src/components/ui/slider.ts +++ b/frontends/nextjs/src/components/ui/slider.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Slider, type SliderProps } from './atoms/Slider' diff --git a/frontends/nextjs/src/components/ui/switch.ts b/frontends/nextjs/src/components/ui/switch.ts index 5c2f14929..39373ed11 100644 --- a/frontends/nextjs/src/components/ui/switch.ts +++ b/frontends/nextjs/src/components/ui/switch.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Switch, type SwitchProps } from './atoms/Switch' diff --git a/frontends/nextjs/src/components/ui/table.ts b/frontends/nextjs/src/components/ui/table.ts index 8f6ece4c9..53eff85c2 100644 --- a/frontends/nextjs/src/components/ui/table.ts +++ b/frontends/nextjs/src/components/ui/table.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption } from './organisms/Table' diff --git a/frontends/nextjs/src/components/ui/tabs.ts b/frontends/nextjs/src/components/ui/tabs.ts index e0be87387..066f7659e 100644 --- a/frontends/nextjs/src/components/ui/tabs.ts +++ b/frontends/nextjs/src/components/ui/tabs.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Tabs, TabsList, TabsTrigger, TabsContent } from './molecules/Tabs' diff --git a/frontends/nextjs/src/components/ui/textarea.ts b/frontends/nextjs/src/components/ui/textarea.ts index 0eb9168a1..80214eb01 100644 --- a/frontends/nextjs/src/components/ui/textarea.ts +++ b/frontends/nextjs/src/components/ui/textarea.ts @@ -1,3 +1,2 @@ // Re-export for backward compatibility -// TODO: Update imports to use @/components/ui directly export { Textarea, type TextareaProps } from './atoms/Textarea' diff --git a/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.test.ts b/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.test.ts index 0562a69e1..93757e872 100644 --- a/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.test.ts +++ b/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.test.ts @@ -2,26 +2,64 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { checkRateLimit } from './check-rate-limit' import { clearRateLimit } from './clear-rate-limit' import { clearAllRateLimits } from './clear-all-rate-limits' -import { MAX_REQUESTS_PER_WINDOW, RATE_LIMIT_WINDOW } from './rate-limit-store' +import { DEFAULT_MAX_REQUESTS_PER_WINDOW, DEFAULT_RATE_LIMIT_WINDOW_MS, getRateLimitConfig } from './rate-limit-store' const BASE_TIME = new Date('2024-01-01T00:00:00Z') +const ENV_RATE_LIMIT_WINDOW_MS = 'MB_RATE_LIMIT_WINDOW_MS' +const ENV_MAX_REQUESTS = 'MB_RATE_LIMIT_MAX_REQUESTS' + +const resetRateLimitEnv = () => { + delete process.env[ENV_RATE_LIMIT_WINDOW_MS] + delete process.env[ENV_MAX_REQUESTS] +} describe('rate limit helpers', () => { beforeEach(() => { vi.useFakeTimers() vi.setSystemTime(BASE_TIME) clearAllRateLimits() + resetRateLimitEnv() }) afterEach(() => { clearAllRateLimits() vi.useRealTimers() + resetRateLimitEnv() + }) + + describe('rate limit config', () => { + it('uses defaults when env is unset', () => { + const { windowMs, maxRequests } = getRateLimitConfig() + + expect(windowMs).toBe(DEFAULT_RATE_LIMIT_WINDOW_MS) + expect(maxRequests).toBe(DEFAULT_MAX_REQUESTS_PER_WINDOW) + }) + + it('uses env overrides when valid', () => { + process.env[ENV_RATE_LIMIT_WINDOW_MS] = '120000' + process.env[ENV_MAX_REQUESTS] = '250' + + const { windowMs, maxRequests } = getRateLimitConfig() + + expect(windowMs).toBe(120000) + expect(maxRequests).toBe(250) + }) + + it('falls back to defaults for invalid env values', () => { + process.env[ENV_RATE_LIMIT_WINDOW_MS] = '-10' + process.env[ENV_MAX_REQUESTS] = 'not-a-number' + + const { windowMs, maxRequests } = getRateLimitConfig() + + expect(windowMs).toBe(DEFAULT_RATE_LIMIT_WINDOW_MS) + expect(maxRequests).toBe(DEFAULT_MAX_REQUESTS_PER_WINDOW) + }) }) describe('checkRateLimit', () => { it.each([ - { name: 'allows requests up to the limit', attempts: MAX_REQUESTS_PER_WINDOW, expected: true }, - { name: 'blocks requests over the limit', attempts: MAX_REQUESTS_PER_WINDOW + 1, expected: false }, + { name: 'allows requests up to the limit', attempts: DEFAULT_MAX_REQUESTS_PER_WINDOW, expected: true }, + { name: 'blocks requests over the limit', attempts: DEFAULT_MAX_REQUESTS_PER_WINDOW + 1, expected: false }, ])('$name', ({ attempts, expected }) => { const userId = 'user-limit' let lastResult = true @@ -34,12 +72,12 @@ describe('rate limit helpers', () => { }) it.each([ - { name: 'block before the window expires', advanceMs: RATE_LIMIT_WINDOW - 1, expected: false }, - { name: 'allow after the window expires', advanceMs: RATE_LIMIT_WINDOW + 1, expected: true }, + { name: 'block before the window expires', advanceMs: DEFAULT_RATE_LIMIT_WINDOW_MS - 1, expected: false }, + { name: 'allow after the window expires', advanceMs: DEFAULT_RATE_LIMIT_WINDOW_MS + 1, expected: true }, ])('should $name', ({ advanceMs, expected }) => { const userId = 'user-window' - for (let i = 0; i < MAX_REQUESTS_PER_WINDOW; i += 1) { + for (let i = 0; i < DEFAULT_MAX_REQUESTS_PER_WINDOW; i += 1) { checkRateLimit(userId) } @@ -49,11 +87,21 @@ describe('rate limit helpers', () => { expect(checkRateLimit(userId)).toBe(expected) }) + + it('respects env-configured limits', () => { + process.env[ENV_RATE_LIMIT_WINDOW_MS] = '1000' + process.env[ENV_MAX_REQUESTS] = '2' + const userId = 'user-env' + + expect(checkRateLimit(userId)).toBe(true) + expect(checkRateLimit(userId)).toBe(true) + expect(checkRateLimit(userId)).toBe(false) + }) }) describe('clearRateLimit', () => { it.each([{ userId: 'user-a' }, { userId: 'user-b' }])('should reset for $userId', ({ userId }) => { - for (let i = 0; i < MAX_REQUESTS_PER_WINDOW; i += 1) { + for (let i = 0; i < DEFAULT_MAX_REQUESTS_PER_WINDOW; i += 1) { checkRateLimit(userId) } @@ -71,7 +119,7 @@ describe('rate limit helpers', () => { { users: ['user-x', 'user-y', 'user-z'] }, ])('should reset all users: $users', ({ users }) => { for (const userId of users) { - for (let i = 0; i < MAX_REQUESTS_PER_WINDOW; i += 1) { + for (let i = 0; i < DEFAULT_MAX_REQUESTS_PER_WINDOW; i += 1) { checkRateLimit(userId) } expect(checkRateLimit(userId)).toBe(false) diff --git a/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.ts b/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.ts index 2c1d967da..51f2e213b 100644 --- a/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.ts +++ b/frontends/nextjs/src/lib/security/secure-db/check-rate-limit.ts @@ -1,17 +1,18 @@ -import { RATE_LIMIT_WINDOW, MAX_REQUESTS_PER_WINDOW, rateLimitMap } from './rate-limit-store' +import { getRateLimitConfig, rateLimitMap } from './rate-limit-store' /** * Check if user is within rate limits */ export function checkRateLimit(userId: string): boolean { + const { windowMs, maxRequests } = getRateLimitConfig() const now = Date.now() const userRequests = rateLimitMap.get(userId) || [] const recentRequests = userRequests.filter( - timestamp => now - timestamp < RATE_LIMIT_WINDOW + timestamp => now - timestamp < windowMs ) - if (recentRequests.length >= MAX_REQUESTS_PER_WINDOW) { + if (recentRequests.length >= maxRequests) { return false } diff --git a/frontends/nextjs/src/lib/security/secure-db/rate-limit-store.ts b/frontends/nextjs/src/lib/security/secure-db/rate-limit-store.ts index 9a1e0662c..db203c24b 100644 --- a/frontends/nextjs/src/lib/security/secure-db/rate-limit-store.ts +++ b/frontends/nextjs/src/lib/security/secure-db/rate-limit-store.ts @@ -1,7 +1,20 @@ -// TODO: Load rate limit settings from config/DB instead of hardcoding. -const RATE_LIMIT_WINDOW = 60000 -const MAX_REQUESTS_PER_WINDOW = 100 +const DEFAULT_RATE_LIMIT_WINDOW_MS = 60000 +const DEFAULT_MAX_REQUESTS_PER_WINDOW = 100 + +const parsePositiveInt = (value: string | undefined, fallback: number): number => { + if (!value) return fallback + + const parsed = Number.parseInt(value, 10) + if (!Number.isFinite(parsed) || parsed <= 0) return fallback + + return parsed +} + +export const getRateLimitConfig = () => ({ + windowMs: parsePositiveInt(process.env.MB_RATE_LIMIT_WINDOW_MS, DEFAULT_RATE_LIMIT_WINDOW_MS), + maxRequests: parsePositiveInt(process.env.MB_RATE_LIMIT_MAX_REQUESTS, DEFAULT_MAX_REQUESTS_PER_WINDOW), +}) const rateLimitMap = new Map() -export { RATE_LIMIT_WINDOW, MAX_REQUESTS_PER_WINDOW, rateLimitMap } +export { DEFAULT_RATE_LIMIT_WINDOW_MS, DEFAULT_MAX_REQUESTS_PER_WINDOW, rateLimitMap } diff --git a/tools/analyze-render-performance.ts b/tools/analyze-render-performance.ts index 4bb3634f6..f9a549643 100644 --- a/tools/analyze-render-performance.ts +++ b/tools/analyze-render-performance.ts @@ -1,8 +1,294 @@ #!/usr/bin/env tsx -console.log(JSON.stringify({ - averageRenderTime: 12, - slowComponents: [], - recommendations: [], - timestamp: new Date().toISOString() -}, null, 2)) +import { existsSync, readdirSync, readFileSync, statSync } from 'fs' +import { basename, extname, join, relative } from 'path' + +interface HookCounts { + [key: string]: number +} + +interface ComponentMetrics { + file: string + component: string + lines: number + bytes: number + hooks: { + builtIn: number + custom: number + total: number + byHook: HookCounts + } + effects: number + memoization: number + estimatedRenderTimeMs: number + reasons: string[] + risk: 'low' | 'medium' | 'high' +} + +const BUILTIN_HOOKS = [ + 'useState', + 'useReducer', + 'useEffect', + 'useLayoutEffect', + 'useInsertionEffect', + 'useMemo', + 'useCallback', + 'useRef', + 'useContext', + 'useSyncExternalStore', + 'useTransition', + 'useDeferredValue', + 'useId', + 'useImperativeHandle', +] + +const BUILTIN_HOOK_SET = new Set(BUILTIN_HOOKS) +const SKIP_DIRS = new Set([ + 'node_modules', + '.next', + 'dist', + 'build', + 'coverage', + '.git', + '__tests__', + '__mocks__', + '__snapshots__', +]) + +const THRESHOLDS = { + slowRenderMs: 16, + largeComponentLines: 200, + veryLargeComponentLines: 300, + highHookCount: 12, + highEffectCount: 3, +} + +const TARGET_EXTENSIONS = new Set(['.tsx']) + +function countMatches(content: string, regex: RegExp): number { + return content.match(regex)?.length ?? 0 +} + +function pickSourceRoot(): string | null { + const candidates = [ + process.env.RENDER_ANALYSIS_ROOT, + join(process.cwd(), 'frontends', 'nextjs', 'src'), + join(process.cwd(), 'src'), + ].filter(Boolean) as string[] + + for (const candidate of candidates) { + if (existsSync(candidate)) { + return candidate + } + } + + return null +} + +function walkDir(dir: string, files: string[]): void { + let entries: string[] + try { + entries = readdirSync(dir) + } catch { + return + } + + for (const entry of entries) { + const fullPath = join(dir, entry) + let stats + try { + stats = statSync(fullPath) + } catch { + continue + } + + if (stats.isDirectory()) { + if (SKIP_DIRS.has(entry)) { + continue + } + walkDir(fullPath, files) + continue + } + + if (!stats.isFile()) { + continue + } + + if (!TARGET_EXTENSIONS.has(extname(entry))) { + continue + } + + if (entry.endsWith('.test.tsx') || entry.endsWith('.spec.tsx') || entry.endsWith('.stories.tsx')) { + continue + } + + files.push(fullPath) + } +} + +function estimateRenderTimeMs(lines: number, hooks: number, effects: number, memoization: number): number { + const base = 1.5 + const lineCost = Math.min(lines, 400) * 0.03 + const hookCost = hooks * 0.4 + const effectCost = effects * 0.8 + const memoSavings = Math.min(memoization, 4) * 0.3 + const estimate = base + lineCost + hookCost + effectCost - memoSavings + return Math.max(0.5, Math.round(estimate * 10) / 10) +} + +function analyzeFile(filePath: string): ComponentMetrics | null { + let content = '' + try { + content = readFileSync(filePath, 'utf8') + } catch { + return null + } + + const lines = content.split(/\r?\n/).length + const bytes = Buffer.byteLength(content, 'utf8') + + const byHook: HookCounts = {} + let builtInCount = 0 + + for (const hook of BUILTIN_HOOKS) { + const count = countMatches(content, new RegExp(`\\b${hook}\\b`, 'g')) + byHook[hook] = count + builtInCount += count + } + + const allHookCalls = content.match(/\buse[A-Z]\w*\b/g) ?? [] + const customHookCount = Math.max(0, allHookCalls.filter(hook => !BUILTIN_HOOK_SET.has(hook)).length) + const hookCount = builtInCount + customHookCount + + const effectCount = (byHook.useEffect ?? 0) + (byHook.useLayoutEffect ?? 0) + (byHook.useInsertionEffect ?? 0) + const memoCount = (byHook.useMemo ?? 0) + (byHook.useCallback ?? 0) + const reactMemoCount = countMatches(content, /\bReact\.memo\b/g) + const memoCallCount = countMatches(content, /\bmemo\s*\(/g) + const memoization = memoCount + reactMemoCount + Math.max(0, memoCallCount - reactMemoCount) + + const estimatedRenderTimeMs = estimateRenderTimeMs(lines, hookCount, effectCount, memoization) + const reasons: string[] = [] + + if (lines >= THRESHOLDS.veryLargeComponentLines) { + reasons.push(`Very large component: ${lines} lines`) + } else if (lines >= THRESHOLDS.largeComponentLines) { + reasons.push(`Large component: ${lines} lines`) + } + + if (hookCount >= THRESHOLDS.highHookCount) { + reasons.push(`High hook count: ${hookCount}`) + } + + if (effectCount >= THRESHOLDS.highEffectCount) { + reasons.push(`Multiple effects: ${effectCount}`) + } + + if (estimatedRenderTimeMs >= THRESHOLDS.slowRenderMs) { + reasons.push(`Estimated render time: ${estimatedRenderTimeMs}ms`) + } + + let risk: ComponentMetrics['risk'] = 'low' + if (reasons.length >= 3 || estimatedRenderTimeMs >= THRESHOLDS.slowRenderMs) { + risk = 'high' + } else if (reasons.length >= 1) { + risk = 'medium' + } + + return { + file: relative(process.cwd(), filePath), + component: basename(filePath, '.tsx'), + lines, + bytes, + hooks: { + builtIn: builtInCount, + custom: customHookCount, + total: hookCount, + byHook, + }, + effects: effectCount, + memoization, + estimatedRenderTimeMs, + reasons, + risk, + } +} + +function buildRecommendations(slowComponents: ComponentMetrics[]): string[] { + const recommendations: string[] = [] + + if (slowComponents.length === 0) { + recommendations.push('No high-risk components detected. Re-run after significant UI changes.') + return recommendations + } + + if (slowComponents.some(component => component.lines >= THRESHOLDS.veryLargeComponentLines)) { + recommendations.push('Split components over 300 lines into smaller pieces to reduce render work.') + } + + if (slowComponents.some(component => component.effects >= THRESHOLDS.highEffectCount)) { + recommendations.push('Reduce the number of effects per component by extracting side effects into hooks.') + } + + if (slowComponents.some(component => component.hooks.total >= THRESHOLDS.highHookCount)) { + recommendations.push('Consider splitting stateful logic across smaller components or hooks.') + } + + if (slowComponents.some(component => component.memoization === 0 && component.estimatedRenderTimeMs >= THRESHOLDS.slowRenderMs)) { + recommendations.push('Add memoization (React.memo/useMemo/useCallback) where render work is heavy.') + } + + if (recommendations.length === 0) { + recommendations.push('Review flagged components for unnecessary renders or expensive computations.') + } + + return recommendations +} + +const rootDir = pickSourceRoot() + +if (!rootDir) { + console.log(JSON.stringify({ + analysisType: 'static-heuristic', + averageRenderTime: 0, + slowComponents: [], + recommendations: ['No source directory found to analyze.'], + timestamp: new Date().toISOString(), + }, null, 2)) + process.exit(0) +} + +const files: string[] = [] +walkDir(rootDir, files) + +const metrics: ComponentMetrics[] = files + .map(file => analyzeFile(file)) + .filter((result): result is ComponentMetrics => result !== null) + +const averageRenderTime = metrics.length === 0 + ? 0 + : Math.round((metrics.reduce((sum, metric) => sum + metric.estimatedRenderTimeMs, 0) / metrics.length) * 10) / 10 + +const slowComponents = metrics + .filter(metric => metric.reasons.length > 0 || metric.estimatedRenderTimeMs >= THRESHOLDS.slowRenderMs) + .sort((a, b) => b.estimatedRenderTimeMs - a.estimatedRenderTimeMs) + +const topByLines = [...metrics].sort((a, b) => b.lines - a.lines).slice(0, 10) +const topByHooks = [...metrics].sort((a, b) => b.hooks.total - a.hooks.total).slice(0, 10) + +const summary = { + analysisType: 'static-heuristic', + rootDir: relative(process.cwd(), rootDir) || '.', + componentsAnalyzed: metrics.length, + averageRenderTime, + averageRenderTimeMs: averageRenderTime, + slowComponentsTotal: slowComponents.length, + thresholds: THRESHOLDS, + slowComponents: slowComponents.slice(0, 15), + topByLines, + topByHooks, + recommendations: buildRecommendations(slowComponents), + note: 'Estimated render times are derived from file size and hook usage. Use React Profiler for real timings.', + timestamp: new Date().toISOString(), +} + +console.log(JSON.stringify(summary, null, 2))