From 50cd5c40b25f64023275c209af076ec11f6bf540 Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Mon, 29 Dec 2025 22:38:34 +0000 Subject: [PATCH] Refactor page rendering functions for consistency and readability - Adjusted indentation and formatting in get-pages.ts for consistency. - Enhanced readability in build-feature-card.ts by formatting parameters and adding commas. - Updated build-features-component.ts to maintain consistent formatting and added missing commas. - Improved formatting in build-hero-component.ts for better readability. - Standardized formatting in build-level1-homepage.ts by adding commas. - Refactored initialize-default-pages.ts for consistent indentation and added commas. - Cleaned up PageRendererUtils.ts by ensuring consistent argument handling. - Streamlined check-permissions.ts for better readability and consistency. - Refined execute-lua-script.ts for consistent error handling and formatting. - Enhanced get-page.ts and get-pages-by-level.ts with consistent return formatting. - Improved load-pages.ts for better readability and consistent formatting. - Standardized on-page-load.ts and on-page-unload.ts for consistent formatting and readability. --- .claude/settings.local.json | 4 +- .../architecture/LUA_UI_MIGRATION_STRATEGY.md | 377 ++++++++++++++++++ .../lib/lua/ui/generate-component-tree.tsx | 100 +++++ .../src/lib/lua/ui/load-lua-ui-package.ts | 66 +++ .../src/lib/lua/ui/types/lua-ui-package.ts | 61 +++ .../src/lib/packages/lua-ui/example-form.lua | 135 +++++++ .../page-renderer/functions/register-page.ts | 22 +- .../nextjs/src/lib/rendering/page/utils.ts | 5 +- ...tive-component-renderer.evaluation.test.ts | 26 +- ...ative-component-renderer.lifecycle.test.ts | 5 +- .../__tests__/schema-utils.migration.test.ts | 11 +- .../schema-utils.serialization.test.ts | 31 +- .../functions/field/get-default-value.ts | 2 +- .../schema/functions/field/validate-field.ts | 7 +- .../schema/functions/record/sort-records.ts | 10 +- .../functions/record/validate-record.ts | 5 +- .../nextjs/src/lib/schema/schema-utils.ts | 6 +- .../screenshot/request-screenshot-analysis.ts | 7 +- .../screenshot-analysis-service.test.ts | 7 +- .../screenshot/screenshot-analysis-service.ts | 4 +- .../functions/patterns/javascript-patterns.ts | 2 +- .../patterns/javascript/injection.ts | 17 +- .../functions/patterns/javascript/misc.ts | 24 +- .../functions/patterns/javascript/xss.ts | 16 +- .../functions/patterns/lua-patterns.ts | 24 +- .../functions/patterns/sql-patterns.ts | 10 +- .../scanners/language-scanners/scan-html.ts | 10 +- .../language-scanners/scan-javascript.ts | 6 +- .../scanners/language-scanners/scan-json.ts | 8 +- .../scanners/language-scanners/scan-lua.ts | 4 +- .../functions/scanners/sanitize-input.ts | 2 +- .../functions/utils/calculate-severity.ts | 2 +- .../security-scanner.detection.test.ts | 59 ++- .../lib/security/scanner/security-scanner.ts | 15 +- .../security/secure-db/audit/log-operation.ts | 2 +- .../src/lib/security/secure-db/index.ts | 10 +- .../login/create-login-security-context.ts | 4 +- .../secure-db/login/login-attempt-tracker.ts | 5 +- .../operations/crud/verify-credentials.ts | 4 +- .../getters/entities/get-comments.ts | 8 +- .../getters/entities/get-lua-scripts.ts | 8 +- .../getters/entities/get-model-schemas.ts | 8 +- .../getters/entities/get-page-configs.ts | 8 +- .../getters/entities/get-workflows.ts | 8 +- .../secure-db/operations/user/create-user.ts | 4 +- .../operations/user/get-user-by-id.ts | 5 +- .../secure-db/operations/user/update-user.ts | 11 +- .../security/secure-db/query/access-rules.ts | 64 ++- .../security/secure-db/query/check-access.ts | 12 +- .../rate-limiting/check-rate-limit.ts | 10 +- .../rate-limiting/store/rate-limit-store.ts | 8 +- .../src/lib/security/secure-db/types.ts | 18 +- frontends/nextjs/src/lib/seed/seed-data.ts | 8 +- .../nextjs/src/lib/types/builder-types.ts | 2 +- .../src/lib/types/guards/guards.test.ts | 26 +- .../src/lib/types/guards/has-property.ts | 5 +- frontends/nextjs/src/lib/types/level-types.ts | 2 +- frontends/nextjs/src/lib/utils.ts | 2 +- .../__tests__/workflow-engine.errors.test.ts | 2 +- .../workflow-engine.execution.test.ts | 2 +- .../workflow-engine.persistence.test.ts | 4 +- .../lib/workflow/execution/execute-node.ts | 5 +- .../src/lib/workflow/log-to-workflow.ts | 2 +- .../lib/workflow/nodes/execute-lua-code.ts | 4 +- .../lib/workflow/nodes/execute-lua-node.ts | 2 +- .../src/seed-data/entities/content/scripts.ts | 16 +- .../seed-data/entities/content/workflows.ts | 62 +-- .../src/tests/package-integration.test.ts | 10 +- frontends/nextjs/src/theme/base/colors.ts | 14 +- frontends/nextjs/src/theme/base/typography.ts | 40 +- frontends/nextjs/src/theme/components.ts | 196 +++++++-- frontends/nextjs/src/theme/dark-theme.ts | 34 +- frontends/nextjs/src/theme/index.ts | 1 - frontends/nextjs/src/theme/light-theme.ts | 34 +- .../nextjs/src/theme/modes/dark-theme.ts | 34 +- .../nextjs/src/theme/modes/light-theme.ts | 34 +- 76 files changed, 1459 insertions(+), 369 deletions(-) create mode 100644 docs/architecture/LUA_UI_MIGRATION_STRATEGY.md create mode 100644 frontends/nextjs/src/lib/lua/ui/generate-component-tree.tsx create mode 100644 frontends/nextjs/src/lib/lua/ui/load-lua-ui-package.ts create mode 100644 frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts create mode 100644 frontends/nextjs/src/lib/packages/lua-ui/example-form.lua diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a2a4c955d..02d057e48 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -34,7 +34,9 @@ "Bash(bun run format:*)", "Bash(while read file)", "Bash(do eslint:*)", - "Bash(done)" + "Bash(done)", + "Bash(eslint:*)", + "Bash(bunx eslint:*)" ] } } diff --git a/docs/architecture/LUA_UI_MIGRATION_STRATEGY.md b/docs/architecture/LUA_UI_MIGRATION_STRATEGY.md new file mode 100644 index 000000000..e7705ba60 --- /dev/null +++ b/docs/architecture/LUA_UI_MIGRATION_STRATEGY.md @@ -0,0 +1,377 @@ +# Lua UI Migration Strategy + +## Vision +Transform MetaBuilder into a **lean framework that loads Lua code** by migrating UI boilerplate from React/TypeScript to Lua packages. + +## Current State Analysis + +### Statistics +- **401 React components** in the codebase +- **0 Lua UI definition files** currently +- **UI already defined in packages** (TypeScript schemas) +- **Lua engine operational** with Fengari runtime + +### Existing Architecture +``` +packages/ + └── core/ + └── package-definitions/ + ├── set-a/ (forum, guestbook, spotify, youtube) + └── set-b/ (ecommerce, irc-webchat, retro-games) + └── irc-webchat/ + ├── schema/layout.ts (UI config) + ├── actions/commands.ts + ├── actions/events.ts + └── validation.ts +``` + +Currently UI is defined in TypeScript but **already structured for package-based loading**. + +## Migration Strategy + +### Phase 1: Create Lua UI Definition Format (Weeks 1-2) + +#### 1.1 Define Lua UI Schema +Create a Lua DSL for defining UI components: + +```lua +-- packages/ui/irc-webchat.lua +return { + metadata = { + id = "irc-webchat", + version = "1.0.0", + name = "IRC Webchat", + description = "Real-time chat interface" + }, + + pages = { + { + id = "page_chat", + path = "/chat", + title = "IRC Webchat", + level = 2, + requiresAuth = true, + requiredRole = "user", + + components = { + { + id = "comp_chat_root", + type = "IRCWebchat", + props = { + channelName = "general", + maxMessages = 100, + enableEmoji = true + } + } + } + } + }, + + components = { + IRCWebchat = { + defaultProps = { + channelName = "general", + theme = "dark" + }, + validation = { + channelName = { type = "string", required = true }, + maxMessages = { type = "number", min = 1, max = 1000 } + } + } + } +} +``` + +#### 1.2 Create Lua-to-React Bridge +**New:** `src/lib/lua/ui/lua-ui-loader.ts` + +```typescript +import { executeLuaScript } from '@/lib/lua/engine/execute' +import type { PackageContent } from '@/lib/packages/package-types' + +export async function loadLuaUIPackage( + luaSource: string +): Promise> { + // Execute Lua and convert to TypeScript types + const result = await executeLuaScript(luaSource, { + sandbox: true, + timeout: 5000 + }) + + return convertLuaToUISchema(result) +} +``` + +#### 1.3 TypeScript Type Definitions for Lua UI +**New:** `src/lib/lua/ui/types.ts` + +```typescript +export interface LuaUIMetadata { + id: string + version: string + name: string + description: string +} + +export interface LuaUIPage { + id: string + path: string + title: string + level: number + requiresAuth?: boolean + requiredRole?: string + components: LuaUIComponent[] +} + +export interface LuaUIComponent { + id: string + type: string + props: Record + children?: LuaUIComponent[] +} +``` + +### Phase 2: Package Storage System (Weeks 2-3) + +#### 2.1 Lua Package Store in Database +Add Lua packages to the database schema: + +```typescript +// New table in Prisma schema +model LuaPackage { + id String @id @default(uuid()) + packageId String @unique + version String + name String + category String // 'ui', 'action', 'validation' + luaSource String @db.Text // Lua code + metadata Json + + tenantId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([tenantId, category]) +} +``` + +#### 2.2 Package Installation Flow +```typescript +// src/lib/packages/lua/install-lua-package.ts +export async function installLuaPackage(params: { + packageId: string + luaSource: string + category: 'ui' | 'action' | 'validation' + tenantId: string +}) { + // 1. Validate Lua syntax + await validateLuaSyntax(params.luaSource) + + // 2. Execute in sandbox to extract metadata + const metadata = await extractPackageMetadata(params.luaSource) + + // 3. Store in database + await db.luaPackage.create({ + data: { + packageId: params.packageId, + luaSource: params.luaSource, + category: params.category, + metadata, + tenantId: params.tenantId, + version: metadata.version, + name: metadata.name + } + }) +} +``` + +### Phase 3: Migration of Existing Packages (Weeks 3-5) + +#### Priority Order + +**Tier 1: Simple UI Packages (Week 3)** +- Guestbook Retro +- Forum Classic +- Spotify Clone UI + +**Tier 2: Medium Complexity (Week 4)** +- IRC Webchat +- YouTube Clone +- Ecommerce Basic + +**Tier 3: Complex Packages (Week 5)** +- Retro Games +- Package Manager UI +- Nerd Mode IDE + +#### Migration Template + +For each package: +1. Create `packages/lua-ui/{package-name}.lua` +2. Convert TypeScript UI schema to Lua DSL +3. Write migration tests +4. Update package loader to support both formats +5. Gradually deprecate TypeScript version + +### Phase 4: Component Abstraction Layer (Weeks 5-6) + +#### 4.1 Core React Components Become "Primitives" + +Keep minimal React components as building blocks: +- Form controls (Input, Button, Select) +- Layout containers (Box, Stack, Grid) +- Data display (Table, List, Card) + +All composition defined in Lua. + +#### 4.2 Component Registry +```lua +-- Lua can reference registered React components +local form = Component.create("Form", { + children = { + Component.create("Input", { name = "username", label = "Username" }), + Component.create("Button", { type = "submit", text = "Login" }) + } +}) +``` + +Maps to React component registry: +```typescript +const ComponentRegistry = { + Form: FormPrimitive, + Input: InputPrimitive, + Button: ButtonPrimitive, + // ... primitives only +} +``` + +### Phase 5: Runtime Package Loading (Weeks 6-7) + +#### 5.1 Dynamic Package Loader +```typescript +// src/lib/packages/lua/runtime-loader.ts +export async function loadPackageAtRuntime(packageId: string) { + // 1. Fetch from database or cache + const luaPackage = await fetchLuaPackage(packageId) + + // 2. Execute Lua to get UI definition + const uiDef = await loadLuaUIPackage(luaPackage.luaSource) + + // 3. Generate React components on-the-fly + return generateComponentTree(uiDef) +} +``` + +#### 5.2 Hot Reload Support +Lua packages can be updated without rebuilding the framework: +- Update Lua code in database +- Invalidate cache +- UI automatically reflects changes + +### Phase 6: Developer Experience (Weeks 7-8) + +#### 6.1 Lua Package Development Environment +- **Lua Editor** with syntax highlighting (Monaco) +- **Live Preview** of UI changes +- **Validation** feedback +- **Version Control** for packages + +#### 6.2 Package Publishing Flow +1. Write Lua UI package in browser +2. Test in sandbox +3. Publish to tenant +4. Package becomes available to all users + +## Benefits + +### 1. **Radical Reduction in Build Artifacts** +- From 401 React components → ~20-30 primitive components +- UI defined in database, not compiled code +- Faster builds, smaller bundles + +### 2. **Runtime Flexibility** +- Update UI without deploying +- Per-tenant customization +- A/B testing at the package level + +### 3. **User Empowerment** +- Advanced users can create packages +- Share packages across tenants +- Package marketplace potential + +### 4. **True Multi-Tenancy** +- Each tenant can have different package versions +- Custom branding via Lua +- Isolated package updates + +### 5. **Simplified Architecture** +``` +BEFORE: +TypeScript Components → Build → Bundle → Deploy + +AFTER: +Lua Packages → Database → Runtime Load → Render +``` + +## Implementation Checklist + +### Foundation +- [ ] Create Lua UI DSL specification +- [ ] Implement Lua-to-React bridge +- [ ] Add LuaPackage database model +- [ ] Create package validation system + +### Core Functionality +- [ ] Implement runtime package loader +- [ ] Create component registry system +- [ ] Build package installation API +- [ ] Add package version management + +### Migration +- [ ] Migrate 3 simple packages (Tier 1) +- [ ] Migrate 3 medium packages (Tier 2) +- [ ] Migrate complex packages (Tier 3) +- [ ] Remove deprecated TypeScript packages + +### Developer Experience +- [ ] Lua package editor UI +- [ ] Live preview system +- [ ] Package testing framework +- [ ] Documentation & examples + +### Production Readiness +- [ ] Performance optimization +- [ ] Caching strategy +- [ ] Error handling & recovery +- [ ] Security review & sandboxing + +## Success Metrics + +1. **Code Reduction:** 401 → 30 components (-92%) +2. **Package Count:** 8 packages successfully running from Lua +3. **Build Time:** Reduce by >50% +4. **Bundle Size:** Reduce by >60% +5. **Deploy Frequency:** UI updates without deploy + +## Timeline + +**8 weeks total** for full migration: +- Weeks 1-2: Foundation & DSL +- Weeks 3-5: Package migration +- Weeks 6-7: Runtime & hot reload +- Week 8: DX & polish + +## Risk Mitigation + +1. **Gradual Migration:** Both systems run in parallel +2. **Feature Flags:** Toggle Lua/TypeScript per package +3. **Rollback Plan:** Keep TypeScript packages until Lua proven +4. **Testing:** Comprehensive e2e tests for each migrated package +5. **Performance:** Monitor Lua execution times, add caching + +## Next Steps + +1. Get approval for Lua UI DSL spec +2. Implement proof-of-concept with IRC Webchat +3. Measure performance & user experience +4. Decide on full migration vs. hybrid approach diff --git a/frontends/nextjs/src/lib/lua/ui/generate-component-tree.tsx b/frontends/nextjs/src/lib/lua/ui/generate-component-tree.tsx new file mode 100644 index 000000000..b7357b7e7 --- /dev/null +++ b/frontends/nextjs/src/lib/lua/ui/generate-component-tree.tsx @@ -0,0 +1,100 @@ +'use client' + +import React from 'react' +import type { LuaUIComponent } from './types/lua-ui-package' + +// Component Registry - Maps Lua component types to React components +import { Box, Stack, Typography, Button, TextField } from '@mui/material' + +const ComponentRegistry: Record> = { + Box, + Stack, + Typography, + Button, + Input: TextField, + TextArea: TextField, + Form: 'form' as any, // Use native HTML form +} + +/** + * Generate React component tree from Lua UI definition + * This is the bridge between Lua-defined UI and React rendering + */ +export function generateComponentTree( + luaComponent: LuaUIComponent, + key?: string | number +): React.ReactElement { + const Component = ComponentRegistry[luaComponent.type] + + if (!Component) { + console.warn(`Unknown component type: ${luaComponent.type}`) + return
Unknown component: {luaComponent.type}
+ } + + // Convert Lua props to React props + const reactProps = convertLuaPropsToReact(luaComponent.props, luaComponent.type) + + // Handle children + const children = luaComponent.children?.map((child, index) => generateComponentTree(child, index)) + + return React.createElement(Component, { ...reactProps, key }, children) +} + +/** + * Convert Lua component props to React component props + * Handles special cases like MUI component props + */ +function convertLuaPropsToReact( + luaProps: Record, + componentType: string +): Record { + const reactProps: Record = { ...luaProps } + + // Special handling for different component types + switch (componentType) { + case 'Input': + case 'TextArea': + // Convert generic props to MUI TextField props + if (luaProps.type === 'email') { + reactProps.type = 'email' + } + if (luaProps.rows) { + reactProps.multiline = true + reactProps.rows = luaProps.rows + } + break + + case 'Button': + // Handle button text + if (luaProps.text) { + // Text will be rendered as children + delete reactProps.text + } + break + + case 'Typography': + // Handle typography text + if (luaProps.text) { + delete reactProps.text + } + break + } + + return reactProps +} + +/** + * Render text content for components + */ +function renderComponentContent( + componentType: string, + props: Record +): React.ReactNode { + switch (componentType) { + case 'Button': + case 'Typography': + return props.text as string + default: + return null + } +} diff --git a/frontends/nextjs/src/lib/lua/ui/load-lua-ui-package.ts b/frontends/nextjs/src/lib/lua/ui/load-lua-ui-package.ts new file mode 100644 index 000000000..340be333f --- /dev/null +++ b/frontends/nextjs/src/lib/lua/ui/load-lua-ui-package.ts @@ -0,0 +1,66 @@ +import { executeLuaCode } from '@/lib/lua/functions/execution/execute-lua-code' +import { createLuaEngine } from '@/lib/lua/engine/core/create-lua-engine' +import type { LuaUIPackage } from './types/lua-ui-package' +import type { LuaValue } from '@/types/utility-types' + +/** + * Load a Lua UI package from Lua source code + * Executes the Lua code using existing Fengari engine and converts result to TypeScript types + */ +export async function loadLuaUIPackage(luaSource: string): Promise { + // Create Lua engine instance using existing infrastructure + const engine = createLuaEngine() + + // Execute Lua code and get the returned table + const executionResult = await executeLuaCode(engine.L, luaSource, {}, []) + + if (!executionResult.success) { + engine.destroy() + throw new Error(`Lua execution failed: ${executionResult.error}`) + } + + const result = executionResult.result + + // Validate result structure + if (!isLuaUIPackage(result)) { + engine.destroy() + throw new Error('Invalid Lua UI package: missing required fields') + } + + // Clean up engine + engine.destroy() + + return convertLuaToUIPackage(result) +} + +/** + * Type guard to check if value is a valid Lua UI package + */ +function isLuaUIPackage(value: unknown): value is LuaValue { + if (typeof value !== 'object' || value === null) { + return false + } + + const pkg = value as Record + + // Check required fields + if (!pkg.metadata || typeof pkg.metadata !== 'object') return false + if (!pkg.pages || !Array.isArray(pkg.pages)) return false + + const metadata = pkg.metadata as Record + if (!metadata.id || typeof metadata.id !== 'string') return false + if (!metadata.version || typeof metadata.version !== 'string') return false + if (!metadata.name || typeof metadata.name !== 'string') return false + + return true +} + +/** + * Convert Lua value to TypeScript UI package + * Uses existing Lua-to-JS converters from the Fengari engine + */ +function convertLuaToUIPackage(luaValue: LuaValue): LuaUIPackage { + // The fromLua converter already handles this conversion in executeLuaCode + // We just need to cast to the correct TypeScript type + return luaValue as unknown as LuaUIPackage +} diff --git a/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts b/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts new file mode 100644 index 000000000..c210e16fe --- /dev/null +++ b/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts @@ -0,0 +1,61 @@ +/** + * TypeScript types for Lua UI packages + * These types define the structure of UI packages written in Lua + */ + +export interface LuaUIMetadata { + id: string + version: string + name: string + description: string + author?: string + category: 'ui' | 'action' | 'validation' | 'workflow' + dependencies?: string[] +} + +export interface LuaUIPage { + id: string + path: string + title: string + level: number + requiresAuth?: boolean + requiredRole?: string + layout: LuaUIComponent +} + +export interface LuaUIComponent { + type: string + props: Record + children?: LuaUIComponent[] +} + +export interface LuaUIAction { + name: string + handler: string | Function +} + +export interface LuaUIValidation { + [fieldName: string]: { + type?: string + required?: boolean + minLength?: number + maxLength?: number + min?: number + max?: number + pattern?: string + format?: string + custom?: string | Function + } +} + +export interface LuaUIPackage { + metadata: LuaUIMetadata + pages: LuaUIPage[] + actions?: Record + validation?: LuaUIValidation + hooks?: { + onMount?: Function + onUnmount?: Function + beforeRender?: Function + } +} diff --git a/frontends/nextjs/src/lib/packages/lua-ui/example-form.lua b/frontends/nextjs/src/lib/packages/lua-ui/example-form.lua new file mode 100644 index 000000000..5ad252fb0 --- /dev/null +++ b/frontends/nextjs/src/lib/packages/lua-ui/example-form.lua @@ -0,0 +1,135 @@ +-- Example: Simple Form UI Package +-- This demonstrates how UI can be defined in Lua instead of TypeScript/React + +return { + metadata = { + id = "example-form", + version = "1.0.0", + name = "Example Form Package", + description = "A simple form demonstrating Lua UI definition", + author = "MetaBuilder", + category = "ui" + }, + + pages = { + { + id = "page_example_form", + path = "/example-form", + title = "Example Form", + level = 2, + requiresAuth = false, + + layout = { + type = "Stack", + props = { + spacing = 3, + padding = 4 + }, + + children = { + -- Header + { + type = "Typography", + props = { + variant = "h4", + text = "Example Form" + } + }, + + -- Form + { + type = "Form", + props = { + id = "example_form", + onSubmit = "handleFormSubmit" + }, + + children = { + -- Name Field + { + type = "Input", + props = { + name = "name", + label = "Your Name", + required = true, + placeholder = "Enter your name" + } + }, + + -- Email Field + { + type = "Input", + props = { + name = "email", + label = "Email Address", + type = "email", + required = true, + validation = { + pattern = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", + message = "Please enter a valid email" + } + } + }, + + -- Message Field + { + type = "TextArea", + props = { + name = "message", + label = "Message", + rows = 4, + placeholder = "Enter your message" + } + }, + + -- Submit Button + { + type = "Button", + props = { + type = "submit", + variant = "contained", + color = "primary", + text = "Submit" + } + } + } + } + } + } + } + }, + + actions = { + handleFormSubmit = function(formData) + -- This Lua function handles form submission + print("Form submitted with data:") + for key, value in pairs(formData) do + print(string.format(" %s = %s", key, value)) + end + + -- Return success response + return { + success = true, + message = "Form submitted successfully!" + } + end + }, + + validation = { + name = { + type = "string", + minLength = 2, + maxLength = 100, + required = true + }, + email = { + type = "string", + format = "email", + required = true + }, + message = { + type = "string", + maxLength = 500 + } + } +} diff --git a/frontends/nextjs/src/lib/rendering/page/page-renderer/functions/register-page.ts b/frontends/nextjs/src/lib/rendering/page/page-renderer/functions/register-page.ts index 4d4766091..757dc4cdf 100644 --- a/frontends/nextjs/src/lib/rendering/page/page-renderer/functions/register-page.ts +++ b/frontends/nextjs/src/lib/rendering/page/page-renderer/functions/register-page.ts @@ -5,15 +5,15 @@ import type { ComponentInstance } from '@/lib/types/builder-types' import type { User } from '@/lib/types/level-types' export async function registerPage(page: PageDefinition): Promise { - this.pages.set(page.id, page) - const pageConfig = { - id: page.id, - path: `/_page_${page.id}`, - title: page.title, - level: page.level, - componentTree: page.components, - requiresAuth: page.permissions?.requiresAuth || false, - requiredRole: page.permissions?.requiredRole as any - } - await Database.addPage(pageConfig) + this.pages.set(page.id, page) + const pageConfig = { + id: page.id, + path: `/_page_${page.id}`, + title: page.title, + level: page.level, + componentTree: page.components, + requiresAuth: page.permissions?.requiresAuth || false, + requiredRole: page.permissions?.requiredRole as any, } + await Database.addPage(pageConfig) +} diff --git a/frontends/nextjs/src/lib/rendering/page/utils.ts b/frontends/nextjs/src/lib/rendering/page/utils.ts index 22472c760..78818b3d0 100644 --- a/frontends/nextjs/src/lib/rendering/page/utils.ts +++ b/frontends/nextjs/src/lib/rendering/page/utils.ts @@ -2,10 +2,7 @@ import type { ComponentInstance } from '@/lib/types/builder-types' import type { User, UserRole } from '@/lib/types/level-types' import type { PageDefinition } from './page-renderer' -export function createMockPage( - id: string, - options: Partial = {} -): PageDefinition { +export function createMockPage(id: string, options: Partial = {}): PageDefinition { return { id, level: options.level ?? 1, diff --git a/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.evaluation.test.ts b/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.evaluation.test.ts index e562eae07..d1c5c4c11 100644 --- a/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.evaluation.test.ts +++ b/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.evaluation.test.ts @@ -76,8 +76,18 @@ describe('declarative-component-renderer evaluation', () => { { name: 'empty string condition', condition: '', context: {}, expected: true }, { name: 'null condition', condition: null as any, context: {}, expected: true }, { name: 'undefined condition', condition: undefined as any, context: {}, expected: true }, - { name: 'truthy context value', condition: 'isActive', context: { isActive: true }, expected: true }, - { name: 'falsy context value', condition: 'isActive', context: { isActive: false }, expected: false }, + { + name: 'truthy context value', + condition: 'isActive', + context: { isActive: true }, + expected: true, + }, + { + name: 'falsy context value', + condition: 'isActive', + context: { isActive: false }, + expected: false, + }, { name: 'missing context key', condition: 'missing', context: {}, expected: false }, { name: 'truthy string value', condition: 'name', context: { name: 'test' }, expected: true }, { name: 'empty string value', condition: 'name', context: { name: '' }, expected: false }, @@ -117,8 +127,16 @@ describe('declarative-component-renderer evaluation', () => { { name: 'object array data source', dataSource: 'users', - context: { users: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }] }, - expected: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }], + context: { + users: [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ], + }, + expected: [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ], }, ])('should resolve $name', ({ dataSource, context, expected }) => { expect(renderer.resolveDataSource(dataSource, context)).toEqual(expected) diff --git a/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.lifecycle.test.ts b/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.lifecycle.test.ts index 05f80594d..b97d4d3d7 100644 --- a/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.lifecycle.test.ts +++ b/frontends/nextjs/src/lib/rendering/tests/declarative-component-renderer.lifecycle.test.ts @@ -127,7 +127,10 @@ describe('declarative-component-renderer lifecycle', () => { }) it('should load Lua scripts from package', () => { - const luaExecuteSpy = vi.spyOn(DeclarativeComponentRenderer.prototype as any, 'executeLuaScript') + const luaExecuteSpy = vi.spyOn( + DeclarativeComponentRenderer.prototype as any, + 'executeLuaScript' + ) loadPackageComponents({ luaScripts: [ diff --git a/frontends/nextjs/src/lib/schema/__tests__/schema-utils.migration.test.ts b/frontends/nextjs/src/lib/schema/__tests__/schema-utils.migration.test.ts index b766616f9..32ae7b2bd 100644 --- a/frontends/nextjs/src/lib/schema/__tests__/schema-utils.migration.test.ts +++ b/frontends/nextjs/src/lib/schema/__tests__/schema-utils.migration.test.ts @@ -9,10 +9,13 @@ describe('schema-utils migration', () => { { appName: 'MyApp', modelName: 'User', expected: 'MyApp_User' }, { appName: 'app-v2', modelName: 'User_Profile', expected: 'app-v2_User_Profile' }, { appName: '', modelName: 'Model', expected: '_Model' }, - ])('should generate key "$expected" for app=$appName, model=$modelName', ({ appName, modelName, expected }) => { - const result = getModelKey(appName, modelName) - expect(result).toBe(expected) - }) + ])( + 'should generate key "$expected" for app=$appName, model=$modelName', + ({ appName, modelName, expected }) => { + const result = getModelKey(appName, modelName) + expect(result).toBe(expected) + } + ) }) describe('getRecordsKey', () => { diff --git a/frontends/nextjs/src/lib/schema/__tests__/schema-utils.serialization.test.ts b/frontends/nextjs/src/lib/schema/__tests__/schema-utils.serialization.test.ts index a6264b22f..bd56b8080 100644 --- a/frontends/nextjs/src/lib/schema/__tests__/schema-utils.serialization.test.ts +++ b/frontends/nextjs/src/lib/schema/__tests__/schema-utils.serialization.test.ts @@ -17,8 +17,16 @@ describe('schema-utils serialization', () => { describe('getFieldLabel', () => { it.each([ { field: createMockField(), expected: 'Email Address', description: 'custom label' }, - { field: { name: 'email', type: 'email' }, expected: 'Email', description: 'auto-capitalized field name' }, - { field: { name: 'firstName', type: 'string' }, expected: 'FirstName', description: 'multi-word field name' }, + { + field: { name: 'email', type: 'email' }, + expected: 'Email', + description: 'auto-capitalized field name', + }, + { + field: { name: 'firstName', type: 'string' }, + expected: 'FirstName', + description: 'multi-word field name', + }, ])('should return $description', ({ field, expected }) => { const result = getFieldLabel(field as FieldSchema) expect(result).toBe(expected) @@ -94,12 +102,20 @@ describe('schema-utils serialization', () => { describe('getDefaultValue', () => { it.each([ - { field: { name: 'count', type: 'number', default: 42 }, expected: 42, description: 'custom default' }, + { + field: { name: 'count', type: 'number', default: 42 }, + expected: 42, + description: 'custom default', + }, { field: { name: 'text', type: 'string' }, expected: '', description: 'string type' }, { field: { name: 'count', type: 'number' }, expected: 0, description: 'number type' }, { field: { name: 'active', type: 'boolean' }, expected: false, description: 'boolean type' }, { field: { name: 'birthDate', type: 'date' }, expected: null, description: 'date type' }, - { field: { name: 'createdAt', type: 'datetime' }, expected: null, description: 'datetime type' }, + { + field: { name: 'createdAt', type: 'datetime' }, + expected: null, + description: 'datetime type', + }, { field: { name: 'status', @@ -198,7 +214,12 @@ describe('schema-utils serialization', () => { { searchTerm: 'ALICE', searchFields: ['name'], filters: {}, expectedCount: 1 }, { searchTerm: 'bob', searchFields: ['name', 'email'], filters: {}, expectedCount: 1 }, { searchTerm: '', searchFields: ['name'], filters: { status: 'active' }, expectedCount: 2 }, - { searchTerm: 'charlie', searchFields: ['name'], filters: { status: 'active' }, expectedCount: 1 }, + { + searchTerm: 'charlie', + searchFields: ['name'], + filters: { status: 'active' }, + expectedCount: 1, + }, { searchTerm: '', searchFields: ['name'], filters: {}, expectedCount: 3 }, ])( 'should filter with searchTerm=$searchTerm, fields=$searchFields, filters=$filters', diff --git a/frontends/nextjs/src/lib/schema/functions/field/get-default-value.ts b/frontends/nextjs/src/lib/schema/functions/field/get-default-value.ts index e9032b2f9..57e00488b 100644 --- a/frontends/nextjs/src/lib/schema/functions/field/get-default-value.ts +++ b/frontends/nextjs/src/lib/schema/functions/field/get-default-value.ts @@ -7,7 +7,7 @@ import type { FieldSchema } from '@/lib/schema-types' */ export const getDefaultValue = (field: FieldSchema): any => { if (field.default !== undefined) return field.default - + switch (field.type) { case 'string': case 'text': diff --git a/frontends/nextjs/src/lib/schema/functions/field/validate-field.ts b/frontends/nextjs/src/lib/schema/functions/field/validate-field.ts index 6aaf96cbc..8ad15bd36 100644 --- a/frontends/nextjs/src/lib/schema/functions/field/validate-field.ts +++ b/frontends/nextjs/src/lib/schema/functions/field/validate-field.ts @@ -27,7 +27,12 @@ export const validateField = (field: FieldSchema, value: any): string | null => } } - if (field.type === 'string' || field.type === 'text' || field.type === 'email' || field.type === 'url') { + if ( + field.type === 'string' || + field.type === 'text' || + field.type === 'email' || + field.type === 'url' + ) { const strValue = String(value) if (minLength !== undefined && strValue.length < minLength) { return `${getFieldLabel(field)} must be at least ${minLength} characters` diff --git a/frontends/nextjs/src/lib/schema/functions/record/sort-records.ts b/frontends/nextjs/src/lib/schema/functions/record/sort-records.ts index 8e32406cc..bd0f2052c 100644 --- a/frontends/nextjs/src/lib/schema/functions/record/sort-records.ts +++ b/frontends/nextjs/src/lib/schema/functions/record/sort-records.ts @@ -5,19 +5,15 @@ * @param direction - Sort direction ('asc' or 'desc') * @returns Sorted copy of the records array */ -export const sortRecords = ( - records: any[], - field: string, - direction: 'asc' | 'desc' -): any[] => { +export const sortRecords = (records: any[], field: string, direction: 'asc' | 'desc'): any[] => { return [...records].sort((a, b) => { const aVal = a[field] const bVal = b[field] - + if (aVal === bVal) return 0 if (aVal === null || aVal === undefined) return 1 if (bVal === null || bVal === undefined) return -1 - + const comparison = aVal < bVal ? -1 : 1 return direction === 'asc' ? comparison : -comparison }) diff --git a/frontends/nextjs/src/lib/schema/functions/record/validate-record.ts b/frontends/nextjs/src/lib/schema/functions/record/validate-record.ts index 77e842a8f..7091f448e 100644 --- a/frontends/nextjs/src/lib/schema/functions/record/validate-record.ts +++ b/frontends/nextjs/src/lib/schema/functions/record/validate-record.ts @@ -7,10 +7,7 @@ import { validateField } from '../field/validate-field' * @param record - The record to validate * @returns Object mapping field names to error messages */ -export const validateRecord = ( - model: ModelSchema, - record: any -): Record => { +export const validateRecord = (model: ModelSchema, record: any): Record => { const errors: Record = {} for (const field of model.fields) { diff --git a/frontends/nextjs/src/lib/schema/schema-utils.ts b/frontends/nextjs/src/lib/schema/schema-utils.ts index 5b8110a05..e67918845 100644 --- a/frontends/nextjs/src/lib/schema/schema-utils.ts +++ b/frontends/nextjs/src/lib/schema/schema-utils.ts @@ -1,14 +1,14 @@ /** * Schema utilities - Re-exports from individual function files - * + * * This file maintains backward compatibility. * Prefer importing from SchemaUtils class or individual functions: - * + * * @example * // Class pattern (recommended) * import { SchemaUtils } from '@/lib/schema/SchemaUtils' * SchemaUtils.validateField(field, value) - * + * * @example * // Individual function import * import { validateField } from '@/lib/schema/functions/validate-field' diff --git a/frontends/nextjs/src/lib/screenshot/request-screenshot-analysis.ts b/frontends/nextjs/src/lib/screenshot/request-screenshot-analysis.ts index 5a57605eb..0468d6ab2 100644 --- a/frontends/nextjs/src/lib/screenshot/request-screenshot-analysis.ts +++ b/frontends/nextjs/src/lib/screenshot/request-screenshot-analysis.ts @@ -22,9 +22,10 @@ export async function requestScreenshotAnalysis( const data = (await response.json().catch(() => null)) as unknown if (!response.ok || !isValidResult(data)) { - const message = (typeof data === 'object' && data !== null && 'error' in data && typeof data.error === 'string') - ? data.error - : 'Analysis failed' + const message = + typeof data === 'object' && data !== null && 'error' in data && typeof data.error === 'string' + ? data.error + : 'Analysis failed' throw new Error(message) } diff --git a/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.test.ts b/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.test.ts index 1b8eaba3e..0fe344f71 100644 --- a/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.test.ts +++ b/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.test.ts @@ -6,7 +6,8 @@ const basePayload = { url: 'https://example.com', viewport: { width: 1280, height: 720 }, textSample: 'Hello world from the screenshot analyzer.', - htmlSample: '

