Skip to content

Commit 0bd298a

Browse files
committed
feat: implement core dashboard functionality with role-based navigation and management pages
1 parent 51addf0 commit 0bd298a

6 files changed

Lines changed: 358 additions & 92 deletions

File tree

client/src/App.jsx

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const AppContent = () => {
5151
const [subModalOpen, setSubModalOpen] = useState(false);
5252
const [requiredTier, setRequiredTier] = useState('basic');
5353
const [timeLeft, setTimeLeft] = useState(null);
54+
const [navBlocker, setNavBlocker] = useState(null); // { message, onConfirm, onCancel }
5455

5556
const isSubscriptionExpired = user && user.subscriptionTier !== 'none' && user.subscriptionExpiresAt && new Date(user.subscriptionExpiresAt).getTime() < Date.now();
5657

@@ -64,31 +65,43 @@ const AppContent = () => {
6465
const targetPath = href.replace(window.location.origin, '');
6566
if (targetPath === window.location.pathname) return;
6667

67-
const confirmed = window.confirm(window.activeSessionBlocker.message);
68-
if (!confirmed) {
69-
e.preventDefault();
70-
e.stopPropagation();
71-
} else {
72-
if (typeof window.activeSessionBlocker.onConfirm === 'function') {
73-
window.activeSessionBlocker.onConfirm();
68+
e.preventDefault();
69+
e.stopPropagation();
70+
71+
setNavBlocker({
72+
message: window.activeSessionBlocker.message,
73+
onConfirm: () => {
74+
if (typeof window.activeSessionBlocker.onConfirm === 'function') {
75+
window.activeSessionBlocker.onConfirm();
76+
}
77+
window.activeSessionBlocker = null;
78+
window.location.href = href;
79+
},
80+
onCancel: () => {
81+
setNavBlocker(null);
7482
}
75-
window.activeSessionBlocker = null;
76-
}
83+
});
7784
}
7885
}
7986
};
8087

8188
const handlePopState = (e) => {
8289
if (window.activeSessionBlocker) {
83-
const confirmed = window.confirm(window.activeSessionBlocker.message);
84-
if (!confirmed) {
85-
window.history.pushState(null, null, window.location.pathname);
86-
} else {
87-
if (typeof window.activeSessionBlocker.onConfirm === 'function') {
88-
window.activeSessionBlocker.onConfirm();
90+
window.history.pushState(null, null, window.location.pathname);
91+
92+
setNavBlocker({
93+
message: window.activeSessionBlocker.message,
94+
onConfirm: () => {
95+
if (typeof window.activeSessionBlocker.onConfirm === 'function') {
96+
window.activeSessionBlocker.onConfirm();
97+
}
98+
window.activeSessionBlocker = null;
99+
window.history.go(-2);
100+
},
101+
onCancel: () => {
102+
setNavBlocker(null);
89103
}
90-
window.activeSessionBlocker = null;
91-
}
104+
});
92105
}
93106
};
94107

@@ -308,6 +321,48 @@ const AppContent = () => {
308321
/>
309322
</Routes>
310323

324+
{/* Nice custom Confirmation / Warning Dialog */}
325+
{navBlocker && (
326+
<div className="fixed inset-0 z-[10000] bg-dark-950/70 backdrop-blur-md flex items-center justify-center p-4 animate-fade-in">
327+
<div className="max-w-md w-full bg-white dark:bg-dark-900 border border-dark-200 dark:border-dark-800 rounded-3xl p-6 text-center shadow-2xl relative overflow-hidden">
328+
{/* Background design glow */}
329+
<div className="absolute -right-10 -top-10 w-32 h-32 bg-primary-500/10 rounded-full blur-2xl pointer-events-none" />
330+
<div className="absolute -left-10 -bottom-10 w-32 h-32 bg-rose-500/10 rounded-full blur-2xl pointer-events-none" />
331+
332+
{/* Warning Icon */}
333+
<div className="w-14 h-14 rounded-2xl bg-amber-500/10 text-amber-500 flex items-center justify-center mx-auto mb-4 border border-amber-500/25 animate-bounce-slow">
334+
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
335+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
336+
</svg>
337+
</div>
338+
339+
<h3 className="text-lg font-bold text-dark-900 dark:text-white mb-2 font-display">Unsaved Session Warning</h3>
340+
<p className="text-xs text-dark-550 dark:text-dark-400 mb-6 leading-relaxed">
341+
{navBlocker.message}
342+
</p>
343+
344+
<div className="flex gap-3">
345+
<button
346+
onClick={navBlocker.onCancel}
347+
className="flex-1 py-3 rounded-xl border border-dark-200 dark:border-dark-850 hover:bg-dark-50 dark:hover:bg-dark-800 text-dark-700 dark:text-dark-300 text-xs font-semibold cursor-pointer transition-colors"
348+
>
349+
Stay & Resume
350+
</button>
351+
<button
352+
onClick={() => {
353+
const onConfirm = navBlocker.onConfirm;
354+
setNavBlocker(null);
355+
onConfirm();
356+
}}
357+
className="flex-1 py-3 rounded-xl bg-amber-500 hover:bg-amber-600 text-white text-xs font-semibold cursor-pointer shadow-lg shadow-amber-500/10 transition-colors"
358+
>
359+
Yes, Leave Page
360+
</button>
361+
</div>
362+
</div>
363+
</div>
364+
)}
365+
311366
<SubscriptionModal
312367
isOpen={subModalOpen}
313368
onClose={() => {

client/src/pages/AIInterview.jsx

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const AIInterview = () => {
8282
const [difficulty, setDifficulty] = useState('Medium');
8383
const [yoe, setYoe] = useState('1-3 years');
8484
const [targetRole, setTargetRole] = useState('Software Engineer');
85+
const [confirmModal, setConfirmModal] = useState(null); // { title, message, onConfirm }
8586

8687
useEffect(() => {
8788
const updateVoices = () => {
@@ -456,10 +457,12 @@ const AIInterview = () => {
456457
// Speech recognition helper
457458
const startListening = () => {
458459
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
459-
if (!SpeechRecognition) {
460-
alert('Speech recognition not supported in this browser. Please use Google Chrome or Safari.');
460+
setConfirmModal({
461+
type: 'alert',
462+
title: 'Speech Recognition Unsupported',
463+
message: 'Speech recognition is not supported in this browser. Please use Google Chrome or Safari for the best experience.'
464+
});
461465
return;
462-
}
463466

464467
window.speechSynthesis.cancel();
465468
setIsAiSpeaking(false);
@@ -535,27 +538,30 @@ const AIInterview = () => {
535538
};
536539

537540
const handleExitSession = () => {
538-
const confirmed = window.confirm("Warning: You are currently in an active interview session. Leaving will immediately terminate the interview, and all progress will be lost. Are you sure you want to exit?");
539-
if (!confirmed) return;
540-
541-
window.speechSynthesis.cancel();
542-
if (idleTimerRef.current) {
543-
clearTimeout(idleTimerRef.current);
544-
idleTimerRef.current = null;
545-
}
546-
if (recognitionRef.current) {
547-
try {
548-
recognitionRef.current.stop();
549-
} catch (e) {
550-
console.error(e);
541+
setConfirmModal({
542+
title: 'End Active Interview?',
543+
message: 'Warning: You are currently in an active interview session. Leaving will immediately terminate the interview, and all progress will be lost. Are you sure you want to exit?',
544+
onConfirm: () => {
545+
window.speechSynthesis.cancel();
546+
if (idleTimerRef.current) {
547+
clearTimeout(idleTimerRef.current);
548+
idleTimerRef.current = null;
549+
}
550+
if (recognitionRef.current) {
551+
try {
552+
recognitionRef.current.stop();
553+
} catch (e) {
554+
console.error(e);
555+
}
556+
recognitionRef.current = null;
557+
}
558+
setStage('idle');
559+
setQuestion('');
560+
setAnswer('');
561+
setFeedback(null);
562+
setSessionLogs([]);
551563
}
552-
recognitionRef.current = null;
553-
}
554-
setStage('idle');
555-
setQuestion('');
556-
setAnswer('');
557-
setFeedback(null);
558-
setSessionLogs([]);
564+
});
559565
};
560566

561567
const generateQuestion = async () => {
@@ -1091,17 +1097,21 @@ JSON Evaluation:`;
10911097
};
10921098

10931099
const clearHistory = async () => {
1094-
if (window.confirm('Are you sure you want to clear your entire interview log history?')) {
1095-
try {
1096-
await interviewAPI.clear();
1097-
setHistory([]);
1098-
localStorage.removeItem('codeforge_interview_history');
1099-
showToast('Interview log history cleared');
1100-
} catch (err) {
1101-
console.error('Failed to clear interview logs from DB', err);
1102-
showToast('Failed to clear history. Try again.');
1100+
setConfirmModal({
1101+
title: 'Clear History?',
1102+
message: 'Are you sure you want to clear your entire interview log history? This action cannot be undone.',
1103+
onConfirm: async () => {
1104+
try {
1105+
await interviewAPI.clear();
1106+
setHistory([]);
1107+
localStorage.removeItem('codeforge_interview_history');
1108+
showToast('Interview log history cleared');
1109+
} catch (err) {
1110+
console.error('Failed to clear interview logs from DB', err);
1111+
showToast('Failed to clear history. Try again.');
1112+
}
11031113
}
1104-
}
1114+
});
11051115
};
11061116

11071117
// Trigger evaluation when answer is set via useEffect
@@ -2095,6 +2105,46 @@ JSON Evaluation:`;
20952105
onClose={() => setShowReviewModal(false)}
20962106
defaultTriggerAction="interview"
20972107
/>
2108+
2109+
{confirmModal && (
2110+
<div className="fixed inset-0 z-[100] bg-dark-950/70 backdrop-blur-md flex items-center justify-center p-4 animate-fade-in">
2111+
<div className="max-w-md w-full bg-white dark:bg-dark-900 border border-dark-200 dark:border-dark-800 rounded-3xl p-6 text-center shadow-2xl relative overflow-hidden">
2112+
{/* Background design glow */}
2113+
<div className="absolute -right-10 -top-10 w-32 h-32 bg-primary-500/10 rounded-full blur-2xl pointer-events-none" />
2114+
<div className="absolute -left-10 -bottom-10 w-32 h-32 bg-rose-500/10 rounded-full blur-2xl pointer-events-none" />
2115+
2116+
{/* Warning Icon */}
2117+
<div className="w-14 h-14 rounded-2xl bg-amber-500/10 text-amber-500 flex items-center justify-center mx-auto mb-4 border border-amber-500/25 animate-bounce-slow">
2118+
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2119+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
2120+
</svg>
2121+
</div>
2122+
2123+
<h3 className="text-lg font-bold text-dark-900 dark:text-white mb-2">{confirmModal.title}</h3>
2124+
<p className="text-xs text-dark-550 dark:text-dark-400 mb-6 leading-relaxed">
2125+
{confirmModal.message}
2126+
</p>
2127+
2128+
<div className="flex gap-3">
2129+
<button
2130+
onClick={() => setConfirmModal(null)}
2131+
className="flex-1 py-3 rounded-xl border border-dark-200 dark:border-dark-850 hover:bg-dark-50 dark:hover:bg-dark-800 text-dark-700 dark:text-dark-300 text-xs font-semibold cursor-pointer transition-colors"
2132+
>
2133+
Cancel
2134+
</button>
2135+
<button
2136+
onClick={() => {
2137+
confirmModal.onConfirm();
2138+
setConfirmModal(null);
2139+
}}
2140+
className="flex-1 py-3 rounded-xl bg-amber-500 hover:bg-amber-600 text-white text-xs font-semibold cursor-pointer shadow-lg shadow-amber-500/10 transition-colors"
2141+
>
2142+
Confirm
2143+
</button>
2144+
</div>
2145+
</div>
2146+
</div>
2147+
)}
20982148
</>
20992149
);
21002150
};

client/src/pages/CollaborativeWorkspace.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -938,11 +938,15 @@ export default function CollaborativeWorkspace() {
938938
{/* Exit Room */}
939939
<button
940940
onClick={() => {
941-
const confirmed = window.confirm("Warning: Leaving this room will disconnect you from the editor, voice channel, and active workspace. Are you sure you want to exit?");
942-
if (confirmed) {
943-
setInLobby(true);
944-
navigate('/code-editor');
945-
}
941+
setModalConfig({
942+
type: 'confirm',
943+
title: 'Leave Workspace?',
944+
message: 'Warning: Leaving this room will disconnect you from the editor, voice channel, and active workspace. Are you sure you want to exit?',
945+
onConfirm: () => {
946+
setInLobby(true);
947+
navigate('/code-editor');
948+
}
949+
});
946950
}}
947951
className="px-3 py-1.5 rounded-xl bg-rose-600/10 hover:bg-rose-600/20 border border-rose-500/20 text-xs text-rose-400 flex items-center gap-1.5 transition"
948952
>

client/src/pages/Dashboard.jsx

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const Dashboard = () => {
8484
const [submitting, setSubmitting] = useState(false);
8585
const [uploading, setUploading] = useState(false);
8686
const [imagePreview, setImagePreview] = useState(null);
87+
const [confirmModal, setConfirmModal] = useState(null); // { title, message, onConfirm }
8788

8889
useEffect(() => {
8990
if (!authLoading) {
@@ -211,25 +212,29 @@ const Dashboard = () => {
211212
}
212213
};
213214

214-
const handleDelete = async (type, id) => {
215-
if (!confirm('Are you sure you want to delete this item?')) return;
216-
217-
try {
218-
if (type === 'project') {
219-
await projectsAPI.delete(id);
220-
} else if (type === 'certificate') {
221-
await certificatesAPI.delete(id);
222-
} else if (type === 'contact') {
223-
await contactAPI.delete(id);
224-
} else if (type === 'learningTopic') {
225-
await learningAPI.delete(id);
226-
} else if (type === 'task') {
227-
await tasksAPI.delete(id);
215+
const handleDelete = (type, id) => {
216+
setConfirmModal({
217+
title: 'Delete Item?',
218+
message: 'Are you sure you want to delete this item? This action cannot be undone.',
219+
onConfirm: async () => {
220+
try {
221+
if (type === 'project') {
222+
await projectsAPI.delete(id);
223+
} else if (type === 'certificate') {
224+
await certificatesAPI.delete(id);
225+
} else if (type === 'contact') {
226+
await contactAPI.delete(id);
227+
} else if (type === 'learningTopic') {
228+
await learningAPI.delete(id);
229+
} else if (type === 'task') {
230+
await tasksAPI.delete(id);
231+
}
232+
fetchAllData();
233+
} catch (error) {
234+
console.error('Delete error:', error);
235+
}
228236
}
229-
fetchAllData();
230-
} catch (error) {
231-
console.error('Delete error:', error);
232-
}
237+
});
233238
};
234239

235240
const handleUpdateStatus = async (id, newStatus) => {
@@ -1376,6 +1381,46 @@ const Dashboard = () => {
13761381
<span className="font-semibold text-sm">{toast}</span>
13771382
</div>
13781383
)}
1384+
1385+
{confirmModal && (
1386+
<div className="fixed inset-0 z-[100] bg-dark-950/70 backdrop-blur-md flex items-center justify-center p-4 animate-fade-in">
1387+
<div className="max-w-md w-full bg-white dark:bg-dark-900 border border-dark-200 dark:border-dark-800 rounded-3xl p-6 text-center shadow-2xl relative overflow-hidden">
1388+
{/* Background design glow */}
1389+
<div className="absolute -right-10 -top-10 w-32 h-32 bg-primary-500/10 rounded-full blur-2xl pointer-events-none" />
1390+
<div className="absolute -left-10 -bottom-10 w-32 h-32 bg-rose-500/10 rounded-full blur-2xl pointer-events-none" />
1391+
1392+
{/* Warning Icon */}
1393+
<div className="w-14 h-14 rounded-2xl bg-amber-500/10 text-amber-500 flex items-center justify-center mx-auto mb-4 border border-amber-500/25 animate-bounce-slow">
1394+
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1395+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
1396+
</svg>
1397+
</div>
1398+
1399+
<h3 className="text-lg font-bold text-dark-900 dark:text-white mb-2">{confirmModal.title}</h3>
1400+
<p className="text-xs text-dark-550 dark:text-dark-400 mb-6 leading-relaxed">
1401+
{confirmModal.message}
1402+
</p>
1403+
1404+
<div className="flex gap-3">
1405+
<button
1406+
onClick={() => setConfirmModal(null)}
1407+
className="flex-1 py-3 rounded-xl border border-dark-200 dark:border-dark-850 hover:bg-dark-50 dark:hover:bg-dark-800 text-dark-700 dark:text-dark-300 text-xs font-semibold cursor-pointer transition-colors"
1408+
>
1409+
Cancel
1410+
</button>
1411+
<button
1412+
onClick={() => {
1413+
confirmModal.onConfirm();
1414+
setConfirmModal(null);
1415+
}}
1416+
className="flex-1 py-3 rounded-xl bg-amber-500 hover:bg-amber-600 text-white text-xs font-semibold cursor-pointer shadow-lg shadow-amber-500/10 transition-colors"
1417+
>
1418+
Confirm
1419+
</button>
1420+
</div>
1421+
</div>
1422+
</div>
1423+
)}
13791424
</div>
13801425
</div>
13811426
);

0 commit comments

Comments
 (0)