From b76ae84bf67824859ff0f9a46ce982cd57f9d0df Mon Sep 17 00:00:00 2001 From: Miguel Ortega Date: Mon, 24 Aug 2026 22:03:16 -0500 Subject: [PATCH] fix al la posicion de donde se encuentra el carrito de compra y animacion al carrito al hacer hover --- src/App.tsx | 24 ++- src/components/layout/Navbar.tsx | 298 ++++++++++++++++++++++--------- src/index.css | 72 ++++++++ src/types/course.ts | 8 + src/views/LoginView.tsx | 30 ++-- src/views/RegisterView.tsx | 11 +- 6 files changed, 347 insertions(+), 96 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8f6f56b..64f8a40 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Routes, Route } from 'react-router'; -import type { Course, CartItem } from './types/course'; +import type { Course, CartItem, User } from './types/course'; import { mockCourses } from './data/mockCourses'; import { Navbar } from './components/layout/Navbar'; import { Footer } from './components/layout/Footer'; @@ -27,6 +27,22 @@ export function App() { const [isCartOpen, setIsCartOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + // Auth State + const [currentUser, setCurrentUser] = useState(() => { + const savedUser = localStorage.getItem('rycourses_user'); + return savedUser ? JSON.parse(savedUser) : null; + }); + + const handleLogin = (user: User) => { + setCurrentUser(user); + localStorage.setItem('rycourses_user', JSON.stringify(user)); + }; + + const handleLogout = () => { + setCurrentUser(null); + localStorage.removeItem('rycourses_user'); + }; + // Toast feedback state const [toastMessage, setToastMessage] = useState(null); @@ -88,6 +104,8 @@ export function App() { {/* Main Navigation Bar */} setIsCartOpen(true)} onOpenCreateCourseModal={() => setIsCreateModalOpen(true)} @@ -171,11 +189,11 @@ export function App() { /> } + element={} /> } + element={} /> diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index f143801..31e30ae 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -1,20 +1,23 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useNavigate, useLocation } from 'react-router'; -import { - FiSearch, - FiShoppingCart, - FiMenu, - FiX, - FiPlusCircle, +import { + FiSearch, + FiShoppingCart, + FiMenu, + FiX, + FiPlusCircle, FiHome, FiGrid, FiUser, FiShield, } from 'react-icons/fi'; -import { FaChalkboardTeacher } from 'react-icons/fa'; +import { FaChalkboardTeacher, FaSignOutAlt } from 'react-icons/fa'; import { BrandLogo } from '../common/BrandLogo'; +import type { User } from '../../types/course'; interface NavbarProps { + currentUser: User | null; + onLogout: () => void; cartCount: number; onOpenCart: () => void; onOpenCreateCourseModal?: () => void; @@ -23,6 +26,8 @@ interface NavbarProps { } export const Navbar: React.FC = ({ + currentUser, + onLogout, cartCount, onOpenCart, searchQuery, @@ -31,6 +36,31 @@ export const Navbar: React.FC = ({ 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) + useEffect(() => { + const styleId = 'cart-run-keyframes'; + if (!document.getElementById(styleId)) { + const style = document.createElement('style'); + style.id = styleId; + style.textContent = ` + @keyframes cart-run { + 0% { transform: rotate(0deg) translateY(0px); } + 25% { transform: rotate(-8deg) translateY(-1px); } + 50% { transform: rotate(0deg) translateY(0px); } + 75% { transform: rotate(-5deg) translateY(-1px); } + 100% { transform: rotate(0deg) translateY(0px); } + } + @keyframes speed-streak { + 0% { opacity: 0; transform: translateX(2px) scaleX(0.1); } + 40% { opacity: 0.6; transform: translateX(-3px) scaleX(1); } + 100% { opacity: 0; transform: translateX(-6px) scaleX(0); } + } + `; + document.head.appendChild(style); + } + }, []); const currentPath = location.pathname; const isSellerMode = currentPath === '/seller' || currentPath.startsWith('/seller/'); @@ -46,7 +76,7 @@ export const Navbar: React.FC = ({
- + {/* 1. Left Brand & Main Links */}
navigate('/')}> @@ -57,11 +87,10 @@ export const Navbar: React.FC = ({
- - + {!currentUser ? ( + <> + + + + ) : ( + <> +
+ {currentUser.avatar ? ( + Avatar + ) : ( +
+ {currentUser.name.charAt(0).toUpperCase()} +
+ )} +
+

{currentUser.name}

+
+
+ + + )}
{isSellerMode && ( diff --git a/src/index.css b/src/index.css index 4555db1..c0afe3b 100644 --- a/src/index.css +++ b/src/index.css @@ -24,4 +24,76 @@ ::-webkit-scrollbar-thumb:hover { background: #475569; +} + +/* Cart Running Animation */ +@keyframes cart-run { + 0% { + transform: translateX(0) rotate(0deg) translateY(0); + } + 20% { + transform: translateX(3px) rotate(-10deg) translateY(-2px); + } + 40% { + transform: translateX(1px) rotate(-5deg) translateY(1px); + } + 60% { + transform: translateX(4px) rotate(-12deg) translateY(-2px); + } + 80% { + transform: translateX(2px) rotate(-8deg) translateY(0px); + } + 100% { + transform: translateX(0) rotate(0deg) translateY(0); + } +} + +.animate-cart-run { + animation: cart-run 0.5s ease-in-out infinite; +} + +/* Speed Streaks for Cart */ +@keyframes speed-streak { + 0% { + opacity: 0; + transform: translateX(0) scaleX(0.2); + } + 50% { + opacity: 0.8; + transform: translateX(-8px) scaleX(1); + } + 100% { + opacity: 0; + transform: translateX(-16px) scaleX(0); + } +} + +.speed-line { + position: absolute; + height: 2px; + background: theme('colors.cyan.400'); + border-radius: 9999px; + opacity: 0; + pointer-events: none; +} + +.group-hover\\:animate-cart-run:hover .speed-line-1 { + animation: speed-streak 0.6s infinite linear; + top: 30%; + right: 110%; + width: 12px; +} + +.group-hover\\:animate-cart-run:hover .speed-line-2 { + animation: speed-streak 0.5s infinite linear 0.2s; + top: 60%; + right: 100%; + width: 8px; +} + +.group-hover\\:animate-cart-run:hover .speed-line-3 { + animation: speed-streak 0.7s infinite linear 0.4s; + bottom: 10%; + right: 120%; + width: 14px; } \ No newline at end of file diff --git a/src/types/course.ts b/src/types/course.ts index 6ee14a6..fff31b3 100644 --- a/src/types/course.ts +++ b/src/types/course.ts @@ -1,5 +1,13 @@ export type CourseLevel = 'Principiante' | 'Intermedio' | 'Avanzado' | 'Todos los niveles'; +export interface User { + id: string; + name: string; + email: string; + role: 'buyer' | 'seller' | 'admin'; + avatar?: string; +} + export interface Lesson { id: string; title: string; diff --git a/src/views/LoginView.tsx b/src/views/LoginView.tsx index c193a59..c207e3e 100644 --- a/src/views/LoginView.tsx +++ b/src/views/LoginView.tsx @@ -3,22 +3,36 @@ import { useNavigate, Link } from 'react-router'; import { BrandLogo } from '../components/common/BrandLogo'; import { FiMail, FiLock, FiArrowRight } from 'react-icons/fi'; import { FaGoogle, FaGithub } from 'react-icons/fa'; +import type { User } from '../types/course'; interface LoginViewProps { onShowToast: (msg: string) => void; + onLogin: (user: User) => void; } -export const LoginView: React.FC = ({ onShowToast }) => { +export const LoginView: React.FC = ({ onShowToast, onLogin }) => { const navigate = useNavigate(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [rememberMe, setRememberMe] = useState(true); + const handleMockLogin = (name: string, role: 'buyer' | 'seller', avatarUrl?: string) => { + const mockUser: User = { + id: Math.random().toString(36).substr(2, 9), + name, + email: email || 'usuario@demo.com', + role, + avatar: avatarUrl, + }; + onLogin(mockUser); + onShowToast(`¡Bienvenido de nuevo a RyCourses, ${name}!`); + navigate('/'); + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (email && password) { - onShowToast('¡Bienvenido de nuevo a RyCourses!'); - navigate('/'); + handleMockLogin('Usuario Demo', 'buyer', 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80'); } }; @@ -47,10 +61,7 @@ export const LoginView: React.FC = ({ onShowToast }) => {