fix(dashboard): MRR calculation and user menu

This commit is contained in:
Sidney Gomes 2026-07-22 21:28:39 -03:00
parent 8236ade11a
commit cc9b906eeb
3 changed files with 98 additions and 7 deletions

View File

@ -803,13 +803,15 @@ app.get('/api/admin/dashboard', authenticateToken, requireAdmin, async (req: Aut
let mrr = 0;
for (const userId of activeUsersIds) {
const studentPayments = allPayments.filter(p => p.userId === userId && (p.status === 'CONFIRMED' || p.status === 'RECEIVED'));
if (studentPayments.length > 0) {
const latest = studentPayments[0];
if (latest.planId && latest.plan) {
if (latest.plan.cycle === 'MONTHLY') mrr += latest.plan.price;
else if (latest.plan.cycle === 'YEARLY') mrr += (latest.plan.price / 12);
} else if (!latest.courseId) {
mrr += latest.value;
// Find the latest payment that is a subscription (either a plan or no courseId)
const latestSubscription = studentPayments.find(p => p.planId || (!p.courseId && !p.planId));
if (latestSubscription) {
if (latestSubscription.planId && latestSubscription.plan) {
if (latestSubscription.plan.cycle === 'MONTHLY') mrr += latestSubscription.plan.price;
else if (latestSubscription.plan.cycle === 'YEARLY') mrr += (latestSubscription.plan.price / 12);
} else if (!latestSubscription.courseId) {
mrr += latestSubscription.value;
}
}
}

View File

@ -220,6 +220,13 @@ export default function Navbar({ user, activeView, setView, onLogout }: NavbarPr
<User className="w-4 h-4" />
<span>Meus dados</span>
</button>
<button
onClick={() => { setView('certificates'); setShowProfileMenu(false); }}
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
>
<GraduationCap className="w-4 h-4" />
<span>Meu Certificado</span>
</button>
<button
onClick={() => { setView('help'); setShowProfileMenu(false); }}
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"

82
src/routes/userRoutes.ts Normal file
View File

@ -0,0 +1,82 @@
import { Router, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import multer from 'multer';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
// Mock types since we will split these out
interface AuthenticatedRequest extends Request {
user?: any;
}
const prisma = new PrismaClient();
const router = Router();
const upload = multer({ storage: multer.memoryStorage() });
const s3 = new S3Client({
region: 'us-east-1',
endpoint: 'https://s3-estudo.microtecinformaticacurso.com.br',
credentials: {
accessKeyId: 'microtecflix_admin',
secretAccessKey: 'MicrotecFlixS3SecurePass2026!'
},
forcePathStyle: true
});
const authenticateToken = (req: any, res: any, next: any) => {
// Logic will be in server.ts or a middleware file, assuming it's passed down
next();
};
router.put('/profile', async (req: AuthenticatedRequest, res: Response) => {
const { name, whatsapp, birthDate, cpf } = req.body;
const userId = req.user!.id;
try {
const user = await prisma.user.update({
where: { id: userId },
data: {
...(name !== undefined && { name }),
...(whatsapp !== undefined && { whatsapp }),
...(birthDate !== undefined && { birthDate }),
...(cpf !== undefined && { cpf })
}
});
res.json({ success: true, user });
} catch (error) {
res.status(404).json({ error: 'Usuário não encontrado' });
}
});
router.post('/avatar', upload.single('avatar'), async (req: AuthenticatedRequest, res: Response) => {
const userId = req.user!.id;
if (!req.file) {
res.status(400).json({ error: 'Nenhum arquivo enviado' });
return;
}
const ext = req.file.originalname.split('.').pop();
const filename = `avatars/${userId}_${Date.now()}.${ext}`;
try {
await s3.send(new PutObjectCommand({
Bucket: 'microtecflix',
Key: filename,
Body: req.file.buffer,
ContentType: req.file.mimetype
}));
const avatarUrl = `https://s3-estudo.microtecinformaticacurso.com.br/microtecflix/${filename}`;
await prisma.user.update({
where: { id: userId },
data: { avatarUrl }
});
res.json({ success: true, avatarUrl });
} catch (err: any) {
console.error('Erro no upload da foto:', err);
res.status(500).json({ error: 'Erro ao salvar foto no MinIO' });
}
});
export default router;