Spaces:
Sleeping
Sleeping
lakshmisravya123
Major upgrade: comprehensive code analysis with security, performance, clean code scoring, and test suggestions
273bb97 | import { useState } from 'react'; | |
| import { roastCode } from './utils/api'; | |
| const LANGUAGES = ['Auto-detect', 'JavaScript', 'Python', 'TypeScript', 'Java', 'C++', 'Go', 'Rust', 'PHP', 'Ruby', 'CSS', 'HTML', 'SQL', 'Other']; | |
| function ScoreBar({ label, value, max = 100 }) { | |
| const pct = Math.round((Number(value) / max) * 100); | |
| const barColor = pct >= 80 ? '#238636' : pct >= 50 ? '#9e6a03' : '#da3633'; | |
| return ( | |
| <div style={{ marginBottom: '0.5rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '0.2rem' }}> | |
| <span style={{ color: '#8b949e' }}>{label}</span> | |
| <span style={{ color: barColor, fontWeight: 700 }}>{value}/{max}</span> | |
| </div> | |
| <div style={{ background: '#21262d', borderRadius: 6, height: 8, overflow: 'hidden' }}> | |
| <div style={{ width: pct + '%', background: barColor, height: '100%', borderRadius: 6, transition: 'width 0.5s' }} /> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function SeverityBadge({ level }) { | |
| const colors = { | |
| critical: '#da3633', high: '#da3633', nuclear: '#da3633', | |
| medium: '#9e6a03', warning: '#9e6a03', spicy: '#9e6a03', | |
| low: '#238636', info: '#388bfd', mild: '#238636' | |
| }; | |
| const bg = colors[level] || '#30363d'; | |
| return <span style={{ display: 'inline-block', padding: '0.1rem 0.5rem', borderRadius: 10, fontSize: '0.75rem', fontWeight: 700, background: bg, color: '#fff' }}>{level}</span>; | |
| } | |
| function GradeDisplay({ grade }) { | |
| const gradeColors = { | |
| 'A+': '#238636', 'A': '#238636', 'A-': '#2ea043', | |
| 'B+': '#56d364', 'B': '#9e6a03', 'B-': '#9e6a03', | |
| 'C+': '#d29922', 'C': '#d29922', 'C-': '#e3b341', | |
| 'D+': '#da3633', 'D': '#da3633', 'D-': '#da3633', | |
| 'F': '#da3633' | |
| }; | |
| const color = gradeColors[grade] || '#8b949e'; | |
| return ( | |
| <div style={{ textAlign: 'center' }}> | |
| <div style={{ fontSize: '3.5rem', fontWeight: 900, color, lineHeight: 1 }}>{grade}</div> | |
| <div style={{ color: '#8b949e', fontSize: '0.8rem', marginTop: '0.3rem' }}>Letter Grade</div> | |
| </div> | |
| ); | |
| } | |
| export default function App() { | |
| const [code, setCode] = useState(''); | |
| const [language, setLanguage] = useState('Auto-detect'); | |
| const [result, setResult] = useState(null); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(''); | |
| const [activeTab, setActiveTab] = useState('overview'); | |
| const handleRoast = async () => { | |
| if (!code.trim()) { setError('Paste some code first!'); return; } | |
| setLoading(true); setError(''); setResult(null); | |
| try { | |
| const data = await roastCode(code, language === 'Auto-detect' ? '' : language); | |
| setResult(data); | |
| setActiveTab('overview'); | |
| } catch (err) { setError(err.message); } | |
| finally { setLoading(false); } | |
| }; | |
| const getHireClass = (v) => { | |
| if (!v) return 'maybe'; | |
| if (v.includes('YES')) return 'yes'; | |
| if (v.includes('MAYBE')) return 'maybe'; | |
| return 'no'; | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className='app'> | |
| <h1><span>Code Roast Battle</span></h1> | |
| <div className='loading'> | |
| <div className='spinner'></div> | |
| <p>Chef CodeRamsay is performing deep analysis...</p> | |
| <p style={{ color: '#8b949e', marginTop: '0.5rem', fontSize: '0.9rem' }}>Security scan, performance review, clean code audit...</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const tabs = [ | |
| { id: 'overview', label: 'Overview' }, | |
| { id: 'security', label: 'Security' }, | |
| { id: 'performance', label: 'Performance' }, | |
| { id: 'cleancode', label: 'Clean Code' }, | |
| { id: 'issues', label: 'Issues' }, | |
| { id: 'tests', label: 'Tests' }, | |
| { id: 'rewrite', label: 'Rewrite' }, | |
| ]; | |
| if (result) { | |
| return ( | |
| <div className='app'> | |
| <h1><span>Code Roast Battle</span></h1> | |
| <div className='results'> | |
| <div className='opening-roast'>“{result.openingRoast}”</div> | |
| {/* Tab Navigation */} | |
| <div className='tab-nav'> | |
| {tabs.map(t => ( | |
| <button key={t.id} className={'tab-btn' + (activeTab === t.id ? ' active' : '')} onClick={() => setActiveTab(t.id)}>{t.label}</button> | |
| ))} | |
| </div> | |
| {/* OVERVIEW TAB */} | |
| {activeTab === 'overview' && ( | |
| <div> | |
| <div className='score-grid'> | |
| <div className='score-card'> | |
| <div className='score-big'>{result.overallScore}<span style={{fontSize:'1.2rem'}}>/100</span></div> | |
| <div style={{textAlign:'center',color:'#8b949e',fontSize:'0.85rem'}}>Overall Score</div> | |
| </div> | |
| {result.letterGrade && <div className='score-card'><GradeDisplay grade={result.letterGrade} /></div>} | |
| <div className='score-card'> | |
| <div className={'hire-badge ' + getHireClass(result.wouldHire)}>{result.wouldHire}</div> | |
| <div style={{color:'#8b949e',fontSize:'0.8rem',marginTop:'0.5rem'}}>{result.roastLevel}</div> | |
| </div> | |
| </div> | |
| {/* Score Overview Bars */} | |
| <div className='roast-card'> | |
| <div className='section-title'>Score Breakdown</div> | |
| {result.securityAnalysis && <ScoreBar label='Security' value={result.securityAnalysis.score} />} | |
| {result.performanceAnalysis && <ScoreBar label='Performance' value={result.performanceAnalysis.score} />} | |
| {result.bestPractices && <ScoreBar label='Best Practices' value={result.bestPractices.score} />} | |
| {result.cleanCodeScore != null && <ScoreBar label='Clean Code' value={result.cleanCodeScore} />} | |
| {result.maintainabilityIndex != null && <ScoreBar label='Maintainability' value={result.maintainabilityIndex} />} | |
| </div> | |
| {/* Dependency Analysis */} | |
| {result.dependencyAnalysis && ( | |
| <div className='roast-card'> | |
| <div className='section-title'>Dependency / Import Analysis</div> | |
| {result.dependencyAnalysis.unusedImports?.length > 0 && ( | |
| <div style={{marginBottom:'0.8rem'}}> | |
| <div style={{color:'#d29922',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Unused Imports</div> | |
| {result.dependencyAnalysis.unusedImports.map((u, i) => <span key={i} className='tag tag-warn'>{u}</span>)} | |
| </div> | |
| )} | |
| {result.dependencyAnalysis.missingImports?.length > 0 && ( | |
| <div style={{marginBottom:'0.8rem'}}> | |
| <div style={{color:'#da3633',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Missing Imports</div> | |
| {result.dependencyAnalysis.missingImports.map((m, i) => <span key={i} className='tag tag-error'>{m}</span>)} | |
| </div> | |
| )} | |
| {result.dependencyAnalysis.suggestions?.length > 0 && ( | |
| <div> | |
| <div style={{color:'#388bfd',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Suggestions</div> | |
| {result.dependencyAnalysis.suggestions.map((s, i) => <div key={i} style={{color:'#8b949e',fontSize:'0.85rem',marginBottom:'0.3rem'}}>- {s}</div>)} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* SECURITY TAB */} | |
| {activeTab === 'security' && result.securityAnalysis && ( | |
| <div> | |
| <div className='roast-card'> | |
| <div className='section-title'>Security Analysis</div> | |
| <ScoreBar label='Security Score' value={result.securityAnalysis.score} /> | |
| <p style={{color:'#8b949e',fontSize:'0.9rem',marginTop:'0.5rem',marginBottom:'1rem'}}>{result.securityAnalysis.summary}</p> | |
| {result.securityAnalysis.vulnerabilities?.map((v, i) => ( | |
| <div key={i} className='vuln-card'> | |
| <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:'0.5rem'}}> | |
| <span style={{color:'#f97316',fontWeight:700}}>{v.type}</span> | |
| <SeverityBadge level={v.severity} /> | |
| </div> | |
| <div style={{color:'#c9d1d9',fontSize:'0.9rem',marginBottom:'0.3rem'}}>{v.description}</div> | |
| {v.location && <div style={{color:'#8b949e',fontSize:'0.8rem',marginBottom:'0.3rem'}}>Location: <code>{v.location}</code></div>} | |
| <div style={{color:'#7ee787',fontSize:'0.85rem',marginBottom:'0.3rem'}}>Fix: {v.fix}</div> | |
| {v.roast && <div style={{color:'#f97316',fontStyle:'italic',fontSize:'0.85rem'}}>{v.roast}</div>} | |
| </div> | |
| ))} | |
| {(!result.securityAnalysis.vulnerabilities || result.securityAnalysis.vulnerabilities.length === 0) && ( | |
| <div style={{color:'#238636',textAlign:'center',padding:'1rem'}}>No security vulnerabilities detected. Chef is impressed... for once.</div> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| {/* PERFORMANCE TAB */} | |
| {activeTab === 'performance' && result.performanceAnalysis && ( | |
| <div> | |
| <div className='roast-card'> | |
| <div className='section-title'>Performance Analysis</div> | |
| <ScoreBar label='Performance Score' value={result.performanceAnalysis.score} /> | |
| <div style={{display:'flex',gap:'1.5rem',margin:'1rem 0',flexWrap:'wrap'}}> | |
| {result.performanceAnalysis.timeComplexity && ( | |
| <div className='complexity-badge'> | |
| <div style={{color:'#8b949e',fontSize:'0.75rem'}}>Time</div> | |
| <div style={{color:'#f97316',fontWeight:700,fontSize:'1.1rem'}}>{result.performanceAnalysis.timeComplexity}</div> | |
| </div> | |
| )} | |
| {result.performanceAnalysis.spaceComplexity && ( | |
| <div className='complexity-badge'> | |
| <div style={{color:'#8b949e',fontSize:'0.75rem'}}>Space</div> | |
| <div style={{color:'#388bfd',fontWeight:700,fontSize:'1.1rem'}}>{result.performanceAnalysis.spaceComplexity}</div> | |
| </div> | |
| )} | |
| </div> | |
| <p style={{color:'#8b949e',fontSize:'0.9rem',marginBottom:'1rem'}}>{result.performanceAnalysis.summary}</p> | |
| {result.performanceAnalysis.issues?.map((issue, i) => ( | |
| <div key={i} className='issue-card'> | |
| <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}> | |
| <span style={{color:'#c9d1d9',fontWeight:600}}>{issue.issue}</span> | |
| <SeverityBadge level={issue.impact} /> | |
| </div> | |
| <div style={{color:'#7ee787',fontSize:'0.85rem'}}>Suggestion: {issue.suggestion}</div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {/* CLEAN CODE TAB */} | |
| {activeTab === 'cleancode' && ( | |
| <div> | |
| {result.cleanCodeBreakdown && ( | |
| <div className='roast-card'> | |
| <div className='section-title'>Clean Code Breakdown (Robert C. Martin)</div> | |
| <ScoreBar label='Overall Clean Code Score' value={result.cleanCodeScore} /> | |
| <div style={{marginTop:'1rem'}}> | |
| <ScoreBar label='Meaningful Names' value={result.cleanCodeBreakdown.meaningfulNames} max={10} /> | |
| <ScoreBar label='Small Functions' value={result.cleanCodeBreakdown.smallFunctions} max={10} /> | |
| <ScoreBar label='Single Responsibility' value={result.cleanCodeBreakdown.singleResponsibility} max={10} /> | |
| <ScoreBar label='DRY Principle' value={result.cleanCodeBreakdown.dryPrinciple} max={10} /> | |
| <ScoreBar label='Error Handling' value={result.cleanCodeBreakdown.errorHandling} max={10} /> | |
| <ScoreBar label='Readability' value={result.cleanCodeBreakdown.readability} max={10} /> | |
| <ScoreBar label='Formatting' value={result.cleanCodeBreakdown.formatting} max={10} /> | |
| <ScoreBar label='Comments' value={result.cleanCodeBreakdown.comments} max={10} /> | |
| <ScoreBar label='No Side Effects' value={result.cleanCodeBreakdown.noSideEffects} max={10} /> | |
| <ScoreBar label='Testability' value={result.cleanCodeBreakdown.testability} max={10} /> | |
| </div> | |
| </div> | |
| )} | |
| {result.maintainabilityIndex != null && ( | |
| <div className='roast-card'> | |
| <div className='section-title'>Maintainability Index</div> | |
| <ScoreBar label='Maintainability' value={result.maintainabilityIndex} /> | |
| </div> | |
| )} | |
| {/* Best Practices */} | |
| {result.bestPractices?.violations?.length > 0 && ( | |
| <div className='roast-card'> | |
| <div className='section-title'>Best Practice Violations</div> | |
| {result.bestPractices.violations.map((v, i) => ( | |
| <div key={i} className='issue-card'> | |
| <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}> | |
| <span style={{color:'#c9d1d9',fontWeight:600}}>{v.practice}</span> | |
| <SeverityBadge level={v.severity} /> | |
| </div> | |
| <div style={{color:'#8b949e',fontSize:'0.85rem',marginBottom:'0.3rem'}}>{v.description}</div> | |
| <div style={{color:'#7ee787',fontSize:'0.85rem'}}>Fix: {v.fix}</div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {/* Code Smells */} | |
| {result.codeSmells?.length > 0 && ( | |
| <div className='roast-card'> | |
| <div className='section-title'>Code Smells</div> | |
| {result.codeSmells.map((s, i) => ( | |
| <div key={i} className='issue-card'> | |
| <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}> | |
| <span style={{color:'#f97316',fontWeight:600}}>{typeof s === 'string' ? s : s.smell}</span> | |
| {s.severity && <SeverityBadge level={s.severity} />} | |
| </div> | |
| {s.location && <div style={{color:'#8b949e',fontSize:'0.8rem'}}>Location: {s.location}</div>} | |
| {s.description && <div style={{color:'#8b949e',fontSize:'0.85rem',marginTop:'0.2rem'}}>{s.description}</div>} | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* ISSUES TAB */} | |
| {activeTab === "issues" && ( | |
| <div> | |
| {result.issues?.length > 0 && ( | |
| <div className="roast-card"> | |
| <div className="section-title">Issues Found</div> | |
| {result.issues.map((issue, i) => ( | |
| <div key={i} className="issue-card"> | |
| <div style={{display:"flex",justifyContent:"space-between",marginBottom:"0.5rem"}}> | |
| <code style={{color:"#8b949e"}}>{issue.line}</code> | |
| <SeverityBadge level={issue.severity} /> | |
| </div> | |
| <div className="roast-text">“{issue.roast}“</div> | |
| <div className="fix-text">Fix: {issue.fix}</div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* TESTS TAB */} | |
| {activeTab === "tests" && ( | |
| <div> | |
| {result.testSuggestions?.length > 0 && ( | |
| <div className="roast-card"> | |
| <div className="section-title">Suggested Tests</div> | |
| {result.testSuggestions.map((t, i) => ( | |
| <div key={i} className="test-card"> | |
| <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"0.3rem"}}> | |
| <span className={"test-type test-" + t.type}>{t.type}</span> | |
| <SeverityBadge level={t.priority} /> | |
| </div> | |
| <div style={{color:"#c9d1d9",fontSize:"0.9rem"}}>{t.description}</div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* REWRITE TAB */} | |
| {activeTab === "rewrite" && ( | |
| <div> | |
| {result.rewrittenCode && ( | |
| <div className="roast-card"> | |
| <div className="section-title">How It SHOULD Look</div> | |
| <pre className="rewritten-code">{result.rewrittenCode}</pre> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| <div className="closing-roast">“{result.closingRoast}“</div> | |
| <button className="btn back-btn" onClick={() => setResult(null)}>Roast More Code</button> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="app"> | |
| <h1><span>Code Roast Battle</span></h1> | |
| <p className="subtitle">“This code is so bad, even the compiler is crying!“ - Chef CodeRamsay</p> | |
| {error && <p style={{ color: "#ef4444", textAlign: "center", marginBottom: "1rem" }}>{error}</p>} | |
| <div className="editor-section"> | |
| <label>Language</label> | |
| <select value={language} onChange={e => setLanguage(e.target.value)}> | |
| {LANGUAGES.map(l => <option key={l}>{l}</option>)} | |
| </select> | |
| <label>Paste your code (if you dare)</label> | |
| <textarea className="code-input" placeholder="// Paste your code here..." value={code} onChange={e => setCode(e.target.value)} onKeyDown={e => { if (e.key === "Tab") { e.preventDefault(); const s = e.target.selectionStart; setCode(code.substring(0, s) + " " + code.substring(e.target.selectionEnd)); setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = s + 2; }, 0); }}} /> | |
| </div> | |
| <button className="btn" onClick={handleRoast}>ROAST MY CODE</button> | |
| </div> | |
| ); | |
| } | |