Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/web/components/DeleteProjectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ export default function DeleteProjectModal({
onClose();
};

// Handle ESC key to close modal
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
handleClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen]);

return (
<AnimatePresence>
{isOpen && (
Expand Down
17 changes: 17 additions & 0 deletions apps/web/components/GitHubRepoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,23 @@ export default function GitHubRepoModal({
}
};

// Handle ESC key to close modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);

return (
<AnimatePresence mode="wait">
{isOpen && (
Expand Down
29 changes: 23 additions & 6 deletions apps/web/components/GlobalSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,23 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
setTimeout(() => setToast(null), 3000);
};

// Handle ESC key to close modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);

// Load all service tokens and CLI data
useEffect(() => {
if (isOpen) {
Expand Down Expand Up @@ -343,7 +360,7 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
return (
<AnimatePresence>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div
<div
className="absolute inset-0 bg-black/60 backdrop-blur-md"
onClick={onClose}
/>
Expand Down Expand Up @@ -485,10 +502,10 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
<select
value={globalSettings.default_cli}
onChange={(e) => setDefaultCLI(e.target.value)}
className="pl-3 pr-8 py-1.5 text-xs font-medium border border-gray-200/50 dark:border-white/5 rounded-full bg-transparent hover:bg-gray-50 dark:hover:bg-white/5 hover:border-gray-300/50 dark:hover:border-white/10 text-gray-700 dark:text-white/80 focus:outline-none focus:ring-0 transition-colors cursor-pointer"
className="pl-3 pr-8 py-1.5 text-xs font-medium border border-gray-200/50 dark:border-white/5 rounded-full bg-transparent hover:bg-gray-50 dark:hover:bg-white/5 hover:border-gray-300/50 dark:hover:border-white/10 text-gray-700 dark:text-white/80 focus:outline-none focus:ring-0 transition-colors cursor-pointer [&>option]:bg-white [&>option]:dark:bg-gray-800 [&>option]:text-gray-900 [&>option]:dark:text-white"
>
{CLI_OPTIONS.filter(cli => cliStatus[cli.id]?.installed && cli.enabled !== false).map(cli => (
<option key={cli.id} value={cli.id}>
<option key={cli.id} value={cli.id} className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
{cli.name}
</option>
))}
Expand Down Expand Up @@ -596,11 +613,11 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
<select
value={settings.model || ''}
onChange={(e) => setDefaultModel(cli.id, e.target.value)}
className="w-full px-3 py-1.5 border border-gray-200/50 dark:border-white/5 rounded-full bg-transparent hover:bg-gray-50 dark:hover:bg-white/5 text-gray-700 dark:text-white/80 text-xs font-medium transition-colors focus:outline-none focus:ring-0"
className="w-full px-3 py-1.5 border border-gray-200/50 dark:border-white/5 rounded-full bg-transparent hover:bg-gray-50 dark:hover:bg-white/5 text-gray-700 dark:text-white/80 text-xs font-medium transition-colors focus:outline-none focus:ring-0 [&>option]:bg-white [&>option]:dark:bg-gray-800 [&>option]:text-gray-900 [&>option]:dark:text-white"
>
<option value="">Select model</option>
<option value="" className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">Select model</option>
{cli.models.map(model => (
<option key={model.id} value={model.id}>
<option key={model.id} value={model.id} className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
{model.name}
</option>
))}
Expand Down
21 changes: 12 additions & 9 deletions apps/web/components/ProjectSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState, useEffect, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import ServiceConnectionModal from '@/components/ServiceConnectionModal';
import EnvironmentVariablesTab from '@/components/EnvironmentVariablesTab';
import GlobalSettings from '@/components/GlobalSettings';

const API_BASE = process.env.NEXT_PUBLIC_API_BASE || 'http://localhost:8080';

Expand Down Expand Up @@ -599,14 +600,16 @@ export default function ProjectSettings({ isOpen, onClose, projectId, projectNam
</p>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
</button>
<div className="flex items-center gap-2">
<button
onClick={onClose}
className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
</button>
</div>
</div>
</div>

Expand Down Expand Up @@ -951,7 +954,7 @@ export default function ProjectSettings({ isOpen, onClose, projectId, projectNam
projectId={projectId}
/>
)}

</AnimatePresence>
);
}
17 changes: 17 additions & 0 deletions apps/web/components/ServiceConnectionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,23 @@ export default function ServiceConnectionModal({

const providerInfo = getProviderInfo();

// Handle ESC key to close modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);

if (!isOpen) return null;

return (
Expand Down
25 changes: 21 additions & 4 deletions apps/web/components/SupabaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,23 @@ export default function SupabaseModal({ isOpen, onClose, projectId, projectName,
}
};

// Handle ESC key to close modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);

if (!isOpen) return null;

return (
Expand Down Expand Up @@ -408,10 +425,10 @@ export default function SupabaseModal({ isOpen, onClose, projectId, projectName,
<select
value={selectedOrgId}
onChange={(e) => setSelectedOrgId(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 [&>option]:bg-white [&>option]:dark:bg-gray-700 [&>option]:text-gray-900 [&>option]:dark:text-white"
>
{organizations.map(org => (
<option key={org.id} value={org.id}>
<option key={org.id} value={org.id} className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
{org.name}
</option>
))}
Expand All @@ -438,10 +455,10 @@ export default function SupabaseModal({ isOpen, onClose, projectId, projectName,
<select
value={selectedRegion}
onChange={(e) => setSelectedRegion(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 [&>option]:bg-white [&>option]:dark:bg-gray-700 [&>option]:text-gray-900 [&>option]:dark:text-white"
>
{regions.map(region => (
<option key={region.id} value={region.id}>
<option key={region.id} value={region.id} className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
{region.name}
</option>
))}
Expand Down
35 changes: 26 additions & 9 deletions apps/web/components/VercelProjectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ export default function VercelProjectModal({
onClose();
};

// Handle ESC key to close modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
handleClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen]);

if (!isOpen) return null;

return (
Expand Down Expand Up @@ -198,17 +215,17 @@ export default function VercelProjectModal({
<select
value={framework}
onChange={(e) => setFramework(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 [&>option]:bg-white [&>option]:dark:bg-gray-700 [&>option]:text-gray-900 [&>option]:dark:text-gray-100"
disabled={isLoading}
>
<option value="nextjs">Next.js</option>
<option value="react">React</option>
<option value="vue">Vue.js</option>
<option value="nuxtjs">Nuxt.js</option>
<option value="svelte">Svelte</option>
<option value="angular">Angular</option>
<option value="static">Static HTML</option>
<option value="other">Other</option>
<option value="nextjs" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Next.js</option>
<option value="react" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">React</option>
<option value="vue" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Vue.js</option>
<option value="nuxtjs" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Nuxt.js</option>
<option value="svelte" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Svelte</option>
<option value="angular" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Angular</option>
<option value="static" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Static HTML</option>
<option value="other" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">Other</option>
</select>
</div>

Expand Down
14 changes: 14 additions & 0 deletions apps/web/components/chat/CLISelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ interface CLISelectorProps {
}

export function CLISelector({ options, selected, onSelect, onClose }: CLISelectorProps) {
// Handle ESC key to close modal
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [onClose]);

return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4">
Expand Down
15 changes: 13 additions & 2 deletions apps/web/components/settings/AIAssistantSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { useCLI } from '@/hooks/useCLI';

interface AIAssistantSettingsProps {
projectId: string;
onOpenGlobalSettings?: () => void;
}

export function AIAssistantSettings({ projectId }: AIAssistantSettingsProps) {
export function AIAssistantSettings({ projectId, onOpenGlobalSettings }: AIAssistantSettingsProps) {
const { cliOptions, preference } = useCLI({ projectId });

const selectedCLIOption = cliOptions.find(opt => opt.id === preference?.preferred_cli);
Expand Down Expand Up @@ -90,7 +91,17 @@ export function AIAssistantSettings({ projectId }: AIAssistantSettingsProps) {
{/* Note */}
<div className="text-center">
<p className="text-sm text-gray-500 dark:text-gray-400">
To modify these settings, use Global Settings
To modify these settings, use{' '}
{onOpenGlobalSettings ? (
<button
onClick={onOpenGlobalSettings}
className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 underline decoration-dotted underline-offset-2 transition-colors cursor-pointer"
>
Global Settings
</button>
) : (
<span className="text-gray-700 dark:text-gray-300">Global Settings</span>
)}
</p>
</div>
</div>
Expand Down
13 changes: 10 additions & 3 deletions apps/web/components/settings/ProjectSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ export function ProjectSettings({
)}

{activeTab === 'ai-assistant' && (
<AIAssistantSettings projectId={projectId} />
<AIAssistantSettings
projectId={projectId}
onOpenGlobalSettings={() => {
// Open Global Settings with ai-agents tab
setShowGlobalSettings(true);
onClose(); // Close current modal
}}
/>
)}

{activeTab === 'environment' && (
Expand All @@ -105,13 +112,13 @@ export function ProjectSettings({

{/* Global Settings Modal */}
{showGlobalSettings && (
<GlobalSettings
<GlobalSettings
isOpen={showGlobalSettings}
onClose={() => {
setShowGlobalSettings(false);
// Note: We could reopen ProjectSettings here if needed
}}
initialTab="services"
initialTab={activeTab === 'ai-assistant' ? 'ai-agents' : 'services'}
/>
)}
</>
Expand Down
17 changes: 17 additions & 0 deletions apps/web/components/settings/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ interface SettingsModalProps {
}

export function SettingsModal({ isOpen, onClose, title, icon, children }: SettingsModalProps) {
// Handle ESC key to close modal
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);

if (!isOpen) return null;

return (
Expand Down