diff --git a/frontends/nextjs/src/app/codegen/CodegenStudioClient.test.tsx b/frontends/nextjs/src/app/codegen/CodegenStudioClient.test.tsx deleted file mode 100644 index e771310b4..000000000 --- a/frontends/nextjs/src/app/codegen/CodegenStudioClient.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import { vi } from 'vitest' - -import CodegenStudioClient from './CodegenStudioClient' - -describe('CodegenStudioClient', () => { - const originalFetch = global.fetch - const originalCreateObjectURL = URL.createObjectURL - const originalRevokeObjectURL = URL.revokeObjectURL - - beforeEach(() => { - global.fetch = vi.fn().mockResolvedValue({ - ok: true, - blob: () => Promise.resolve(new Blob(['zip-binary'])), - headers: { get: () => 'attachment; filename="codegen-studio.zip"' }, - }) as unknown as typeof fetch - - URL.createObjectURL = vi.fn(() => 'blob://codegen') - URL.revokeObjectURL = vi.fn() - }) - - afterEach(() => { - global.fetch = originalFetch - URL.createObjectURL = originalCreateObjectURL - URL.revokeObjectURL = originalRevokeObjectURL - vi.clearAllMocks() - }) - - it('renders form and triggers ZIP download', async () => { - render() - - const button = screen.getByRole('button', { name: /generate zip/i }) - fireEvent.click(button) - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalled() - }) - }) -}) diff --git a/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx b/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx deleted file mode 100644 index 4cf29988d..000000000 --- a/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx +++ /dev/null @@ -1,151 +0,0 @@ -'use client' - -import { type ChangeEvent, useMemo, useState } from 'react' - -import { - Alert, - Box, - Button, - CircularProgress, - Container, - MenuItem, - Paper, - Stack, - TextField, - Typography, -} from '@/fakemui' - -import Header from './components/Header' -import Sidebar from './components/Sidebar' -import { type CodegenRequest, useCodegenData } from './hooks/useCodegenData' - -const runtimeOptions = [ - { value: 'web', label: 'Next.js web' }, - { value: 'cli', label: 'Command line' }, - { value: 'desktop', label: 'Desktop shell' }, - { value: 'hybrid', label: 'Hybrid web + desktop' }, - { value: 'server', label: 'Server service' }, -] - -const initialFormState: CodegenRequest = { - projectName: 'nebula-launch', - packageId: 'codegen_studio', - runtime: 'web', - tone: 'newsroom', - brief: 'Modern web interface with CLI companions', -} - -type FormState = typeof initialFormState - -export default function CodegenStudioClient() { - const [form, setForm] = useState(initialFormState) - const { status, message, error, manifest, generate } = useCodegenData() - - const runtimeDescription = useMemo(() => { - switch (form.runtime) { - case 'cli': - return 'Generates a CLI entry point plus Next.js wrappers.' - case 'desktop': - return 'Includes desktop-ready shell + companion CLI.' - case 'hybrid': - return 'Bundles both web and desktop artifacts.' - case 'server': - return 'Prepares a backend service project.' - default: - return 'Focuses on a Next.js web experience.' - } - }, [form.runtime]) - - const previewFiles = useMemo(() => { - const root = - form.projectName - .toLowerCase() - .trim() - .replace(/[^a-z0-9_-]+/g, '-') - .replace(/^-+|-+$/g, '') || 'metabuilder-starter' - return [ - `${root}/README.md`, - `${root}/package.json`, - `${root}/src/app/page.tsx`, - `${root}/cli/main.cpp`, - `${root}/spec.json`, - ] - }, [form.projectName]) - - const handleChange = (key: keyof FormState) => (event: ChangeEvent) => { - setForm(prev => ({ ...prev, [key]: event.target.value })) - } - - const handleSubmit = () => generate(form) - - return ( - - - -
- - - - - - {runtimeOptions.map(option => ( - - {option.label} - - ))} - - - {runtimeDescription} - - - - - - - - {message && {message}} - {error && {error}} - - - - - - - - - ) -} diff --git a/frontends/nextjs/src/app/codegen/components/Header.tsx b/frontends/nextjs/src/app/codegen/components/Header.tsx deleted file mode 100644 index f248adadf..000000000 --- a/frontends/nextjs/src/app/codegen/components/Header.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client' - -import { Stack, Typography } from '@/fakemui' - -interface HeaderProps { - title: string - subtitle: string -} - -export default function Header({ title, subtitle }: HeaderProps) { - return ( - - - {title} - - - {subtitle} - - - ) -} diff --git a/frontends/nextjs/src/app/codegen/components/Sidebar.tsx b/frontends/nextjs/src/app/codegen/components/Sidebar.tsx deleted file mode 100644 index 795322f19..000000000 --- a/frontends/nextjs/src/app/codegen/components/Sidebar.tsx +++ /dev/null @@ -1,56 +0,0 @@ -'use client' - -import { Paper, Stack, Typography } from '@/fakemui' -import type { CodegenManifest } from '@/lib/codegen/codegen-types' - -interface SidebarProps { - manifest: CodegenManifest | null - previewFiles: string[] -} - -export default function Sidebar({ manifest, previewFiles }: SidebarProps) { - return ( - - {manifest && ( - - - Manifest preview - - - - Project: {manifest.projectName} - - - Package: {manifest.packageId} - - - Runtime: {manifest.runtime} - - - Tone: {manifest.tone ?? 'adaptive'} - - - Generated at: {new Date(manifest.generatedAt).toLocaleString()} - - - - )} - - Bundle contents - {previewFiles.map(entry => ( - - • {entry} - - ))} - - - ) -} diff --git a/frontends/nextjs/src/app/codegen/hooks/useCodegenData.ts b/frontends/nextjs/src/app/codegen/hooks/useCodegenData.ts deleted file mode 100644 index fab2ad405..000000000 --- a/frontends/nextjs/src/app/codegen/hooks/useCodegenData.ts +++ /dev/null @@ -1,78 +0,0 @@ -'use client' - -import { useCallback, useState } from 'react' - -import type { CodegenManifest } from '@/lib/codegen/codegen-types' - -export type CodegenRequest = { - projectName: string - packageId: string - runtime: string - tone: string - brief: string -} - -export type FetchStatus = 'idle' | 'loading' | 'success' - -const createFilename = (header: string | null, fallback: string) => { - const match = header?.match(/filename="?([^"]+)"?/) ?? null - return match ? match[1] : fallback -} - -const downloadBlob = (blob: Blob, filename: string) => { - const url = URL.createObjectURL(blob) - const anchor = document.createElement('a') - anchor.href = url - anchor.download = filename - document.body.appendChild(anchor) - anchor.click() - anchor.remove() - URL.revokeObjectURL(url) -} - -const fetchZip = async (values: CodegenRequest) => { - const response = await fetch('/api/codegen/studio', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(values), - }) - if (!response.ok) { - throw new Error('Codegen Studio service returned an error') - } - const blob = await response.blob() - const filename = createFilename( - response.headers.get('content-disposition'), - `${values.projectName}.zip` - ) - downloadBlob(blob, filename) - const manifestHeader = response.headers.get('x-codegen-manifest') - const manifest = manifestHeader - ? (JSON.parse(decodeURIComponent(manifestHeader)) as CodegenManifest) - : null - return { filename, manifest } -} - -export function useCodegenData() { - const [status, setStatus] = useState('idle') - const [message, setMessage] = useState(null) - const [error, setError] = useState(null) - const [manifest, setManifest] = useState(null) - - const generate = useCallback(async (values: CodegenRequest) => { - setStatus('loading') - setError(null) - setMessage(null) - try { - const { filename, manifest: manifestResult } = await fetchZip(values) - setMessage(`Zip ${filename} created successfully.`) - setManifest(manifestResult) - setStatus('success') - } catch (err) { - setError(err instanceof Error ? err.message : 'Unable to generate the zip') - setManifest(null) - setStatus('idle') - } - }, []) - - return { status, message, error, manifest, generate } -} diff --git a/frontends/nextjs/src/app/codegen/page.tsx b/frontends/nextjs/src/app/codegen/page.tsx deleted file mode 100644 index 608a74376..000000000 --- a/frontends/nextjs/src/app/codegen/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Metadata } from 'next' - -import CodegenStudioClient from './CodegenStudioClient' - -export const metadata: Metadata = { - title: 'Codegen Studio', - description: 'Generate and download custom starter bundles for MetaBuilder packages.', -} - -export default function CodegenStudioPage() { - return -} diff --git a/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx b/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx deleted file mode 100644 index c60ee79f3..000000000 --- a/frontends/nextjs/src/components/ComponentHierarchyEditor.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ComponentHierarchyEditor } from './managers/component/ComponentHierarchyEditor' diff --git a/frontends/nextjs/src/components/CssClassManager.tsx b/frontends/nextjs/src/components/CssClassManager.tsx deleted file mode 100644 index 8f178573d..000000000 --- a/frontends/nextjs/src/components/CssClassManager.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CssClassManager } from './managers/css/CssClassManager' diff --git a/frontends/nextjs/src/components/GodCredentialsSettings.tsx b/frontends/nextjs/src/components/GodCredentialsSettings.tsx deleted file mode 100644 index 6e4fd7a16..000000000 --- a/frontends/nextjs/src/components/GodCredentialsSettings.tsx +++ /dev/null @@ -1 +0,0 @@ -export { GodCredentialsSettings } from './misc/auth/GodCredentialsSettings' diff --git a/frontends/nextjs/src/components/Level1.tsx b/frontends/nextjs/src/components/Level1.tsx deleted file mode 100644 index 9ed4cb524..000000000 --- a/frontends/nextjs/src/components/Level1.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Level1, type Level1Props } from './level/levels/Level1' diff --git a/frontends/nextjs/src/components/Level2.tsx b/frontends/nextjs/src/components/Level2.tsx deleted file mode 100644 index 0ebea34b8..000000000 --- a/frontends/nextjs/src/components/Level2.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Level2, type Level2Props } from './level/levels/Level2' diff --git a/frontends/nextjs/src/components/Level3.tsx b/frontends/nextjs/src/components/Level3.tsx deleted file mode 100644 index 2c617a031..000000000 --- a/frontends/nextjs/src/components/Level3.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Level3, type Level3Props } from './level/levels/Level3' diff --git a/frontends/nextjs/src/components/Level4.tsx b/frontends/nextjs/src/components/Level4.tsx deleted file mode 100644 index 2475b1e88..000000000 --- a/frontends/nextjs/src/components/Level4.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Level4, type Level4Props } from './level/levels/Level4' diff --git a/frontends/nextjs/src/components/Level5.tsx b/frontends/nextjs/src/components/Level5.tsx deleted file mode 100644 index 8cc7c2161..000000000 --- a/frontends/nextjs/src/components/Level5.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Level5, type Level5Props } from './level/levels/Level5' diff --git a/frontends/nextjs/src/components/QuickGuide.tsx b/frontends/nextjs/src/components/QuickGuide.tsx deleted file mode 100644 index 8f59591b9..000000000 --- a/frontends/nextjs/src/components/QuickGuide.tsx +++ /dev/null @@ -1 +0,0 @@ -export { QuickGuide } from './misc/data/QuickGuide' diff --git a/frontends/nextjs/src/components/SMTPConfigEditor.tsx b/frontends/nextjs/src/components/SMTPConfigEditor.tsx deleted file mode 100644 index 3490fa8ae..000000000 --- a/frontends/nextjs/src/components/SMTPConfigEditor.tsx +++ /dev/null @@ -1 +0,0 @@ -export { SMTPConfigEditor } from './misc/data/SMTPConfigEditor' diff --git a/frontends/nextjs/src/components/SchemaEditorLevel4.tsx b/frontends/nextjs/src/components/SchemaEditorLevel4.tsx deleted file mode 100644 index 92db14859..000000000 --- a/frontends/nextjs/src/components/SchemaEditorLevel4.tsx +++ /dev/null @@ -1 +0,0 @@ -export { SchemaEditorLevel4 } from './editors/schema/SchemaEditorLevel4' diff --git a/frontends/nextjs/src/components/UnifiedLogin.tsx b/frontends/nextjs/src/components/UnifiedLogin.tsx deleted file mode 100644 index 3dc9ec5ee..000000000 --- a/frontends/nextjs/src/components/UnifiedLogin.tsx +++ /dev/null @@ -1 +0,0 @@ -export { UnifiedLogin } from './misc/auth/UnifiedLogin' diff --git a/frontends/nextjs/src/components/UserManagement.tsx b/frontends/nextjs/src/components/UserManagement.tsx deleted file mode 100644 index 6687dcecc..000000000 --- a/frontends/nextjs/src/components/UserManagement.tsx +++ /dev/null @@ -1 +0,0 @@ -export { UserManagement } from './managers/UserManagement' diff --git a/frontends/nextjs/src/components/WorkflowEditor.tsx b/frontends/nextjs/src/components/WorkflowEditor.tsx deleted file mode 100644 index 2a6a900b5..000000000 --- a/frontends/nextjs/src/components/WorkflowEditor.tsx +++ /dev/null @@ -1 +0,0 @@ -export { WorkflowEditor } from './workflow/WorkflowEditor' diff --git a/frontends/nextjs/src/components/auth/AccessDenied.tsx b/frontends/nextjs/src/components/auth/AccessDenied.tsx deleted file mode 100644 index 5de0ec99c..000000000 --- a/frontends/nextjs/src/components/auth/AccessDenied.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Button, Stack, Typography } from '@/fakemui' -import { LockRounded } from '@/fakemui/icons' - -interface AccessDeniedProps { - title?: string - description?: string - actionLabel?: string - onAction?: () => void -} - -export function AccessDenied({ - title = 'Access restricted', - description = 'You do not have permission to view this area.', - actionLabel, - onAction, -}: AccessDeniedProps) { - return ( - - - - {title} - - {description} - - - {actionLabel && onAction ? ( - - ) : null} - - ) -} diff --git a/frontends/nextjs/src/components/auth/AuthGate.tsx b/frontends/nextjs/src/components/auth/AuthGate.tsx deleted file mode 100644 index f62f03c5d..000000000 --- a/frontends/nextjs/src/components/auth/AuthGate.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client' - -import { useRouter } from 'next/navigation' -import { useEffect } from 'react' - -import { CircularProgress, Stack, Typography } from '@/fakemui' -import { useAuth } from '@/hooks/useAuth' -import { getRoleLevel } from '@/lib/auth/get-role-level' -import { resolveAccessDecision } from '@/lib/auth/resolve-access-decision' -import type { AppLevel, UserRole } from '@/lib/level-types' - -import { AccessDenied } from './AccessDenied' - -interface AuthGateProps { - children: React.ReactNode - minLevel?: AppLevel - requiredRole?: UserRole - requiresAuth?: boolean - redirectTo?: string - fallback?: React.ReactNode -} - -export function AuthGate({ - children, - minLevel, - requiredRole, - requiresAuth, - redirectTo = '/login', - fallback, -}: AuthGateProps) { - const router = useRouter() - const { user, isAuthenticated, isLoading } = useAuth() - const mustAuthenticate = requiresAuth ?? Boolean(minLevel || requiredRole) - const requiredLevel = minLevel ?? (requiredRole ? getRoleLevel(requiredRole) : undefined) - - const decision = resolveAccessDecision({ - isAuthenticated, - isLoading, - requiresAuth: mustAuthenticate, - requiredLevel, - userRole: user?.role, - userLevel: user?.level, - }) - - useEffect(() => { - if (!decision.allowed && decision.reason !== 'loading' && redirectTo) { - router.replace(redirectTo) - } - }, [decision.allowed, decision.reason, redirectTo, router]) - - if (decision.reason === 'loading') { - return ( - - - - Checking access... - - - ) - } - - if (!decision.allowed) { - if (fallback) { - return <>{fallback} - } - - const description = - decision.reason === 'unauthenticated' - ? 'Please sign in to continue.' - : 'Your account does not have the required access level.' - - return ( - router.replace(redirectTo)} - /> - ) - } - - return <>{children} -} diff --git a/frontends/nextjs/src/components/auth/PageLoader.tsx b/frontends/nextjs/src/components/auth/PageLoader.tsx deleted file mode 100644 index becb4bbd7..000000000 --- a/frontends/nextjs/src/components/auth/PageLoader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { CircularProgress, Stack, Typography } from '@/fakemui' - -interface PageLoaderProps { - message?: string -} - -export function PageLoader({ message = 'Loading...' }: PageLoaderProps) { - return ( - - - - {message} - - - ) -} diff --git a/frontends/nextjs/src/components/auth/god-credentials/Form.tsx b/frontends/nextjs/src/components/auth/god-credentials/Form.tsx deleted file mode 100644 index 27a1623df..000000000 --- a/frontends/nextjs/src/components/auth/god-credentials/Form.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { - Button, - Input, - Label, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui' - -export interface GodCredentialsFormProps { - duration: number - unit: 'minutes' | 'hours' - onDurationChange: (value: number) => void - onUnitChange: (unit: 'minutes' | 'hours') => void - onSave: () => void - onResetExpiry: () => void - onClearExpiry: () => void -} - -export function GodCredentialsForm({ - duration, - unit, - onDurationChange, - onUnitChange, - onSave, - onResetExpiry, - onClearExpiry, -}: GodCredentialsFormProps) { - return ( -
-
-
- -
- onDurationChange(Number(e.target.value))} - className="flex-1" - /> - -
-

