@@ -2,6 +2,7 @@ import Editor, { DiffEditor } from '@monaco-editor/react';
22import {
33 Bug ,
44 Check ,
5+ Clipboard ,
56 Code2 ,
67 Copy ,
78 RotateCcw ,
@@ -11,6 +12,7 @@ import {
1112} from 'lucide-react' ;
1213import { useEffect , useState } from 'react' ;
1314import { fixBugs , reviewCode , translateCode } from './api/client' ;
15+ import FileUploader from './components/FileUploader' ;
1416import { LanguageSelector } from './components/LanguageSelector' ;
1517
1618type Mode = 'translate' | 'review' | 'fix' ;
@@ -24,6 +26,7 @@ function App() {
2426 const [ mode , setMode ] = useState < Mode > ( 'translate' ) ;
2527 const [ isProcessing , setIsProcessing ] = useState ( false ) ;
2628 const [ copied , setCopied ] = useState ( false ) ;
29+ const [ mobileNavOpen , setMobileNavOpen ] = useState ( false ) ;
2730
2831 // --- Logic: Syncing & Placeholders ---
2932 useEffect ( ( ) => {
@@ -37,7 +40,6 @@ function App() {
3740 else setTargetLang ( 'typescript' ) ; // Default for most modern migrations
3841 } else {
3942 // If user switches back to a plain language, force them to pick a new target
40- setTargetLang ( '' ) ;
4143 setOutputCode ( '' ) ;
4244 }
4345 }
@@ -73,6 +75,31 @@ function App() {
7375 . trim ( ) ;
7476 } ;
7577
78+ // --- Input helpers (upload / paste / clear) ---
79+ const handleFileRead = ( content : string , _filename ?: string ) => {
80+ setSourceCode ( content ) ;
81+ setOutputCode ( '' ) ;
82+ } ;
83+
84+ const handlePasteFromClipboard = async ( ) => {
85+ try {
86+ const text = await navigator . clipboard . readText ( ) ;
87+ if ( ! text ) {
88+ alert ( 'Clipboard is empty' ) ;
89+ return ;
90+ }
91+ setSourceCode ( text ) ;
92+ setOutputCode ( '' ) ;
93+ } catch ( err ) {
94+ alert ( 'Unable to read clipboard' ) ;
95+ }
96+ } ;
97+
98+ const handleClearInput = ( ) => {
99+ setSourceCode ( '// Your code here...' ) ;
100+ setOutputCode ( '' ) ;
101+ } ;
102+
76103 const handleAction = async ( ) => {
77104 if ( ! sourceCode . trim ( ) || sourceCode === '// Your code here...' ) return ;
78105 if ( mode === 'translate' && ! targetLang ) {
@@ -112,7 +139,7 @@ function App() {
112139 className = { `min-h-screen bg-[#0b0e14] text-slate-200 font-sans antialiased ${ isProcessing ? 'cursor-wait' : '' } ` }
113140 >
114141 { /* Navbar */ }
115- < nav className = "border-b border-slate-800 px-6 py-4 flex justify-between items-center bg-[#0b0e14]/80 backdrop-blur-md sticky top-0 z-50" >
142+ < nav className = "border-b border-slate-800 px-4 sm:px-6 py-3 sm: py-4 flex justify-between items-center bg-[#0b0e14]/80 backdrop-blur-md sticky top-0 z-50" >
116143 < div className = "flex items-center gap-2" >
117144 < div className = "bg-blue-600 p-1.5 rounded-lg" >
118145 < Zap size = { 20 } className = "fill-white text-white" />
@@ -123,31 +150,71 @@ function App() {
123150 </ div >
124151
125152 { /* Mode Switcher */ }
126- < div
127- className = { `flex bg-slate-900 border border-slate-800 p-1 rounded-xl transition-all ${ isProcessing ? 'opacity-40 pointer-events-none scale-95' : '' } ` }
128- >
129- { [
130- { id : 'translate' , label : 'Translate' , icon : < Code2 size = { 16 } /> } ,
131- { id : 'review' , label : 'Review' , icon : < Search size = { 16 } /> } ,
132- { id : 'fix' , label : 'Check Bugs' , icon : < Bug size = { 16 } /> } ,
133- ] . map ( ( m ) => (
134- < button
135- type = "button"
136- key = { m . id }
137- disabled = { isProcessing }
138- onClick = { ( ) => setMode ( m . id as Mode ) }
139- className = { `flex items-center gap-2 px-4 py-1.5 rounded-lg text-sm font-medium transition-all ${
140- mode === m . id
141- ? 'bg-slate-800 text-blue-400 shadow-sm'
142- : 'text-slate-400 hover:text-slate-200'
143- } `}
144- >
145- { m . icon } { m . label }
146- </ button >
147- ) ) }
153+ < div className = "hidden sm:flex" >
154+ < div
155+ className = { `flex bg-slate-900 border border-slate-800 p-1 rounded-xl transition-all ${ isProcessing ? 'opacity-40 pointer-events-none scale-95' : '' } ` }
156+ >
157+ { [
158+ {
159+ id : 'translate' ,
160+ label : 'Translate' ,
161+ icon : < Code2 size = { 16 } /> ,
162+ } ,
163+ { id : 'review' , label : 'Review' , icon : < Search size = { 16 } /> } ,
164+ { id : 'fix' , label : 'Check Bugs' , icon : < Bug size = { 16 } /> } ,
165+ ] . map ( ( m ) => (
166+ < button
167+ type = "button"
168+ key = { m . id }
169+ disabled = { isProcessing }
170+ onClick = { ( ) => setMode ( m . id as Mode ) }
171+ className = { `flex items-center gap-2 px-4 py-1.5 rounded-lg text-sm font-medium transition-all ${
172+ mode === m . id
173+ ? 'bg-slate-800 text-blue-400 shadow-sm'
174+ : 'text-slate-400 hover:text-slate-200'
175+ } `}
176+ >
177+ { m . icon } { m . label }
178+ </ button >
179+ ) ) }
180+ </ div >
148181 </ div >
149182
150- < div className = "flex gap-4" >
183+ { /* Mobile menu toggle */ }
184+ < button
185+ type = "button"
186+ className = "sm:hidden p-2 rounded-md text-slate-300 hover:bg-slate-800"
187+ onClick = { ( ) => setMobileNavOpen ( ( v ) => ! v ) }
188+ aria-label = "Toggle menu"
189+ >
190+ < svg
191+ xmlns = "http://www.w3.org/2000/svg"
192+ className = "h-6 w-6"
193+ fill = "none"
194+ viewBox = "0 0 24 24"
195+ stroke = "currentColor"
196+ role = "img"
197+ >
198+ < title > { mobileNavOpen ? 'Close menu' : 'Open menu' } </ title >
199+ { mobileNavOpen ? (
200+ < path
201+ strokeLinecap = "round"
202+ strokeLinejoin = "round"
203+ strokeWidth = { 2 }
204+ d = "M6 18L18 6M6 6l12 12"
205+ />
206+ ) : (
207+ < path
208+ strokeLinecap = "round"
209+ strokeLinejoin = "round"
210+ strokeWidth = { 2 }
211+ d = "M4 6h16M4 12h16M4 18h16"
212+ />
213+ ) }
214+ </ svg >
215+ </ button >
216+
217+ < div className = "hidden sm:flex gap-4" >
151218 < button
152219 type = "button"
153220 disabled = { isProcessing }
@@ -182,6 +249,58 @@ function App() {
182249 </ div >
183250 </ nav >
184251
252+ { /* Mobile nav dropdown */ }
253+ { mobileNavOpen && (
254+ < div className = "sm:hidden bg-[#0b0e14]/95 border-b border-slate-800 px-4 py-3" >
255+ < div className = "flex flex-col gap-3" >
256+ < div className = "flex bg-slate-900 border border-slate-800 p-1 rounded-xl" >
257+ { [
258+ { id : 'translate' , label : 'Translate' } ,
259+ { id : 'review' , label : 'Review' } ,
260+ { id : 'fix' , label : 'Check Bugs' } ,
261+ ] . map ( ( m ) => (
262+ < button
263+ key = { m . id }
264+ type = "button"
265+ onClick = { ( ) => {
266+ setMode ( m . id as Mode ) ;
267+ setMobileNavOpen ( false ) ;
268+ } }
269+ className = { `flex-1 text-sm py-2 rounded-md ${ mode === m . id ? 'bg-slate-800 text-blue-400' : 'text-slate-300' } ` }
270+ >
271+ { m . label }
272+ </ button >
273+ ) ) }
274+ </ div >
275+
276+ < div className = "flex items-center gap-2" >
277+ < button
278+ type = "button"
279+ onClick = { ( ) => {
280+ setSourceCode ( '// Your code here...' ) ;
281+ setOutputCode ( '' ) ;
282+ setMobileNavOpen ( false ) ;
283+ } }
284+ className = "flex-1 py-2 rounded-md text-sm text-slate-300 hover:bg-slate-800"
285+ >
286+ Reset
287+ </ button >
288+ < button
289+ type = "button"
290+ onClick = { ( ) => {
291+ handleAction ( ) ;
292+ setMobileNavOpen ( false ) ;
293+ } }
294+ disabled = { isProcessing || ( mode === 'translate' && ! targetLang ) }
295+ className = { `flex-1 py-2 rounded-md text-sm font-bold ${ isProcessing || ( mode === 'translate' && ! targetLang ) ? 'bg-slate-700 opacity-50' : 'bg-blue-600' } ` }
296+ >
297+ Run AI
298+ </ button >
299+ </ div >
300+ </ div >
301+ </ div >
302+ ) }
303+
185304 < main className = "p-6 max-w-[1600px] mx-auto" >
186305 < div className = "grid grid-cols-1 lg:grid-cols-2 gap-6 h-[calc(100vh-140px)]" >
187306 { /* Input Panel */ }
@@ -195,6 +314,33 @@ function App() {
195314 onChange = { setSourceLang }
196315 />
197316 </ div >
317+ < div className = "flex items-center gap-2" >
318+ < div className = "flex-1" />
319+ < div className = "flex items-center gap-2" >
320+ < FileUploader
321+ onFileRead = { handleFileRead }
322+ disabled = { isProcessing }
323+ />
324+ < button
325+ type = "button"
326+ onClick = { handlePasteFromClipboard }
327+ disabled = { isProcessing }
328+ className = "flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-slate-800 text-sm"
329+ title = "Paste from clipboard"
330+ >
331+ < Clipboard size = { 14 } /> Paste
332+ </ button >
333+ < button
334+ type = "button"
335+ onClick = { handleClearInput }
336+ disabled = { isProcessing }
337+ className = "flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-slate-800 text-sm"
338+ title = "Clear input"
339+ >
340+ Clear
341+ </ button >
342+ </ div >
343+ </ div >
198344 < div className = "flex-1 rounded-xl overflow-hidden border border-slate-800 bg-[#1e1e1e]" >
199345 < Editor
200346 height = "100%"
0 commit comments