From 83fcf29516f85f00b0b1e42eb45bbb16b20ddf80 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:31:10 +0800 Subject: [PATCH 01/15] feat(theme): add modern theme with light and dark modes - Add .modern and .modern.dark CSS variable blocks in globals.css - Register 'modern' in ThemeProvider themes list - Use semantic color variables based on Tailwind zinc/gray palette - Set modern as default theme --- app/globals.css | 132 +++++++++++++++++++++++++++++++++++++++++ provider/providers.tsx | 4 +- 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/app/globals.css b/app/globals.css index 1efedca..8e5007a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -173,6 +173,138 @@ --tab-hover-background: #3e3e42; } +/* Modern Theme - Light Mode */ +.modern { + /* === Base Colors === */ + --background: #ffffff; + --foreground: #09090b; + + /* === UI Components === */ + --card: #f9fafb; + --card-foreground: #09090b; + --popover: #ffffff; + --popover-foreground: #09090b; + + /* === Brand Colors === */ + --primary: #3b82f6; + --primary-foreground: #ffffff; + --primary-hover: #2563eb; + + --secondary: #f3f4f6; + --secondary-foreground: #64748b; + + --accent: #f3f4f6; + --accent-foreground: #09090b; + + --muted: #f3f4f6; + --muted-foreground: #64748b; + + --destructive: #ef4444; + --destructive-foreground: #ffffff; + + /* === Borders & Inputs === */ + --border: #e5e7eb; + --input: #e5e7eb; + --ring: #3b82f6; + + /* === Sidebar === */ + --sidebar: #f3f4f6; + --sidebar-foreground: #09090b; + --sidebar-background: #f3f4f6; + --sidebar-project-background: #ffffff; + --sidebar-accent: #e5e7eb; + --sidebar-border: #e5e7eb; + + /* === Charts === */ + --chart-1: #3b82f6; + --chart-2: #64748b; + --chart-3: #10b981; + --chart-4: #f59e0b; + --chart-5: #ef4444; + + /* === Content === */ + --content-background: #ffffff; + + /* === Tabs === */ + --tabs-background: #f3f4f6; + --tab-background: #f3f4f6; + --tab-foreground: #64748b; + --tab-active-background: #ffffff; + --tab-active-foreground: #09090b; + --tab-hover-background: #e5e7eb; + + /* === Typography === */ + --font-sans: Inter, sans-serif; + --font-mono: JetBrains Mono, monospace; + --radius: 0.375rem; +} + +/* Modern Theme - Dark Mode */ +.modern.dark { + /* === Base Colors === */ + --background: #09090b; + --foreground: #fafafa; + + /* === UI Components === */ + --card: #18181b; + --card-foreground: #fafafa; + --popover: #18181b; + --popover-foreground: #fafafa; + + /* === Brand Colors === */ + --primary: #3b82f6; + --primary-foreground: #ffffff; + --primary-hover: #60a5fa; + + --secondary: #27272a; + --secondary-foreground: #a1a1aa; + + --accent: #27272a; + --accent-foreground: #fafafa; + + --muted: #27272a; + --muted-foreground: #a1a1aa; + + --destructive: #ef4444; + --destructive-foreground: #ffffff; + + /* === Borders & Inputs === */ + --border: #27272a; + --input: #27272a; + --ring: #3b82f6; + + /* === Sidebar === */ + --sidebar: #18181b; + --sidebar-foreground: #fafafa; + --sidebar-background: #18181b; + --sidebar-project-background: #09090b; + --sidebar-accent: #27272a; + --sidebar-border: #27272a; + + /* === Charts === */ + --chart-1: #3b82f6; + --chart-2: #94a3b8; + --chart-3: #34d399; + --chart-4: #fbbf24; + --chart-5: #f87171; + + /* === Content === */ + --content-background: #09090b; + + /* === Tabs === */ + --tabs-background: #18181b; + --tab-background: #18181b; + --tab-foreground: #a1a1aa; + --tab-active-background: #09090b; + --tab-active-foreground: #ffffff; + --tab-hover-background: #27272a; + + /* === Typography === */ + --font-sans: Inter, sans-serif; + --font-mono: JetBrains Mono, monospace; + --radius: 0.375rem; +} + @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); diff --git a/provider/providers.tsx b/provider/providers.tsx index 84d1818..1ea3e2b 100644 --- a/provider/providers.tsx +++ b/provider/providers.tsx @@ -28,9 +28,9 @@ export function Providers({ children }: { children: React.ReactNode }) { {children} From a7803dc312c9dccdf0d4e4ba461cfdcea2a5cb96 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:10:01 +0800 Subject: [PATCH 02/15] refactor(ui): redesign PrimarySidebar with Material Icons - Install material-icons package and import outlined.css in globals.css - Redesign PrimarySidebar with top/bottom button layout using Material Icons - Remove project list, create project button, and related query logic - Remove props from PrimarySidebar (currentProjectId, userId no longer needed) - Update layout.tsx to use PrimarySidebar without props - Make modern theme default to dark mode, rename light variant to modern-light --- app/(dashboard)/projects/[id]/layout.tsx | 2 +- app/globals.css | 135 ++++++++++++----------- components/sidebars/primary-sidebar.tsx | 107 ++++-------------- package.json | 1 + pnpm-lock.yaml | 8 ++ 5 files changed, 97 insertions(+), 156 deletions(-) diff --git a/app/(dashboard)/projects/[id]/layout.tsx b/app/(dashboard)/projects/[id]/layout.tsx index 5514edd..e862f08 100644 --- a/app/(dashboard)/projects/[id]/layout.tsx +++ b/app/(dashboard)/projects/[id]/layout.tsx @@ -44,7 +44,7 @@ export default async function ProjectLayout({
{/* Primary Sidebar - VSCode style */} - + {/* Secondary Sidebar - Project Settings */} ({ - queryKey: ['projects'], - queryFn: async () => { - const response = await fetch('/api/projects'); - if (!response.ok) { - throw new Error('Failed to fetch projects'); - } - return response.json(); - }, - refetchInterval: 5000, // Poll every 5 seconds - staleTime: 4000, // Data is fresh for 4 seconds - retry: 2, - }); +export default function PrimarySidebar() { const [showSettings, setShowSettings] = useState(false); - const [showCreateProject, setShowCreateProject] = useState(false); return ( -
- {/* Header */} -
- -
- - {/* Navigation Items */} -
- {/* Home Link */} - - + ); } diff --git a/package.json b/package.json index 358918a..4730aad 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "croner": "^9.1.0", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.545.0", + "material-icons": "^1.13.14", "nanoid": "^5.1.6", "next": "16.0.10", "next-auth": "^5.0.0-beta.29", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b1037e..13239a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: lucide-react: specifier: ^0.545.0 version: 0.545.0(react@19.2.1) + material-icons: + specifier: ^1.13.14 + version: 1.13.14 nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -2501,6 +2504,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + material-icons@1.13.14: + resolution: {integrity: sha512-kZOfc7xCC0rAT8Q3DQixYAeT+tBqZnxkseQtp2bxBxz7q5pMAC+wmit7vJn1g/l7wRU+HEPq23gER4iPjGs5Cg==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5673,6 +5679,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + material-icons@1.13.14: {} + math-intrinsics@1.1.0: {} merge2@1.4.1: {} From d9e8a20dfab59063ea9531ca9172eda4b4782daf Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 20 Jan 2026 19:00:16 +0800 Subject: [PATCH 03/15] Refactor PrimarySidebar and update Button styles - Modularize PrimarySidebar into sub-components for better maintainability. - Add English JSDoc documentation to PrimarySidebar. - Update Button component variants for consistent styling. --- components/sidebars/primary-sidebar.tsx | 86 ++++++++++++++++++------- components/ui/button.tsx | 12 ++-- 2 files changed, 69 insertions(+), 29 deletions(-) diff --git a/components/sidebars/primary-sidebar.tsx b/components/sidebars/primary-sidebar.tsx index d13580d..5027b58 100644 --- a/components/sidebars/primary-sidebar.tsx +++ b/components/sidebars/primary-sidebar.tsx @@ -1,43 +1,81 @@ 'use client'; import { useState } from 'react'; +import Image from 'next/image'; import Link from 'next/link'; import SettingsDialog from '@/components/dialog/settings-dialog'; +import { Button } from '@/components/ui/button'; +/** + * PrimarySidebar Component + * + * This is the persistent left-most command center of the application with a fixed width. + * It remains visible across page transitions, providing global access to core functionalities. + * + * Structure: + * 1. Top Section: Contains 'Home' and 'Projects' navigation buttons. + * 2. Bottom Section: Contains 'Settings' button which triggers the settings dialog. + * + * Note: Marked with 'use client' as it manages local state (useState) for the settings dialog. + */ export default function PrimarySidebar() { - const [showSettings, setShowSettings] = useState(false); - return ( + ); +} + +function HomeButton() { + return ( + + ); +} - {/* Settings Dialog */} +function ProjectsButton() { + return ( + + ); +} + +function SettingsButton() { + const [showSettings, setShowSettings] = useState(false); + + return ( + <> + - + ); } diff --git a/components/ui/button.tsx b/components/ui/button.tsx index ca0c4c8..37a7d4b 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -9,15 +9,15 @@ const buttonVariants = cva( { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary-hover", + default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: - "bg-secondary text-secondary-foreground border border-border hover:bg-border", + "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: - "text-muted-foreground hover:bg-border hover:text-foreground", + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { @@ -38,8 +38,8 @@ const buttonVariants = cva( function Button({ className, - variant, - size, + variant = "default", + size = "default", asChild = false, ...props }: React.ComponentProps<"button"> & @@ -51,6 +51,8 @@ function Button({ return ( From 73d35dc6cda7457980bfec4a416af1f6d23810ee Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 20 Jan 2026 19:11:47 +0800 Subject: [PATCH 04/15] Update ProjectSidebar background color to match global sidebar theme --- components/sidebars/project-sidebar-new.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sidebars/project-sidebar-new.tsx b/components/sidebars/project-sidebar-new.tsx index 25652ee..d30e289 100644 --- a/components/sidebars/project-sidebar-new.tsx +++ b/components/sidebars/project-sidebar-new.tsx @@ -68,14 +68,14 @@ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { return (
{/* Toggle Button */} From e6708963b3c632adfe7c7b45b995840d64da75ec Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:40:22 +0800 Subject: [PATCH 05/15] Rename project-sidebar-new to project-sidebar and update imports --- app/(dashboard)/projects/[id]/layout.tsx | 2 +- .../sidebars/{project-sidebar-new.tsx => project-sidebar.tsx} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename components/sidebars/{project-sidebar-new.tsx => project-sidebar.tsx} (100%) diff --git a/app/(dashboard)/projects/[id]/layout.tsx b/app/(dashboard)/projects/[id]/layout.tsx index e862f08..2075527 100644 --- a/app/(dashboard)/projects/[id]/layout.tsx +++ b/app/(dashboard)/projects/[id]/layout.tsx @@ -4,7 +4,7 @@ import { notFound } from 'next/navigation'; import { ProjectContentWrapper } from '@/components/layout/project-content-wrapper'; import { StatusBar } from '@/components/layout/status-bar'; import PrimarySidebar from '@/components/sidebars/primary-sidebar'; -import ProjectSidebar from '@/components/sidebars/project-sidebar-new'; +import ProjectSidebar from '@/components/sidebars/project-sidebar'; import { auth } from '@/lib/auth'; import { prisma } from '@/lib/db'; diff --git a/components/sidebars/project-sidebar-new.tsx b/components/sidebars/project-sidebar.tsx similarity index 100% rename from components/sidebars/project-sidebar-new.tsx rename to components/sidebars/project-sidebar.tsx From da57dfdf68e8b2c6fa46648b7986b9ca5bd968dd Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:44:53 +0800 Subject: [PATCH 06/15] refactor(sidebar): hoist menu config to module level and cleanup unused code - Move WORKSPACE_SECTIONS and CONFIG_SECTIONS outside component - Use for type safety - Add getHref helper function for dynamic URL generation - Remove ChevronsUpDown icon and interactive header styles (not yet implemented) - Clean up unused Lucide icon imports - Switch to Material Icons for menu items - Improve layout spacing and styling consistency --- components/sidebars/project-sidebar.tsx | 139 ++++++++++-------------- 1 file changed, 59 insertions(+), 80 deletions(-) diff --git a/components/sidebars/project-sidebar.tsx b/components/sidebars/project-sidebar.tsx index d30e289..0bc2fbc 100644 --- a/components/sidebars/project-sidebar.tsx +++ b/components/sidebars/project-sidebar.tsx @@ -1,23 +1,27 @@ 'use client'; import { useState } from 'react'; -import { - ChevronLeft, - ChevronRight, - CreditCard, - Database, - Github, - Key, - Package, - Shield, - Terminal, -} from 'lucide-react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useProject } from '@/hooks/use-project'; import { cn } from '@/lib/utils'; +// Static menu configuration - hoisted outside component to avoid recreation on every render +const WORKSPACE_SECTIONS = [ + { id: 'terminal', label: 'Web Terminal', icon: 'terminal' }, + { id: 'database', label: 'Database', icon: 'dns' }, +] as const; + +const CONFIG_SECTIONS = [ + { id: 'environment', label: 'Environment Variables', icon: 'layers' }, + { id: 'secrets', label: 'Secret Configuration', icon: 'vpn_key' }, + { id: 'auth', label: 'Auth Configuration', icon: 'security' }, + { id: 'payment', label: 'Payment Configuration', icon: 'credit_card' }, + { id: 'github', label: 'GitHub Integration', icon: 'code' }, +] as const; + interface ProjectSidebarProps { projectId: string; } @@ -27,43 +31,8 @@ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { const [isCollapsed, setIsCollapsed] = useState(false); const pathname = usePathname(); - const topSections = [ - { - id: 'terminal', - label: 'Web Terminal', - icon: Terminal, - href: `/projects/${projectId}/terminal`, - }, - { id: 'database', label: 'Database', icon: Database, href: `/projects/${projectId}/database` }, - ]; - - const configSections = [ - { - id: 'environment', - label: 'Environment Variables', - icon: Package, - href: `/projects/${projectId}/environment`, - }, - { - id: 'secrets', - label: 'Secret Configuration', - icon: Key, - href: `/projects/${projectId}/secrets`, - }, - { id: 'auth', label: 'Auth Configuration', icon: Shield, href: `/projects/${projectId}/auth` }, - { - id: 'payment', - label: 'Payment Configuration', - icon: CreditCard, - href: `/projects/${projectId}/payment`, - }, - { - id: 'github', - label: 'GitHub Integration', - icon: Github, - href: `/projects/${projectId}/github`, - }, - ]; + // Generate href with projectId + const getHref = (sectionId: string) => `/projects/${projectId}/${sectionId}`; return (
{/* Header */} -
+
{!isCollapsed && ( Project {project?.name ?? 'Loading...'} )}
{!isCollapsed && ( -
+
{/* Top sections */}
-
WORKSPACE
- {topSections.map((section) => { - const Icon = section.icon; - const isActive = pathname === section.href; +
Workspace
+
    + {WORKSPACE_SECTIONS.map((section) => { + const href = getHref(section.id); + const isActive = pathname === href; return ( - - - {section.label} - +
  • + + {section.icon} + {section.label} + +
  • ); })} +
{/* Configuration Group */}
-
CONFIGURATION
- {configSections.map((section) => { - const Icon = section.icon; - const isActive = pathname === section.href; +
Configuration
+
    + {CONFIG_SECTIONS.map((section) => { + const href = getHref(section.id); + const isActive = pathname === href; return ( - - - {section.label} - +
  • + + {section.icon} + {section.label} + +
  • ); })} +
)} From e707c67ccdd8dcef5be84bc0593e5b7ac6892f54 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:51:31 +0800 Subject: [PATCH 07/15] docs(sidebar): add JSDoc documentation for ProjectSidebar component --- components/sidebars/project-sidebar.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/components/sidebars/project-sidebar.tsx b/components/sidebars/project-sidebar.tsx index 0bc2fbc..7b719ce 100644 --- a/components/sidebars/project-sidebar.tsx +++ b/components/sidebars/project-sidebar.tsx @@ -26,6 +26,19 @@ interface ProjectSidebarProps { projectId: string; } +/** + * A collapsible navigation sidebar for project detail pages. + * + * Features: + * - Displays current project name in header + * - Provides navigation links grouped into Workspace and Configuration sections + * - Supports collapse/expand toggle with smooth transition + * - Highlights active route based on current pathname + * + * Navigation Structure: + * - Workspace: Web Terminal, Database + * - Configuration: Environment Variables, Secrets, Auth, Payment, GitHub Integration + */ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { const { data: project } = useProject(projectId); const [isCollapsed, setIsCollapsed] = useState(false); From b2107e9b401c6f8178ce70c95ba7c7b95df3cee8 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:39:11 +0800 Subject: [PATCH 08/15] refactor(terminal): extract components and migrate to modern theme - Extract NetworkDialog into standalone component - Extract TerminalTabs into standalone component - Migrate hardcoded colors to modern theme variables - Update toolbar background to use sidebar-background - Update Run App and Network buttons with theme colors - Update terminal tabs with zinc-800/sidebar-background colors - Update TerminalDisplay and XtermTerminal to modern theme palette - Add blue-500 accents for active states and icons --- components/terminal/terminal-container.tsx | 3 +- components/terminal/terminal-display.tsx | 50 ++--- components/terminal/toolbar/app-runner.tsx | 4 +- .../terminal/toolbar/network-dialog.tsx | 157 +++++++++++++++ components/terminal/toolbar/terminal-tabs.tsx | 85 ++++++++ components/terminal/toolbar/toolbar.tsx | 190 ++---------------- 6 files changed, 291 insertions(+), 198 deletions(-) create mode 100644 components/terminal/toolbar/network-dialog.tsx create mode 100644 components/terminal/toolbar/terminal-tabs.tsx diff --git a/components/terminal/terminal-container.tsx b/components/terminal/terminal-container.tsx index e6884c5..f88588f 100644 --- a/components/terminal/terminal-container.tsx +++ b/components/terminal/terminal-container.tsx @@ -16,7 +16,8 @@ import { useState } from 'react'; import type { Prisma } from '@prisma/client'; -import { type Tab, TerminalToolbar } from './toolbar/toolbar'; +import { type Tab } from './toolbar/terminal-tabs'; +import { TerminalToolbar } from './toolbar/toolbar'; import { TerminalDisplay } from './terminal-display'; // ============================================================================ diff --git a/components/terminal/terminal-display.tsx b/components/terminal/terminal-display.tsx index d93561d..de8071f 100644 --- a/components/terminal/terminal-display.tsx +++ b/components/terminal/terminal-display.tsx @@ -102,13 +102,13 @@ export function TerminalDisplay({ const showErrorIndicator = connectionStatus === 'error' && terminalReady; return ( -
+
{/* Loading Overlay */} {isLoading && ( -
+
- - + + {!terminalReady ? 'Initializing terminal...' : 'Establishing connection...'}
@@ -127,25 +127,25 @@ export function TerminalDisplay({ wsUrl={ttydUrl} sandboxId={sandboxId} theme={{ - foreground: '#d2d2d2', - background: '#1e1e1e', - cursor: '#adadad', - black: '#000000', - red: '#d81e00', - green: '#5ea702', - yellow: '#cfae00', - blue: '#427ab3', - magenta: '#89658e', - cyan: '#00a7aa', - white: '#dbded8', - brightBlack: '#686a66', - brightRed: '#f54235', - brightGreen: '#99e343', - brightYellow: '#fdeb61', - brightBlue: '#84b0d8', - brightMagenta: '#bc94b7', - brightCyan: '#37e6e8', - brightWhite: '#f1f1f0', + foreground: '#fafafa', + background: '#09090b', + cursor: '#fafafa', + black: '#09090b', + red: '#ef4444', + green: '#22c55e', + yellow: '#eab308', + blue: '#3b82f6', + magenta: '#a855f7', + cyan: '#06b6d4', + white: '#fafafa', + brightBlack: '#71717a', + brightRed: '#f87171', + brightGreen: '#4ade80', + brightYellow: '#facc15', + brightBlue: '#60a5fa', + brightMagenta: '#c084fc', + brightCyan: '#22d3ee', + brightWhite: '#ffffff', }} fontSize={14} fontFamily="Consolas, Liberation Mono, Menlo, Courier, monospace" @@ -191,10 +191,10 @@ export function TerminalDisplay({ : TerminalIcon; return ( -
+
- {getStatusMessage(status)} + {getStatusMessage(status)}
); diff --git a/components/terminal/toolbar/app-runner.tsx b/components/terminal/toolbar/app-runner.tsx index 5d06726..4a327b2 100644 --- a/components/terminal/toolbar/app-runner.tsx +++ b/components/terminal/toolbar/app-runner.tsx @@ -61,7 +61,7 @@ export function AppRunner({ sandbox }: AppRunnerProps) { 'px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 disabled:cursor-not-allowed', isAppRunning ? 'text-green-400 hover:text-red-400 hover:bg-red-400/10 bg-green-400/10' - : 'text-gray-300 hover:text-white hover:bg-[#37373d] disabled:opacity-50' + : 'text-foreground font-semibold hover:text-white hover:bg-zinc-800 disabled:opacity-50' )} title={ isAppRunning @@ -74,7 +74,7 @@ export function AppRunner({ sandbox }: AppRunnerProps) { ) : isAppRunning ? ( ) : ( - + )} {isStartingApp ? 'Starting...' : isStoppingApp ? 'Stopping...' : isAppRunning ? 'Running' : 'Run App'} diff --git a/components/terminal/toolbar/network-dialog.tsx b/components/terminal/toolbar/network-dialog.tsx new file mode 100644 index 0000000..77804da --- /dev/null +++ b/components/terminal/toolbar/network-dialog.tsx @@ -0,0 +1,157 @@ +'use client'; + +import { useState } from 'react'; +import { Copy, Eye, EyeOff } from 'lucide-react'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; + +// ============================================================================ +// Types +// ============================================================================ + +export interface NetworkEndpoint { + domain: string | null | undefined; + port: number; + protocol: string; + label: string; + hasCredentials?: boolean; +} + +export interface NetworkDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + endpoints: NetworkEndpoint[]; + fileBrowserCredentials?: { + username: string; + password: string; + }; +} + +// ============================================================================ +// Component +// ============================================================================ + +export function NetworkDialog({ + open, + onOpenChange, + endpoints, + fileBrowserCredentials, +}: NetworkDialogProps) { + const [showPassword, setShowPassword] = useState(false); + const [copiedField, setCopiedField] = useState(null); + + const copyToClipboard = async (text: string, field: string) => { + try { + await navigator.clipboard.writeText(text); + setCopiedField(field); + setTimeout(() => setCopiedField(null), 2000); + } catch (err) { + console.error('Failed to copy:', err); + } + }; + + return ( + + + + Network Endpoints + + All publicly accessible endpoints for this sandbox + + +
+ {endpoints.map((endpoint, index) => ( +
+
+
+ Port {endpoint.port} + + {endpoint.label} + +
+ {endpoint.protocol} +
+ + {endpoint.domain} + + + {/* Show credentials for File Browser */} + {endpoint.hasCredentials && fileBrowserCredentials && ( +
+
Login Credentials:
+ + {/* Username */} +
+
+
Username
+ + {fileBrowserCredentials.username} + +
+ +
+ + {/* Password */} +
+
+
Password
+ + {showPassword ? fileBrowserCredentials.password : '••••••••••••••••'} + +
+ + +
+
+ )} +
+ ))} +
+
+
+ ); +} diff --git a/components/terminal/toolbar/terminal-tabs.tsx b/components/terminal/toolbar/terminal-tabs.tsx new file mode 100644 index 0000000..2f786c8 --- /dev/null +++ b/components/terminal/toolbar/terminal-tabs.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { Plus, Terminal as TerminalIcon, X } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +// ============================================================================ +// Types +// ============================================================================ + +export interface Tab { + id: string; + name: string; +} + +export interface TerminalTabsProps { + tabs: Tab[]; + activeTabId: string; + onTabSelect: (tabId: string) => void; + onTabClose: (tabId: string) => void; + onTabAdd: () => void; +} + +// ============================================================================ +// Component +// ============================================================================ + +export function TerminalTabs({ + tabs, + activeTabId, + onTabSelect, + onTabClose, + onTabAdd, +}: TerminalTabsProps) { + return ( +
+ {tabs.map((tab) => ( +
onTabSelect(tab.id)} + > + {/* Top Accent Line for Active Tab */} + {activeTabId === tab.id && ( +
+ )} + + + {tab.name} + {tabs.length > 1 && ( + + )} +
+ ))} + +
+ ); +} diff --git a/components/terminal/toolbar/toolbar.tsx b/components/terminal/toolbar/toolbar.tsx index 23523fb..e88403e 100644 --- a/components/terminal/toolbar/toolbar.tsx +++ b/components/terminal/toolbar/toolbar.tsx @@ -6,20 +6,13 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import type { Prisma } from '@prisma/client'; -import { Copy, Eye, EyeOff, Network, Plus, Terminal as TerminalIcon, X } from 'lucide-react'; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { cn } from '@/lib/utils'; +import { Network } from 'lucide-react'; import { AppRunner } from './app-runner'; +import { NetworkDialog } from './network-dialog'; +import { TerminalTabs, type Tab } from './terminal-tabs'; type Project = Prisma.ProjectGetPayload<{ include: { @@ -30,11 +23,6 @@ type Project = Prisma.ProjectGetPayload<{ type Sandbox = Prisma.SandboxGetPayload; -export interface Tab { - id: string; - name: string; -} - export interface TerminalToolbarProps { /** Project data */ project: Project; @@ -70,8 +58,6 @@ export function TerminalToolbar({ fileBrowserCredentials, }: TerminalToolbarProps) { const [showNetworkDialog, setShowNetworkDialog] = useState(false); - const [showPassword, setShowPassword] = useState(false); - const [copiedField, setCopiedField] = useState(null); // Build network endpoints list, filtering out any without URLs const allEndpoints = [ @@ -89,63 +75,17 @@ export function TerminalToolbar({ // Only show endpoints that have a valid domain URL const networkEndpoints = allEndpoints.filter((endpoint) => endpoint.domain); - const copyToClipboard = async (text: string, field: string) => { - try { - await navigator.clipboard.writeText(text); - setCopiedField(field); - setTimeout(() => setCopiedField(null), 2000); - } catch (err) { - console.error('Failed to copy:', err); - } - }; - return ( <> -
+
{/* Terminal Tabs */} -
- {tabs.map((tab) => ( -
onTabSelect(tab.id)} - > - {/* Top Accent Line for Active Tab */} - {activeTabId === tab.id && ( -
- )} - - - {tab.name} - {tabs.length > 1 && ( - - )} -
- ))} - -
+ {/* Action Buttons */}
@@ -154,112 +94,22 @@ export function TerminalToolbar({ {/* Network Button */}
{/* Network Dialog */} - - - - Network Endpoints - - All publicly accessible endpoints for this sandbox - - -
- {networkEndpoints.map((endpoint, index) => ( -
-
-
- Port {endpoint.port} - - {endpoint.label} - -
- {endpoint.protocol} -
- - {endpoint.domain} - - - {/* Show credentials for File Browser */} - {endpoint.hasCredentials && fileBrowserCredentials && ( -
-
Login Credentials:
- - {/* Username */} -
-
-
Username
- - {fileBrowserCredentials.username} - -
- -
- - {/* Password */} -
-
-
Password
- - {showPassword ? fileBrowserCredentials.password : '••••••••••••••••'} - -
- - -
-
- )} -
- ))} -
-
-
+ ); } From 2f0f4ca499f3cb1dc91ba6f622072691adfeae40 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Wed, 21 Jan 2026 18:27:11 +0800 Subject: [PATCH 09/15] refactor(layout): move StatusBar to terminal panel and update styling - Move StatusBar from layout to terminal panel (only visible on terminal page) - Update StatusBar background to sidebar-background - Replace Box/Database icons with circular status indicators - Add subtle radial glow effect to status dots - Replace GitHub icon with RefreshCw in RepoStatusIndicator - Apply blue-500 color to sync button icon and text - Fix font size consistency (remove text-sm, inherit text-xs) - Resize icons to 12px for consistency with text - Update terminal container to use flex-1 layout for proper StatusBar positioning --- app/(dashboard)/projects/[id]/layout.tsx | 3 --- .../layout/project-content-wrapper.module.css | 5 +++-- components/layout/project-content-wrapper.tsx | 14 +++++++++----- components/layout/repo-status-indicator.tsx | 10 +++++----- components/layout/status-bar.tsx | 13 ++++++------- components/terminal/terminal-container.tsx | 4 ++-- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/app/(dashboard)/projects/[id]/layout.tsx b/app/(dashboard)/projects/[id]/layout.tsx index 2075527..855bbd7 100644 --- a/app/(dashboard)/projects/[id]/layout.tsx +++ b/app/(dashboard)/projects/[id]/layout.tsx @@ -2,7 +2,6 @@ import { redirect } from 'next/navigation'; import { notFound } from 'next/navigation'; import { ProjectContentWrapper } from '@/components/layout/project-content-wrapper'; -import { StatusBar } from '@/components/layout/status-bar'; import PrimarySidebar from '@/components/sidebars/primary-sidebar'; import ProjectSidebar from '@/components/sidebars/project-sidebar'; import { auth } from '@/lib/auth'; @@ -58,8 +57,6 @@ export default async function ProjectLayout({
- - ); } diff --git a/components/layout/project-content-wrapper.module.css b/components/layout/project-content-wrapper.module.css index 95f0aa6..d5e749b 100644 --- a/components/layout/project-content-wrapper.module.css +++ b/components/layout/project-content-wrapper.module.css @@ -22,9 +22,10 @@ content-visibility: hidden; /* Modern CSS optimization */ } -/* Terminal panel - block layout */ +/* Terminal panel - flex column layout for terminal + status bar */ .terminalPanel[data-visible='true'] { - display: block; + display: flex; + flex-direction: column; content-visibility: auto; } diff --git a/components/layout/project-content-wrapper.tsx b/components/layout/project-content-wrapper.tsx index 29a4a59..9b4c9ac 100644 --- a/components/layout/project-content-wrapper.tsx +++ b/components/layout/project-content-wrapper.tsx @@ -23,6 +23,7 @@ import { usePathname } from 'next/navigation'; import { TerminalContainer } from '@/components/terminal/terminal-container'; import { useProject } from '@/hooks/use-project'; +import { StatusBar } from './status-bar'; import styles from './project-content-wrapper.module.css'; // ============================================================================ @@ -63,11 +64,14 @@ export function ProjectContentWrapper({ aria-label="Terminal Console" > {project && ( - + <> + + + )} diff --git a/components/layout/repo-status-indicator.tsx b/components/layout/repo-status-indicator.tsx index d751713..1e5b814 100644 --- a/components/layout/repo-status-indicator.tsx +++ b/components/layout/repo-status-indicator.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { Project } from '@prisma/client' -import { Github, Loader2, RefreshCw } from 'lucide-react' +import { Loader2, RefreshCw } from 'lucide-react' import { useRouter } from 'next/navigation' import { toast } from 'sonner' @@ -77,9 +77,9 @@ export function RepoStatusIndicator({ project }: RepoStatusIndicatorProps) {
{(!project.githubRepo && isInitializing) ? ( - + ) : ( - + )}
@@ -88,13 +88,13 @@ export function RepoStatusIndicator({ project }: RepoStatusIndicatorProps) { href={project.githubRepo} target="_blank" rel="noopener noreferrer" - className="hover:underline cursor-pointer text-sm" + className="hover:underline cursor-pointer" > {project.name} ) : ( ); @@ -73,7 +74,7 @@ function SettingsButton() { onClick={() => setShowSettings(true)} className="group hover:bg-transparent" > - settings + diff --git a/components/sidebars/project-sidebar.tsx b/components/sidebars/project-sidebar.tsx index 7b719ce..3b8f920 100644 --- a/components/sidebars/project-sidebar.tsx +++ b/components/sidebars/project-sidebar.tsx @@ -4,10 +4,31 @@ import { useState } from 'react'; import { ChevronLeft, ChevronRight } from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import { + MdOutlineTerminal, + MdOutlineDns, + MdOutlineLayers, + MdOutlineVpnKey, + MdOutlineSecurity, + MdOutlineCreditCard, + MdOutlineCode, +} from 'react-icons/md'; +import { IconType } from 'react-icons'; import { useProject } from '@/hooks/use-project'; import { cn } from '@/lib/utils'; +// Icon mapping for dynamic icon rendering +const ICON_MAP: Record = { + terminal: MdOutlineTerminal, + dns: MdOutlineDns, + layers: MdOutlineLayers, + vpn_key: MdOutlineVpnKey, + security: MdOutlineSecurity, + credit_card: MdOutlineCreditCard, + code: MdOutlineCode, +}; + // Static menu configuration - hoisted outside component to avoid recreation on every render const WORKSPACE_SECTIONS = [ { id: 'terminal', label: 'Web Terminal', icon: 'terminal' }, @@ -78,6 +99,7 @@ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { {WORKSPACE_SECTIONS.map((section) => { const href = getHref(section.id); const isActive = pathname === href; + const IconComponent = ICON_MAP[section.icon]; return (
  • @@ -90,7 +112,7 @@ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { : 'text-muted-foreground hover:bg-accent hover:text-foreground' )} > - {section.icon} + {section.label}
  • @@ -106,6 +128,7 @@ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { {CONFIG_SECTIONS.map((section) => { const href = getHref(section.id); const isActive = pathname === href; + const IconComponent = ICON_MAP[section.icon]; return (
  • @@ -118,7 +141,7 @@ export default function ProjectSidebar({ projectId }: ProjectSidebarProps) { : 'text-muted-foreground hover:bg-accent hover:text-foreground' )} > - {section.icon} + {section.label}
  • diff --git a/package.json b/package.json index 4730aad..6b32522 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "croner": "^9.1.0", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.545.0", - "material-icons": "^1.13.14", "nanoid": "^5.1.6", "next": "16.0.10", "next-auth": "^5.0.0-beta.29", @@ -48,6 +47,7 @@ "prisma": "^6.17.1", "react": "19.2.1", "react-dom": "19.2.1", + "react-icons": "^5.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "tus-js-client": "^4.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13239a9..bc381c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,9 +95,6 @@ importers: lucide-react: specifier: ^0.545.0 version: 0.545.0(react@19.2.1) - material-icons: - specifier: ^1.13.14 - version: 1.13.14 nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -122,6 +119,9 @@ importers: react-dom: specifier: 19.2.1 version: 19.2.1(react@19.2.1) + react-icons: + specifier: ^5.5.0 + version: 5.5.0(react@19.2.1) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -2504,9 +2504,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - material-icons@1.13.14: - resolution: {integrity: sha512-kZOfc7xCC0rAT8Q3DQixYAeT+tBqZnxkseQtp2bxBxz7q5pMAC+wmit7vJn1g/l7wRU+HEPq23gER4iPjGs5Cg==} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2808,6 +2805,11 @@ packages: peerDependencies: react: ^19.2.1 + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -5679,8 +5681,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - material-icons@1.13.14: {} - math-intrinsics@1.1.0: {} merge2@1.4.1: {} @@ -5966,6 +5966,10 @@ snapshots: react: 19.2.1 scheduler: 0.27.0 + react-icons@5.5.0(react@19.2.1): + dependencies: + react: 19.2.1 + react-is@16.13.1: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.1): From 5ebc41d39a4e19d148cc69f163cf89cfaf142164 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:12:10 +0800 Subject: [PATCH 11/15] refactor: migrate settings pages to SettingsLayout component - Create new SettingsLayout component in route-specific _components folder - Migrate all 6 settings pages: auth, database, environment, github, payment, secrets - Replace hardcoded VSCode colors with theme variables (foreground, muted-foreground, card, border, primary) - Remove redundant nested overflow and flex-1 declarations - Follow Next.js co-location best practice for route-specific components --- .../[id]/_components/settings-layout.tsx | 33 ++-- app/(dashboard)/projects/[id]/auth/page.tsx | 34 ++-- .../projects/[id]/database/page.tsx | 150 +++++++++--------- .../projects/[id]/environment/page.tsx | 14 +- app/(dashboard)/projects/[id]/github/page.tsx | 9 +- .../projects/[id]/payment/page.tsx | 30 ++-- .../projects/[id]/secrets/page.tsx | 14 +- 7 files changed, 137 insertions(+), 147 deletions(-) rename components/config/config-layout.tsx => app/(dashboard)/projects/[id]/_components/settings-layout.tsx (54%) diff --git a/components/config/config-layout.tsx b/app/(dashboard)/projects/[id]/_components/settings-layout.tsx similarity index 54% rename from components/config/config-layout.tsx rename to app/(dashboard)/projects/[id]/_components/settings-layout.tsx index 6e45792..cda4e2f 100644 --- a/components/config/config-layout.tsx +++ b/app/(dashboard)/projects/[id]/_components/settings-layout.tsx @@ -1,6 +1,7 @@ /** - * Shared configuration page layout - * VSCode Dark Modern style with clean line-based design + * Settings page layout component + * Used for project settings pages (database, environments, etc.) + * VSCode Dark Modern style with clean design */ 'use client'; @@ -9,8 +10,7 @@ import type { ReactNode } from 'react'; import { Spinner } from '@/components/ui/spinner'; - -interface ConfigLayoutProps { +interface SettingsLayoutProps { /** Page title */ title: string; /** Page description */ @@ -22,15 +22,15 @@ interface ConfigLayoutProps { } /** - * Configuration page layout with VSCode Dark Modern styling + * Layout wrapper for project settings pages */ -export function ConfigLayout({ title, description, children, loading }: ConfigLayoutProps) { +export function SettingsLayout({ title, description, children, loading }: SettingsLayoutProps) { if (loading) { return (
    - Loading configuration... + Loading...
    ); @@ -39,20 +39,17 @@ export function ConfigLayout({ title, description, children, loading }: ConfigLa return (
    - {/* Header - Clean VSCode style */} -
    -
    -

    {title}

    -

    {description}

    -
    -
    - - {/* Content - Scrollable area */} -
    + {/* Page Header */} +
    +

    {title}

    +

    {description}

    +
    + + {/* Page Content */} +
    {children}
    - ); } diff --git a/app/(dashboard)/projects/[id]/auth/page.tsx b/app/(dashboard)/projects/[id]/auth/page.tsx index 984930a..7ab31ca 100644 --- a/app/(dashboard)/projects/[id]/auth/page.tsx +++ b/app/(dashboard)/projects/[id]/auth/page.tsx @@ -1,7 +1,6 @@ /** * Authentication Configuration Page * Configure OAuth providers and NextAuth settings - * VSCode Dark Modern style */ 'use client'; @@ -10,7 +9,6 @@ import { ExternalLink, Key } from 'lucide-react'; import { Github } from 'lucide-react'; import { useParams } from 'next/navigation'; -import { ConfigLayout } from '@/components/config/config-layout'; import { EnvVarSection } from '@/components/config/env-var-section'; import { useBatchUpdateEnvironmentVariables, @@ -18,6 +16,8 @@ import { } from '@/hooks/use-environment-variables'; import { useProject } from '@/hooks/use-project'; +import { SettingsLayout } from '../_components/settings-layout'; + /** * Generate a secure random secret */ @@ -87,24 +87,24 @@ function AuthPageContent() { ]; return ( - -
    +
    {/* GitHub OAuth Section */}
    - -

    GitHub OAuth

    + +

    GitHub OAuth

    GitHub Developer Settings @@ -121,9 +121,9 @@ function AuthPageContent() { /> {/* Setup Instructions */} -
    -

    Setup Instructions

    -
      +
      +

      Setup Instructions

      +
      1. Go to GitHub Settings → Developer settings → OAuth Apps
      2. Click "New OAuth App" or select an existing app
      3. Set the Homepage URL and Authorization callback URL
      4. @@ -134,13 +134,13 @@ function AuthPageContent() {
      {/* Divider */} -
      +
      {/* NextAuth Configuration Section */}
      - -

      NextAuth Configuration

      + +

      NextAuth Configuration

      {/* Important Notes */} -
      -

      Important Notes

      -
        +
        +

        Important Notes

        +
        • The NextAuth URL must match your application URL exactly
        • The secret should be at least 32 characters long
        • Never commit your NEXTAUTH_SECRET to version control
        • @@ -164,7 +164,7 @@ function AuthPageContent() {
      - + ); } diff --git a/app/(dashboard)/projects/[id]/database/page.tsx b/app/(dashboard)/projects/[id]/database/page.tsx index 1485e8e..b9b3f34 100644 --- a/app/(dashboard)/projects/[id]/database/page.tsx +++ b/app/(dashboard)/projects/[id]/database/page.tsx @@ -1,16 +1,15 @@ /** * Database Information Page * Display-only page showing database connection details - * VSCode Dark Modern style */ import { Info } from 'lucide-react'; import { notFound, redirect } from 'next/navigation'; -import { ConfigLayout } from '@/components/config/config-layout'; import { auth } from '@/lib/auth'; import { prisma } from '@/lib/db'; +import { SettingsLayout } from '../_components/settings-layout'; import { ConnectionString } from './connection-string'; export default async function DatabasePage({ params }: { params: Promise<{ id: string }> }) { @@ -48,85 +47,78 @@ export default async function DatabasePage({ params }: { params: Promise<{ id: s } return ( - -
      - {/* Content */} -
      -
      - {connectionString ? ( - <> - {/* Connection Details */} -
      -

      PostgreSQL Connection

      - - {/* Host */} -
      - -
      - {host} -
      -
      - - {/* Port */} -
      - -
      - {port} -
      -
      - - {/* Database Name */} -
      - -
      - {dbName} -
      -
      - - {/* Username */} -
      - -
      - {username} -
      -
      - - {/* Password */} -
      - -
      - {'•'.repeat(Math.min(password.length, 20))} -
      -
      - - {/* Full Connection String */} - -
      - - {/* Info Panel */} -
      -
      - -
      -

      • Database is automatically provisioned with your sandbox

      -

      • Managed by KubeBlocks with high availability

      -

      • SSL encryption enabled by default

      -

      • Connection string available via DATABASE_URL environment variable

      -
      -
      -
      - - ) : ( -
      -

      No database configured

      -

      - Database will be automatically provisioned when sandbox is created -

      + + {connectionString ? ( + <> + {/* Connection Details */} +
      +

      PostgreSQL Connection

      + + {/* Host */} +
      + +
      + {host}
      - )} +
      + + {/* Port */} +
      + +
      + {port} +
      +
      + + {/* Database Name */} +
      + +
      + {dbName} +
      +
      + + {/* Username */} +
      + +
      + {username} +
      +
      + + {/* Password */} +
      + +
      + {'•'.repeat(Math.min(password.length, 20))} +
      +
      + + {/* Full Connection String */} + +
      + + {/* Info Panel */} +
      +
      + +
      +

      • Database is automatically provisioned with your sandbox

      +

      • Managed by KubeBlocks with high availability

      +

      • SSL encryption enabled by default

      +

      • Connection string available via DATABASE_URL environment variable

      +
      +
      + + ) : ( +
      +

      No database configured

      +

      + Database will be automatically provisioned when sandbox is created +

      -
      - + )} + ); } diff --git a/app/(dashboard)/projects/[id]/environment/page.tsx b/app/(dashboard)/projects/[id]/environment/page.tsx index acc8284..2dd5515 100644 --- a/app/(dashboard)/projects/[id]/environment/page.tsx +++ b/app/(dashboard)/projects/[id]/environment/page.tsx @@ -1,14 +1,12 @@ /** * Environment Variables Configuration Page * Configure custom environment variables - * VSCode Dark Modern style */ 'use client'; import { useParams } from 'next/navigation'; -import { ConfigLayout } from '@/components/config/config-layout'; import { EnvVarSection } from '@/components/config/env-var-section'; import { useBatchUpdateEnvironmentVariables, @@ -16,6 +14,8 @@ import { } from '@/hooks/use-environment-variables'; import { useProject } from '@/hooks/use-project'; +import { SettingsLayout } from '../_components/settings-layout'; + function EnvironmentPageContent() { const params = useParams(); const projectId = params.id as string; @@ -36,7 +36,7 @@ function EnvironmentPageContent() { }; return ( - {/* Usage Information */} -
      -

      Environment Variable Usage

      -
        +
        +

        Environment Variable Usage

        +
        • Environment variables are available in your application via process.env
        • Changes require an application restart to take effect
        • For authentication providers, use the Authentication page
        • @@ -65,7 +65,7 @@ function EnvironmentPageContent() {
      -
      + ); } diff --git a/app/(dashboard)/projects/[id]/github/page.tsx b/app/(dashboard)/projects/[id]/github/page.tsx index d3fb898..25b7de2 100644 --- a/app/(dashboard)/projects/[id]/github/page.tsx +++ b/app/(dashboard)/projects/[id]/github/page.tsx @@ -5,12 +5,13 @@ import { ExternalLink, Github, Loader2, RefreshCw } from 'lucide-react'; import { useParams, useRouter } from 'next/navigation'; import { toast } from 'sonner'; -import { ConfigLayout } from '@/components/config/config-layout'; import SettingsDialog from '@/components/dialog/settings-dialog'; import { Button } from '@/components/ui/button'; import { useProject } from '@/hooks/use-project'; import { commitChanges, initializeRepo } from '@/lib/services/repoService'; +import { SettingsLayout } from '../_components/settings-layout'; + export default function GithubPage() { const params = useParams(); const projectId = params.id as string; @@ -72,14 +73,14 @@ export default function GithubPage() { }; return ( -
      {/* Connection Status Section */} -
      +
      {/* Visual Header */}
      @@ -179,6 +180,6 @@ export default function GithubPage() { defaultTab="github" />
      - + ); } diff --git a/app/(dashboard)/projects/[id]/payment/page.tsx b/app/(dashboard)/projects/[id]/payment/page.tsx index 723f54d..381dad9 100644 --- a/app/(dashboard)/projects/[id]/payment/page.tsx +++ b/app/(dashboard)/projects/[id]/payment/page.tsx @@ -1,7 +1,6 @@ /** * Payment Configuration Page * Configure payment providers (Stripe, PayPal) - * VSCode Dark Modern style */ 'use client'; @@ -9,7 +8,6 @@ import { ExternalLink } from 'lucide-react'; import { useParams } from 'next/navigation'; -import { ConfigLayout } from '@/components/config/config-layout'; import { EnvVarSection } from '@/components/config/env-var-section'; import { useBatchUpdateEnvironmentVariables, @@ -17,6 +15,8 @@ import { } from '@/hooks/use-environment-variables'; import { useProject } from '@/hooks/use-project'; +import { SettingsLayout } from '../_components/settings-layout'; + function PaymentPageContent() { const params = useParams(); const projectId = params.id as string; @@ -80,7 +80,7 @@ function PaymentPageContent() { ]; return ( -
      Stripe -

      Stripe

      +

      Stripe

      Stripe Dashboard @@ -114,9 +114,9 @@ function PaymentPageContent() { /> {/* Setup Instructions */} -
      -

      Setup Instructions

      -
        +
        +

        Setup Instructions

        +
        1. Go to Stripe Dashboard → Developers → API keys
        2. Copy the Publishable key and Secret key
        3. For webhooks: Developers → Webhooks → Add endpoint
        4. @@ -126,20 +126,20 @@ function PaymentPageContent() {
        {/* Divider */} -
        +
        {/* PayPal Section */}
        - + ); } diff --git a/app/(dashboard)/projects/[id]/secrets/page.tsx b/app/(dashboard)/projects/[id]/secrets/page.tsx index b769a6f..e1ee3b4 100644 --- a/app/(dashboard)/projects/[id]/secrets/page.tsx +++ b/app/(dashboard)/projects/[id]/secrets/page.tsx @@ -1,14 +1,12 @@ /** * Secrets Configuration Page * Manage sensitive environment variables and API keys - * VSCode Dark Modern style */ 'use client'; import { useParams } from 'next/navigation'; -import { ConfigLayout } from '@/components/config/config-layout'; import { EnvVarSection } from '@/components/config/env-var-section'; import { useBatchUpdateEnvironmentVariables, @@ -16,6 +14,8 @@ import { } from '@/hooks/use-environment-variables'; import { useProject } from '@/hooks/use-project'; +import { SettingsLayout } from '../_components/settings-layout'; + function SecretsPageContent() { const params = useParams(); const projectId = params.id as string; @@ -36,7 +36,7 @@ function SecretsPageContent() { }; return ( - {/* Security Notice */} -
        -

        Security Best Practices

        -
          +
          +

          Security Best Practices

          +
          • All secret values are masked by default for security
          • Never commit secrets to Git
          • Rotate secrets regularly to maintain security
          • @@ -65,7 +65,7 @@ function SecretsPageContent() {
        -
        + ); } From 7e23b916f88636a1751ed4b130242109689b38b7 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:53:24 +0800 Subject: [PATCH 12/15] fix: increase project creation timeout and update ingress domain logic - Increase Prisma transaction timeout to 20s in project creation API - Update getIngressDomain to replace 'sealos.io' with 'sealos.app' --- app/api/projects/route.ts | 2 ++ lib/k8s/kubernetes-utils.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts index a43e31e..48f677e 100644 --- a/app/api/projects/route.ts +++ b/app/api/projects/route.ts @@ -287,6 +287,8 @@ export const POST = withAuth(async (req, _context, session) fileBrowserUsernameEnv, fileBrowserPasswordEnv, } + }, { + timeout: 20000, }) logger.info( diff --git a/lib/k8s/kubernetes-utils.ts b/lib/k8s/kubernetes-utils.ts index e1a0fbe..eca918f 100644 --- a/lib/k8s/kubernetes-utils.ts +++ b/lib/k8s/kubernetes-utils.ts @@ -42,6 +42,11 @@ export class KubernetesUtils { const url = new URL(cluster.server) const hostname = url.hostname + // If domain ends with sealos.io, replace it with sealos.app + if (hostname.endsWith('sealos.io')) { + return hostname.replace('sealos.io', 'sealos.app') + } + return hostname } From 7dee87bbe6dd036dc551b0b77de1d80791a7e65e Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:56:39 +0800 Subject: [PATCH 13/15] refactor(layout): improve SettingsLayout with skeleton loading pattern - Replace centered spinner with inline skeleton screens - Increase max-width from 4xl to 7xl for better content space - Adjust padding to px-8 py-10 pb-20 for better proportions - Increase content spacing from space-y-6 to space-y-8 - Remove redundant JSDoc and JSX comments - Maintain page header visibility during loading state Benefits: - Layout stability during loading (no content jump) - Better user context (header always visible) - Cleaner code following Clean Code principles Co-Authored-By: Claude Sonnet 4.5 --- .../[id]/_components/settings-layout.tsx | 40 ++++++++----------- .../projects/[id]/database/page.tsx | 5 --- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/app/(dashboard)/projects/[id]/_components/settings-layout.tsx b/app/(dashboard)/projects/[id]/_components/settings-layout.tsx index cda4e2f..aca701c 100644 --- a/app/(dashboard)/projects/[id]/_components/settings-layout.tsx +++ b/app/(dashboard)/projects/[id]/_components/settings-layout.tsx @@ -8,46 +8,38 @@ import type { ReactNode } from 'react'; -import { Spinner } from '@/components/ui/spinner'; +import { Skeleton } from '@/components/ui/skeleton'; interface SettingsLayoutProps { - /** Page title */ title: string; - /** Page description */ description: string; - /** Main content */ children: ReactNode; - /** Loading state */ loading?: boolean; } /** * Layout wrapper for project settings pages + * Uses skeleton to maintain layout stability during loading */ export function SettingsLayout({ title, description, children, loading }: SettingsLayoutProps) { - if (loading) { - return ( -
        -
        - - Loading... -
        -
        - ); - } - return ( -
        -
        - {/* Page Header */} +
        +
        -

        {title}

        -

        {description}

        +

        {title}

        +

        {description}

        - {/* Page Content */} -
        - {children} +
        + {loading ? ( + <> + + + + + ) : ( + children + )}
        diff --git a/app/(dashboard)/projects/[id]/database/page.tsx b/app/(dashboard)/projects/[id]/database/page.tsx index b9b3f34..b9d99f8 100644 --- a/app/(dashboard)/projects/[id]/database/page.tsx +++ b/app/(dashboard)/projects/[id]/database/page.tsx @@ -1,8 +1,3 @@ -/** - * Database Information Page - * Display-only page showing database connection details - */ - import { Info } from 'lucide-react'; import { notFound, redirect } from 'next/navigation'; From dead9f0657ffb90f396e3f7d6a81dbd508c5608a Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:56:17 +0800 Subject: [PATCH 14/15] refactor(database): introduce data layer and componentize database page - Add lib/data/project.ts with cached getProject() using React.cache() - Add lib/data/database.ts with parseConnectionUrl() utility - Extract ReadOnlyField, FeatureCards, ConnectionString components - Replace Lucide icons with Material Icons for consistency - Optimize with parallel session/params fetching via Promise.all() --- .../_components/connection-string.tsx | 94 +++++++++++++++ .../database/_components/feature-cards.tsx | 58 +++++++++ .../database/_components/read-only-field.tsx | 38 ++++++ .../[id]/database/connection-string.tsx | 39 ------- .../projects/[id]/database/page.tsx | 110 +++++------------- lib/data/database.ts | 19 +++ lib/data/project.ts | 55 +++++++++ 7 files changed, 293 insertions(+), 120 deletions(-) create mode 100644 app/(dashboard)/projects/[id]/database/_components/connection-string.tsx create mode 100644 app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx create mode 100644 app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx delete mode 100644 app/(dashboard)/projects/[id]/database/connection-string.tsx create mode 100644 lib/data/database.ts create mode 100644 lib/data/project.ts diff --git a/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx b/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx new file mode 100644 index 0000000..bf9446d --- /dev/null +++ b/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { + MdContentCopy, + MdCheck, + MdVisibility, + MdVisibilityOff, + MdInfo, +} from 'react-icons/md'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +interface ConnectionStringProps { + connectionString: string; +} + +export function ConnectionString({ connectionString }: ConnectionStringProps) { + const [isVisible, setIsVisible] = useState(false); + const [copied, setCopied] = useState(false); + + // Memoize masked string to avoid recalculation on every render + const displayValue = useMemo(() => { + if (isVisible) return connectionString; + return '•'.repeat(Math.min(connectionString.length, 50)); + }, [connectionString, isVisible]); + + const handleCopy = async () => { + await navigator.clipboard.writeText(connectionString); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
        + {/* Header with title and badge */} +
        +

        + Full Connection String +

        + + Read-only + +
        + + {/* Connection string display */} +
        +
        + {displayValue} +
        + + {/* Action buttons */} +
        + {/* Toggle visibility */} + + + {/* Copy button */} + +
        +
        + + {/* Footer with info */} +

        + + Use this connection string in your application to connect to the database. +

        +
        + ); +} diff --git a/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx b/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx new file mode 100644 index 0000000..57d6f54 --- /dev/null +++ b/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx @@ -0,0 +1,58 @@ +import { MdAutoAwesome, MdHttps, MdStorage, MdTerminal } from 'react-icons/md'; +import { type IconType } from 'react-icons'; + +// Feature card component +function FeatureCard({ + icon: Icon, + title, + description, +}: { + icon: IconType; + title: string; + description: string; +}) { + return ( +
        +
        + +
        +

        {title}

        +

        {description}

        +
        + ); +} + +// Feature cards data +const DATABASE_FEATURES = [ + { + icon: MdAutoAwesome, + title: 'Auto Provisioned', + description: 'Database is automatically provisioned and ready to use with your sandbox environment.', + }, + { + icon: MdStorage, + title: 'High Availability', + description: 'Managed by KubeBlocks with high availability and automatic failover.', + }, + { + icon: MdHttps, + title: 'SSL Encrypted', + description: 'SSL encryption enabled by default for secure database connections.', + }, + { + icon: MdTerminal, + title: 'Environment Variable', + description: 'Connection string available via DATABASE_URL environment variable.', + }, +] as const; + +// Export complete component +export function FeatureCards() { + return ( +
        + {DATABASE_FEATURES.map(({ icon, title, description }) => ( + + ))} +
        + ); +} diff --git a/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx b/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx new file mode 100644 index 0000000..c80b8a5 --- /dev/null +++ b/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx @@ -0,0 +1,38 @@ +/** + * Read-only field component for displaying data + * Mimics input styling for display-only scenarios + */ + +import { useId } from 'react'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; + +interface ReadOnlyFieldProps { + /** Field label */ + label: string; + /** Field value */ + value: string | number; + /** Full width on mobile (col-span-2) */ + fullWidth?: boolean; +} + +export function ReadOnlyField({ label, value, fullWidth }: ReadOnlyFieldProps) { + const fieldId = useId(); + + return ( +
        + + +
        + ); +} diff --git a/app/(dashboard)/projects/[id]/database/connection-string.tsx b/app/(dashboard)/projects/[id]/database/connection-string.tsx deleted file mode 100644 index 74d13e0..0000000 --- a/app/(dashboard)/projects/[id]/database/connection-string.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Eye, EyeOff } from 'lucide-react'; - -import { Input } from '@/components/ui/input'; - -interface ConnectionStringProps { - connectionString: string; -} - -export function ConnectionString({ connectionString }: ConnectionStringProps) { - const [isVisible, setIsVisible] = useState(false); - - return ( -
        - -
        - - -
        -

        - Use this connection string in your application to connect to the database -

        -
        - ); -} diff --git a/app/(dashboard)/projects/[id]/database/page.tsx b/app/(dashboard)/projects/[id]/database/page.tsx index b9d99f8..09e5771 100644 --- a/app/(dashboard)/projects/[id]/database/page.tsx +++ b/app/(dashboard)/projects/[id]/database/page.tsx @@ -1,110 +1,58 @@ -import { Info } from 'lucide-react'; import { notFound, redirect } from 'next/navigation'; import { auth } from '@/lib/auth'; -import { prisma } from '@/lib/db'; +import { getProject } from '@/lib/data/project'; +import { parseConnectionUrl } from '@/lib/data/database'; import { SettingsLayout } from '../_components/settings-layout'; -import { ConnectionString } from './connection-string'; +import { ConnectionString } from './_components/connection-string'; +import { FeatureCards } from './_components/feature-cards'; +import { ReadOnlyField } from './_components/read-only-field'; export default async function DatabasePage({ params }: { params: Promise<{ id: string }> }) { - const session = await auth(); + // Parallel: fetch session and params simultaneously + const [session, paramsResolved] = await Promise.all([ + auth(), + params + ]); + if (!session) redirect('/login'); - const { id } = await params; + const { id } = paramsResolved; - const project = await prisma.project.findFirst({ - where: { id, userId: session.user.id }, - include: { databases: true, environments: true }, + const project = await getProject(id, session.user.id, { + databases: true, }); if (!project) notFound(); const database = project.databases[0]; - const connectionString = database?.connectionUrl || ''; - let host = ''; - let port = ''; - let dbName = ''; - let username = ''; - let password = ''; - - // Parse connection string - if (connectionString) { - try { - const match = connectionString.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/([^?]+)/); - if (match) { - [, username, password, host, port, dbName] = match; - } - } catch (e) { - console.error('Failed to parse database URL:', e); - } - } + const connectionInfo = parseConnectionUrl(connectionString) || { + host: '', port: '', database: '', username: '', password: '' + }; return ( {connectionString ? ( <> - {/* Connection Details */}
        -

        PostgreSQL Connection

        - - {/* Host */} -
        - -
        - {host} -
        +

        PostgreSQL Connection

        + +
        + + + + +
        - {/* Port */} -
        - -
        - {port} -
        +
        +
        - - {/* Database Name */} -
        - -
        - {dbName} -
        -
        - - {/* Username */} -
        - -
        - {username} -
        -
        - - {/* Password */} -
        - -
        - {'•'.repeat(Math.min(password.length, 20))} -
        -
        - - {/* Full Connection String */} -
        - {/* Info Panel */} -
        -
        - -
        -

        • Database is automatically provisioned with your sandbox

        -

        • Managed by KubeBlocks with high availability

        -

        • SSL encryption enabled by default

        -

        • Connection string available via DATABASE_URL environment variable

        -
        -
        -
        + ) : (
        @@ -116,4 +64,4 @@ export default async function DatabasePage({ params }: { params: Promise<{ id: s )} ); -} +} \ No newline at end of file diff --git a/lib/data/database.ts b/lib/data/database.ts new file mode 100644 index 0000000..b4fe53f --- /dev/null +++ b/lib/data/database.ts @@ -0,0 +1,19 @@ +export interface ConnectionInfo { + host: string; + port: string; + database: string; + username: string; + password: string; +} + +export function parseConnectionUrl(connectionString: string): ConnectionInfo | null { + try { + const match = connectionString.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/([^?]+)/); + if (!match) return null; + + const [, username, password, host, port, database] = match; + return { host, port, database, username, password }; + } catch { + return null; + } +} diff --git a/lib/data/project.ts b/lib/data/project.ts new file mode 100644 index 0000000..44055f1 --- /dev/null +++ b/lib/data/project.ts @@ -0,0 +1,55 @@ +import { cache } from 'react'; +import type { Prisma } from '@prisma/client'; + +import { prisma } from '@/lib/db'; +import { logger } from '@/lib/logger'; + +/** + * Project relation include options + */ +export type ProjectInclude = { + sandboxes?: boolean; + databases?: boolean; + environments?: boolean; +}; + +/** + * Project with relations based on include options + */ +export type ProjectWithRelations = + Prisma.ProjectGetPayload<{ include: T }>; + +/** + * Get a project by ID and user ID + * Uses React.cache() to deduplicate calls within the same request + * @param include - Optional relation loading (default: all false) + */ +export const getProject = cache(async function getProject( + projectId: string, + userId: string, + include?: ProjectInclude +): Promise { + const shouldInclude: Required = { + sandboxes: false, + databases: false, + environments: false, + ...include, + }; + + try { + const project = await prisma.project.findFirst({ + where: { id: projectId, userId }, + include: shouldInclude, + }); + + if (!project) { + logger.warn(`Project ${projectId} not found for user ${userId}`); + return null; + } + + return project; + } catch (error) { + logger.error(`Failed to fetch project ${projectId}: ${error}`); + throw error; + } +}); From 74742b0d8a9a5760a4f996ad2089575360948159 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:59:59 +0800 Subject: [PATCH 15/15] chore: lint --- .../database/_components/connection-string.tsx | 5 +++-- .../[id]/database/_components/feature-cards.tsx | 2 +- .../database/_components/read-only-field.tsx | 1 + app/(dashboard)/projects/[id]/database/page.tsx | 3 ++- components/layout/project-content-wrapper.tsx | 1 + components/sidebars/primary-sidebar.tsx | 2 +- components/sidebars/project-sidebar.tsx | 16 ++++++++-------- components/terminal/toolbar/toolbar.tsx | 2 +- 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx b/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx index bf9446d..e37ec94 100644 --- a/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx +++ b/app/(dashboard)/projects/[id]/database/_components/connection-string.tsx @@ -2,12 +2,13 @@ import { useMemo, useState } from 'react'; import { - MdContentCopy, MdCheck, + MdContentCopy, + MdInfo, MdVisibility, MdVisibilityOff, - MdInfo, } from 'react-icons/md'; + import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; diff --git a/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx b/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx index 57d6f54..73c6599 100644 --- a/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx +++ b/app/(dashboard)/projects/[id]/database/_components/feature-cards.tsx @@ -1,5 +1,5 @@ -import { MdAutoAwesome, MdHttps, MdStorage, MdTerminal } from 'react-icons/md'; import { type IconType } from 'react-icons'; +import { MdAutoAwesome, MdHttps, MdStorage, MdTerminal } from 'react-icons/md'; // Feature card component function FeatureCard({ diff --git a/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx b/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx index c80b8a5..f923c8a 100644 --- a/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx +++ b/app/(dashboard)/projects/[id]/database/_components/read-only-field.tsx @@ -4,6 +4,7 @@ */ import { useId } from 'react'; + import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; diff --git a/app/(dashboard)/projects/[id]/database/page.tsx b/app/(dashboard)/projects/[id]/database/page.tsx index 09e5771..edc13f4 100644 --- a/app/(dashboard)/projects/[id]/database/page.tsx +++ b/app/(dashboard)/projects/[id]/database/page.tsx @@ -1,10 +1,11 @@ import { notFound, redirect } from 'next/navigation'; import { auth } from '@/lib/auth'; -import { getProject } from '@/lib/data/project'; import { parseConnectionUrl } from '@/lib/data/database'; +import { getProject } from '@/lib/data/project'; import { SettingsLayout } from '../_components/settings-layout'; + import { ConnectionString } from './_components/connection-string'; import { FeatureCards } from './_components/feature-cards'; import { ReadOnlyField } from './_components/read-only-field'; diff --git a/components/layout/project-content-wrapper.tsx b/components/layout/project-content-wrapper.tsx index 9b4c9ac..52019d6 100644 --- a/components/layout/project-content-wrapper.tsx +++ b/components/layout/project-content-wrapper.tsx @@ -24,6 +24,7 @@ import { TerminalContainer } from '@/components/terminal/terminal-container'; import { useProject } from '@/hooks/use-project'; import { StatusBar } from './status-bar'; + import styles from './project-content-wrapper.module.css'; // ============================================================================ diff --git a/components/sidebars/primary-sidebar.tsx b/components/sidebars/primary-sidebar.tsx index 602660d..36c86d9 100644 --- a/components/sidebars/primary-sidebar.tsx +++ b/components/sidebars/primary-sidebar.tsx @@ -1,9 +1,9 @@ 'use client'; import { useState } from 'react'; +import { MdOutlineGridView, MdOutlineSettings } from 'react-icons/md'; import Image from 'next/image'; import Link from 'next/link'; -import { MdOutlineGridView, MdOutlineSettings } from 'react-icons/md'; import SettingsDialog from '@/components/dialog/settings-dialog'; import { Button } from '@/components/ui/button'; diff --git a/components/sidebars/project-sidebar.tsx b/components/sidebars/project-sidebar.tsx index 3b8f920..0a25896 100644 --- a/components/sidebars/project-sidebar.tsx +++ b/components/sidebars/project-sidebar.tsx @@ -1,19 +1,19 @@ 'use client'; import { useState } from 'react'; -import { ChevronLeft, ChevronRight } from 'lucide-react'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; +import { IconType } from 'react-icons'; import { - MdOutlineTerminal, + MdOutlineCode, + MdOutlineCreditCard, MdOutlineDns, MdOutlineLayers, - MdOutlineVpnKey, MdOutlineSecurity, - MdOutlineCreditCard, - MdOutlineCode, + MdOutlineTerminal, + MdOutlineVpnKey, } from 'react-icons/md'; -import { IconType } from 'react-icons'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; import { useProject } from '@/hooks/use-project'; import { cn } from '@/lib/utils'; diff --git a/components/terminal/toolbar/toolbar.tsx b/components/terminal/toolbar/toolbar.tsx index e88403e..918bccd 100644 --- a/components/terminal/toolbar/toolbar.tsx +++ b/components/terminal/toolbar/toolbar.tsx @@ -12,7 +12,7 @@ import { Network } from 'lucide-react'; import { AppRunner } from './app-runner'; import { NetworkDialog } from './network-dialog'; -import { TerminalTabs, type Tab } from './terminal-tabs'; +import { type Tab,TerminalTabs } from './terminal-tabs'; type Project = Prisma.ProjectGetPayload<{ include: {