- Set the duration for how long credentials are visible (1 minute to 24 hours) -

-
- -
- -
-
- -
-
- -

Reset or clear the current expiry timer

-
- -
- - -
- -

- Reset Timer: Restart the countdown using the configured duration -
- Clear Expiry: Remove expiry time (credentials will show on next page - load) -

-
-
- ) -} diff --git a/frontends/nextjs/src/components/auth/god-credentials/Summary.tsx b/frontends/nextjs/src/components/auth/god-credentials/Summary.tsx deleted file mode 100644 index 798a56dc5..000000000 --- a/frontends/nextjs/src/components/auth/god-credentials/Summary.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Alert, AlertDescription, Badge } from '@/components/ui' -import { CheckCircle, WarningCircle } from '@/fakemui/icons' - -export interface GodCredentialsSummaryProps { - isActive: boolean - expiryTime: number - timeRemaining: string -} - -export function GodCredentialsSummary({ - isActive, - expiryTime, - timeRemaining, -}: GodCredentialsSummaryProps) { - if (isActive) { - return ( - - - -
-

- God credentials are currently visible - - Active - -

-

- Time remaining: {timeRemaining} -

-
-
-
- ) - } - - if (!isActive && expiryTime > 0) { - return ( - - - -

God credentials have expired or been hidden

-
-
- ) - } - - return null -} diff --git a/frontends/nextjs/src/components/auth/unified-login/LoginForm.tsx b/frontends/nextjs/src/components/auth/unified-login/LoginForm.tsx deleted file mode 100644 index 4b3614e7a..000000000 --- a/frontends/nextjs/src/components/auth/unified-login/LoginForm.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Alert, AlertDescription, Button, Input, Label } from '@/components/ui' -import { SignIn } from '@/fakemui/icons' - -export interface LoginFormProps { - username: string - password: string - onUsernameChange: (value: string) => void - onPasswordChange: (value: string) => void - onSubmit: () => void -} - -export function LoginForm({ - username, - password, - onUsernameChange, - onPasswordChange, - onSubmit, -}: LoginFormProps) { - return ( -
-
- - onUsernameChange(e.target.value)} - placeholder="Enter username" - onKeyDown={e => e.key === 'Enter' && onSubmit()} - /> -
-
- - onPasswordChange(e.target.value)} - placeholder="Enter password" - onKeyDown={e => e.key === 'Enter' && onSubmit()} - /> -
- - - -

