diff --git a/src/App.tsx b/src/App.tsx index 76bf989..61c06e3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,11 +7,13 @@ import { Footer } from './components/layout/Footer'; import { HomeView } from './views/HomeView'; import { BuyerView } from './views/BuyerView'; import { SellerView } from './views/SellerView'; +import { TeachersView } from './views/TeachersView'; import { CourseDetailView } from './views/CourseDetailView'; import { CreateCourseView } from './views/CreateCourseView'; import { AdminDashboardView } from './views/AdminDashboardView'; import { LoginView } from './views/LoginView'; import { RegisterView } from './views/RegisterView'; +import { TermsView } from './views/TermsView'; import { CourseModal } from './components/buyer/CourseModal'; import { CartDrawer } from './components/buyer/CartDrawer'; import { CreateCourseModal } from './components/seller/CreateCourseModal'; @@ -19,7 +21,9 @@ import { FiCheckCircle } from 'react-icons/fi'; export function App() { const location = useLocation(); - const isAuthPage = location.pathname === '/login' || location.pathname === '/register'; + const isAuthPage = location.pathname === '/login' || location.pathname === '/register' || location.pathname === '/terms'; + const isDashboardPage = location.pathname === '/admin' || location.pathname === '/teachers'; + const hideNavbarFooter = isAuthPage || isDashboardPage; const [courses, setCourses] = useState(mockCourses); const [cartItems, setCartItems] = useState([]); @@ -27,6 +31,7 @@ export function App() { // Modals & Drawers state const [selectedCourseForModal, setSelectedCourseForModal] = useState(null); + const [isTeacherPreviewModal, setIsTeacherPreviewModal] = useState(false); const [isCartOpen, setIsCartOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); @@ -95,18 +100,18 @@ export function App() { const cartCourseIds = cartItems.map((i) => i.course.id); return ( -
+
{/* Toast notification banner */} {toastMessage && ( -
- +
+ {toastMessage}
)} - {/* Main Navigation Bar (hidden on Auth pages) */} - {!isAuthPage && ( + {/* Main Navigation Bar (hidden on Auth & Dashboard pages) */} + {!hideNavbarFooter && ( { const target = courses.find((c) => c.id === courseId) || courses[0]; setSelectedCourseForModal(target); + setIsTeacherPreviewModal(false); + }} + onOpenDetails={(course) => { + setSelectedCourseForModal(course); + setIsTeacherPreviewModal(false); }} - onOpenDetails={(course) => setSelectedCourseForModal(course)} onClearSearch={() => setSearchQuery('')} /> } @@ -146,7 +155,10 @@ export function App() { cartCourseIds={cartCourseIds} searchQuery={searchQuery} onAddToCart={handleAddToCart} - onOpenDetails={(course) => setSelectedCourseForModal(course)} + onOpenDetails={(course) => { + setSelectedCourseForModal(course); + setIsTeacherPreviewModal(false); + }} onClearSearch={() => setSearchQuery('')} /> } @@ -172,13 +184,31 @@ export function App() { /> } /> + setIsCreateModalOpen(true)} + onPreviewCourse={(course) => { + setSelectedCourseForModal(course); + setIsTeacherPreviewModal(true); + }} + onDeleteCourse={handleDeleteCourse} + onShowToast={showToast} + /> + } + /> setIsCreateModalOpen(true)} - onPreviewCourse={(course) => setSelectedCourseForModal(course)} + onPreviewCourse={(course) => { + setSelectedCourseForModal(course); + setIsTeacherPreviewModal(true); + }} onDeleteCourse={handleDeleteCourse} /> } @@ -192,6 +222,10 @@ export function App() { /> } /> + } + /> } @@ -208,7 +242,11 @@ export function App() { course={selectedCourseForModal} isOpen={!!selectedCourseForModal} isInCart={selectedCourseForModal ? cartCourseIds.includes(selectedCourseForModal.id) : false} - onClose={() => setSelectedCourseForModal(null)} + isTeacherPreview={isTeacherPreviewModal} + onClose={() => { + setSelectedCourseForModal(null); + setIsTeacherPreviewModal(false); + }} onAddToCart={handleAddToCart} onBuyNow={handleBuyNow} /> @@ -229,8 +267,8 @@ export function App() { onCreateCourse={handleCreateCourse} /> - {/* Global Footer (hidden on Auth pages) */} - {!isAuthPage &&
} + {/* Global Footer (hidden on Auth & Dashboard pages) */} + {!hideNavbarFooter &&
}
); diff --git a/src/components/buyer/CourseModal.tsx b/src/components/buyer/CourseModal.tsx index 5e6b808..be8eee1 100644 --- a/src/components/buyer/CourseModal.tsx +++ b/src/components/buyer/CourseModal.tsx @@ -11,7 +11,8 @@ import { FiShield, FiChevronDown, FiChevronUp, - FiShoppingCart + FiShoppingCart, + FiLock } from 'react-icons/fi'; import type { Course } from '../../types/course'; @@ -19,6 +20,7 @@ interface CourseModalProps { course: Course | null; isOpen: boolean; isInCart: boolean; + isTeacherPreview?: boolean; onClose: () => void; onAddToCart: (course: Course) => void; onBuyNow: (course: Course) => void; @@ -28,6 +30,7 @@ export const CourseModal: React.FC = ({ course, isOpen, isInCart, + isTeacherPreview = false, onClose, onAddToCart, onBuyNow, @@ -53,24 +56,29 @@ export const CourseModal: React.FC = ({ return (
- {/* Modal Container */} + {/* Modal Container with full Light / Dark Mode support */}
e.stopPropagation()} > {/* Top Header Bar */} -
+
- + {course.category} - • Nivel: {course.level} + • Nivel: {course.level} + {isTeacherPreview && ( + + Vista Previa de Docente + + )}
+ {/* If isTeacherPreview: Disable Buy & Cart buttons */} + {isTeacherPreview ? ( +
+ - -
+ +
+ ) : ( +
+ -
+ +
+ )} + +
- {course.duration} de video bajo demanda + {course.duration} de video bajo demanda
- {course.lessonsCount} lecciones descargables + {course.lessonsCount} lecciones descargables
- Garantía de 30 días o te devolvemos el dinero + Garantía de 30 días o te devolvemos el dinero
- Certificado de acreditación oficial + Certificado de acreditación oficial
@@ -227,13 +255,13 @@ export const CourseModal: React.FC = ({
{/* Nav Tabs inside modal */} -
+
{isExpanded && ( -
+
{mod.lessons.map((lesson) => (
-
-
+
+
{lesson.title} @@ -301,11 +329,11 @@ export const CourseModal: React.FC = ({
{lesson.isFreePreview && ( - + Vista Previa )} - {lesson.duration} + {lesson.duration}
))} @@ -320,7 +348,7 @@ export const CourseModal: React.FC = ({ {/* Tab Content 2: Instructor */} {activeTab === 'instructor' && ( -
+
= ({ className="w-16 h-16 rounded-full object-cover ring-2 ring-cyan-500/50" />
-

{course.instructor.name}

-

{course.instructor.role}

+

{course.instructor.name}

+

{course.instructor.role}

-
- +
+ {course.instructor.rating} Calificación 👨‍🎓 {course.instructor.totalStudents.toLocaleString()} Estudiantes @@ -341,7 +369,7 @@ export const CourseModal: React.FC = ({
-

+

{course.instructor.bio}

@@ -351,7 +379,7 @@ export const CourseModal: React.FC = ({ {activeTab === 'reviews' && (
{course.reviews.map((rev) => ( -
+
= ({ className="w-8 h-8 rounded-full object-cover" />
-

{rev.userName}

+

{rev.userName}

{rev.date}
@@ -372,7 +400,7 @@ export const CourseModal: React.FC = ({
-

+

"{rev.comment}"

diff --git a/src/components/common/BrandLogo.tsx b/src/components/common/BrandLogo.tsx index 00fc6dc..4523b49 100644 --- a/src/components/common/BrandLogo.tsx +++ b/src/components/common/BrandLogo.tsx @@ -21,7 +21,7 @@ export const BrandLogo: React.FC = ({ size = 'md', showSubtitle return (
{/* Official PNG Logo Image with sleek container styling */} -
+
RyCourses Emblem = ({ size = 'md', showSubtitle {/* Typography */}
- - RyCourses + + RyCourses
{showSubtitle && ( -

Plataforma Educativa

+

Plataforma Educativa

)}
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 2bc164b..d076ff4 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router'; import { FiGlobe, FiShield, @@ -74,8 +75,8 @@ export const Footer: React.FC = () => {
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index f0d9831..96d1f86 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -1,19 +1,16 @@ - import React, { useState, useEffect } from 'react'; import { useNavigate, useLocation } from 'react-router'; import { - FiSearch, FiShoppingCart, FiMenu, FiX, - FiPlusCircle, FiHome, FiGrid, - FiUser, - FiShield, + FiUser } from 'react-icons/fi'; import { FaChalkboardTeacher, FaSignOutAlt } from 'react-icons/fa'; import { BrandLogo } from '../common/BrandLogo'; +import { ThemeToggle, Button } from '../ui'; import type { User } from '../../types/course'; interface NavbarProps { @@ -31,15 +28,13 @@ export const Navbar: React.FC = ({ onLogout, cartCount, onOpenCart, - searchQuery, - onSearchChange, }) => { const navigate = useNavigate(); const location = useLocation(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [cartHovered, setCartHovered] = useState(false); - // Inject keyframe animation styles inline (no external CSS needed) + // Inject keyframe animation styles inline useEffect(() => { const styleId = 'cart-run-keyframes'; if (!document.getElementById(styleId)) { @@ -64,17 +59,9 @@ export const Navbar: React.FC = ({ }, []); const currentPath = location.pathname; - const isSellerMode = currentPath === '/seller' || currentPath.startsWith('/seller/'); - - const handleSearchInputChange = (e: React.ChangeEvent) => { - onSearchChange(e.target.value); - if (currentPath !== '/courses' && e.target.value.trim() !== '') { - navigate('/courses'); - } - }; return ( -
+
@@ -84,14 +71,15 @@ export const Navbar: React.FC = ({
- {/* Quick Nav Links */} + {/* Main Nav Links */}
- {/* 3. Right Actions & Role Switcher */} -
- - {/* Buyer vs Seller Switcher pill */} -
- - - -
- - {/* If seller mode: Create Course button */} - {isSellerMode && ( - - )} + {/* 2. Right Actions & ThemeToggle Beside Cart */} +
{/* Login & Register Buttons OR User Profile */} {!currentUser ? ( -
- - +
) : ( -
+
-

{currentUser.name}

-

{currentUser.role === 'buyer' ? 'Estudiante' : 'Instructor'}

+

+ {currentUser.name} +

+

+ {currentUser.role === 'buyer' ? 'Estudiante' : 'Instructor'} +

{currentUser.avatar ? ( @@ -193,18 +152,18 @@ export const Navbar: React.FC = ({ className="w-9 h-9 rounded-full object-cover ring-2 ring-indigo-500/50 group-hover:ring-cyan-400 transition" /> ) : ( -
+
{currentUser.name.charAt(0).toUpperCase()}
)} - +
{/* Dropdown Menu */} -
+
- @@ -213,16 +172,17 @@ export const Navbar: React.FC = ({
)} - {/* Cart button (Buyer mode) - Fixed Far Right, inline animation */} - {!isSellerMode && ( + {/* ThemeToggle and Cart Button Grouped Together */} +
+ + - )} +
{/* Mobile menu trigger */}
- {/* Mobile search & menu expanded */} + {/* Mobile menu expanded */} {mobileMenuOpen && ( -
-
- - -
- +
-
+
{!currentUser ? ( <> @@ -365,36 +297,27 @@ export const Navbar: React.FC = ({ ) : ( <> -
+
{currentUser.avatar ? ( Avatar ) : ( -
+
{currentUser.name.charAt(0).toUpperCase()}
)}
-

{currentUser.name}

+

{currentUser.name}

)}
- - {isSellerMode && ( - - )}
)}
diff --git a/src/components/seller/CreateCourseModal.tsx b/src/components/seller/CreateCourseModal.tsx index 1e60270..3cf2743 100644 --- a/src/components/seller/CreateCourseModal.tsx +++ b/src/components/seller/CreateCourseModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import { FiX, FiCheck, @@ -7,7 +7,12 @@ import { FiBookOpen, FiDollarSign, FiPlus, - FiCheckCircle + FiCheckCircle, + FiUploadCloud, + FiVideo, + FiFilm, + FiLink, + FiImage } from 'react-icons/fi'; import type { Course, CourseLevel } from '../../types/course'; import { mockInstructors } from '../../data/mockCourses'; @@ -24,6 +29,8 @@ export const CreateCourseModal: React.FC = ({ onCreateCourse, }) => { const [step, setStep] = useState<1 | 2 | 3>(1); + const fileInputRef = useRef(null); + const imageInputRef = useRef(null); // Form State const [title, setTitle] = useState(''); @@ -32,10 +39,22 @@ export const CreateCourseModal: React.FC = ({ const [level, setLevel] = useState('Todos los niveles'); const [price, setPrice] = useState('29.99'); const [originalPrice, setOriginalPrice] = useState('99.99'); + + // Thumbnail Image Drag & Drop State + const [thumbnailFile, setThumbnailFile] = useState(null); const [thumbnail, setThumbnail] = useState( 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=800&auto=format&fit=crop&q=80' ); + const [duration, setDuration] = useState('20h 30min'); + + // Video Upload States + const [videoFile, setVideoFile] = useState(null); + const [videoPreviewUrl, setVideoPreviewUrl] = useState(''); + const [videoEmbedUrl, setVideoEmbedUrl] = useState(''); + const [isUploading, setIsUploading] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); + const [whatYouWillLearn, setWhatYouWillLearn] = useState([ 'Aprenderás conceptos clave y buenas prácticas de la industria', 'Construirás proyectos prácticos de portafolio para conseguir empleo' @@ -44,6 +63,77 @@ export const CreateCourseModal: React.FC = ({ if (!isOpen) return null; + const handleCloseModal = () => { + setStep(1); + setTitle(''); + setSubtitle(''); + setVideoFile(null); + setVideoPreviewUrl(''); + setVideoEmbedUrl(''); + setThumbnailFile(null); + setUploadProgress(0); + setIsUploading(false); + onClose(); + }; + + const handleNextStep = () => { + if (step === 1 && !title.trim()) { + setTitle('Nuevo Curso Especializado'); + } + if (step < 3) { + setStep((s) => (s + 1) as 1 | 2 | 3); + } + }; + + const handlePrevStep = () => { + if (step > 1) { + setStep((s) => (s - 1) as 1 | 2 | 3); + } + }; + + const handleImageFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setThumbnailFile(file); + const objectUrl = URL.createObjectURL(file); + setThumbnail(objectUrl); + }; + + const handleRemoveThumbnail = () => { + setThumbnailFile(null); + setThumbnail('https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=800&auto=format&fit=crop&q=80'); + if (imageInputRef.current) imageInputRef.current.value = ''; + }; + + const handleVideoFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setVideoFile(file); + setIsUploading(true); + setUploadProgress(20); + + const interval = setInterval(() => { + setUploadProgress((prev) => { + if (prev >= 100) { + clearInterval(interval); + setIsUploading(false); + setVideoPreviewUrl(URL.createObjectURL(file)); + return 100; + } + return prev + 25; + }); + }, 200); + }; + + const handleRemoveVideo = () => { + setVideoFile(null); + setVideoPreviewUrl(''); + setUploadProgress(0); + if (fileInputRef.current) fileInputRef.current.value = ''; + }; + const handleAddOutcome = () => { if (newOutcome.trim()) { setWhatYouWillLearn([...whatYouWillLearn, newOutcome.trim()]); @@ -55,9 +145,7 @@ export const CreateCourseModal: React.FC = ({ setWhatYouWillLearn(whatYouWillLearn.filter((_, i) => i !== index)); }; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - + const handleFinalPublish = () => { const createdCourse: Course = { id: `course-${Date.now()}`, title: title || 'Nuevo Curso Especializado', @@ -103,88 +191,88 @@ export const CreateCourseModal: React.FC = ({ }; onCreateCourse(createdCourse); - onClose(); + handleCloseModal(); }; return (
e.stopPropagation()} > {/* Header */} -
+
-
+
-

Publicar Nuevo Curso

-

Paso {step} de 3

+

Publicar Nuevo Curso & Cargar Videos

+

Paso {step} de 3

{/* Step Progress Bar */} -
+
- {/* Form Body */} -
+ {/* Modal Container Body */} +
{/* Step 1: Basic Information */} {step === 1 && (
-
-