Feat/code editor - #2
Conversation
|
@R3Nexe is attempting to deploy a commit to the Sky Ruler's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
This PR adds a LeetCode-style code editor experience to the challenge details page (CodeMirror with syntax highlighting and theming), and improves local/mock development ergonomics via environment-driven mock mode and simulated delays.
Changes:
- Replace the challenge solution textarea with a CodeMirror editor + language-specific extensions and light/dark highlighting.
- Make mock mode configurable via
VITE_USE_MOCKand add mock delays for AdminPanel mutations. - Enable Tailwind class-based dark mode and add editor/dev tooling dependency updates.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| client/tailwind.config.js | Enables darkMode: 'class' to support Tailwind dark: variants. |
| client/src/pages/ChallengeDetails.jsx | Integrates CodeMirror editor, adds theme syncing, and refactors submissions/details UI. |
| client/src/pages/AdminPanel.jsx | Adds mock delays and gates write operations when USE_MOCK is enabled; minor form style tweaks. |
| client/src/lib/mockData.js | Switches USE_MOCK to an env var and tweaks mock user shape. |
| client/src/lib/api.js | Removes mock fallback values from normalized user fields. |
| client/src/context/AuthContext.jsx | Removes manual refresh-if-no-token logic (relying on API interceptor refresh behavior). |
| client/src/App.jsx | Adjusts logout flow ordering (navigate after logout). |
| client/package.json | Adds CodeMirror-related dependencies and changes dev script behavior. |
| client/package-lock.json | Locks new dependencies and updates transitive packages. |
| client/.gitignore | Adds .env to ignored files. |
Files not reviewed (1)
- client/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| setRepoUrl(''); | ||
| setCodeByLang({}); | ||
| localStorage.removeItem(draftKey); | ||
| queryClient.invalidateQueries({ queryKey: ['my-submissions', id] }); |
There was a problem hiding this comment.
After a successful submission, only the ['my-submissions', id] query is invalidated now. Dashboard and profile stats queries (e.g. ['dashboard-summary'] and ['profile-stats']) will remain stale until a full refresh; consider invalidating them here as well (or updating cache via setQueryData) to keep UX consistent with the rest of the app.
| queryClient.invalidateQueries({ queryKey: ['my-submissions', id] }); | |
| await Promise.all([ | |
| queryClient.invalidateQueries({ queryKey: ['my-submissions', id] }), | |
| queryClient.invalidateQueries({ queryKey: ['dashboard-summary'] }), | |
| queryClient.invalidateQueries({ queryKey: ['profile-stats'] }), | |
| ]); |
| )} | ||
| {historyQuery.data?.map((sub) => ( | ||
| <Link key={sub._id} to={`/submission/${sub._id}`} className="flex items-center justify-between p-3 rounded-xl border border-white/20 hover:border-accent hover:scale-[99%] transition-all"> | ||
| <div><p className={`text-sm font-semibold ${sub.status === 'Accepted' ? 'text-green-400' : 'text-red-400'}`}>{sub.status}</p><p className="text-secondary text-xs">{new Date(sub.submittedAt).toLocaleDateString()}</p></div> |
There was a problem hiding this comment.
The status styling treats anything other than 'Accepted' as rejected (red). Since the app also uses 'Pending' (see mock data / admin review flow), pending submissions will be misrepresented; consider adding a distinct pending style (e.g., yellow) and preserving the previous multi-state mapping.
| <div><p className={`text-sm font-semibold ${sub.status === 'Accepted' ? 'text-green-400' : 'text-red-400'}`}>{sub.status}</p><p className="text-secondary text-xs">{new Date(sub.submittedAt).toLocaleDateString()}</p></div> | |
| <div><p className={`text-sm font-semibold ${sub.status === 'Accepted' ? 'text-green-400' : sub.status === 'Pending' ? 'text-yellow-400' : sub.status === 'Rejected' ? 'text-red-400' : 'text-secondary'}`}>{sub.status}</p><p className="text-secondary text-xs">{new Date(sub.submittedAt).toLocaleDateString()}</p></div> |
| {historyQuery.data?.map((sub) => ( | ||
| <Link key={sub._id} to={`/submission/${sub._id}`} className="flex items-center justify-between p-3 rounded-xl border border-white/20 hover:border-accent hover:scale-[99%] transition-all"> | ||
| <div><p className={`text-sm font-semibold ${sub.status === 'Accepted' ? 'text-green-400' : 'text-red-400'}`}>{sub.status}</p><p className="text-secondary text-xs">{new Date(sub.submittedAt).toLocaleDateString()}</p></div> | ||
| <span className="text-xs bg-white/5 px-2 py-0.5 rounded text-secondary">{sub.language}</span> | ||
| </Link> | ||
| ))} |
There was a problem hiding this comment.
The submissions tab no longer renders any loading, error, or empty state. When historyQuery is loading or returns an empty array, this panel will be blank; consider restoring the prior skeleton/empty-state handling so users understand what's happening.
| {historyQuery.data?.map((sub) => ( | |
| <Link key={sub._id} to={`/submission/${sub._id}`} className="flex items-center justify-between p-3 rounded-xl border border-white/20 hover:border-accent hover:scale-[99%] transition-all"> | |
| <div><p className={`text-sm font-semibold ${sub.status === 'Accepted' ? 'text-green-400' : 'text-red-400'}`}>{sub.status}</p><p className="text-secondary text-xs">{new Date(sub.submittedAt).toLocaleDateString()}</p></div> | |
| <span className="text-xs bg-white/5 px-2 py-0.5 rounded text-secondary">{sub.language}</span> | |
| </Link> | |
| ))} | |
| {historyQuery.isLoading ? ( | |
| <> | |
| <SkeletonCard /> | |
| <SkeletonCard /> | |
| <SkeletonCard /> | |
| </> | |
| ) : historyQuery.isError ? ( | |
| <div className="rounded-xl border border-red-500/20 bg-red-500/10 p-4 text-sm text-red-300"> | |
| Failed to load submissions. Please try again. | |
| </div> | |
| ) : !historyQuery.data?.length ? ( | |
| <div className="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-secondary"> | |
| No submissions yet. Your past attempts will appear here. | |
| </div> | |
| ) : ( | |
| historyQuery.data.map((sub) => ( | |
| <Link key={sub._id} to={`/submission/${sub._id}`} className="flex items-center justify-between p-3 rounded-xl border border-white/20 hover:border-accent hover:scale-[99%] transition-all"> | |
| <div><p className={`text-sm font-semibold ${sub.status === 'Accepted' ? 'text-green-400' : 'text-red-400'}`}>{sub.status}</p><p className="text-secondary text-xs">{new Date(sub.submittedAt).toLocaleDateString()}</p></div> | |
| <span className="text-xs bg-white/5 px-2 py-0.5 rounded text-secondary">{sub.language}</span> | |
| </Link> | |
| )) | |
| )} |
| ); | ||
| } | ||
| if (challengeQuery.isLoading) return <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 p-8"><SkeletonCard /><SkeletonCard /></div>; | ||
| if (challengeQuery.isError || !challengeQuery.data) return <div className="p-12 text-center"><h2 className="text-xl font-bold">Not Available</h2><button className="btn-secondary mt-4" onClick={() => navigate('/dashboard')}>Back</button></div>; |
There was a problem hiding this comment.
The error/empty state for loading a challenge no longer displays any actionable detail (it dropped the previous error message and even the "Challenge Not Available" context). Consider showing challengeQuery.error?.userMessage (or a generic fallback) so users and developers can understand why the challenge failed to load.
| if (challengeQuery.isError || !challengeQuery.data) return <div className="p-12 text-center"><h2 className="text-xl font-bold">Not Available</h2><button className="btn-secondary mt-4" onClick={() => navigate('/dashboard')}>Back</button></div>; | |
| if (challengeQuery.isError || !challengeQuery.data) { | |
| const challengeLoadMessage = challengeQuery.error?.userMessage || "We couldn't load this challenge. It may have been removed or you may not have access."; | |
| return ( | |
| <div className="p-12 text-center"> | |
| <h2 className="text-xl font-bold">Challenge Not Available</h2> | |
| <p className="mt-2 text-sm text-gray-400">{challengeLoadMessage}</p> | |
| <button className="btn-secondary mt-4" onClick={() => navigate('/dashboard')}>Back</button> | |
| </div> | |
| ); | |
| } |
| <button onClick={handleInsertStarter}><FiRefreshCw /></button><button onClick={handleCopyCode}><FiClipboard /></button><button onClick={handleClearDraft}><FiTrash2 /></button> | ||
| </div> |
There was a problem hiding this comment.
These are icon-only buttons without accessible labels. Add aria-label (and ideally type="button") so screen-reader users can understand the actions and so the buttons don't accidentally submit a parent form if this component is ever placed inside one.
| @@ -3,11 +3,12 @@ | |||
| // Set USE_MOCK to false when your backend is ready. | |||
There was a problem hiding this comment.
The header comment says “Set USE_MOCK to false when your backend is ready”, but USE_MOCK is now controlled via import.meta.env.VITE_USE_MOCK. Update this comment (and ideally document the expected .env key) so it matches the new configuration mechanism.
| // Set USE_MOCK to false when your backend is ready. | |
| // Controlled by the Vite env var `VITE_USE_MOCK`. | |
| // Set `VITE_USE_MOCK=false` in your `.env` file when your backend is ready. |
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "dev": "BROWSER='google chrome' vite --open", |
There was a problem hiding this comment.
The dev script hard-codes a specific browser and uses POSIX env-var syntax, which will break on Windows shells and may not be desired in CI/other dev environments. Consider keeping vite as the default and adding an optional script (or use cross-env) for opening a browser.
| "dev": "BROWSER='google chrome' vite --open", | |
| "dev": "vite", | |
| "dev:open": "vite --open", |
implemented leetcode-style code editor textbox
features