Test Credentials:

-

Check browser console for default user passwords (they are scrambled on first run)

-
-
-
- ) -} diff --git a/frontends/nextjs/src/components/auth/unified-login/ProviderList.tsx b/frontends/nextjs/src/components/auth/unified-login/ProviderList.tsx deleted file mode 100644 index b48beea25..000000000 --- a/frontends/nextjs/src/components/auth/unified-login/ProviderList.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Button, Separator } from '@/components/ui' -import { GithubLogo, GoogleLogo, type IconProps } from '@/fakemui/icons' - -export interface Provider { - name: string - description?: string - icon?: React.ComponentType -} - -export interface ProviderListProps { - providers: Provider[] - onSelect?: (provider: Provider) => void -} - -const FALLBACK_PROVIDERS: Provider[] = [ - { name: 'Google', description: 'Use your Google Workspace account', icon: GoogleLogo }, - { name: 'GitHub', description: 'Developer SSO via GitHub', icon: GithubLogo }, -] - -export function ProviderList({ providers, onSelect }: ProviderListProps) { - const entries = providers.length > 0 ? providers : FALLBACK_PROVIDERS - - return ( -
- -

Or continue with

-
- {entries.map(provider => { - const Icon = provider.icon - return ( - - ) - })} -
-
- ) -} diff --git a/frontends/nextjs/src/components/dialogs/PasswordChangeDialog.tsx b/frontends/nextjs/src/components/dialogs/PasswordChangeDialog.tsx deleted file mode 100644 index 0e5282e13..000000000 --- a/frontends/nextjs/src/components/dialogs/PasswordChangeDialog.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { useState } from 'react' - -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 { Eye, EyeSlash, Warning } from '@/fakemui/icons' - -interface PasswordChangeDialogProps { - open: boolean - username: string - onPasswordChanged: (newPassword: string) => void - isFirstLogin?: boolean -} - -export function PasswordChangeDialog({ - open, - username, - onPasswordChanged, - isFirstLogin = false, -}: PasswordChangeDialogProps) { - const [newPassword, setNewPassword] = useState('') - const [confirmPassword, setConfirmPassword] = useState('') - const [error, setError] = useState('') - const [showPassword, setShowPassword] = useState(false) - - const handleSubmit = () => { - setError('') - - if (!newPassword) { - setError('Password is required') - return - } - - if (newPassword.length < 6) { - setError('Password must be at least 6 characters') - return - } - - if (newPassword !== confirmPassword) { - setError('Passwords do not match') - return - } - - onPasswordChanged(newPassword) - } - - return ( - {}} disableEscapeKeyDown> - - - - {isFirstLogin && } - {isFirstLogin ? 'Change Your Password' : 'Update Password'} - - - {isFirstLogin - ? 'For security reasons, you must change your password on first login.' - : `Change password for user: ${username}`} - - - - {isFirstLogin && ( - - - - Your account is using default credentials. Please choose a strong, unique password. - - - )} - -
-
- -
- setNewPassword(e.target.value)} - placeholder="Enter new password" - className="pr-10" - /> - -
-
- -
- - setConfirmPassword(e.target.value)} - placeholder="Confirm new password" - onKeyDown={e => { - if (e.key === 'Enter') { - handleSubmit() - } - }} - /> -
- - {error && ( - - {error} - - )} -
- - - - -
-
- ) -} diff --git a/frontends/nextjs/src/components/editors/CodeEditor.tsx b/frontends/nextjs/src/components/editors/CodeEditor.tsx deleted file mode 100644 index cb71fec51..000000000 --- a/frontends/nextjs/src/components/editors/CodeEditor.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import Editor from '@monaco-editor/react' -import { useState } from 'react' -import { toast } from 'sonner' - -import { SecurityWarningDialog } from '@/components/organisms/security/SecurityWarningDialog' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui' -import { Button } from '@/components/ui' -import { Alert, AlertDescription } from '@/components/ui' -import { FloppyDisk, ShieldCheck, Warning, X } from '@/fakemui/icons' -import { securityScanner, type SecurityScanResult } from '@/lib/security-scanner' - -interface CodeEditorProps { - open: boolean - onClose: () => void - code: string - onSave: (code: string) => void - componentName: string -} - -export function CodeEditor({ open, onClose, code, onSave, componentName }: CodeEditorProps) { - const [editorCode, setEditorCode] = useState(code || '// Write your custom code here\n') - const [securityScanResult, setSecurityScanResult] = useState(null) - const [showSecurityDialog, setShowSecurityDialog] = useState(false) - const [pendingSave, setPendingSave] = useState(false) - - const handleSave = () => { - const scanResult = securityScanner.scanJavaScript(editorCode) - setSecurityScanResult(scanResult) - - if (scanResult.severity === 'critical') { - setShowSecurityDialog(true) - toast.error('Critical security issues detected - save blocked') - return - } - - if (scanResult.severity === 'high' || scanResult.severity === 'medium') { - setPendingSave(true) - setShowSecurityDialog(true) - toast.warning('Security issues detected - review before saving') - return - } - - if (scanResult.issues.length > 0) { - toast.warning(`${scanResult.issues.length} minor security warning(s)`) - } - - onSave(editorCode) - onClose() - } - - const handleForceSave = () => { - setPendingSave(false) - setShowSecurityDialog(false) - onSave(editorCode) - onClose() - } - - const handleScan = () => { - const scanResult = securityScanner.scanJavaScript(editorCode) - setSecurityScanResult(scanResult) - setShowSecurityDialog(true) - - if (scanResult.safe) { - toast.success('No security issues detected') - } else { - toast.warning(`${scanResult.issues.length} security issue(s) detected`) - } - } - - const handleEditorChange = (value: string | undefined) => { - setEditorCode(value || '') - } - - return ( - <> - - - - Code Editor - {componentName} - - Write custom JavaScript code for this component. Access component props via{' '} - props. - - - - {securityScanResult && - securityScanResult.severity !== 'safe' && - securityScanResult.severity !== 'low' && - !showSecurityDialog && ( - - - - {securityScanResult.issues.length} security{' '} - {securityScanResult.issues.length === 1 ? 'issue' : 'issues'} detected. Click - Security Scan to review. - - - )} - -
- -
- -
- -
- - -
-
-
-
- - {securityScanResult && ( - { - setShowSecurityDialog(false) - setPendingSave(false) - }} - codeType="JavaScript code" - showProceedButton={pendingSave} - /> - )} - - ) -} diff --git a/frontends/nextjs/src/components/editors/JsonEditor.tsx b/frontends/nextjs/src/components/editors/JsonEditor.tsx deleted file mode 100644 index 38cc69c10..000000000 --- a/frontends/nextjs/src/components/editors/JsonEditor.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import Editor from '@monaco-editor/react' -import { useEffect, useState } from 'react' -import { toast } from 'sonner' - -import { SecurityWarningDialog } from '@/components/organisms/security/SecurityWarningDialog' -import { - Alert, - AlertDescription, - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from '@/components/ui' -import { Warning } from '@/fakemui/icons' -import { securityScanner, type SecurityScanResult } from '@/lib/security-scanner' -import type { JsonValue } from '@/types/utility-types' - -import { SchemaSection } from './json/SchemaSection' -import { Toolbar } from './json/Toolbar' - -interface JsonEditorProps { - open: boolean - onClose: () => void - title: string - value: JsonValue - onSave: (value: JsonValue) => void - schema?: JsonValue -} - -export function JsonEditor({ open, onClose, title, value, onSave, schema }: JsonEditorProps) { - const [jsonText, setJsonText] = useState('') - const [error, setError] = useState(null) - const [securityScanResult, setSecurityScanResult] = useState(null) - const [showSecurityDialog, setShowSecurityDialog] = useState(false) - const [pendingSave, setPendingSave] = useState(false) - - useEffect(() => { - if (open) { - setJsonText(JSON.stringify(value, null, 2)) - setError(null) - setSecurityScanResult(null) - } - }, [open, value]) - - const parseJson = (): JsonValue => JSON.parse(jsonText) as JsonValue - - const handleSave = () => { - try { - const parsed = parseJson() - - const scanResult = securityScanner.scanJSON(jsonText) - setSecurityScanResult(scanResult) - - if (scanResult.severity === 'critical') { - setShowSecurityDialog(true) - toast.error('Critical security issues detected - save blocked') - return - } - - if (scanResult.severity === 'high' || scanResult.severity === 'medium') { - setPendingSave(true) - setShowSecurityDialog(true) - toast.warning('Security issues detected - review before saving') - return - } - - if (scanResult.issues.length > 0) { - toast.warning(`${scanResult.issues.length} minor security warning(s)`) - } - - onSave(parsed) - setError(null) - onClose() - } catch (err) { - setError(err instanceof Error ? err.message : 'Invalid JSON') - } - } - - const handleForceSave = () => { - try { - onSave(parseJson()) - setError(null) - setPendingSave(false) - setShowSecurityDialog(false) - onClose() - } catch (err) { - setError(err instanceof Error ? err.message : 'Invalid JSON') - } - } - - const handleScan = () => { - const scanResult = securityScanner.scanJSON(jsonText) - setSecurityScanResult(scanResult) - setShowSecurityDialog(true) - - if (scanResult.safe) { - toast.success('No security issues detected') - } else { - toast.warning(`${scanResult.issues.length} security issue(s) detected`) - } - } - - const handleFormat = () => { - try { - setJsonText(JSON.stringify(parseJson(), null, 2)) - setError(null) - } catch (err) { - setError(err instanceof Error ? err.message : 'Invalid JSON - cannot format') - } - } - - return ( - <> - - - - {title} - - -
- {error && ( - - - {error} - - )} - - {securityScanResult && - securityScanResult.severity !== 'safe' && - securityScanResult.severity !== 'low' && - !showSecurityDialog && ( - - - - {securityScanResult.issues.length} security{' '} - {securityScanResult.issues.length === 1 ? 'issue' : 'issues'} - detected. Click Security Scan to review. - - - )} - - - -
- { - setJsonText(value || '') - setError(null) - }} - theme="vs-dark" - options={{ - minimap: { enabled: true }, - fontSize: 14, - fontFamily: 'JetBrains Mono, monospace', - lineNumbers: 'on', - roundedSelection: true, - scrollBeyondLastLine: false, - automaticLayout: true, - tabSize: 2, - wordWrap: 'on', - formatOnPaste: true, - formatOnType: true, - bracketPairColorization: { - enabled: true, - }, - folding: true, - foldingStrategy: 'indentation', - }} - /> -
-
- - -
-
- - {securityScanResult && ( - { - setShowSecurityDialog(false) - setPendingSave(false) - }} - codeType="JSON data" - showProceedButton={pendingSave} - /> - )} - - ) -} diff --git a/frontends/nextjs/src/components/editors/json/SchemaSection.tsx b/frontends/nextjs/src/components/editors/json/SchemaSection.tsx deleted file mode 100644 index 3c1b367ba..000000000 --- a/frontends/nextjs/src/components/editors/json/SchemaSection.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' - -interface SchemaSectionProps { - schema?: unknown -} - -export function SchemaSection({ schema }: SchemaSectionProps) { - if (!schema) return null - - const formattedSchema = typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2) - - return ( - - - Schema - Reference for the expected JSON structure - - -
-          {formattedSchema}
-        
-
-
- ) -} diff --git a/frontends/nextjs/src/components/editors/json/Toolbar.tsx b/frontends/nextjs/src/components/editors/json/Toolbar.tsx deleted file mode 100644 index c69bda7f4..000000000 --- a/frontends/nextjs/src/components/editors/json/Toolbar.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Button, DialogFooter } from '@/components/ui' -import { FloppyDisk, ShieldCheck, X } from '@/fakemui/icons' - -interface ToolbarProps { - onScan: () => void - onFormat: () => void - onCancel: () => void - onSave: () => void -} - -export function Toolbar({ onScan, onFormat, onCancel, onSave }: ToolbarProps) { - return ( - - - - - - - ) -} diff --git a/frontends/nextjs/src/components/editors/schema/SchemaEditor.tsx b/frontends/nextjs/src/components/editors/schema/SchemaEditor.tsx deleted file mode 100644 index 865905481..000000000 --- a/frontends/nextjs/src/components/editors/schema/SchemaEditor.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import Editor from '@monaco-editor/react' -import { useState } from 'react' - -import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui' -import { Button } from '@/components/ui' -import { Alert, AlertDescription } from '@/components/ui' -import { FloppyDisk, Warning, X } from '@/fakemui/icons' -import type { SchemaConfig } from '@/lib/schema-types' - -interface SchemaEditorProps { - open: boolean - onClose: () => void - schema: SchemaConfig - onSave: (schema: SchemaConfig) => void -} - -export function SchemaEditor({ open, onClose, schema, onSave }: SchemaEditorProps) { - const [schemaText, setSchemaText] = useState(JSON.stringify(schema, null, 2)) - const [error, setError] = useState(null) - - const handleSave = () => { - try { - const parsed = JSON.parse(schemaText) - - if (!parsed.apps || !Array.isArray(parsed.apps)) { - setError('Schema must have an "apps" array') - return - } - - onSave(parsed) - setError(null) - onClose() - } catch (err) { - setError(err instanceof Error ? err.message : 'Invalid JSON') - } - } - - return ( - - - - Edit Schema Configuration - - -
- {error && ( - - - {error} - - )} - -
- { - setSchemaText(value || '') - setError(null) - }} - theme="vs-dark" - options={{ - minimap: { enabled: true }, - fontSize: 14, - fontFamily: 'JetBrains Mono, monospace', - lineNumbers: 'on', - roundedSelection: true, - scrollBeyondLastLine: false, - automaticLayout: true, - tabSize: 2, - wordWrap: 'on', - formatOnPaste: true, - formatOnType: true, - }} - /> -
-
- - - - - -
-
- ) -} diff --git a/frontends/nextjs/src/components/editors/schema/SchemaEditorLevel4.tsx b/frontends/nextjs/src/components/editors/schema/SchemaEditorLevel4.tsx deleted file mode 100644 index 4a6dc4468..000000000 --- a/frontends/nextjs/src/components/editors/schema/SchemaEditorLevel4.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { SchemaTabs } from '@/components/schema/level4/Tabs' -import { useSchemaLevel4 } from '@/components/schema/level4/useSchemaLevel4' -import { Button } from '@/components/ui' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' -import { Plus, Trash } from '@/fakemui/icons' -import type { ModelSchema } from '@/lib/schema-types' - -interface SchemaEditorLevel4Props { - schemas: ModelSchema[] - onSchemasChange: (schemas: ModelSchema[]) => void -} - -export function SchemaEditorLevel4({ schemas, onSchemasChange }: SchemaEditorLevel4Props) { - const { - currentModel, - selectedModel, - selectModel, - handleAddField, - handleAddModel, - handleDeleteField, - handleDeleteModel, - handleUpdateField, - handleUpdateModel, - } = useSchemaLevel4({ schemas, onSchemasChange }) - - return ( -
- - -
- Models - -
- Data model definitions -
- -
- {schemas.length === 0 ? ( -

- No models yet. Create one to start. -

- ) : ( - schemas.map(schema => ( -
selectModel(schema.name)} - > -
-
{schema.label || schema.name}
-
- {schema.fields.length} fields -
-
- -
- )) - )} -
-
-
- - - {!currentModel ? ( - -
-

Select or create a model to edit

-
-
- ) : ( - - )} -
-
- ) -} diff --git a/frontends/nextjs/src/components/level/ModeratorPanel.tsx b/frontends/nextjs/src/components/level/ModeratorPanel.tsx deleted file mode 100644 index a46f33c57..000000000 --- a/frontends/nextjs/src/components/level/ModeratorPanel.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ModeratorPanel, type ModeratorPanelProps } from './panels/ModeratorPanel' diff --git a/frontends/nextjs/src/components/level/level1/CredentialsSection.tsx b/frontends/nextjs/src/components/level/level1/CredentialsSection.tsx deleted file mode 100644 index a4ffde684..000000000 --- a/frontends/nextjs/src/components/level/level1/CredentialsSection.tsx +++ /dev/null @@ -1,115 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' - -import { GodCredentialsBanner } from '@/components/level1/GodCredentialsBanner' -import { getScrambledPassword } from '@/lib/auth' - -import { ChallengePanel } from '../sections/ChallengePanel' - -export function CredentialsSection() { - const [showGodCredentials, setShowGodCredentials] = useState(false) - const [showSuperGodCredentials, setShowSuperGodCredentials] = useState(false) - const [showPassword, setShowPassword] = useState(false) - const [showSuperGodPassword, setShowSuperGodPassword] = useState(false) - const [copied, setCopied] = useState(false) - const [copiedSuper, setCopiedSuper] = useState(false) - const [timeRemaining, setTimeRemaining] = useState('') - - useEffect(() => { - let interval: ReturnType | undefined - - const checkCredentials = async () => { - try { - const { Database } = await import('@/lib/database') - - const shouldShow = await Database.shouldShowGodCredentials() - setShowGodCredentials(shouldShow) - - const superGod = await Database.getSuperGod() - const firstLoginFlags = await Database.getFirstLoginFlags() - setShowSuperGodCredentials(superGod !== null && firstLoginFlags['supergod'] === true) - - if (shouldShow) { - const expiry = await Database.getGodCredentialsExpiry() - const updateTimer = () => { - const now = Date.now() - const diff = expiry - now - - if (diff <= 0) { - setShowGodCredentials(false) - setTimeRemaining('') - return - } - - const minutes = Math.floor(diff / 60000) - const seconds = Math.floor((diff % 60000) / 1000) - setTimeRemaining(`${minutes}m ${seconds}s`) - } - - updateTimer() - interval = setInterval(updateTimer, 1000) - } - } catch { - setShowGodCredentials(false) - setShowSuperGodCredentials(false) - setTimeRemaining('') - } - } - - void checkCredentials() - - return () => { - if (interval) clearInterval(interval) - } - }, []) - - const handleCopyPassword = async () => { - await navigator.clipboard.writeText(getScrambledPassword('god')) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } - - const handleCopySuperGodPassword = async () => { - await navigator.clipboard.writeText(getScrambledPassword('supergod')) - setCopiedSuper(true) - setTimeout(() => setCopiedSuper(false), 2000) - } - - if (!showGodCredentials && !showSuperGodCredentials) return null - - return ( - -
- {showSuperGodCredentials && ( - setShowSuperGodPassword(!showSuperGodPassword)} - copied={copiedSuper} - onCopy={handleCopySuperGodPassword} - timeRemaining="" - variant="supergod" - /> - )} - - {showGodCredentials && ( - setShowPassword(!showPassword)} - copied={copied} - onCopy={handleCopyPassword} - timeRemaining={timeRemaining} - variant="god" - /> - )} -
-
- ) -} diff --git a/frontends/nextjs/src/components/level/level1/Level1Tabs.tsx b/frontends/nextjs/src/components/level/level1/Level1Tabs.tsx deleted file mode 100644 index 2611f5443..000000000 --- a/frontends/nextjs/src/components/level/level1/Level1Tabs.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' - -import { ContactSection } from '../../level1/ContactSection' -import { FeaturesSection } from '../../level1/FeaturesSection' -import { HeroSection } from '../../level1/HeroSection' -import { GitHubActionsFetcher } from '../../misc/github/GitHubActionsFetcher' -import { ServerStatusPanel } from '../../status/ServerStatusPanel' -import { IntroSection } from '../sections/IntroSection' - -interface Level1TabsProps { - onNavigate: (level: number) => void -} - -export function Level1Tabs({ onNavigate }: Level1TabsProps) { - return ( - - - Home - GitHub Actions - Server Status - - - - - - -

