fix
This commit is contained in:
parent
c14c3a235a
commit
37907ef098
@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import Ace from 'react-ace';
|
import Ace from "react-ace";
|
||||||
import ace from 'ace-builds';
|
import ace from "ace-builds";
|
||||||
|
|
||||||
// ── Vite ?url imports for ACE modes ──────────────────────────────────────
|
// ── Vite ?url imports for ACE modes ──────────────────────────────────────
|
||||||
// These resolve at build time to asset URLs. We register them with ACE's
|
// These resolve at build time to asset URLs. We register them with ACE's
|
||||||
@ -10,44 +10,44 @@ import ace from 'ace-builds';
|
|||||||
// See: https://github.com/ajaxorg/ace/issues/4597
|
// See: https://github.com/ajaxorg/ace/issues/4597
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url';
|
import modeJavascriptUrl from "ace-builds/src-noconflict/mode-javascript?url";
|
||||||
import modeTypescriptUrl from 'ace-builds/src-noconflict/mode-typescript?url';
|
import modeTypescriptUrl from "ace-builds/src-noconflict/mode-typescript?url";
|
||||||
import modePythonUrl from 'ace-builds/src-noconflict/mode-python?url';
|
import modePythonUrl from "ace-builds/src-noconflict/mode-python?url";
|
||||||
import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url';
|
import modeJsonUrl from "ace-builds/src-noconflict/mode-json?url";
|
||||||
import modeHtmlUrl from 'ace-builds/src-noconflict/mode-html?url';
|
import modeHtmlUrl from "ace-builds/src-noconflict/mode-html?url";
|
||||||
import modeCssUrl from 'ace-builds/src-noconflict/mode-css?url';
|
import modeCssUrl from "ace-builds/src-noconflict/mode-css?url";
|
||||||
import modeYamlUrl from 'ace-builds/src-noconflict/mode-yaml?url';
|
import modeYamlUrl from "ace-builds/src-noconflict/mode-yaml?url";
|
||||||
import modeMarkdownUrl from 'ace-builds/src-noconflict/mode-markdown?url';
|
import modeMarkdownUrl from "ace-builds/src-noconflict/mode-markdown?url";
|
||||||
import modeShUrl from 'ace-builds/src-noconflict/mode-sh?url';
|
import modeShUrl from "ace-builds/src-noconflict/mode-sh?url";
|
||||||
import modeSqlUrl from 'ace-builds/src-noconflict/mode-sql?url';
|
import modeSqlUrl from "ace-builds/src-noconflict/mode-sql?url";
|
||||||
import modeJavaUrl from 'ace-builds/src-noconflict/mode-java?url';
|
import modeJavaUrl from "ace-builds/src-noconflict/mode-java?url";
|
||||||
import modeGolangUrl from 'ace-builds/src-noconflict/mode-golang?url';
|
import modeGolangUrl from "ace-builds/src-noconflict/mode-golang?url";
|
||||||
import modeRustUrl from 'ace-builds/src-noconflict/mode-rust?url';
|
import modeRustUrl from "ace-builds/src-noconflict/mode-rust?url";
|
||||||
import modeCsharpUrl from 'ace-builds/src-noconflict/mode-csharp?url';
|
import modeCsharpUrl from "ace-builds/src-noconflict/mode-csharp?url";
|
||||||
import modePhpUrl from 'ace-builds/src-noconflict/mode-php?url';
|
import modePhpUrl from "ace-builds/src-noconflict/mode-php?url";
|
||||||
import modeRubyUrl from 'ace-builds/src-noconflict/mode-ruby?url';
|
import modeRubyUrl from "ace-builds/src-noconflict/mode-ruby?url";
|
||||||
import modeCcppUrl from 'ace-builds/src-noconflict/mode-c_cpp?url';
|
import modeCcppUrl from "ace-builds/src-noconflict/mode-c_cpp?url";
|
||||||
import modeScssUrl from 'ace-builds/src-noconflict/mode-scss?url';
|
import modeScssUrl from "ace-builds/src-noconflict/mode-scss?url";
|
||||||
import modeLessUrl from 'ace-builds/src-noconflict/mode-less?url';
|
import modeLessUrl from "ace-builds/src-noconflict/mode-less?url";
|
||||||
import modeXmlUrl from 'ace-builds/src-noconflict/mode-xml?url';
|
import modeXmlUrl from "ace-builds/src-noconflict/mode-xml?url";
|
||||||
import modeDockerfileUrl from 'ace-builds/src-noconflict/mode-dockerfile?url';
|
import modeDockerfileUrl from "ace-builds/src-noconflict/mode-dockerfile?url";
|
||||||
import modeMakefileUrl from 'ace-builds/src-noconflict/mode-makefile?url';
|
import modeMakefileUrl from "ace-builds/src-noconflict/mode-makefile?url";
|
||||||
import modeSassUrl from 'ace-builds/src-noconflict/mode-sass?url';
|
import modeSassUrl from "ace-builds/src-noconflict/mode-sass?url";
|
||||||
import modeTextUrl from 'ace-builds/src-noconflict/mode-text?url';
|
import modeTextUrl from "ace-builds/src-noconflict/mode-text?url";
|
||||||
|
|
||||||
// Workers (for syntax validation — currently disabled via useWorker:false,
|
// Workers (for syntax validation — currently disabled via useWorker:false,
|
||||||
// but registered in case we want to enable them later)
|
// but registered in case we want to enable them later)
|
||||||
import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url';
|
import workerJavascriptUrl from "ace-builds/src-noconflict/worker-javascript?url";
|
||||||
import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url';
|
import workerJsonUrl from "ace-builds/src-noconflict/worker-json?url";
|
||||||
import workerCssUrl from 'ace-builds/src-noconflict/worker-css?url';
|
import workerCssUrl from "ace-builds/src-noconflict/worker-css?url";
|
||||||
import workerHtmlUrl from 'ace-builds/src-noconflict/worker-html?url';
|
import workerHtmlUrl from "ace-builds/src-noconflict/worker-html?url";
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
import themeTomorrowUrl from 'ace-builds/src-noconflict/theme-tomorrow?url';
|
import themeTomorrowUrl from "ace-builds/src-noconflict/theme-tomorrow?url";
|
||||||
|
|
||||||
// Extensions
|
// Extensions
|
||||||
import extLanguageToolsUrl from 'ace-builds/src-noconflict/ext-language_tools?url';
|
import extLanguageToolsUrl from "ace-builds/src-noconflict/ext-language_tools?url";
|
||||||
import extSearchboxUrl from 'ace-builds/src-noconflict/ext-searchbox?url';
|
import extSearchboxUrl from "ace-builds/src-noconflict/ext-searchbox?url";
|
||||||
|
|
||||||
// ── Register all modules with ACE ────────────────────────────────────────
|
// ── Register all modules with ACE ────────────────────────────────────────
|
||||||
|
|
||||||
@ -97,14 +97,14 @@ for (const [mode, url] of Object.entries(WORKER_URLS)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register theme and extensions
|
// Register theme and extensions
|
||||||
ace.config.setModuleUrl('ace/theme/tomorrow', themeTomorrowUrl);
|
ace.config.setModuleUrl("ace/theme/tomorrow", themeTomorrowUrl);
|
||||||
ace.config.setModuleUrl('ace/ext/language_tools', extLanguageToolsUrl);
|
ace.config.setModuleUrl("ace/ext/language_tools", extLanguageToolsUrl);
|
||||||
ace.config.setModuleUrl('ace/ext/searchbox', extSearchboxUrl);
|
ace.config.setModuleUrl("ace/ext/searchbox", extSearchboxUrl);
|
||||||
|
|
||||||
// ── Component ────────────────────────────────────────────────────────────
|
// ── Component ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
import { WorkspaceMode } from '../lib/types';
|
import { WorkspaceMode } from "../lib/types";
|
||||||
import { socketClient } from '../lib/socket';
|
import { socketClient } from "../lib/socket";
|
||||||
|
|
||||||
interface EditorPanelProps {
|
interface EditorPanelProps {
|
||||||
workspaceMode: WorkspaceMode;
|
workspaceMode: WorkspaceMode;
|
||||||
@ -125,51 +125,55 @@ interface EditorState {
|
|||||||
|
|
||||||
// Map file extensions to ACE language modes
|
// Map file extensions to ACE language modes
|
||||||
function detectLanguage(filePath: string): string {
|
function detectLanguage(filePath: string): string {
|
||||||
const ext = filePath.split('.').pop()?.toLowerCase();
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
||||||
const languageMap: Record<string, string> = {
|
const languageMap: Record<string, string> = {
|
||||||
'js': 'javascript',
|
js: "javascript",
|
||||||
'jsx': 'javascript',
|
jsx: "javascript",
|
||||||
'ts': 'typescript',
|
ts: "typescript",
|
||||||
'tsx': 'typescript',
|
tsx: "typescript",
|
||||||
'py': 'python',
|
py: "python",
|
||||||
'rb': 'ruby',
|
rb: "ruby",
|
||||||
'java': 'java',
|
java: "java",
|
||||||
'c': 'c_cpp',
|
c: "c_cpp",
|
||||||
'cpp': 'c_cpp',
|
cpp: "c_cpp",
|
||||||
'h': 'c_cpp',
|
h: "c_cpp",
|
||||||
'hpp': 'c_cpp',
|
hpp: "c_cpp",
|
||||||
'cs': 'csharp',
|
cs: "csharp",
|
||||||
'go': 'golang',
|
go: "golang",
|
||||||
'rs': 'rust',
|
rs: "rust",
|
||||||
'php': 'php',
|
php: "php",
|
||||||
'html': 'html',
|
html: "html",
|
||||||
'htm': 'html',
|
htm: "html",
|
||||||
'css': 'css',
|
css: "css",
|
||||||
'scss': 'scss',
|
scss: "scss",
|
||||||
'sass': 'sass',
|
sass: "sass",
|
||||||
'less': 'less',
|
less: "less",
|
||||||
'json': 'json',
|
json: "json",
|
||||||
'xml': 'xml',
|
xml: "xml",
|
||||||
'yaml': 'yaml',
|
yaml: "yaml",
|
||||||
'yml': 'yaml',
|
yml: "yaml",
|
||||||
'md': 'markdown',
|
md: "markdown",
|
||||||
'sql': 'sql',
|
sql: "sql",
|
||||||
'sh': 'sh',
|
sh: "sh",
|
||||||
'bash': 'sh',
|
bash: "sh",
|
||||||
'dockerfile': 'dockerfile',
|
dockerfile: "dockerfile",
|
||||||
'makefile': 'makefile',
|
makefile: "makefile",
|
||||||
};
|
};
|
||||||
return languageMap[ext || ''] || 'text';
|
return languageMap[ext || ""] || "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: EditorPanelProps) {
|
export default function EditorPanel({
|
||||||
|
workspaceMode,
|
||||||
|
filePath,
|
||||||
|
onCloseFile,
|
||||||
|
}: EditorPanelProps) {
|
||||||
const [state, setState] = useState<EditorState>({
|
const [state, setState] = useState<EditorState>({
|
||||||
content: '',
|
content: "",
|
||||||
originalContent: '',
|
originalContent: "",
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
language: 'text',
|
language: "text",
|
||||||
});
|
});
|
||||||
|
|
||||||
const isReadOnly = workspaceMode === WorkspaceMode.Agent;
|
const isReadOnly = workspaceMode === WorkspaceMode.Agent;
|
||||||
@ -182,18 +186,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
} else {
|
} else {
|
||||||
// Clear editor when no file is selected
|
// Clear editor when no file is selected
|
||||||
setState({
|
setState({
|
||||||
content: '',
|
content: "",
|
||||||
originalContent: '',
|
originalContent: "",
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
language: 'text',
|
language: "text",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [filePath]);
|
}, [filePath]);
|
||||||
|
|
||||||
const loadFile = useCallback(async (path: string) => {
|
const loadFile = useCallback(async (path: string) => {
|
||||||
setState(prev => ({ ...prev, isLoading: true, error: undefined }));
|
setState((prev) => ({ ...prev, isLoading: true, error: undefined }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await socketClient.requestFileRead({ path });
|
const result = await socketClient.requestFileRead({ path });
|
||||||
@ -201,7 +205,6 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
if (result.success && result.content !== undefined) {
|
if (result.success && result.content !== undefined) {
|
||||||
const language = detectLanguage(path);
|
const language = detectLanguage(path);
|
||||||
// No dynamic import needed — all modes are registered via setModuleUrl at module load time
|
// No dynamic import needed — all modes are registered via setModuleUrl at module load time
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
content: result.content,
|
content: result.content,
|
||||||
originalContent: result.content,
|
originalContent: result.content,
|
||||||
@ -211,17 +214,17 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
language,
|
language,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: result.error || 'Failed to load file',
|
error: result.error || "Failed to load file",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to load file',
|
error: error instanceof Error ? error.message : "Failed to load file",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@ -229,7 +232,7 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
const saveFile = useCallback(async () => {
|
const saveFile = useCallback(async () => {
|
||||||
if (!filePath || isReadOnly) return;
|
if (!filePath || isReadOnly) return;
|
||||||
|
|
||||||
setState(prev => ({ ...prev, isSaving: true, error: undefined }));
|
setState((prev) => ({ ...prev, isSaving: true, error: undefined }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await socketClient.requestFileWrite({
|
const result = await socketClient.requestFileWrite({
|
||||||
@ -238,56 +241,59 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
originalContent: prev.content,
|
originalContent: prev.content,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
successMessage: 'File saved successfully',
|
successMessage: "File saved successfully",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Clear success message after 3 seconds
|
// Clear success message after 3 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setState(prev => ({ ...prev, successMessage: undefined }));
|
setState((prev) => ({ ...prev, successMessage: undefined }));
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
error: result.error || 'Failed to save file',
|
error: result.error || "Failed to save file",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to save file',
|
error: error instanceof Error ? error.message : "Failed to save file",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [filePath, state.content, isReadOnly]);
|
}, [filePath, state.content, isReadOnly]);
|
||||||
|
|
||||||
const handleContentChange = useCallback((newValue: string) => {
|
const handleContentChange = useCallback((newValue: string) => {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
content: newValue,
|
content: newValue,
|
||||||
isDirty: newValue !== prev.originalContent,
|
isDirty: newValue !== prev.originalContent,
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
// Ctrl+S or Cmd+S to save
|
// Ctrl+S or Cmd+S to save
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!isReadOnly && state.isDirty) {
|
if (!isReadOnly && state.isDirty) {
|
||||||
saveFile();
|
saveFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [saveFile, isReadOnly, state.isDirty]);
|
},
|
||||||
|
[saveFile, isReadOnly, state.isDirty],
|
||||||
|
);
|
||||||
|
|
||||||
// Add keyboard listener
|
// Add keyboard listener
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [handleKeyDown]);
|
}, [handleKeyDown]);
|
||||||
|
|
||||||
// If no file is selected, show placeholder
|
// If no file is selected, show placeholder
|
||||||
@ -295,8 +301,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
return (
|
return (
|
||||||
<div className="flex-1 flex items-center justify-center bg-bg-secondary">
|
<div className="flex-1 flex items-center justify-center bg-bg-secondary">
|
||||||
<div className="text-center text-text-muted">
|
<div className="text-center text-text-muted">
|
||||||
<svg className="w-16 h-16 mx-auto mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
className="w-16 h-16 mx-auto mb-4 opacity-50"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<p className="text-lg font-medium">No file open</p>
|
<p className="text-lg font-medium">No file open</p>
|
||||||
<p className="text-sm mt-2">Select a file from the tree to edit</p>
|
<p className="text-sm mt-2">Select a file from the tree to edit</p>
|
||||||
@ -338,11 +354,11 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
disabled={isReadOnly || !state.isDirty || state.isSaving}
|
disabled={isReadOnly || !state.isDirty || state.isSaving}
|
||||||
className={`px-3 py-1.5 text-xs font-medium rounded transition-colors ${
|
className={`px-3 py-1.5 text-xs font-medium rounded transition-colors ${
|
||||||
isReadOnly || !state.isDirty || state.isSaving
|
isReadOnly || !state.isDirty || state.isSaving
|
||||||
? 'bg-bg-secondary text-text-muted cursor-not-allowed'
|
? "bg-bg-secondary text-text-muted cursor-not-allowed"
|
||||||
: 'bg-brand text-white hover:bg-brand/80'
|
: "bg-brand text-white hover:bg-brand/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{state.isSaving ? 'Saving...' : 'Save'}
|
{state.isSaving ? "Saving..." : "Save"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{onCloseFile && (
|
{onCloseFile && (
|
||||||
@ -351,8 +367,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
className="p-1.5 text-text-muted hover:text-text-primary hover:bg-bg-secondary rounded transition-colors"
|
className="p-1.5 text-text-muted hover:text-text-primary hover:bg-bg-secondary rounded transition-colors"
|
||||||
title="Close file"
|
title="Close file"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
className="w-4 h-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -363,8 +389,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed
|
|||||||
{state.error && (
|
{state.error && (
|
||||||
<div className="px-4 py-3 bg-red-500/10 border-b border-red-500/20">
|
<div className="px-4 py-3 bg-red-500/10 border-b border-red-500/20">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<svg className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm text-red-500">{state.error}</p>
|
<p className="text-sm text-red-500">{state.error}</p>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user