Title

Subtitle

descLink', + htmlSample: + '

Title

Subtitle

descLink', } describe('ScreenshotAnalysisService', () => { @@ -32,7 +33,7 @@ describe('ScreenshotAnalysisService', () => { htmlSample: '', }) - expect(result.warnings.some((warning) => warning.includes('No H1'))).toBe(true) - expect(result.warnings.some((warning) => warning.includes('missing alt'))).toBe(true) + expect(result.warnings.some(warning => warning.includes('No H1'))).toBe(true) + expect(result.warnings.some(warning => warning.includes('missing alt'))).toBe(true) }) }) diff --git a/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.ts b/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.ts index 279bfa95f..ff793a08e 100644 --- a/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.ts +++ b/frontends/nextjs/src/lib/screenshot/screenshot-analysis-service.ts @@ -33,7 +33,7 @@ export class ScreenshotAnalysisService { h2Count, h3Count, imgCount: imgTags.length, - imgMissingAltCount: imgTags.filter((tag) => !/\salt=/.test(tag)).length, + imgMissingAltCount: imgTags.filter(tag => !/\salt=/.test(tag)).length, linkCount: this.countMatches(/]*>/gi, htmlSample), buttonCount: this.countMatches(/]*>/gi, htmlSample), formCount: this.countMatches(/]*>/gi, htmlSample), @@ -96,7 +96,7 @@ export class ScreenshotAnalysisService { if (warnings.length === 0) { lines.push('- None detected') } else { - warnings.forEach((warning) => lines.push(`- ${warning}`)) + warnings.forEach(warning => lines.push(`- ${warning}`)) } return lines.join('\n') diff --git a/frontends/nextjs/src/lib/security/functions/patterns/javascript-patterns.ts b/frontends/nextjs/src/lib/security/functions/patterns/javascript-patterns.ts index 266675ae6..a9e39f841 100644 --- a/frontends/nextjs/src/lib/security/functions/patterns/javascript-patterns.ts +++ b/frontends/nextjs/src/lib/security/functions/patterns/javascript-patterns.ts @@ -11,5 +11,5 @@ import { JAVASCRIPT_XSS_PATTERNS } from './javascript/xss' export const JAVASCRIPT_PATTERNS: SecurityPattern[] = [ ...JAVASCRIPT_INJECTION_PATTERNS, ...JAVASCRIPT_XSS_PATTERNS, - ...JAVASCRIPT_MISC_PATTERNS + ...JAVASCRIPT_MISC_PATTERNS, ] diff --git a/frontends/nextjs/src/lib/security/functions/patterns/javascript/injection.ts b/frontends/nextjs/src/lib/security/functions/patterns/javascript/injection.ts index a3a5a0cdd..42034869a 100644 --- a/frontends/nextjs/src/lib/security/functions/patterns/javascript/injection.ts +++ b/frontends/nextjs/src/lib/security/functions/patterns/javascript/injection.ts @@ -6,48 +6,49 @@ export const JAVASCRIPT_INJECTION_PATTERNS: SecurityPattern[] = [ type: 'dangerous', severity: 'critical', message: 'Use of eval() detected - can execute arbitrary code', - recommendation: 'Use safe alternatives like JSON.parse() or Function constructor with strict validation' + recommendation: + 'Use safe alternatives like JSON.parse() or Function constructor with strict validation', }, { pattern: /Function\s*\(/gi, type: 'dangerous', severity: 'high', message: 'Dynamic Function constructor detected', - recommendation: 'Avoid dynamic code generation or use with extreme caution' + recommendation: 'Avoid dynamic code generation or use with extreme caution', }, { pattern: /import\s+.*\s+from\s+['"]https?:/gi, type: 'dangerous', severity: 'critical', message: 'Remote code import detected', - recommendation: 'Only import from trusted, local sources' + recommendation: 'Only import from trusted, local sources', }, { pattern: /setTimeout\s*\(\s*['"`]/gi, type: 'dangerous', severity: 'high', message: 'setTimeout with string argument detected', - recommendation: 'Use setTimeout with function reference instead' + recommendation: 'Use setTimeout with function reference instead', }, { pattern: /setInterval\s*\(\s*['"`]/gi, type: 'dangerous', severity: 'high', message: 'setInterval with string argument detected', - recommendation: 'Use setInterval with function reference instead' + recommendation: 'Use setInterval with function reference instead', }, { pattern: /require\s*\(\s*[^'"`]/gi, type: 'dangerous', severity: 'high', message: 'Dynamic require() detected', - recommendation: 'Use static imports only' + recommendation: 'Use static imports only', }, { pattern: /\.exec\s*\(|child_process|spawn|fork|execFile/gi, type: 'malicious', severity: 'critical', message: 'System command execution attempt detected', - recommendation: 'This is not allowed in browser environment' - } + recommendation: 'This is not allowed in browser environment', + }, ] diff --git a/frontends/nextjs/src/lib/security/functions/patterns/javascript/misc.ts b/frontends/nextjs/src/lib/security/functions/patterns/javascript/misc.ts index 3b1efdaf9..4228ee4fd 100644 --- a/frontends/nextjs/src/lib/security/functions/patterns/javascript/misc.ts +++ b/frontends/nextjs/src/lib/security/functions/patterns/javascript/misc.ts @@ -6,76 +6,76 @@ export const JAVASCRIPT_MISC_PATTERNS: SecurityPattern[] = [ type: 'suspicious', severity: 'medium', message: 'Calling functions with window context', - recommendation: 'Be careful with context manipulation' + recommendation: 'Be careful with context manipulation', }, { pattern: /\.apply\s*\(\s*window/gi, type: 'suspicious', severity: 'medium', message: 'Applying functions with window context', - recommendation: 'Be careful with context manipulation' + recommendation: 'Be careful with context manipulation', }, { pattern: /__proto__/gi, type: 'dangerous', severity: 'critical', message: 'Prototype pollution attempt detected', - recommendation: 'Never manipulate __proto__ directly' + recommendation: 'Never manipulate __proto__ directly', }, { pattern: /constructor\s*\[\s*['"]prototype['"]\s*\]/gi, type: 'dangerous', severity: 'critical', message: 'Prototype manipulation detected', - recommendation: 'Use Object.create() or proper class syntax' + recommendation: 'Use Object.create() or proper class syntax', }, { pattern: /localStorage|sessionStorage/gi, type: 'warning', severity: 'low', message: 'Local/session storage usage detected', - recommendation: 'Use useKV hook for persistent data instead' + recommendation: 'Use useKV hook for persistent data instead', }, { pattern: /crypto\.subtle|atob|btoa/gi, type: 'warning', severity: 'low', message: 'Cryptographic operation detected', - recommendation: 'Ensure proper key management and secure practices' + recommendation: 'Ensure proper key management and secure practices', }, { pattern: /XMLHttpRequest|fetch\s*\(\s*['"`]http/gi, type: 'warning', severity: 'medium', message: 'External HTTP request detected', - recommendation: 'Ensure CORS and security headers are properly configured' + recommendation: 'Ensure CORS and security headers are properly configured', }, { pattern: /window\.open/gi, type: 'suspicious', severity: 'medium', message: 'window.open detected', - recommendation: 'Be cautious with popup windows' + recommendation: 'Be cautious with popup windows', }, { pattern: /location\.href\s*=/gi, type: 'suspicious', severity: 'medium', message: 'Direct location manipulation detected', - recommendation: 'Use React Router or validate URLs carefully' + recommendation: 'Use React Router or validate URLs carefully', }, { pattern: /fs\.|path\.|os\./gi, type: 'malicious', severity: 'critical', message: 'Node.js system module usage detected', - recommendation: 'File system access not allowed in browser' + recommendation: 'File system access not allowed in browser', }, { pattern: /process\.env|process\.exit/gi, type: 'suspicious', severity: 'medium', message: 'Process manipulation detected', - recommendation: 'Not applicable in browser environment' - } + recommendation: 'Not applicable in browser environment', + }, ] diff --git a/frontends/nextjs/src/lib/security/functions/patterns/javascript/xss.ts b/frontends/nextjs/src/lib/security/functions/patterns/javascript/xss.ts index 378fa8fd4..1039327bf 100644 --- a/frontends/nextjs/src/lib/security/functions/patterns/javascript/xss.ts +++ b/frontends/nextjs/src/lib/security/functions/patterns/javascript/xss.ts @@ -6,48 +6,48 @@ export const JAVASCRIPT_XSS_PATTERNS: SecurityPattern[] = [ type: 'dangerous', severity: 'high', message: 'innerHTML assignment detected - XSS vulnerability risk', - recommendation: 'Use textContent, createElement, or React JSX instead' + recommendation: 'Use textContent, createElement, or React JSX instead', }, { pattern: /dangerouslySetInnerHTML/gi, type: 'dangerous', severity: 'high', message: 'dangerouslySetInnerHTML detected - XSS vulnerability risk', - recommendation: 'Sanitize HTML content or use safe alternatives' + recommendation: 'Sanitize HTML content or use safe alternatives', }, { pattern: /document\.write\s*\(/gi, type: 'dangerous', severity: 'medium', message: 'document.write() detected - can cause security issues', - recommendation: 'Use DOM manipulation methods instead' + recommendation: 'Use DOM manipulation methods instead', }, { pattern: /]*>/gi, type: 'dangerous', severity: 'critical', message: 'Script tag injection detected', - recommendation: 'Never inject script tags dynamically' + recommendation: 'Never inject script tags dynamically', }, { pattern: /on(click|load|error|mouseover|mouseout|focus|blur)\s*=/gi, type: 'suspicious', severity: 'medium', message: 'Inline event handler detected', - recommendation: 'Use addEventListener or React event handlers' + recommendation: 'Use addEventListener or React event handlers', }, { pattern: /javascript:\s*/gi, type: 'dangerous', severity: 'high', message: 'javascript: protocol detected', - recommendation: 'Never use javascript: protocol in URLs' + recommendation: 'Never use javascript: protocol in URLs', }, { pattern: /data:\s*text\/html/gi, type: 'dangerous', severity: 'high', message: 'Data URI with HTML detected', - recommendation: 'Avoid data URIs with executable content' - } + recommendation: 'Avoid data URIs with executable content', + }, ] diff --git a/frontends/nextjs/src/lib/security/functions/patterns/lua-patterns.ts b/frontends/nextjs/src/lib/security/functions/patterns/lua-patterns.ts index 41d71a44e..1b207e9fc 100644 --- a/frontends/nextjs/src/lib/security/functions/patterns/lua-patterns.ts +++ b/frontends/nextjs/src/lib/security/functions/patterns/lua-patterns.ts @@ -11,76 +11,76 @@ export const LUA_PATTERNS: SecurityPattern[] = [ type: 'malicious', severity: 'critical', message: 'Lua OS module system call detected', - recommendation: 'OS module access is disabled for security' + recommendation: 'OS module access is disabled for security', }, { pattern: /io\.(popen|tmpfile|open|input|output|lines)/gi, type: 'malicious', severity: 'critical', message: 'Lua file I/O operation detected', - recommendation: 'File system access is disabled for security' + recommendation: 'File system access is disabled for security', }, { pattern: /loadfile|dofile/gi, type: 'dangerous', severity: 'critical', message: 'Lua file loading function detected', - recommendation: 'File loading is disabled for security' + recommendation: 'File loading is disabled for security', }, { pattern: /package\.(loadlib|searchpath|cpath)/gi, type: 'dangerous', severity: 'critical', message: 'Lua dynamic library loading detected', - recommendation: 'Dynamic library loading is disabled' + recommendation: 'Dynamic library loading is disabled', }, { pattern: /debug\.(getinfo|setmetatable|getfenv|setfenv)/gi, type: 'dangerous', severity: 'high', message: 'Lua debug module advanced features detected', - recommendation: 'Limited debug functionality available' + recommendation: 'Limited debug functionality available', }, { pattern: /loadstring\s*\(/gi, type: 'dangerous', severity: 'high', message: 'Lua dynamic code execution detected', - recommendation: 'Use with extreme caution' + recommendation: 'Use with extreme caution', }, { pattern: /\.\.\s*[[\]]/gi, type: 'suspicious', severity: 'medium', message: 'Potential Lua table manipulation', - recommendation: 'Ensure proper validation' + recommendation: 'Ensure proper validation', }, { pattern: /_G\s*\[/gi, type: 'suspicious', severity: 'high', message: 'Global environment manipulation detected', - recommendation: 'Avoid modifying global environment' + recommendation: 'Avoid modifying global environment', }, { pattern: /getmetatable|setmetatable/gi, type: 'suspicious', severity: 'medium', message: 'Metatable manipulation detected', - recommendation: 'Use carefully to avoid security issues' + recommendation: 'Use carefully to avoid security issues', }, { pattern: /while\s+true\s+do/gi, type: 'warning', severity: 'medium', message: 'Infinite loop detected', - recommendation: 'Ensure proper break conditions exist' + recommendation: 'Ensure proper break conditions exist', }, { pattern: /function\s+(\w+)\s*\([^)]*\)\s*\{[^}]*\1\s*\(/gi, type: 'warning', severity: 'low', message: 'Potential recursive function', - recommendation: 'Ensure recursion has proper termination' - } + recommendation: 'Ensure recursion has proper termination', + }, ] diff --git a/frontends/nextjs/src/lib/security/functions/patterns/sql-patterns.ts b/frontends/nextjs/src/lib/security/functions/patterns/sql-patterns.ts index a127f5d03..28220179f 100644 --- a/frontends/nextjs/src/lib/security/functions/patterns/sql-patterns.ts +++ b/frontends/nextjs/src/lib/security/functions/patterns/sql-patterns.ts @@ -11,27 +11,27 @@ export const SQL_INJECTION_PATTERNS: SecurityPattern[] = [ type: 'malicious', severity: 'critical', message: 'SQL injection attempt detected', - recommendation: 'Use parameterized queries' + recommendation: 'Use parameterized queries', }, { pattern: /UNION\s+SELECT/gi, type: 'malicious', severity: 'critical', message: 'SQL UNION injection attempt', - recommendation: 'Use parameterized queries' + recommendation: 'Use parameterized queries', }, { pattern: /'[\s]*OR[\s]*'1'[\s]*=[\s]*'1/gi, type: 'malicious', severity: 'critical', message: 'SQL authentication bypass attempt', - recommendation: 'Never concatenate user input into SQL' + recommendation: 'Never concatenate user input into SQL', }, { pattern: /--[\s]*$/gm, type: 'suspicious', severity: 'high', message: 'SQL comment pattern detected', - recommendation: 'May indicate SQL injection attempt' - } + recommendation: 'May indicate SQL injection attempt', + }, ] diff --git a/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-html.ts b/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-html.ts index 96cdb0813..7bcf62c88 100644 --- a/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-html.ts +++ b/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-html.ts @@ -23,7 +23,7 @@ export const scanHTML = (html: string): SecurityScanResult => { severity: 'critical', message: 'Script tag detected in HTML', pattern: match[0].substring(0, 50) + '...', - recommendation: 'Remove script tags or use proper React components' + recommendation: 'Remove script tags or use proper React components', }) } @@ -36,7 +36,7 @@ export const scanHTML = (html: string): SecurityScanResult => { severity: 'high', message: 'Inline event handler in HTML', pattern: match[0], - recommendation: 'Use React event handlers instead' + recommendation: 'Use React event handlers instead', }) } @@ -48,7 +48,7 @@ export const scanHTML = (html: string): SecurityScanResult => { severity: 'critical', message: 'javascript: protocol in href', pattern: 'javascript:', - recommendation: 'Use proper URLs or event handlers' + recommendation: 'Use proper URLs or event handlers', }) } @@ -62,7 +62,7 @@ export const scanHTML = (html: string): SecurityScanResult => { severity: 'medium', message: 'Iframe without sandbox attribute', pattern: match[0], - recommendation: 'Add sandbox attribute to iframes for security' + recommendation: 'Add sandbox attribute to iframes for security', }) } } @@ -73,6 +73,6 @@ export const scanHTML = (html: string): SecurityScanResult => { return { safe, severity, - issues + issues, } } diff --git a/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-javascript.ts b/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-javascript.ts index cb3b0e892..88a9002eb 100644 --- a/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-javascript.ts +++ b/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-javascript.ts @@ -28,7 +28,7 @@ export const scanJavaScript = (code: string): SecurityScanResult => { message: pattern.message, pattern: match[0], line: lineNumber, - recommendation: pattern.recommendation + recommendation: pattern.recommendation, }) } } @@ -44,7 +44,7 @@ export const scanJavaScript = (code: string): SecurityScanResult => { message: pattern.message, pattern: match[0], line: lineNumber, - recommendation: pattern.recommendation + recommendation: pattern.recommendation, }) } } @@ -56,6 +56,6 @@ export const scanJavaScript = (code: string): SecurityScanResult => { safe, severity, issues, - sanitizedCode: safe ? code : undefined + sanitizedCode: safe ? code : undefined, } } diff --git a/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-json.ts b/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-json.ts index 1722f867a..fd15d94f3 100644 --- a/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-json.ts +++ b/frontends/nextjs/src/lib/security/functions/scanners/language-scanners/scan-json.ts @@ -23,7 +23,7 @@ export const scanJSON = (jsonString: string): SecurityScanResult => { severity: 'medium', message: 'Invalid JSON format', pattern: 'JSON parse error', - recommendation: 'Ensure JSON is properly formatted' + recommendation: 'Ensure JSON is properly formatted', }) } @@ -35,7 +35,7 @@ export const scanJSON = (jsonString: string): SecurityScanResult => { severity: 'critical', message: 'Prototype pollution attempt in JSON', pattern: '__proto__', - recommendation: 'Remove prototype manipulation from JSON' + recommendation: 'Remove prototype manipulation from JSON', }) } @@ -46,7 +46,7 @@ export const scanJSON = (jsonString: string): SecurityScanResult => { severity: 'critical', message: 'Script tag in JSON data', pattern: 'x', + input: + '
Click
x', type: 'text' as const, shouldExclude: [' { +export const createLoginSecurityContext = (params: LoginSecurityContextParams): SecurityContext => { const sanitizedIdentifier = sanitizeInput(params.identifier).trim() const loginKey = sanitizedIdentifier.length > 0 ? sanitizedIdentifier : 'public' diff --git a/frontends/nextjs/src/lib/security/secure-db/login/login-attempt-tracker.ts b/frontends/nextjs/src/lib/security/secure-db/login/login-attempt-tracker.ts index c35238b1c..53ea2b841 100644 --- a/frontends/nextjs/src/lib/security/secure-db/login/login-attempt-tracker.ts +++ b/frontends/nextjs/src/lib/security/secure-db/login/login-attempt-tracker.ts @@ -17,7 +17,10 @@ const parsePositiveInt = (value: string | undefined, fallback: number): number = const lockoutConfig = { windowMs: parsePositiveInt(process.env.MB_AUTH_LOCKOUT_WINDOW_MS, DEFAULT_AUTH_LOCKOUT_WINDOW_MS), - maxAttempts: parsePositiveInt(process.env.MB_AUTH_LOCKOUT_MAX_ATTEMPTS, DEFAULT_AUTH_LOCKOUT_MAX_ATTEMPTS), + maxAttempts: parsePositiveInt( + process.env.MB_AUTH_LOCKOUT_MAX_ATTEMPTS, + DEFAULT_AUTH_LOCKOUT_MAX_ATTEMPTS + ), lockoutMs: parsePositiveInt(process.env.MB_AUTH_LOCKOUT_MS, DEFAULT_AUTH_LOCKOUT_MS), } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/crud/verify-credentials.ts b/frontends/nextjs/src/lib/security/secure-db/operations/crud/verify-credentials.ts index 916a882b7..8f7d97a4e 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/crud/verify-credentials.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/crud/verify-credentials.ts @@ -8,8 +8,8 @@ import { loginAttemptTracker } from '../login-attempt-tracker' * Verify user credentials with security checks */ export async function verifyCredentials( - ctx: SecurityContext, - username: string, + ctx: SecurityContext, + username: string, password: string ): Promise { const sanitizedUsername = sanitizeInput(username).trim() diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-comments.ts b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-comments.ts index 7984be386..12d93a2dd 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-comments.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-comments.ts @@ -7,11 +7,5 @@ import { executeQuery } from '../../execute-query' * Get comments with security checks */ export async function getComments(ctx: SecurityContext): Promise { - return executeQuery( - ctx, - 'comment', - 'READ', - async () => fetchComments(), - 'all_comments' - ) + return executeQuery(ctx, 'comment', 'READ', async () => fetchComments(), 'all_comments') } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-lua-scripts.ts b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-lua-scripts.ts index b7ac3a1a1..586c7e3da 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-lua-scripts.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-lua-scripts.ts @@ -7,11 +7,5 @@ import { executeQuery } from '../../execute-query' * Get Lua scripts with security checks */ export async function getLuaScripts(ctx: SecurityContext): Promise { - return executeQuery( - ctx, - 'luaScript', - 'READ', - async () => fetchLuaScripts(), - 'all_lua_scripts' - ) + return executeQuery(ctx, 'luaScript', 'READ', async () => fetchLuaScripts(), 'all_lua_scripts') } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-model-schemas.ts b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-model-schemas.ts index 60663e52f..981ffae86 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-model-schemas.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-model-schemas.ts @@ -7,11 +7,5 @@ import { executeQuery } from '../../execute-query' * Get model schemas with security checks */ export async function getModelSchemas(ctx: SecurityContext): Promise { - return executeQuery( - ctx, - 'modelSchema', - 'READ', - async () => fetchSchemas(), - 'all_model_schemas' - ) + return executeQuery(ctx, 'modelSchema', 'READ', async () => fetchSchemas(), 'all_model_schemas') } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-page-configs.ts b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-page-configs.ts index 5325f2de7..7a078c55e 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-page-configs.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-page-configs.ts @@ -7,11 +7,5 @@ import { executeQuery } from '../../execute-query' * Get page configs with security checks */ export async function getPageConfigs(ctx: SecurityContext): Promise { - return executeQuery( - ctx, - 'pageConfig', - 'READ', - async () => fetchPages(), - 'all_page_configs' - ) + return executeQuery(ctx, 'pageConfig', 'READ', async () => fetchPages(), 'all_page_configs') } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-workflows.ts b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-workflows.ts index 4833807f7..1de0f742c 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-workflows.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/getters/entities/get-workflows.ts @@ -7,11 +7,5 @@ import { executeQuery } from '../../execute-query' * Get workflows with security checks */ export async function getWorkflows(ctx: SecurityContext): Promise { - return executeQuery( - ctx, - 'workflow', - 'READ', - async () => fetchWorkflows(), - 'all_workflows' - ) + return executeQuery(ctx, 'workflow', 'READ', async () => fetchWorkflows(), 'all_workflows') } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/user/create-user.ts b/frontends/nextjs/src/lib/security/secure-db/operations/user/create-user.ts index aedda3ccd..5cfc6c681 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/user/create-user.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/user/create-user.ts @@ -8,7 +8,7 @@ import { sanitizeInput } from '../sanitize-input' * Create a new user with security checks */ export async function createUser( - ctx: SecurityContext, + ctx: SecurityContext, userData: Omit ): Promise { const sanitized = sanitizeInput(userData) @@ -29,7 +29,7 @@ export async function createUser( tenantId: tenantId ?? sanitized.tenantId, isInstanceOwner: sanitized.isInstanceOwner ?? false, } - + return executeQuery( ctx, 'user', diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/user/get-user-by-id.ts b/frontends/nextjs/src/lib/security/secure-db/operations/user/get-user-by-id.ts index 6e9a999f6..45f73a8fb 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/user/get-user-by-id.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/user/get-user-by-id.ts @@ -12,10 +12,7 @@ export async function getUserById(ctx: SecurityContext, userId: string): Promise 'user', 'READ', async () => - Database.getUserById( - userId, - ctx.user.tenantId ? { tenantId: ctx.user.tenantId } : undefined - ), + Database.getUserById(userId, ctx.user.tenantId ? { tenantId: ctx.user.tenantId } : undefined), userId ) } diff --git a/frontends/nextjs/src/lib/security/secure-db/operations/user/update-user.ts b/frontends/nextjs/src/lib/security/secure-db/operations/user/update-user.ts index 4d6db76a7..1d37f9918 100644 --- a/frontends/nextjs/src/lib/security/secure-db/operations/user/update-user.ts +++ b/frontends/nextjs/src/lib/security/secure-db/operations/user/update-user.ts @@ -8,8 +8,8 @@ import { sanitizeInput } from '../sanitize-input' * Update a user with security checks */ export async function updateUser( - ctx: SecurityContext, - userId: string, + ctx: SecurityContext, + userId: string, updates: Partial ): Promise { const sanitized = sanitizeInput(updates) @@ -18,7 +18,7 @@ export async function updateUser( if (tenantId && sanitized.tenantId && sanitized.tenantId !== tenantId) { throw new Error('Access denied. Cannot change user tenant.') } - + return executeQuery( ctx, 'user', @@ -32,10 +32,7 @@ export async function updateUser( } await Database.updateUser(userId, sanitized) - const updated = await Database.getUserById( - userId, - tenantId ? { tenantId } : undefined - ) + const updated = await Database.getUserById(userId, tenantId ? { tenantId } : undefined) if (!updated) { throw new Error('User not found after update') } diff --git a/frontends/nextjs/src/lib/security/secure-db/query/access-rules.ts b/frontends/nextjs/src/lib/security/secure-db/query/access-rules.ts index 46ca07c2e..a8b518e2c 100644 --- a/frontends/nextjs/src/lib/security/secure-db/query/access-rules.ts +++ b/frontends/nextjs/src/lib/security/secure-db/query/access-rules.ts @@ -1,42 +1,74 @@ import type { AccessRule } from './types' export const ACCESS_RULES: AccessRule[] = [ - { resource: 'user', operation: 'READ', allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'] }, + { + resource: 'user', + operation: 'READ', + allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'], + }, { resource: 'user', operation: 'CREATE', allowedRoles: ['god', 'supergod'] }, { resource: 'user', operation: 'UPDATE', allowedRoles: ['admin', 'god', 'supergod'] }, { resource: 'user', operation: 'DELETE', allowedRoles: ['god', 'supergod'] }, - + { resource: 'workflow', operation: 'READ', allowedRoles: ['admin', 'god', 'supergod'] }, { resource: 'workflow', operation: 'CREATE', allowedRoles: ['god', 'supergod'] }, { resource: 'workflow', operation: 'UPDATE', allowedRoles: ['god', 'supergod'] }, { resource: 'workflow', operation: 'DELETE', allowedRoles: ['god', 'supergod'] }, - + { resource: 'luaScript', operation: 'READ', allowedRoles: ['god', 'supergod'] }, { resource: 'luaScript', operation: 'CREATE', allowedRoles: ['god', 'supergod'] }, { resource: 'luaScript', operation: 'UPDATE', allowedRoles: ['god', 'supergod'] }, { resource: 'luaScript', operation: 'DELETE', allowedRoles: ['god', 'supergod'] }, - - { resource: 'pageConfig', operation: 'READ', allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'] }, + + { + resource: 'pageConfig', + operation: 'READ', + allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'], + }, { resource: 'pageConfig', operation: 'CREATE', allowedRoles: ['god', 'supergod'] }, { resource: 'pageConfig', operation: 'UPDATE', allowedRoles: ['god', 'supergod'] }, { resource: 'pageConfig', operation: 'DELETE', allowedRoles: ['god', 'supergod'] }, - + { resource: 'modelSchema', operation: 'READ', allowedRoles: ['admin', 'god', 'supergod'] }, { resource: 'modelSchema', operation: 'CREATE', allowedRoles: ['god', 'supergod'] }, { resource: 'modelSchema', operation: 'UPDATE', allowedRoles: ['god', 'supergod'] }, { resource: 'modelSchema', operation: 'DELETE', allowedRoles: ['god', 'supergod'] }, - - { resource: 'comment', operation: 'READ', allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'] }, - { resource: 'comment', operation: 'CREATE', allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'] }, - { resource: 'comment', operation: 'UPDATE', allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'] }, - { resource: 'comment', operation: 'DELETE', allowedRoles: ['moderator', 'admin', 'god', 'supergod'] }, - + + { + resource: 'comment', + operation: 'READ', + allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'], + }, + { + resource: 'comment', + operation: 'CREATE', + allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'], + }, + { + resource: 'comment', + operation: 'UPDATE', + allowedRoles: ['user', 'moderator', 'admin', 'god', 'supergod'], + }, + { + resource: 'comment', + operation: 'DELETE', + allowedRoles: ['moderator', 'admin', 'god', 'supergod'], + }, + { resource: 'smtpConfig', operation: 'READ', allowedRoles: ['god', 'supergod'] }, { resource: 'smtpConfig', operation: 'UPDATE', allowedRoles: ['supergod'] }, - - { resource: 'credential', operation: 'READ', allowedRoles: ['public', 'user', 'admin', 'god', 'supergod'] }, - { resource: 'credential', operation: 'UPDATE', allowedRoles: ['user', 'admin', 'god', 'supergod'] }, - + + { + resource: 'credential', + operation: 'READ', + allowedRoles: ['public', 'user', 'admin', 'god', 'supergod'], + }, + { + resource: 'credential', + operation: 'UPDATE', + allowedRoles: ['user', 'admin', 'god', 'supergod'], + }, + { resource: 'tenant', operation: 'READ', allowedRoles: ['god', 'supergod'] }, { resource: 'tenant', operation: 'CREATE', allowedRoles: ['supergod'] }, { resource: 'tenant', operation: 'UPDATE', allowedRoles: ['supergod'] }, diff --git a/frontends/nextjs/src/lib/security/secure-db/query/check-access.ts b/frontends/nextjs/src/lib/security/secure-db/query/check-access.ts index 005fea32d..9809564d6 100644 --- a/frontends/nextjs/src/lib/security/secure-db/query/check-access.ts +++ b/frontends/nextjs/src/lib/security/secure-db/query/check-access.ts @@ -10,21 +10,19 @@ export async function checkAccess( operation: OperationType, resourceId?: string ): Promise { - const rule = ACCESS_RULES.find( - r => r.resource === resource && r.operation === operation - ) - + const rule = ACCESS_RULES.find(r => r.resource === resource && r.operation === operation) + if (!rule) { return false } - + if (!rule.allowedRoles.includes(ctx.user.role)) { return false } - + if (rule.customCheck) { return await rule.customCheck(ctx, resourceId) } - + return true } diff --git a/frontends/nextjs/src/lib/security/secure-db/rate-limiting/check-rate-limit.ts b/frontends/nextjs/src/lib/security/secure-db/rate-limiting/check-rate-limit.ts index 6548f5eb6..4e9ac7818 100644 --- a/frontends/nextjs/src/lib/security/secure-db/rate-limiting/check-rate-limit.ts +++ b/frontends/nextjs/src/lib/security/secure-db/rate-limiting/check-rate-limit.ts @@ -7,15 +7,13 @@ 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 < windowMs - ) - + + const recentRequests = userRequests.filter(timestamp => now - timestamp < windowMs) + if (recentRequests.length >= maxRequests) { return false } - + recentRequests.push(now) rateLimitMap.set(userId, recentRequests) return true diff --git a/frontends/nextjs/src/lib/security/secure-db/rate-limiting/store/rate-limit-store.ts b/frontends/nextjs/src/lib/security/secure-db/rate-limiting/store/rate-limit-store.ts index 8c2115f1b..cb1900fbc 100644 --- a/frontends/nextjs/src/lib/security/secure-db/rate-limiting/store/rate-limit-store.ts +++ b/frontends/nextjs/src/lib/security/secure-db/rate-limiting/store/rate-limit-store.ts @@ -15,7 +15,10 @@ const parsePositiveInt = (value: string | null | undefined, fallback: number): n const getEnvRateLimitConfig = () => ({ 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), + maxRequests: parsePositiveInt( + process.env.MB_RATE_LIMIT_MAX_REQUESTS, + DEFAULT_MAX_REQUESTS_PER_WINDOW + ), }) let rateLimitConfig = getEnvRateLimitConfig() @@ -31,7 +34,8 @@ export async function loadRateLimitConfig(): Promise { configLoadPromise = (async () => { try { const envConfig = getEnvRateLimitConfig() - const { getSystemConfigValue } = await import('@/lib/db/system-config/get-system-config-value') + const { getSystemConfigValue } = + await import('@/lib/db/system-config/get-system-config-value') const [windowValue, maxRequestsValue] = await Promise.all([ getSystemConfigValue(RATE_LIMIT_WINDOW_KEY), getSystemConfigValue(MAX_REQUESTS_KEY), diff --git a/frontends/nextjs/src/lib/security/secure-db/types.ts b/frontends/nextjs/src/lib/security/secure-db/types.ts index 287cd25ec..9ae652359 100644 --- a/frontends/nextjs/src/lib/security/secure-db/types.ts +++ b/frontends/nextjs/src/lib/security/secure-db/types.ts @@ -1,9 +1,21 @@ import type { User } from '../../types/level-types' export type OperationType = 'CREATE' | 'READ' | 'UPDATE' | 'DELETE' -export type ResourceType = 'user' | 'workflow' | 'luaScript' | 'pageConfig' | - 'modelSchema' | 'comment' | 'componentNode' | 'componentConfig' | 'cssCategory' | - 'dropdownConfig' | 'tenant' | 'powerTransfer' | 'smtpConfig' | 'credential' +export type ResourceType = + | 'user' + | 'workflow' + | 'luaScript' + | 'pageConfig' + | 'modelSchema' + | 'comment' + | 'componentNode' + | 'componentConfig' + | 'cssCategory' + | 'dropdownConfig' + | 'tenant' + | 'powerTransfer' + | 'smtpConfig' + | 'credential' export interface AuditLog { id: string diff --git a/frontends/nextjs/src/lib/seed/seed-data.ts b/frontends/nextjs/src/lib/seed/seed-data.ts index 1fd15dafa..085fb93dc 100644 --- a/frontends/nextjs/src/lib/seed/seed-data.ts +++ b/frontends/nextjs/src/lib/seed/seed-data.ts @@ -1,12 +1,12 @@ /** * Seed Data Module - * + * * Initializes the database with seed data including: * - Default users at each permission level * - Initial configuration * - Package metadata * - System templates - * + * * This runs once during application initialization */ @@ -14,11 +14,11 @@ import { initializeAllSeedData } from '@/seed-data' /** * Seeds the database with initial data - * + * * This function should be called during app initialization * to populate the database with default users, configurations, * and system data needed for the application to function. - * + * * @async * @returns {Promise} */ diff --git a/frontends/nextjs/src/lib/types/builder-types.ts b/frontends/nextjs/src/lib/types/builder-types.ts index 064e72450..dad400840 100644 --- a/frontends/nextjs/src/lib/types/builder-types.ts +++ b/frontends/nextjs/src/lib/types/builder-types.ts @@ -1,7 +1,7 @@ /** * ComponentType - All supported UI component types in the builder * @description Union type of all available components - * Supports: Layout (Flex, Grid, Container), Input (Input, Select, Switch), + * Supports: Layout (Flex, Grid, Container), Input (Input, Select, Switch), * Display (Text, Heading, Badge), Interactive (Button, Dialog, Tabs) */ export type ComponentType = diff --git a/frontends/nextjs/src/lib/types/guards/guards.test.ts b/frontends/nextjs/src/lib/types/guards/guards.test.ts index cda953e77..303086665 100644 --- a/frontends/nextjs/src/lib/types/guards/guards.test.ts +++ b/frontends/nextjs/src/lib/types/guards/guards.test.ts @@ -53,8 +53,16 @@ describe('Type Guards', () => { describe('getErrorMessage', () => { it.each([ - { value: new Error('error message'), expected: 'error message', description: 'Error instance' }, - { value: { message: 'custom error' }, expected: 'custom error', description: 'error-like object' }, + { + value: new Error('error message'), + expected: 'error message', + description: 'Error instance', + }, + { + value: { message: 'custom error' }, + expected: 'custom error', + description: 'error-like object', + }, { value: 'string error', expected: 'string error', description: 'string' }, { value: null, expected: 'An unknown error occurred', description: 'null' }, { value: undefined, expected: 'An unknown error occurred', description: 'undefined' }, @@ -68,11 +76,21 @@ describe('Type Guards', () => { describe('hasProperty', () => { it.each([ { obj: { name: 'test' }, key: 'name', expected: true, description: 'object with property' }, - { obj: { name: 'test' }, key: 'age', expected: false, description: 'object without property' }, + { + obj: { name: 'test' }, + key: 'age', + expected: false, + description: 'object without property', + }, { obj: {}, key: 'name', expected: false, description: 'empty object' }, { obj: null, key: 'name', expected: false, description: 'null' }, { obj: undefined, key: 'name', expected: false, description: 'undefined' }, - { obj: 'string', key: 'length', expected: false, description: 'string (primitive, not object)' }, + { + obj: 'string', + key: 'length', + expected: false, + description: 'string (primitive, not object)', + }, ])('should return $expected for $description', ({ obj, key, expected }) => { expect(hasProperty(obj, key)).toBe(expected) }) diff --git a/frontends/nextjs/src/lib/types/guards/has-property.ts b/frontends/nextjs/src/lib/types/guards/has-property.ts index 900ce98cb..2684d9698 100644 --- a/frontends/nextjs/src/lib/types/guards/has-property.ts +++ b/frontends/nextjs/src/lib/types/guards/has-property.ts @@ -1,9 +1,6 @@ /** * Check if value has a specific property */ -export function hasProperty( - obj: unknown, - key: K -): obj is Record { +export function hasProperty(obj: unknown, key: K): obj is Record { return typeof obj === 'object' && obj !== null && key in obj } diff --git a/frontends/nextjs/src/lib/types/level-types.ts b/frontends/nextjs/src/lib/types/level-types.ts index 111d72a8b..d44c58047 100644 --- a/frontends/nextjs/src/lib/types/level-types.ts +++ b/frontends/nextjs/src/lib/types/level-types.ts @@ -50,7 +50,7 @@ export interface Comment { /** * WorkflowNode - Individual step in a workflow - * @property type - Node type: trigger (event), action (do something), + * @property type - Node type: trigger (event), action (do something), * condition (if/then), lua (execute script), transform (map data) * @property position - X/Y coordinates for visual editor */ diff --git a/frontends/nextjs/src/lib/utils.ts b/frontends/nextjs/src/lib/utils.ts index ab7b1eccf..69efced43 100644 --- a/frontends/nextjs/src/lib/utils.ts +++ b/frontends/nextjs/src/lib/utils.ts @@ -1,4 +1,4 @@ -import { clsx, type ClassValue } from "clsx" +import { clsx, type ClassValue } from 'clsx' /** * Utility function to merge class names diff --git a/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.errors.test.ts b/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.errors.test.ts index 5e4aba120..d11086c55 100644 --- a/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.errors.test.ts +++ b/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.errors.test.ts @@ -42,7 +42,7 @@ describe('workflow-engine errors', () => { expect(result.success).toBe(false) expect(result.error).toContain('Transform failed') - expect(result.logs.filter((log) => log.includes('Retrying node'))).toHaveLength(1) + expect(result.logs.filter(log => log.includes('Retrying node'))).toHaveLength(1) }) it('propagates Lua script resolution errors', async () => { diff --git a/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.execution.test.ts b/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.execution.test.ts index b2dc093ff..018d7d250 100644 --- a/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.execution.test.ts +++ b/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.execution.test.ts @@ -37,7 +37,7 @@ describe('workflow-engine execution', () => { expect(result.success).toBe(true) expect(result.outputs.action).toBeUndefined() expect(Object.keys(result.outputs)).toHaveLength(2) - expect(result.logs.some((log) => log.includes('Condition node returned false'))).toBe(true) + expect(result.logs.some(log => log.includes('Condition node returned false'))).toBe(true) }) it('passes user context through to Lua nodes', async () => { diff --git a/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.persistence.test.ts b/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.persistence.test.ts index adcc6d40f..089501967 100644 --- a/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.persistence.test.ts +++ b/frontends/nextjs/src/lib/workflow/engine/__tests__/workflow-engine.persistence.test.ts @@ -23,7 +23,7 @@ describe('workflow-engine persistence', () => { expect(result.success).toBe(false) expect(result.outputs.trigger).toEqual({ value: 3 }) expect(result.outputs.action).toBeUndefined() - expect(result.logs.some((log) => log.includes('Break here'))).toBe(true) + expect(result.logs.some(log => log.includes('Break here'))).toBe(true) expect(result.error).toContain('Transform failed') }) @@ -49,7 +49,7 @@ describe('workflow-engine persistence', () => { expect(result.success).toBe(true) expect(result.outputs.lua).toBe(99) expect(result.securityWarnings).toContain('Security issues detected: uses os') - expect(result.logs.some((log) => log.includes('[Lua] lua log'))).toBe(true) + expect(result.logs.some(log => log.includes('[Lua] lua log'))).toBe(true) expect(mockEngine.destroy).toHaveBeenCalled() }) }) diff --git a/frontends/nextjs/src/lib/workflow/execution/execute-node.ts b/frontends/nextjs/src/lib/workflow/execution/execute-node.ts index ca7791fa7..2e868605b 100644 --- a/frontends/nextjs/src/lib/workflow/execution/execute-node.ts +++ b/frontends/nextjs/src/lib/workflow/execution/execute-node.ts @@ -48,10 +48,7 @@ function normalizeRetryConfig(config: unknown) { } } -function calculateRetryDelayMs( - attempt: number, - config: ReturnType -) { +function calculateRetryDelayMs(attempt: number, config: ReturnType) { if (config.delayMs <= 0) { return 0 } diff --git a/frontends/nextjs/src/lib/workflow/log-to-workflow.ts b/frontends/nextjs/src/lib/workflow/log-to-workflow.ts index 87e6f9d71..9b4219002 100644 --- a/frontends/nextjs/src/lib/workflow/log-to-workflow.ts +++ b/frontends/nextjs/src/lib/workflow/log-to-workflow.ts @@ -4,5 +4,5 @@ import type { WorkflowState } from './workflow-state' * Log a message to workflow state */ export function logToWorkflow(state: WorkflowState, ...args: any[]): void { - state.logs.push(args.map((arg) => String(arg)).join(' ')) + state.logs.push(args.map(arg => String(arg)).join(' ')) } diff --git a/frontends/nextjs/src/lib/workflow/nodes/execute-lua-code.ts b/frontends/nextjs/src/lib/workflow/nodes/execute-lua-code.ts index 9e27e1a5e..0bc7561d1 100644 --- a/frontends/nextjs/src/lib/workflow/nodes/execute-lua-code.ts +++ b/frontends/nextjs/src/lib/workflow/nodes/execute-lua-code.ts @@ -26,11 +26,11 @@ export async function executeLuaCode( if (result.security.severity === 'critical' || result.security.severity === 'high') { state.securityWarnings.push( - `Security issues detected: ${result.security.issues.map((i) => i.message).join(', ')}` + `Security issues detected: ${result.security.issues.map(i => i.message).join(', ')}` ) } - result.execution.logs.forEach((log) => logToWorkflow(state, `[Lua] ${log}`)) + result.execution.logs.forEach(log => logToWorkflow(state, `[Lua] ${log}`)) if (!result.execution.success) { return { diff --git a/frontends/nextjs/src/lib/workflow/nodes/execute-lua-node.ts b/frontends/nextjs/src/lib/workflow/nodes/execute-lua-node.ts index a98c6763a..50cd062ed 100644 --- a/frontends/nextjs/src/lib/workflow/nodes/execute-lua-node.ts +++ b/frontends/nextjs/src/lib/workflow/nodes/execute-lua-node.ts @@ -19,7 +19,7 @@ export async function executeLuaNode( return await executeLuaCode(luaCode, data, context, state) } - const script = context.scripts.find((s) => s.id === scriptId) + const script = context.scripts.find(s => s.id === scriptId) if (!script) { return { success: false, diff --git a/frontends/nextjs/src/seed-data/entities/content/scripts.ts b/frontends/nextjs/src/seed-data/entities/content/scripts.ts index 5848df8e5..ffe69c6c3 100644 --- a/frontends/nextjs/src/seed-data/entities/content/scripts.ts +++ b/frontends/nextjs/src/seed-data/entities/content/scripts.ts @@ -19,9 +19,7 @@ end return generateWelcome `, - parameters: [ - { name: 'username', type: 'string' } - ], + parameters: [{ name: 'username', type: 'string' }], returnType: 'string', }, { @@ -37,9 +35,7 @@ end return formatDate `, - parameters: [ - { name: 'timestamp', type: 'number' } - ], + parameters: [{ name: 'timestamp', type: 'number' }], returnType: 'string', }, { @@ -55,9 +51,7 @@ end return validateEmail `, - parameters: [ - { name: 'email', type: 'string' } - ], + parameters: [{ name: 'email', type: 'string' }], returnType: 'boolean', }, { @@ -85,7 +79,7 @@ return checkPermission `, parameters: [ { name: 'userRole', type: 'string' }, - { name: 'requiredRole', type: 'string' } + { name: 'requiredRole', type: 'string' }, ], returnType: 'boolean', }, @@ -104,7 +98,7 @@ return logPageView parameters: [ { name: 'pageId', type: 'string' }, { name: 'userId', type: 'string' }, - { name: 'timestamp', type: 'number' } + { name: 'timestamp', type: 'number' }, ], returnType: 'boolean', }, diff --git a/frontends/nextjs/src/seed-data/entities/content/workflows.ts b/frontends/nextjs/src/seed-data/entities/content/workflows.ts index 96861234e..df90b1185 100644 --- a/frontends/nextjs/src/seed-data/entities/content/workflows.ts +++ b/frontends/nextjs/src/seed-data/entities/content/workflows.ts @@ -19,27 +19,27 @@ export async function initializeWorkflows() { type: 'condition', label: 'Validate Input', config: { action: 'validate_email' }, - position: { x: 100, y: 100 } + position: { x: 100, y: 100 }, }, { id: 'node_create', type: 'action', label: 'Create User', config: { action: 'create_user' }, - position: { x: 100, y: 200 } + position: { x: 100, y: 200 }, }, { id: 'node_notify', type: 'action', label: 'Send Welcome Email', config: { action: 'send_email' }, - position: { x: 100, y: 300 } - } + position: { x: 100, y: 300 }, + }, ], edges: [ { id: 'edge_1', source: 'node_validate', target: 'node_create', label: 'valid' }, - { id: 'edge_2', source: 'node_create', target: 'node_notify', label: 'success' } - ] + { id: 'edge_2', source: 'node_create', target: 'node_notify', label: 'success' }, + ], }, { id: 'workflow_page_access', @@ -52,27 +52,27 @@ export async function initializeWorkflows() { type: 'condition', label: 'Check Authentication', config: { action: 'check_auth' }, - position: { x: 100, y: 100 } + position: { x: 100, y: 100 }, }, { id: 'node_perm_check', type: 'lua', label: 'Check Permission', config: { scriptId: 'script_permission_check' }, - position: { x: 100, y: 200 } + position: { x: 100, y: 200 }, }, { id: 'node_log', type: 'action', label: 'Log Access', config: { action: 'log_page_view' }, - position: { x: 100, y: 300 } - } + position: { x: 100, y: 300 }, + }, ], edges: [ { id: 'edge_1', source: 'node_auth_check', target: 'node_perm_check', label: 'authorized' }, - { id: 'edge_2', source: 'node_perm_check', target: 'node_log', label: 'allowed' } - ] + { id: 'edge_2', source: 'node_perm_check', target: 'node_log', label: 'allowed' }, + ], }, { id: 'workflow_comment_submission', @@ -85,27 +85,37 @@ export async function initializeWorkflows() { type: 'condition', label: 'Validate Comment', config: { action: 'validate_content' }, - position: { x: 100, y: 100 } + position: { x: 100, y: 100 }, }, { id: 'node_save_comment', type: 'action', label: 'Save to Database', config: { action: 'create_comment' }, - position: { x: 100, y: 200 } + position: { x: 100, y: 200 }, }, { id: 'node_notify_success', type: 'action', label: 'Show Success', config: { action: 'show_toast' }, - position: { x: 100, y: 300 } - } + position: { x: 100, y: 300 }, + }, ], edges: [ - { id: 'edge_1', source: 'node_validate_comment', target: 'node_save_comment', label: 'valid' }, - { id: 'edge_2', source: 'node_save_comment', target: 'node_notify_success', label: 'saved' } - ] + { + id: 'edge_1', + source: 'node_validate_comment', + target: 'node_save_comment', + label: 'valid', + }, + { + id: 'edge_2', + source: 'node_save_comment', + target: 'node_notify_success', + label: 'saved', + }, + ], }, { id: 'workflow_package_export', @@ -118,28 +128,28 @@ export async function initializeWorkflows() { type: 'condition', label: 'Validate Package', config: { action: 'validate_package' }, - position: { x: 100, y: 100 } + position: { x: 100, y: 100 }, }, { id: 'node_build_zip', type: 'action', label: 'Build Zip', config: { action: 'build_package_zip' }, - position: { x: 100, y: 200 } + position: { x: 100, y: 200 }, }, { id: 'node_publish_package', type: 'action', label: 'Publish Artifact', config: { action: 'publish_package' }, - position: { x: 100, y: 300 } - } + position: { x: 100, y: 300 }, + }, ], edges: [ { id: 'edge_1', source: 'node_validate_package', target: 'node_build_zip', label: 'valid' }, - { id: 'edge_2', source: 'node_build_zip', target: 'node_publish_package', label: 'built' } - ] - } + { id: 'edge_2', source: 'node_build_zip', target: 'node_publish_package', label: 'built' }, + ], + }, ] for (const workflow of workflows) { diff --git a/frontends/nextjs/src/tests/package-integration.test.ts b/frontends/nextjs/src/tests/package-integration.test.ts index 1abfb56e6..23916c60d 100644 --- a/frontends/nextjs/src/tests/package-integration.test.ts +++ b/frontends/nextjs/src/tests/package-integration.test.ts @@ -12,7 +12,7 @@ const packages = [ dataTableMetadata, formBuilderMetadata, navMenuMetadata, - notificationCenterMetadata + notificationCenterMetadata, ] describe('Package System Integration', () => { @@ -63,14 +63,14 @@ describe('Package System Integration', () => { throw new Error(`Circular dependency detected: ${pkgId}`) } visited.add(pkgId) - + const pkg = packages.find(p => p.packageId === pkgId) if (!pkg) return visited - + pkg.dependencies.forEach((depId: string) => { getDependencies(depId, new Set(visited)) }) - + return visited } @@ -81,7 +81,7 @@ describe('Package System Integration', () => { it('should have all dependencies reference valid packages', () => { const allPackageIds = packages.map(pkg => pkg.packageId) - + packages.forEach(pkg => { pkg.dependencies.forEach((depId: string) => { expect(allPackageIds).toContain(depId) diff --git a/frontends/nextjs/src/theme/base/colors.ts b/frontends/nextjs/src/theme/base/colors.ts index c2bfe28d3..ce3dd9c25 100644 --- a/frontends/nextjs/src/theme/base/colors.ts +++ b/frontends/nextjs/src/theme/base/colors.ts @@ -24,8 +24,16 @@ export const colors = { dark: { main: '#34d399', light: '#6ee7b7', dark: '#10b981' }, }, neutral: { - 50: '#fafafa', 100: '#f4f4f5', 200: '#e4e4e7', 300: '#d4d4d8', - 400: '#a1a1aa', 500: '#71717a', 600: '#52525b', 700: '#3f3f46', - 800: '#27272a', 900: '#18181b', 950: '#09090b', + 50: '#fafafa', + 100: '#f4f4f5', + 200: '#e4e4e7', + 300: '#d4d4d8', + 400: '#a1a1aa', + 500: '#71717a', + 600: '#52525b', + 700: '#3f3f46', + 800: '#27272a', + 900: '#18181b', + 950: '#09090b', }, } diff --git a/frontends/nextjs/src/theme/base/typography.ts b/frontends/nextjs/src/theme/base/typography.ts index b52e27c54..f4ef8a989 100644 --- a/frontends/nextjs/src/theme/base/typography.ts +++ b/frontends/nextjs/src/theme/base/typography.ts @@ -2,8 +2,20 @@ import { fonts } from './fonts' export const typography = { fontFamily: fonts.body, - h1: { fontFamily: fonts.heading, fontWeight: 700, fontSize: '3rem', lineHeight: 1.2, letterSpacing: '-0.02em' }, - h2: { fontFamily: fonts.heading, fontWeight: 700, fontSize: '2.25rem', lineHeight: 1.25, letterSpacing: '-0.01em' }, + h1: { + fontFamily: fonts.heading, + fontWeight: 700, + fontSize: '3rem', + lineHeight: 1.2, + letterSpacing: '-0.02em', + }, + h2: { + fontFamily: fonts.heading, + fontWeight: 700, + fontSize: '2.25rem', + lineHeight: 1.25, + letterSpacing: '-0.01em', + }, h3: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.875rem', lineHeight: 1.3 }, h4: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.5rem', lineHeight: 1.35 }, h5: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.25rem', lineHeight: 1.4 }, @@ -13,9 +25,27 @@ export const typography = { body1: { fontSize: '1rem', lineHeight: 1.6 }, body2: { fontSize: '0.875rem', lineHeight: 1.5 }, caption: { fontSize: '0.75rem', lineHeight: 1.4 }, - overline: { fontWeight: 600, fontSize: '0.75rem', letterSpacing: '0.08em', textTransform: 'uppercase' as const }, + overline: { + fontWeight: 600, + fontSize: '0.75rem', + letterSpacing: '0.08em', + textTransform: 'uppercase' as const, + }, button: { fontWeight: 500, fontSize: '0.875rem', textTransform: 'none' as const }, - code: { fontFamily: fonts.mono, fontSize: '0.875rem', backgroundColor: 'rgba(0,0,0,0.06)', padding: '2px 6px', borderRadius: 4 }, - kbd: { fontFamily: fonts.mono, fontSize: '0.75rem', fontWeight: 500, padding: '2px 8px', borderRadius: 4, border: '1px solid' }, + code: { + fontFamily: fonts.mono, + fontSize: '0.875rem', + backgroundColor: 'rgba(0,0,0,0.06)', + padding: '2px 6px', + borderRadius: 4, + }, + kbd: { + fontFamily: fonts.mono, + fontSize: '0.75rem', + fontWeight: 500, + padding: '2px 8px', + borderRadius: 4, + border: '1px solid', + }, label: { fontSize: '0.875rem', fontWeight: 500, lineHeight: 1.5 }, } diff --git a/frontends/nextjs/src/theme/components.ts b/frontends/nextjs/src/theme/components.ts index de3ba5a59..2d4a3f76c 100644 --- a/frontends/nextjs/src/theme/components.ts +++ b/frontends/nextjs/src/theme/components.ts @@ -9,11 +9,18 @@ export const getComponentOverrides = (mode: 'light' | 'dark'): ThemeOptions['com return { MuiCssBaseline: { styleOverrides: { - ':root': { '--font-body': fonts.body, '--font-heading': fonts.heading, '--font-mono': fonts.mono }, + ':root': { + '--font-body': fonts.body, + '--font-heading': fonts.heading, + '--font-mono': fonts.mono, + }, html: { scrollBehavior: 'smooth' }, body: { WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale' }, 'code, pre, kbd': { fontFamily: fonts.mono }, - '::selection': { backgroundColor: isDark ? colors.primary.dark.main : colors.primary.light.main, color: '#fff' }, + '::selection': { + backgroundColor: isDark ? colors.primary.dark.main : colors.primary.light.main, + color: '#fff', + }, '::-webkit-scrollbar': { width: 8, height: 8 }, '::-webkit-scrollbar-track': { backgroundColor: isDark ? n[800] : n[100] }, '::-webkit-scrollbar-thumb': { backgroundColor: isDark ? n[600] : n[400], borderRadius: 4 }, @@ -25,12 +32,22 @@ export const getComponentOverrides = (mode: 'light' | 'dark'): ThemeOptions['com root: { borderRadius: 8, textTransform: 'none', fontWeight: 500, padding: '8px 16px' }, sizeSmall: { padding: '4px 12px', fontSize: '0.8125rem' }, sizeLarge: { padding: '12px 24px', fontSize: '0.9375rem' }, - containedPrimary: { '&:hover': { boxShadow: `0 4px 12px ${alpha(isDark ? colors.primary.dark.main : colors.primary.light.main, 0.4)}` } }, + containedPrimary: { + '&:hover': { + boxShadow: `0 4px 12px ${alpha(isDark ? colors.primary.dark.main : colors.primary.light.main, 0.4)}`, + }, + }, }, }, MuiCard: { defaultProps: { elevation: 0 }, - styleOverrides: { root: { borderRadius: 12, border: `1px solid ${isDark ? n[800] : n[200]}`, backgroundImage: 'none' } }, + styleOverrides: { + root: { + borderRadius: 12, + border: `1px solid ${isDark ? n[800] : n[200]}`, + backgroundImage: 'none', + }, + }, }, MuiCardHeader: { styleOverrides: { @@ -39,56 +56,177 @@ export const getComponentOverrides = (mode: 'light' | 'dark'): ThemeOptions['com subheader: { fontSize: '0.875rem', color: isDark ? n[400] : n[600] }, }, }, - MuiCardContent: { styleOverrides: { root: { padding: '16px 24px', '&:last-child': { paddingBottom: 24 } } } }, - MuiCardActions: { styleOverrides: { root: { padding: '16px 24px', borderTop: `1px solid ${isDark ? n[800] : n[200]}` } } }, - MuiPaper: { defaultProps: { elevation: 0 }, styleOverrides: { root: { backgroundImage: 'none' }, outlined: { borderColor: isDark ? n[800] : n[200] } } }, + MuiCardContent: { + styleOverrides: { root: { padding: '16px 24px', '&:last-child': { paddingBottom: 24 } } }, + }, + MuiCardActions: { + styleOverrides: { + root: { padding: '16px 24px', borderTop: `1px solid ${isDark ? n[800] : n[200]}` }, + }, + }, + MuiPaper: { + defaultProps: { elevation: 0 }, + styleOverrides: { + root: { backgroundImage: 'none' }, + outlined: { borderColor: isDark ? n[800] : n[200] }, + }, + }, MuiTextField: { defaultProps: { variant: 'outlined', size: 'small' }, styleOverrides: { root: { - '& .MuiOutlinedInput-root': { borderRadius: 8, backgroundColor: isDark ? n[900] : '#fff' }, + '& .MuiOutlinedInput-root': { + borderRadius: 8, + backgroundColor: isDark ? n[900] : '#fff', + }, '& .MuiOutlinedInput-notchedOutline': { borderColor: isDark ? n[700] : n[300] }, }, }, }, MuiOutlinedInput: { styleOverrides: { root: { borderRadius: 8 } } }, MuiSelect: { styleOverrides: { root: { borderRadius: 8 } } }, - MuiChip: { styleOverrides: { root: { borderRadius: 6, fontWeight: 500 }, sizeSmall: { height: 24 } } }, + MuiChip: { + styleOverrides: { root: { borderRadius: 6, fontWeight: 500 }, sizeSmall: { height: 24 } }, + }, MuiAlert: { styleOverrides: { root: { borderRadius: 8, alignItems: 'center' }, - standardSuccess: { backgroundColor: alpha(isDark ? colors.success.dark.main : colors.success.light.main, isDark ? 0.15 : 0.1) }, - standardError: { backgroundColor: alpha(isDark ? colors.error.dark.main : colors.error.light.main, isDark ? 0.15 : 0.1) }, - standardWarning: { backgroundColor: alpha(isDark ? colors.warning.dark.main : colors.warning.light.main, isDark ? 0.15 : 0.1) }, - standardInfo: { backgroundColor: alpha(isDark ? colors.info.dark.main : colors.info.light.main, isDark ? 0.15 : 0.1) }, + standardSuccess: { + backgroundColor: alpha( + isDark ? colors.success.dark.main : colors.success.light.main, + isDark ? 0.15 : 0.1 + ), + }, + standardError: { + backgroundColor: alpha( + isDark ? colors.error.dark.main : colors.error.light.main, + isDark ? 0.15 : 0.1 + ), + }, + standardWarning: { + backgroundColor: alpha( + isDark ? colors.warning.dark.main : colors.warning.light.main, + isDark ? 0.15 : 0.1 + ), + }, + standardInfo: { + backgroundColor: alpha( + isDark ? colors.info.dark.main : colors.info.light.main, + isDark ? 0.15 : 0.1 + ), + }, }, }, MuiDialog: { styleOverrides: { paper: { borderRadius: 16 } } }, - MuiDialogTitle: { styleOverrides: { root: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.25rem', padding: '24px 24px 16px' } } }, + MuiDialogTitle: { + styleOverrides: { + root: { + fontFamily: fonts.heading, + fontWeight: 600, + fontSize: '1.25rem', + padding: '24px 24px 16px', + }, + }, + }, MuiDialogContent: { styleOverrides: { root: { padding: '16px 24px' } } }, MuiDialogActions: { styleOverrides: { root: { padding: '16px 24px 24px', gap: 8 } } }, MuiTable: { styleOverrides: { root: { borderCollapse: 'separate', borderSpacing: 0 } } }, - MuiTableHead: { styleOverrides: { root: { '& .MuiTableCell-root': { fontWeight: 600, backgroundColor: isDark ? n[900] : n[50], borderBottom: `2px solid ${isDark ? n[700] : n[300]}` } } } }, - MuiTableCell: { styleOverrides: { root: { borderBottom: `1px solid ${isDark ? n[800] : n[200]}`, padding: '12px 16px' } } }, - MuiTableRow: { styleOverrides: { root: { '&:hover': { backgroundColor: isDark ? alpha(n[700], 0.3) : alpha(n[100], 0.5) } } } }, - MuiTabs: { styleOverrides: { root: { minHeight: 48 }, indicator: { height: 3, borderRadius: '3px 3px 0 0' } } }, - MuiTab: { styleOverrides: { root: { textTransform: 'none', fontWeight: 500, minHeight: 48, padding: '12px 16px' } } }, - MuiTooltip: { styleOverrides: { tooltip: { backgroundColor: isDark ? n[700] : n[900], fontSize: '0.75rem', padding: '6px 12px', borderRadius: 6 }, arrow: { color: isDark ? n[700] : n[900] } } }, - MuiMenu: { styleOverrides: { paper: { borderRadius: 8, border: `1px solid ${isDark ? n[800] : n[200]}` } } }, - MuiMenuItem: { styleOverrides: { root: { padding: '8px 16px', borderRadius: 4, margin: '2px 8px' } } }, - MuiDrawer: { styleOverrides: { paper: { borderRight: `1px solid ${isDark ? n[800] : n[200]}` } } }, - MuiAppBar: { defaultProps: { elevation: 0 }, styleOverrides: { root: { backgroundColor: isDark ? n[900] : '#fff', borderBottom: `1px solid ${isDark ? n[800] : n[200]}` } } }, + MuiTableHead: { + styleOverrides: { + root: { + '& .MuiTableCell-root': { + fontWeight: 600, + backgroundColor: isDark ? n[900] : n[50], + borderBottom: `2px solid ${isDark ? n[700] : n[300]}`, + }, + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { borderBottom: `1px solid ${isDark ? n[800] : n[200]}`, padding: '12px 16px' }, + }, + }, + MuiTableRow: { + styleOverrides: { + root: { '&:hover': { backgroundColor: isDark ? alpha(n[700], 0.3) : alpha(n[100], 0.5) } }, + }, + }, + MuiTabs: { + styleOverrides: { + root: { minHeight: 48 }, + indicator: { height: 3, borderRadius: '3px 3px 0 0' }, + }, + }, + MuiTab: { + styleOverrides: { + root: { textTransform: 'none', fontWeight: 500, minHeight: 48, padding: '12px 16px' }, + }, + }, + MuiTooltip: { + styleOverrides: { + tooltip: { + backgroundColor: isDark ? n[700] : n[900], + fontSize: '0.75rem', + padding: '6px 12px', + borderRadius: 6, + }, + arrow: { color: isDark ? n[700] : n[900] }, + }, + }, + MuiMenu: { + styleOverrides: { + paper: { borderRadius: 8, border: `1px solid ${isDark ? n[800] : n[200]}` }, + }, + }, + MuiMenuItem: { + styleOverrides: { root: { padding: '8px 16px', borderRadius: 4, margin: '2px 8px' } }, + }, + MuiDrawer: { + styleOverrides: { paper: { borderRight: `1px solid ${isDark ? n[800] : n[200]}` } }, + }, + MuiAppBar: { + defaultProps: { elevation: 0 }, + styleOverrides: { + root: { + backgroundColor: isDark ? n[900] : '#fff', + borderBottom: `1px solid ${isDark ? n[800] : n[200]}`, + }, + }, + }, MuiDivider: { styleOverrides: { root: { borderColor: isDark ? n[800] : n[200] } } }, MuiAvatar: { styleOverrides: { root: { fontFamily: fonts.heading, fontWeight: 600 } } }, MuiBadge: { styleOverrides: { badge: { fontWeight: 600, fontSize: '0.6875rem' } } }, MuiLinearProgress: { styleOverrides: { root: { borderRadius: 4, height: 6 } } }, - MuiSkeleton: { styleOverrides: { root: { borderRadius: 4, backgroundColor: isDark ? n[800] : n[200] } } }, - MuiAccordion: { defaultProps: { elevation: 0 }, styleOverrides: { root: { border: `1px solid ${isDark ? n[800] : n[200]}`, borderRadius: 8, '&:before': { display: 'none' } } } }, - MuiAccordionSummary: { styleOverrides: { root: { padding: '0 16px', minHeight: 56 }, content: { fontWeight: 500 } } }, + MuiSkeleton: { + styleOverrides: { root: { borderRadius: 4, backgroundColor: isDark ? n[800] : n[200] } }, + }, + MuiAccordion: { + defaultProps: { elevation: 0 }, + styleOverrides: { + root: { + border: `1px solid ${isDark ? n[800] : n[200]}`, + borderRadius: 8, + '&:before': { display: 'none' }, + }, + }, + }, + MuiAccordionSummary: { + styleOverrides: { root: { padding: '0 16px', minHeight: 56 }, content: { fontWeight: 500 } }, + }, MuiAccordionDetails: { styleOverrides: { root: { padding: '0 16px 16px' } } }, - MuiSwitch: { styleOverrides: { root: { width: 46, height: 26, padding: 0 }, switchBase: { padding: 2, '&.Mui-checked': { transform: 'translateX(20px)' } }, thumb: { width: 22, height: 22 }, track: { borderRadius: 13, opacity: 1, backgroundColor: isDark ? n[700] : n[300] } } }, + MuiSwitch: { + styleOverrides: { + root: { width: 46, height: 26, padding: 0 }, + switchBase: { padding: 2, '&.Mui-checked': { transform: 'translateX(20px)' } }, + thumb: { width: 22, height: 22 }, + track: { borderRadius: 13, opacity: 1, backgroundColor: isDark ? n[700] : n[300] }, + }, + }, MuiIconButton: { styleOverrides: { root: { borderRadius: 8 } } }, - MuiListItemButton: { styleOverrides: { root: { borderRadius: 8, margin: '2px 8px', padding: '8px 12px' } } }, + MuiListItemButton: { + styleOverrides: { root: { borderRadius: 8, margin: '2px 8px', padding: '8px 12px' } }, + }, MuiSnackbar: { styleOverrides: { root: { '& .MuiPaper-root': { borderRadius: 8 } } } }, } } diff --git a/frontends/nextjs/src/theme/dark-theme.ts b/frontends/nextjs/src/theme/dark-theme.ts index 57f16b719..237fab6e9 100644 --- a/frontends/nextjs/src/theme/dark-theme.ts +++ b/frontends/nextjs/src/theme/dark-theme.ts @@ -8,8 +8,24 @@ import { layout } from './layout' import { typography } from './typography' import { getComponentOverrides } from './components' -const custom = { fonts, borderRadius: layout.borderRadius, contentWidth: layout.contentWidth, sidebar: layout.sidebar, header: layout.header } -const shadows = (o: number): Shadows => ['none', `0 1px 2px rgba(0,0,0,${o/2})`, `0 1px 3px rgba(0,0,0,${o})`, `0 4px 6px rgba(0,0,0,${o})`, `0 10px 15px rgba(0,0,0,${o})`, `0 20px 25px rgba(0,0,0,${o})`, `0 25px 50px rgba(0,0,0,${o*2.5})`, ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o*2.5})`)] as Shadows +const custom = { + fonts, + borderRadius: layout.borderRadius, + contentWidth: layout.contentWidth, + sidebar: layout.sidebar, + header: layout.header, +} +const shadows = (o: number): Shadows => + [ + 'none', + `0 1px 2px rgba(0,0,0,${o / 2})`, + `0 1px 3px rgba(0,0,0,${o})`, + `0 4px 6px rgba(0,0,0,${o})`, + `0 10px 15px rgba(0,0,0,${o})`, + `0 20px 25px rgba(0,0,0,${o})`, + `0 25px 50px rgba(0,0,0,${o * 2.5})`, + ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o * 2.5})`), + ] as Shadows export const darkTheme = createTheme({ palette: { @@ -22,9 +38,19 @@ export const darkTheme = createTheme({ success: colors.success.dark, neutral: colors.neutral, background: { default: colors.neutral[950], paper: colors.neutral[900] }, - text: { primary: colors.neutral[100], secondary: colors.neutral[400], disabled: colors.neutral[600] }, + text: { + primary: colors.neutral[100], + secondary: colors.neutral[400], + disabled: colors.neutral[600], + }, divider: colors.neutral[800], - action: { active: colors.neutral[300], hover: alpha(colors.neutral[400], 0.12), selected: alpha(colors.primary.dark.main, 0.2), disabled: colors.neutral[600], disabledBackground: colors.neutral[800] }, + action: { + active: colors.neutral[300], + hover: alpha(colors.neutral[400], 0.12), + selected: alpha(colors.primary.dark.main, 0.2), + disabled: colors.neutral[600], + disabledBackground: colors.neutral[800], + }, }, typography, spacing: layout.spacing, diff --git a/frontends/nextjs/src/theme/index.ts b/frontends/nextjs/src/theme/index.ts index 83dca8427..11f120862 100644 --- a/frontends/nextjs/src/theme/index.ts +++ b/frontends/nextjs/src/theme/index.ts @@ -8,4 +8,3 @@ export { lightTheme } from './light-theme' export { darkTheme } from './dark-theme' export { useTheme, styled, alpha } from '@mui/material/styles' export type { Theme, SxProps } from '@mui/material/styles' - diff --git a/frontends/nextjs/src/theme/light-theme.ts b/frontends/nextjs/src/theme/light-theme.ts index b440aebe2..d62fd61d8 100644 --- a/frontends/nextjs/src/theme/light-theme.ts +++ b/frontends/nextjs/src/theme/light-theme.ts @@ -8,8 +8,24 @@ import { layout } from './layout' import { typography } from './typography' import { getComponentOverrides } from './components' -const custom = { fonts, borderRadius: layout.borderRadius, contentWidth: layout.contentWidth, sidebar: layout.sidebar, header: layout.header } -const shadows = (o: number): Shadows => ['none', `0 1px 2px rgba(0,0,0,${o/2})`, `0 1px 3px rgba(0,0,0,${o})`, `0 4px 6px rgba(0,0,0,${o})`, `0 10px 15px rgba(0,0,0,${o})`, `0 20px 25px rgba(0,0,0,${o})`, `0 25px 50px rgba(0,0,0,${o*2.5})`, ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o*2.5})`)] as Shadows +const custom = { + fonts, + borderRadius: layout.borderRadius, + contentWidth: layout.contentWidth, + sidebar: layout.sidebar, + header: layout.header, +} +const shadows = (o: number): Shadows => + [ + 'none', + `0 1px 2px rgba(0,0,0,${o / 2})`, + `0 1px 3px rgba(0,0,0,${o})`, + `0 4px 6px rgba(0,0,0,${o})`, + `0 10px 15px rgba(0,0,0,${o})`, + `0 20px 25px rgba(0,0,0,${o})`, + `0 25px 50px rgba(0,0,0,${o * 2.5})`, + ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o * 2.5})`), + ] as Shadows export const lightTheme = createTheme({ palette: { @@ -22,9 +38,19 @@ export const lightTheme = createTheme({ success: colors.success.light, neutral: colors.neutral, background: { default: '#ffffff', paper: colors.neutral[50] }, - text: { primary: colors.neutral[900], secondary: colors.neutral[600], disabled: colors.neutral[400] }, + text: { + primary: colors.neutral[900], + secondary: colors.neutral[600], + disabled: colors.neutral[400], + }, divider: colors.neutral[200], - action: { active: colors.neutral[700], hover: alpha(colors.neutral[500], 0.08), selected: alpha(colors.primary.light.main, 0.12), disabled: colors.neutral[400], disabledBackground: colors.neutral[200] }, + action: { + active: colors.neutral[700], + hover: alpha(colors.neutral[500], 0.08), + selected: alpha(colors.primary.light.main, 0.12), + disabled: colors.neutral[400], + disabledBackground: colors.neutral[200], + }, }, typography, spacing: layout.spacing, diff --git a/frontends/nextjs/src/theme/modes/dark-theme.ts b/frontends/nextjs/src/theme/modes/dark-theme.ts index 18b113297..ff72d24db 100644 --- a/frontends/nextjs/src/theme/modes/dark-theme.ts +++ b/frontends/nextjs/src/theme/modes/dark-theme.ts @@ -7,8 +7,24 @@ import { layout } from '../layout' import { typography } from '../typography' import { getComponentOverrides } from '../components' -const custom = { fonts, borderRadius: layout.borderRadius, contentWidth: layout.contentWidth, sidebar: layout.sidebar, header: layout.header } -const shadows = (o: number): Shadows => ['none', `0 1px 2px rgba(0,0,0,${o/2})`, `0 1px 3px rgba(0,0,0,${o})`, `0 4px 6px rgba(0,0,0,${o})`, `0 10px 15px rgba(0,0,0,${o})`, `0 20px 25px rgba(0,0,0,${o})`, `0 25px 50px rgba(0,0,0,${o*2.5})`, ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o*2.5})`)] as Shadows +const custom = { + fonts, + borderRadius: layout.borderRadius, + contentWidth: layout.contentWidth, + sidebar: layout.sidebar, + header: layout.header, +} +const shadows = (o: number): Shadows => + [ + 'none', + `0 1px 2px rgba(0,0,0,${o / 2})`, + `0 1px 3px rgba(0,0,0,${o})`, + `0 4px 6px rgba(0,0,0,${o})`, + `0 10px 15px rgba(0,0,0,${o})`, + `0 20px 25px rgba(0,0,0,${o})`, + `0 25px 50px rgba(0,0,0,${o * 2.5})`, + ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o * 2.5})`), + ] as Shadows export const darkTheme = createTheme({ palette: { @@ -21,9 +37,19 @@ export const darkTheme = createTheme({ success: colors.success.dark, neutral: colors.neutral, background: { default: colors.neutral[950], paper: colors.neutral[900] }, - text: { primary: colors.neutral[100], secondary: colors.neutral[400], disabled: colors.neutral[600] }, + text: { + primary: colors.neutral[100], + secondary: colors.neutral[400], + disabled: colors.neutral[600], + }, divider: colors.neutral[800], - action: { active: colors.neutral[300], hover: alpha(colors.neutral[400], 0.12), selected: alpha(colors.primary.dark.main, 0.2), disabled: colors.neutral[600], disabledBackground: colors.neutral[800] }, + action: { + active: colors.neutral[300], + hover: alpha(colors.neutral[400], 0.12), + selected: alpha(colors.primary.dark.main, 0.2), + disabled: colors.neutral[600], + disabledBackground: colors.neutral[800], + }, }, typography, spacing: layout.spacing, diff --git a/frontends/nextjs/src/theme/modes/light-theme.ts b/frontends/nextjs/src/theme/modes/light-theme.ts index b12d91c1a..dc4b317d2 100644 --- a/frontends/nextjs/src/theme/modes/light-theme.ts +++ b/frontends/nextjs/src/theme/modes/light-theme.ts @@ -7,8 +7,24 @@ import { layout } from '../layout' import { typography } from '../typography' import { getComponentOverrides } from '../components' -const custom = { fonts, borderRadius: layout.borderRadius, contentWidth: layout.contentWidth, sidebar: layout.sidebar, header: layout.header } -const shadows = (o: number): Shadows => ['none', `0 1px 2px rgba(0,0,0,${o/2})`, `0 1px 3px rgba(0,0,0,${o})`, `0 4px 6px rgba(0,0,0,${o})`, `0 10px 15px rgba(0,0,0,${o})`, `0 20px 25px rgba(0,0,0,${o})`, `0 25px 50px rgba(0,0,0,${o*2.5})`, ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o*2.5})`)] as Shadows +const custom = { + fonts, + borderRadius: layout.borderRadius, + contentWidth: layout.contentWidth, + sidebar: layout.sidebar, + header: layout.header, +} +const shadows = (o: number): Shadows => + [ + 'none', + `0 1px 2px rgba(0,0,0,${o / 2})`, + `0 1px 3px rgba(0,0,0,${o})`, + `0 4px 6px rgba(0,0,0,${o})`, + `0 10px 15px rgba(0,0,0,${o})`, + `0 20px 25px rgba(0,0,0,${o})`, + `0 25px 50px rgba(0,0,0,${o * 2.5})`, + ...Array(18).fill(`0 25px 50px rgba(0,0,0,${o * 2.5})`), + ] as Shadows export const lightTheme = createTheme({ palette: { @@ -21,9 +37,19 @@ export const lightTheme = createTheme({ success: colors.success.light, neutral: colors.neutral, background: { default: '#ffffff', paper: colors.neutral[50] }, - text: { primary: colors.neutral[900], secondary: colors.neutral[600], disabled: colors.neutral[400] }, + text: { + primary: colors.neutral[900], + secondary: colors.neutral[600], + disabled: colors.neutral[400], + }, divider: colors.neutral[200], - action: { active: colors.neutral[700], hover: alpha(colors.neutral[500], 0.08), selected: alpha(colors.primary.light.main, 0.12), disabled: colors.neutral[400], disabledBackground: colors.neutral[200] }, + action: { + active: colors.neutral[700], + hover: alpha(colors.neutral[500], 0.08), + selected: alpha(colors.primary.light.main, 0.12), + disabled: colors.neutral[400], + disabledBackground: colors.neutral[200], + }, }, typography, spacing: layout.spacing,