-
-
+
+
{!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 (
+
+ );
+}
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..918bccd 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 { type Tab,TerminalTabs } from './terminal-tabs';
type Project = Prisma.ProjectGetPayload<{
include: {
@@ -30,11 +23,6 @@ type Project = Prisma.ProjectGetPayload<{
type Sandbox = Prisma.SandboxGetPayload