feat: add superadmin management
This commit is contained in:
@@ -4,7 +4,6 @@ import { Section, Course } from '../types';
|
||||
import { getCourses } from '../services/db';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { Loader2, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { CATEGORIES } from '../constants';
|
||||
|
||||
interface CourseGridProps {
|
||||
onCourseSelect?: (course: Course) => void;
|
||||
@@ -100,6 +99,9 @@ export const CourseGrid: React.FC<CourseGridProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const categories = Array.from(new Set<string>(courses.map((course) => course.category)))
|
||||
.sort((left, right) => left.localeCompare(right, 'pt-BR'));
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-64 w-full">
|
||||
@@ -108,6 +110,17 @@ export const CourseGrid: React.FC<CourseGridProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (courses.length === 0) {
|
||||
return (
|
||||
<section className="min-h-[60vh] flex items-center justify-center px-6 pt-28 pb-20">
|
||||
<div className="max-w-md text-center rounded-2xl border border-white/10 bg-zinc-950/80 p-8">
|
||||
<h2 className="text-xl font-bold text-white">Cursos em breve</h2>
|
||||
<p className="mt-3 text-sm leading-relaxed text-gray-400">Ainda não há cursos publicados. Cadastre o primeiro curso no Painel do Instrutor.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Filter courses if a specific category is active
|
||||
const filteredCourses = activeCategory === 'all'
|
||||
? courses
|
||||
@@ -178,7 +191,7 @@ export const CourseGrid: React.FC<CourseGridProps> = ({
|
||||
Todos os Cursos ({courses.length})
|
||||
</button>
|
||||
|
||||
{CATEGORIES.map(cat => {
|
||||
{categories.map(cat => {
|
||||
const count = courses.filter(c => c.category === cat).length;
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Play, Plus, Sparkles } from 'lucide-react';
|
||||
import { FEATURED_COURSE } from '../constants';
|
||||
import { Course } from '../types';
|
||||
import { getCourses } from '../services/db';
|
||||
|
||||
interface HeroProps {
|
||||
onPlay?: (course: Course) => void;
|
||||
}
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ onPlay }) => {
|
||||
const [featuredCourse, setFeaturedCourse] = useState<Course | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getCourses().then((courses) => setFeaturedCourse(courses[0] || null)).catch(() => setFeaturedCourse(null));
|
||||
}, []);
|
||||
|
||||
if (!featuredCourse) return null;
|
||||
|
||||
return (
|
||||
<div className="relative w-full min-h-[75vh] lg:min-h-[82vh] max-h-[800px] h-auto overflow-hidden group flex flex-col justify-end pt-28 pb-12 sm:pb-16 px-6 md:px-12">
|
||||
{/* Background Image with Cinematic Gradient Overlay */}
|
||||
<div className="absolute inset-0 transition-transform duration-[20s] ease-in-out group-hover:scale-105">
|
||||
<img
|
||||
src={FEATURED_COURSE.thumbnail}
|
||||
alt="Featured Course"
|
||||
src={featuredCourse.thumbnail}
|
||||
alt={featuredCourse.title}
|
||||
className="w-full h-full object-cover object-center"
|
||||
/>
|
||||
{/* Gradients for text readability and seamless transition */}
|
||||
@@ -33,26 +40,26 @@ export const Hero: React.FC<HeroProps> = ({ onPlay }) => {
|
||||
<Sparkles className="w-3 h-3 fill-current" />
|
||||
Destaque
|
||||
</span>
|
||||
<span className="text-gray-300">{FEATURED_COURSE.category}</span>
|
||||
<span className="text-gray-300">{featuredCourse.category}</span>
|
||||
<span className="text-gray-500">•</span>
|
||||
<span className="text-gray-300">{FEATURED_COURSE.duration}</span>
|
||||
<span className="text-gray-300">{featuredCourse.duration}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Display Headline */}
|
||||
<h1 className="font-display text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight drop-shadow-2xl mb-4">
|
||||
{FEATURED_COURSE.title}
|
||||
{featuredCourse.title}
|
||||
</h1>
|
||||
|
||||
{/* Subhead / Lead Description */}
|
||||
<p className="text-sm sm:text-base md:text-lg font-normal text-gray-200 line-clamp-2 sm:line-clamp-3 max-w-2xl leading-relaxed opacity-90 mb-6 sm:mb-8">
|
||||
{FEATURED_COURSE.description}
|
||||
{featuredCourse.description}
|
||||
</p>
|
||||
|
||||
{/* Action Buttons with comfortable width, borders, and spacing */}
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{/* Primary CTA */}
|
||||
<button
|
||||
onClick={() => onPlay?.(FEATURED_COURSE)}
|
||||
onClick={() => onPlay?.(featuredCourse)}
|
||||
className="flex items-center justify-center gap-3 bg-white text-black px-8 sm:px-10 py-3.5 sm:py-4 rounded-[980px] text-sm sm:text-base font-bold hover:bg-gray-100 active:scale-95 transition-all duration-200 shadow-xl min-w-[200px]"
|
||||
>
|
||||
<Play className="w-4 h-4 sm:w-5 sm:h-5 fill-current" />
|
||||
@@ -72,4 +79,3 @@ export const Hero: React.FC<HeroProps> = ({ onPlay }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X, Loader2, GraduationCap, School, KeyRound, UserPlus } from 'lucide-react';
|
||||
import { X, Loader2, GraduationCap, School, UserPlus } from 'lucide-react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { UserRole } from '../types';
|
||||
@@ -45,7 +45,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signedInUser.role !== role) {
|
||||
if (signedInUser.role !== 'superadmin' && signedInUser.role !== role) {
|
||||
logout();
|
||||
setError(role === 'professor'
|
||||
? 'Esta conta não possui acesso de instrutor.'
|
||||
@@ -54,20 +54,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
||||
}
|
||||
|
||||
onClose();
|
||||
navigate(signedInUser.role === 'professor' ? '/gerenciar' : '/');
|
||||
};
|
||||
|
||||
const handleDemoLogin = (roleType: UserRole) => {
|
||||
setRole(roleType);
|
||||
setIsRegistering(false);
|
||||
if (roleType === 'professor') {
|
||||
setEmail('admin@compor.local');
|
||||
setPassword('ComporAdmin123!');
|
||||
} else {
|
||||
setEmail('student@compor.local');
|
||||
setPassword('ComporStudent123!');
|
||||
}
|
||||
setError('');
|
||||
navigate(signedInUser.role === 'superadmin' ? '/admin' : signedInUser.role === 'professor' ? '/gerenciar' : '/');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -103,17 +90,6 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
|
||||
<input type="password" placeholder="Sua senha" value={password} onChange={(event) => setPassword(event.target.value)} minLength={12} className="w-full bg-white/10 text-white placeholder:text-white/30 px-4 py-3 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:bg-white/15 transition-all text-body tracking-body" required />
|
||||
</div>
|
||||
|
||||
{!isRegistering && (
|
||||
<div className="flex gap-2 justify-center">
|
||||
<button type="button" onClick={() => handleDemoLogin('student')} className="text-caption font-semibold tracking-caption text-orange-400 hover:text-orange-300 px-3 py-1 rounded-[980px] bg-orange-500/10 border border-orange-500/20 flex items-center gap-1 transition-colors">
|
||||
<KeyRound className="w-3 h-3" /> Acesso Aluno (Demo)
|
||||
</button>
|
||||
<button type="button" onClick={() => handleDemoLogin('professor')} className="text-caption font-semibold tracking-caption text-emerald-400 hover:text-emerald-300 px-3 py-1 rounded-[980px] bg-emerald-500/10 border border-emerald-500/20 flex items-center gap-1 transition-colors">
|
||||
<KeyRound className="w-3 h-3" /> Acesso Instrutor (Demo)
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="bg-red-500/10 border border-red-500/20 rounded-[8px] p-3"><p className="text-red-400 text-caption font-semibold tracking-caption text-center">{error}</p></div>}
|
||||
|
||||
<button type="submit" disabled={isSubmitting} className="w-full bg-white text-black text-body-sm font-semibold tracking-body-sm py-3.5 rounded-[980px] hover:bg-gray-200 active:scale-95 transition-all duration-200 flex items-center justify-center gap-2 shadow-xl">
|
||||
|
||||
@@ -100,7 +100,18 @@ export const Navbar: React.FC<NavbarProps> = ({ onPlay }) => {
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex items-center gap-3">
|
||||
{user.role === 'professor' && (
|
||||
{user.role === 'superadmin' ? (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className={`text-sm font-semibold px-5 py-2.5 rounded-[980px] transition-colors ${
|
||||
location.pathname === '/admin'
|
||||
? 'bg-orange-500/20 text-orange-400 border border-orange-500/30'
|
||||
: 'text-gray-300 hover:text-white bg-white/5 hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
Painel Administrativo
|
||||
</button>
|
||||
) : user.role === 'professor' && (
|
||||
<button
|
||||
onClick={() => navigate('/gerenciar')}
|
||||
className={`text-sm font-semibold px-5 py-2.5 rounded-[980px] transition-colors ${
|
||||
@@ -140,4 +151,3 @@ export const Navbar: React.FC<NavbarProps> = ({ onPlay }) => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user