- Whether you're a designer who wants to create without code, or a developer who wants to - work at a higher level of abstraction, MetaBuilder adapts to your needs. -

-
- -
- - - - - - - - - - -
- ) -} diff --git a/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx b/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx deleted file mode 100644 index 1bd8b337e..000000000 --- a/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Box, Typography } from '@/fakemui' - -import { ResultsPane } from '../sections/ResultsPane' - -export function ChatTabContent() { - return ( - - - IRC Webchat - - This component is now a Lua package. See packages/irc_webchat/ - - - - ) -} diff --git a/frontends/nextjs/src/components/level/level2/CommentsTabContent.tsx b/frontends/nextjs/src/components/level/level2/CommentsTabContent.tsx deleted file mode 100644 index 43eee0913..000000000 --- a/frontends/nextjs/src/components/level/level2/CommentsTabContent.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useMemo } from 'react' - -import { Button } from '@/components/ui' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' -import { Textarea } from '@/components/ui' -import type { Comment, User } from '@/lib/level-types' - -import { CommentsList } from '../../level2/CommentsList' -import { ChallengePanel } from '../sections/ChallengePanel' - -interface CommentsTabContentProps { - comments: Comment[] - users: User[] - currentUserId: string - newComment: string - onChangeComment: (value: string) => void - onPostComment: () => void - onDeleteComment: (commentId: string) => void -} - -export function CommentsTabContent({ - comments, - users, - currentUserId, - newComment, - onChangeComment, - onPostComment, - onDeleteComment, -}: CommentsTabContentProps) { - const userComments = useMemo( - () => comments.filter(c => c.userId === currentUserId), - [comments, currentUserId] - ) - - return ( - - - - Post a Comment - Share your thoughts with the community - - -