updated task

This commit is contained in:
ryanwong
2024-12-09 05:08:35 -05:00
parent 167e9fe6cd
commit 05811962b2
97 changed files with 11496 additions and 13 deletions
+17
View File
@@ -0,0 +1,17 @@
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Landing } from './pages/Landing';
import { Editor } from './pages/Editor';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/editor" element={<Editor />} />
</Routes>
</BrowserRouter>
);
}
export default App;
+23
View File
@@ -0,0 +1,23 @@
import React from 'react';
import { ChevronRight } from 'lucide-react';
interface BreadcrumbProps {
path: string;
}
export function Breadcrumb({ path }: BreadcrumbProps) {
const parts = path.split('/').filter(Boolean);
return (
<div className="flex items-center gap-1 px-3 py-1 bg-[#1E1E1E] text-gray-400 text-sm border-b border-gray-800">
{parts.map((part, index) => (
<React.Fragment key={index}>
{index > 0 && <ChevronRight className="w-3 h-3" />}
<span className="hover:text-white cursor-pointer">
{part}
</span>
</React.Fragment>
))}
</div>
);
}
+51
View File
@@ -0,0 +1,51 @@
import React from 'react';
import { MessageSquare, Send } from 'lucide-react';
export function Chat() {
const [messages, setMessages] = React.useState([
{ role: 'assistant', content: 'Hello! I\'m Bolt, your AI programming assistant. How can I help you today?' }
]);
return (
<div className="h-full flex flex-col bg-white">
<div className="flex items-center gap-2 p-3 border-b">
<MessageSquare className="w-5 h-5" />
<span className="font-medium">Chat</span>
</div>
<div className="flex-1 overflow-auto p-4 space-y-4">
{messages.map((message, i) => (
<div
key={i}
className={`flex ${
message.role === 'assistant' ? 'justify-start' : 'justify-end'
}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${
message.role === 'assistant'
? 'bg-gray-100'
: 'bg-blue-500 text-white'
}`}
>
{message.content}
</div>
</div>
))}
</div>
<div className="p-4 border-t">
<div className="flex gap-2">
<input
type="text"
placeholder="Ask me anything..."
className="flex-1 px-3 py-2 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button className="p-2 rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors">
<Send className="w-5 h-5" />
</button>
</div>
</div>
</div>
);
}
+48
View File
@@ -0,0 +1,48 @@
import React from 'react';
import Prism from 'prismjs';
import 'prismjs/themes/prism-tomorrow.css';
import 'prismjs/components/prism-typescript';
import 'prismjs/components/prism-jsx';
import 'prismjs/components/prism-tsx';
interface CodeEditorProps {
value: string;
onChange: (value: string) => void;
}
export function CodeEditor({ value, onChange }: CodeEditorProps) {
const lines = value.split('\n');
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
const [highlighted, setHighlighted] = React.useState('');
React.useEffect(() => {
const highlighted = Prism.highlight(
value,
Prism.languages.tsx,
'tsx'
);
setHighlighted(highlighted);
}, [value]);
return (
<div className="relative h-full bg-[#1E1E1E] text-gray-300 font-mono text-sm">
<div className="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-end pr-2 pt-4 text-gray-500 select-none bg-[#1E1E1E] border-r border-gray-800">
{lines.map((_, i) => (
<div key={i} className="leading-6">
{i + 1}
</div>
))}
</div>
<pre className="absolute left-12 right-0 top-0 bottom-0 m-0 p-4 overflow-hidden pointer-events-none">
<code dangerouslySetInnerHTML={{ __html: highlighted }} />
</pre>
<textarea
ref={textareaRef}
value={value}
onChange={(e) => onChange(e.target.value)}
className="w-full h-full pl-14 pr-4 pt-4 bg-transparent resize-none focus:outline-none leading-6 text-transparent caret-white"
spellCheck={false}
/>
</div>
);
}
+53
View File
@@ -0,0 +1,53 @@
import React from 'react';
import { FileTree } from './FileTree';
import { CodeEditor } from './CodeEditor';
import { EditorTabs } from './EditorTabs';
import { EditorNavBar } from './EditorNavBar';
import { Breadcrumb } from './Breadcrumb';
export function Editor() {
const [code, setCode] = React.useState(`// Your code will appear here
import React from 'react';
import { Prompt } from './components/Prompt';
import { Chat } from './components/Chat';
import { Editor } from './components/Editor';
function App() {
return (
<div className="min-h-screen flex flex-col bg-gray-50">
<header className="bg-white border-b">
<div className="text-xl font-bold text-blue-500">bolt.new</div>
</header>
<Prompt />
<main className="flex-1 grid grid-cols-2 gap-4 p-4">
<Chat />
<Editor />
</main>
</div>
);
}
export default App;`);
const [currentFile, setCurrentFile] = React.useState('/src/App.tsx');
return (
<>
<EditorTabs />
<EditorNavBar />
<div className="h-full flex bg-[#1E1E1E]">
<FileTree onFileSelect={(path) => {
setCurrentFile(path);
// In a real app, we would load the file content here
}} />
<div className="flex-1 flex flex-col">
<Breadcrumb path={currentFile} />
<CodeEditor
value={code}
onChange={setCode}
/>
</div>
</div>
</>
);
}
+18
View File
@@ -0,0 +1,18 @@
import React from 'react';
import { RefreshCw } from 'lucide-react';
export function EditorNavBar() {
return (
<div className="flex items-center gap-2 p-2 bg-[#1E1E1E] border-b border-gray-800">
<input
type="text"
value="http://localhost:5173"
readOnly
className="flex-1 px-3 py-1 text-sm bg-[#2D2D2D] text-gray-300 rounded border border-gray-800 focus:outline-none focus:border-gray-600"
/>
<button className="p-1.5 text-gray-400 hover:text-white rounded hover:bg-[#2D2D2D] transition-colors">
<RefreshCw className="w-4 h-4" />
</button>
</div>
);
}
+14
View File
@@ -0,0 +1,14 @@
import React from 'react';
export function EditorTabs() {
return (
<div className="flex items-center bg-[#252526] border-b border-gray-800">
<button className="px-4 py-2 text-sm text-white bg-[#1E1E1E] border-r border-gray-800">
Code
</button>
<button className="px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors">
Preview
</button>
</div>
);
}
+83
View File
@@ -0,0 +1,83 @@
import React from 'react';
import { ChevronDown, File, Folder } from 'lucide-react';
interface FileTreeProps {
onFileSelect: (path: string) => void;
}
export function FileTree({ onFileSelect }: FileTreeProps) {
const [expandedFolders, setExpandedFolders] = React.useState<Set<string>>(new Set(['src', 'components']));
const toggleFolder = (folder: string) => {
setExpandedFolders(prev => {
const next = new Set(prev);
if (next.has(folder)) {
next.delete(folder);
} else {
next.add(folder);
}
return next;
});
};
return (
<div className="h-full w-64 bg-[#1E1E1E] text-gray-300 border-r border-gray-800">
<div
className="flex items-center gap-2 p-2 text-sm cursor-pointer hover:bg-[#2D2D2D]"
onClick={() => toggleFolder('src')}
>
<span className="flex items-center gap-1">
<ChevronDown className={`w-4 h-4 transition-transform ${
expandedFolders.has('src') ? '' : '-rotate-90'
}`} />
<Folder className="w-4 h-4" />
src
</span>
</div>
{expandedFolders.has('src') && <div className="pl-4">
<div
className="flex items-center gap-2 p-2 text-sm cursor-pointer hover:bg-[#2D2D2D]"
onClick={() => toggleFolder('components')}
>
<span className="flex items-center gap-1">
<ChevronDown className={`w-4 h-4 transition-transform ${
expandedFolders.has('components') ? '' : '-rotate-90'
}`} />
<Folder className="w-4 h-4" />
components
</span>
</div>
{expandedFolders.has('components') && <div className="pl-4">
<div
className="flex items-center gap-2 p-2 text-sm text-gray-400 cursor-pointer hover:bg-[#2D2D2D]"
onClick={() => onFileSelect('/src/components/Chat.tsx')}
>
<File className="w-4 h-4" />
Chat.tsx
</div>
<div
className="flex items-center gap-2 p-2 text-sm text-gray-400 cursor-pointer hover:bg-[#2D2D2D]"
onClick={() => onFileSelect('/src/components/Editor.tsx')}
>
<File className="w-4 h-4" />
Editor.tsx
</div>
<div
className="flex items-center gap-2 p-2 text-sm text-gray-400 cursor-pointer hover:bg-[#2D2D2D]"
onClick={() => onFileSelect('/src/components/Prompt.tsx')}
>
<File className="w-4 h-4" />
Prompt.tsx
</div>
</div>}
<div
className="flex items-center gap-2 p-2 text-sm cursor-pointer hover:bg-[#2D2D2D] bg-[#2D2D2D] border-l-2 border-blue-500"
onClick={() => onFileSelect('/src/App.tsx')}
>
<File className="w-4 h-4" />
App.tsx
</div>
</div>}
</div>
);
}
+21
View File
@@ -0,0 +1,21 @@
import React from 'react';
import { Sparkles } from 'lucide-react';
export function Prompt() {
return (
<div className="border-b bg-white">
<div className="max-w-screen-xl mx-auto p-4">
<div className="flex items-start gap-4">
<textarea
className="flex-1 p-3 h-32 rounded-lg border resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Describe what you want to build..."
/>
<button className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
<Sparkles className="w-5 h-5" />
Generate
</button>
</div>
</div>
</div>
);
}
+22
View File
@@ -0,0 +1,22 @@
import React from 'react';
interface TextEditorProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export function TextEditor({ value, onChange, placeholder }: TextEditorProps) {
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
onChange(e.target.value);
};
return (
<textarea
value={value}
onChange={handleChange}
placeholder={placeholder}
className="w-full min-h-[100px] p-5 bg-white border border-[#E5E5E5] rounded-lg shadow-[0_2px_4px_rgba(0,0,0,0.1)] text-[#333333] text-base leading-relaxed resize-y font-sans placeholder:text-gray-400 hover:border-[#D1D1D1] focus:border-[#D1D1D1] focus:outline-none transition-colors duration-200"
/>
);
}
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { Chat } from '../components/Chat';
import { Editor as CodeEditor } from '../components/Editor';
export function Editor() {
return (
<div className="min-h-screen flex flex-col bg-gray-50">
<header className="bg-white border-b">
<div className="max-w-screen-xl mx-auto px-4 py-3">
<div className="text-xl font-bold text-blue-500">Coding Challenge</div>
</div>
</header>
<main className="flex-1 grid grid-cols-[25%_75%] gap-4 p-4">
<div className="h-[calc(100vh-5rem)] rounded-lg border shadow-sm overflow-hidden">
<Chat />
</div>
<div className="h-[calc(100vh-5rem)] rounded-lg border shadow-sm overflow-hidden">
<CodeEditor />
</div>
</main>
</div>
);
}
+44
View File
@@ -0,0 +1,44 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Sparkles } from 'lucide-react';
export function Landing() {
const navigate = useNavigate();
const [prompt, setPrompt] = React.useState('');
const handleGenerate = () => {
if (prompt.trim()) {
navigate('/editor');
}
};
return (
<div className="min-h-screen flex flex-col bg-gray-50">
<header className="bg-white border-b">
<div className="max-w-screen-xl mx-auto px-4 py-3">
<div className="text-xl font-bold text-blue-500">Coding Challenge</div>
</div>
</header>
<main className="flex-1 flex items-center justify-center p-4">
<div className="w-full max-w-3xl space-y-4">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
className="w-full p-4 h-40 text-lg rounded-lg border resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Describe what you want to build..."
/>
<div className="flex justify-end">
<button
onClick={handleGenerate}
className="flex items-center gap-2 px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-lg"
>
<Sparkles className="w-5 h-5" />
Generate
</button>
</div>
</div>
</main>
</div>
);
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />