From 2ef1416d8d9e8c257b8510074280419c74c62600 Mon Sep 17 00:00:00 2001 From: Sidney Gomes Date: Mon, 20 Jul 2026 20:45:44 -0300 Subject: [PATCH] feat: navbar menu order, notifications, user profile, help view and module activities --- server.ts | 59 ++++++++ src/App.tsx | 18 +++ src/components/AdminContentManager.tsx | 45 +++++++ src/components/AdminDashboard.tsx | 11 ++ src/components/HelpView.tsx | 62 +++++++++ src/components/Navbar.tsx | 178 +++++++++++++++++++++---- src/components/ProfileView.tsx | 113 ++++++++++++++++ src/db/db.ts | 9 +- src/types.ts | 18 ++- 9 files changed, 485 insertions(+), 28 deletions(-) create mode 100644 src/components/HelpView.tsx create mode 100644 src/components/ProfileView.tsx diff --git a/server.ts b/server.ts index 16f930f..ecfaa5b 100644 --- a/server.ts +++ b/server.ts @@ -1212,6 +1212,65 @@ app.get('/api/certificates', authenticateToken, (req: AuthenticatedRequest, res: res.json(myCerts); }); +// --- NOTIFICATIONS --- +app.get('/api/notifications', authenticateToken, (req: AuthenticatedRequest, res: Response) => { + const db = loadDb(); + const myNotifications = db.notifications.filter(n => n.userId === req.user!.id || n.userId === 'ALL'); + res.json(myNotifications.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())); +}); + +app.put('/api/notifications/:id/read', authenticateToken, (req: AuthenticatedRequest, res: Response) => { + const db = loadDb(); + const notification = db.notifications.find(n => n.id === req.params.id); + if (notification && (notification.userId === req.user!.id || notification.userId === 'ALL')) { + notification.read = true; + saveDb(db); + } + res.json({ success: true }); +}); + +// --- HELP DESK --- +app.get('/api/help', authenticateToken, (req: AuthenticatedRequest, res: Response) => { + const db = loadDb(); + res.json({ helpText: db.settings?.helpText || '' }); +}); + +app.put('/api/admin/help', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { + const db = loadDb(); + if (!db.settings) db.settings = {}; + db.settings.helpText = req.body.helpText; + saveDb(db); + res.json({ success: true }); +}); + +// --- MODULE ACTIVITIES (ADMIN) --- +app.get('/api/admin/modules/:moduleId/activity', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { + const db = loadDb(); + const activity = db.activities.find(a => a.moduleId === req.params.moduleId); + res.json(activity || { moduleId: req.params.moduleId, questions: [] }); +}); + +app.put('/api/admin/modules/:moduleId/activity', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { + const db = loadDb(); + const { moduleId } = req.params; + const { questions } = req.body; + + let activity = db.activities.find(a => a.moduleId === moduleId); + if (!activity) { + activity = { + id: `act_${Math.random().toString(36).substring(2, 9)}`, + moduleId, + questions: [], + createdAt: new Date().toISOString() + }; + db.activities.push(activity); + } + + activity.questions = questions; + saveDb(db); + res.json(activity); +}); + // --- VITE MIDDLEWARE / SPA FALLBACK --- async function startServer() { if (process.env.NODE_ENV !== 'production') { diff --git a/src/App.tsx b/src/App.tsx index fa803c9..5dd0393 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,8 @@ import CourseView from './components/CourseView'; import SubscriptionView from './components/SubscriptionView'; import CourseUnlockModal from './components/CourseUnlockModal'; import CertificatesTab from './components/CertificatesTab'; +import ProfileView from './components/ProfileView'; +import HelpView from './components/HelpView'; import { User as UserType, Course as CourseType, SubscriptionStatus } from './types'; export default function App() { @@ -261,6 +263,22 @@ export default function App() { /> )} + {/* RENDER VIEW: PROFILE */} + {activeView === 'profile' && ( + setView('home')} + /> + )} + + {/* RENDER VIEW: HELP */} + {activeView === 'help' && ( + setView('home')} + /> + )} + {/* Unlock Modal */} diff --git a/src/components/AdminContentManager.tsx b/src/components/AdminContentManager.tsx index fc8993c..bffde60 100644 --- a/src/components/AdminContentManager.tsx +++ b/src/components/AdminContentManager.tsx @@ -62,6 +62,8 @@ export default function AdminContentManager({ token }: AdminContentManagerProps) title: '', videoUrl: '', videoType: 'youtube' as 'youtube' | 'direct', order: 1, content: '' }); + const [managingActivityForModule, setManagingActivityForModule] = useState(null); + const [showActivityModal, setShowActivityModal] = useState(false); const [activeModuleForActivity, setActiveModuleForActivity] = useState(null); const [activityQuestions, setActivityQuestions] = useState<{question: string, options: string[], correctIndex: number}[]>([]); @@ -297,6 +299,49 @@ export default function AdminContentManager({ token }: AdminContentManagerProps) document.getElementById('admin-content-top')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 50); }; + const handleManageActivity = async (moduleId: string) => { + setActiveModuleForActivity(moduleId); + setActivityQuestions([]); + + try { + const res = await fetch(`/api/admin/modules/${moduleId}/activity`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + if (res.ok) { + const data = await res.json(); + setActivityQuestions(data.questions || []); + } + } catch (err) { + console.error('Error fetching activity:', err); + } + + setShowActivityModal(true); + }; + + const handleSaveActivity = async () => { + if (!activeModuleForActivity) return; + + try { + const res = await fetch(`/api/admin/modules/${activeModuleForActivity}/activity`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ questions: activityQuestions }) + }); + if (res.ok) { + alert('Atividade salva com sucesso!'); + setShowActivityModal(false); + } else { + alert('Erro ao salvar atividade.'); + } + } catch (err) { + console.error('Error saving activity:', err); + alert('Erro ao salvar atividade.'); + } + }; + const handleDeleteLesson = async (lessonId: string) => { if (!window.confirm('Excluir esta aula?')) return; diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 6264505..21a64de 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -764,6 +764,17 @@ export default function AdminDashboard({ token }: AdminDashboardProps) { />

Dias grátis que um novo aluno ganha ao se cadastrar.

+ +
+ +