feat: reorganización de categorías y refactorización de CourseGrid
This commit is contained in:
1 parent
7a5786eaa8
commit
ae415fef4e
8 files changed
+621
-248
No files matched your search
Generated
+13
@@ -62,6 +62,7 @@
|
|||||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.7",
|
"@babel/generator": "^7.29.7",
|
||||||
@@ -1324,6 +1325,7 @@
|
|||||||
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
|
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.18.0"
|
"undici-types": "~7.18.0"
|
||||||
}
|
}
|
||||||
@@ -1334,6 +1336,7 @@
|
|||||||
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -1393,6 +1396,7 @@
|
|||||||
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.67.0",
|
"@typescript-eslint/scope-manager": "8.67.0",
|
||||||
"@typescript-eslint/types": "8.67.0",
|
"@typescript-eslint/types": "8.67.0",
|
||||||
@@ -1627,6 +1631,7 @@
|
|||||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -1717,6 +1722,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.11.12",
|
"baseline-browser-mapping": "^2.11.12",
|
||||||
"caniuse-lite": "^1.0.30001809",
|
"caniuse-lite": "^1.0.30001809",
|
||||||
@@ -1877,6 +1883,7 @@
|
|||||||
"integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==",
|
"integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
@@ -2778,6 +2785,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -2838,6 +2846,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||||
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -2847,6 +2856,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||||
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -3039,6 +3049,7 @@
|
|||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -3124,6 +3135,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
|
||||||
"integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
|
"integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.33.0",
|
"lightningcss": "^1.33.0",
|
||||||
"picomatch": "^4.0.5",
|
"picomatch": "^4.0.5",
|
||||||
@@ -3248,6 +3260,7 @@
|
|||||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
+536
-181
@@ -1,16 +1,23 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
FiFilter,
|
FiFilter,
|
||||||
FiFlag,
|
|
||||||
FiStar,
|
|
||||||
FiPercent,
|
|
||||||
FiSpeaker,
|
|
||||||
FiLayers,
|
FiLayers,
|
||||||
FiRefreshCw
|
FiSearch,
|
||||||
|
FiCode,
|
||||||
|
FiShield,
|
||||||
|
FiTrendingUp,
|
||||||
|
FiChevronRight,
|
||||||
|
FiGlobe,
|
||||||
|
FiFeather,
|
||||||
|
FiActivity,
|
||||||
|
FiUsers,
|
||||||
|
FiZap,
|
||||||
|
FiBriefcase,
|
||||||
|
FiMonitor,
|
||||||
|
FiGrid
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import type { Course, ActiveTabFilter } from '../../types/course';
|
import type { Course } from '../../types/course';
|
||||||
import { CourseCard } from './CourseCard';
|
import { CourseCard } from './CourseCard';
|
||||||
import { CategoryPills } from './CategoryPills';
|
|
||||||
|
|
||||||
interface CourseGridProps {
|
interface CourseGridProps {
|
||||||
courses: Course[];
|
courses: Course[];
|
||||||
@@ -19,6 +26,7 @@ interface CourseGridProps {
|
|||||||
onAddToCart: (course: Course) => void;
|
onAddToCart: (course: Course) => void;
|
||||||
onOpenDetails: (course: Course) => void;
|
onOpenDetails: (course: Course) => void;
|
||||||
onClearSearch: () => void;
|
onClearSearch: () => void;
|
||||||
|
showBanners?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CourseGrid: React.FC<CourseGridProps> = ({
|
export const CourseGrid: React.FC<CourseGridProps> = ({
|
||||||
@@ -29,18 +37,139 @@ export const CourseGrid: React.FC<CourseGridProps> = ({
|
|||||||
onOpenDetails,
|
onOpenDetails,
|
||||||
onClearSearch,
|
onClearSearch,
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState<ActiveTabFilter>('all');
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string>('Todos');
|
const [selectedCategory, setSelectedCategory] = useState<string>('Todos');
|
||||||
|
const [selectedSubcategory, setSelectedSubcategory] = useState<string>('Todos');
|
||||||
|
const [selectedPopularArea, setSelectedPopularArea] = useState<string>('Todos');
|
||||||
const [selectedLevel, setSelectedLevel] = useState<string>('Todos');
|
const [selectedLevel, setSelectedLevel] = useState<string>('Todos');
|
||||||
|
const [categorySearch, setCategorySearch] = useState<string>('');
|
||||||
const [sortBy, setSortBy] = useState<'rating' | 'price-asc' | 'price-desc' | 'students'>('students');
|
const [sortBy, setSortBy] = useState<'rating' | 'price-asc' | 'price-desc' | 'students'>('students');
|
||||||
|
const [localSearch, setLocalSearch] = useState('');
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
|
||||||
|
// Sync local search when global searchQuery prop changes (e.g. from navbar or clear button)
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalSearch(searchQuery);
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
// Reset subcategory and level when selected category changes
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedSubcategory('Todos');
|
||||||
|
setSelectedLevel('Todos');
|
||||||
|
}, [selectedCategory]);
|
||||||
|
|
||||||
|
// Reset popular area when category changes (avoid conflicts)
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedCategory !== 'Todos') {
|
||||||
|
setSelectedPopularArea('Todos');
|
||||||
|
setShowAll(false);
|
||||||
|
}
|
||||||
|
}, [selectedCategory]);
|
||||||
|
|
||||||
|
// Reset showAll when search is entered
|
||||||
|
useEffect(() => {
|
||||||
|
if (localSearch.trim() !== '') {
|
||||||
|
setShowAll(false);
|
||||||
|
}
|
||||||
|
}, [localSearch]);
|
||||||
|
|
||||||
|
// Helper to map and sanitize category names for flexible database mapping
|
||||||
|
const getCourseCategory = (course: Course) => {
|
||||||
|
const cat = course.category;
|
||||||
|
if (cat === 'Desarrollo Web') return 'Informática';
|
||||||
|
if (cat === 'Diseño UI/UX') return 'Informática'; // UI/UX maps to Informática
|
||||||
|
if (cat === 'Inteligencia Artificial') return 'Ciencia de datos';
|
||||||
|
if (cat === 'Ciberseguridad') return 'Tecnologías de la información';
|
||||||
|
if (cat === 'Marketing Digital') return 'Empresas';
|
||||||
|
if (cat === 'Fotografía') return 'Artes y Humanidades';
|
||||||
|
return cat;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to map subcategory topic names dynamically
|
||||||
|
const getCourseSubcategory = (course: Course) => {
|
||||||
|
if (course.subcategory) return course.subcategory;
|
||||||
|
|
||||||
|
// Fallback topic mappings based on keywords in title
|
||||||
|
const title = course.title.toLowerCase();
|
||||||
|
const cat = course.category;
|
||||||
|
if (cat === 'Desarrollo Web') {
|
||||||
|
if (title.includes('react') || title.includes('next.js') || title.includes('typescript')) return 'Desarrollo Web';
|
||||||
|
if (title.includes('aws') || title.includes('cloud') || title.includes('devops') || title.includes('docker')) return 'DevOps & Cloud';
|
||||||
|
return 'Desarrollo Web';
|
||||||
|
}
|
||||||
|
if (cat === 'Diseño UI/UX') {
|
||||||
|
if (title.includes('ux') || title.includes('figma')) return 'Diseño UX/UI';
|
||||||
|
return 'Sistemas de diseño';
|
||||||
|
}
|
||||||
|
if (cat === 'Fotografía') return 'Fotografía Digital';
|
||||||
|
if (cat === 'Inteligencia Artificial') return 'IA Generativa & LLMs';
|
||||||
|
if (cat === 'Ciberseguridad') return 'Ethical Hacking';
|
||||||
|
if (cat === 'Marketing Digital') return 'Embudos de venta';
|
||||||
|
return 'General';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compute subcategory list dynamically based on selected category
|
||||||
|
const subcategories = useMemo(() => {
|
||||||
|
if (selectedCategory === 'Todos') return [];
|
||||||
|
|
||||||
|
const subcats = courses
|
||||||
|
.filter((c) => getCourseCategory(c) === selectedCategory)
|
||||||
|
.map((c) => getCourseSubcategory(c));
|
||||||
|
|
||||||
|
return ['Todos', ...Array.from(new Set(subcats))];
|
||||||
|
}, [courses, selectedCategory]);
|
||||||
|
|
||||||
|
// Category information for dedicated page headers (description and metrics)
|
||||||
|
const categoryDetails = useMemo(() => {
|
||||||
|
if (selectedCategory === 'Todos') return null;
|
||||||
|
|
||||||
|
switch (selectedCategory) {
|
||||||
|
case 'Informática':
|
||||||
|
return {
|
||||||
|
description: 'Domina la programación web, algoritmos avanzados, TypeScript, frameworks modernos como React y Next.js, y el desarrollo estructurado de software.',
|
||||||
|
credentials: 53,
|
||||||
|
totalCourses: 310
|
||||||
|
};
|
||||||
|
case 'Ciencia de datos':
|
||||||
|
return {
|
||||||
|
description: 'Conviértete en experto en analizar grandes conjuntos de datos, modelado de Machine Learning, visualización científica e integración de Inteligencia Artificial.',
|
||||||
|
credentials: 35,
|
||||||
|
totalCourses: 182
|
||||||
|
};
|
||||||
|
case 'Tecnologías de la información':
|
||||||
|
return {
|
||||||
|
description: 'Capacítate en administración de redes, infraestructura cloud en AWS, automatización DevOps y auditorías avanzadas de ciberseguridad ofensiva.',
|
||||||
|
credentials: 29,
|
||||||
|
totalCourses: 150
|
||||||
|
};
|
||||||
|
case 'Empresas':
|
||||||
|
return {
|
||||||
|
description: 'Desarrolla habilidades comerciales estratégicas, administración ágil de proyectos (Scrum/Kanban) y embudos de marketing digital de alta conversión.',
|
||||||
|
credentials: 42,
|
||||||
|
totalCourses: 220
|
||||||
|
};
|
||||||
|
case 'Artes y Humanidades':
|
||||||
|
return {
|
||||||
|
description: 'Explora tu potencial creativo y artístico mediante cursos de fotografía digital profesional, composición visual y edición avanzada de imágenes.',
|
||||||
|
credentials: 18,
|
||||||
|
totalCourses: 94
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
description: 'Explora cursos líderes en esta disciplina, obtén credenciales de nivel profesional y acelera tu inserción en el mercado laboral.',
|
||||||
|
credentials: 12,
|
||||||
|
totalCourses: 60
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedCategory]);
|
||||||
|
|
||||||
// Filtered & Sorted courses computation
|
// Filtered & Sorted courses computation
|
||||||
const filteredCourses = useMemo(() => {
|
const filteredCourses = useMemo(() => {
|
||||||
return courses
|
return courses
|
||||||
.filter((course) => {
|
.filter((course) => {
|
||||||
// Search query filter
|
// Search query filter
|
||||||
if (searchQuery.trim()) {
|
const activeSearch = localSearch.trim() || searchQuery.trim();
|
||||||
const q = searchQuery.toLowerCase();
|
if (activeSearch) {
|
||||||
|
const q = activeSearch.toLowerCase();
|
||||||
const matchTitle = course.title.toLowerCase().includes(q);
|
const matchTitle = course.title.toLowerCase().includes(q);
|
||||||
const matchSub = course.subtitle.toLowerCase().includes(q);
|
const matchSub = course.subtitle.toLowerCase().includes(q);
|
||||||
const matchCat = course.category.toLowerCase().includes(q);
|
const matchCat = course.category.toLowerCase().includes(q);
|
||||||
@@ -48,21 +177,28 @@ export const CourseGrid: React.FC<CourseGridProps> = ({
|
|||||||
if (!matchTitle && !matchSub && !matchCat && !matchInst) return false;
|
if (!matchTitle && !matchSub && !matchCat && !matchInst) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category filter
|
// Category Page Filter (active only when selectedCategory !== 'Todos')
|
||||||
if (selectedCategory !== 'Todos') {
|
if (selectedCategory !== 'Todos') {
|
||||||
if (course.category !== selectedCategory) return false;
|
const cat = getCourseCategory(course);
|
||||||
}
|
if (cat !== selectedCategory) return false;
|
||||||
|
|
||||||
// Level filter
|
// Level Filter (inside category page level pills)
|
||||||
if (selectedLevel !== 'Todos') {
|
if (selectedLevel !== 'Todos') {
|
||||||
if (course.level !== selectedLevel && course.level !== 'Todos los niveles') return false;
|
if (course.level !== selectedLevel && course.level !== 'Todos los niveles') return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab filter
|
// Subcategory filter
|
||||||
if (activeTab === 'popular' && course.studentsCount < 6000) return false;
|
if (selectedSubcategory !== 'Todos') {
|
||||||
if (activeTab === 'top-rated' && course.rating < 4.9) return false;
|
const sub = getCourseSubcategory(course);
|
||||||
if (activeTab === 'offers' && !course.badge?.includes('Oferta') && course.originalPrice <= course.price) return false;
|
if (sub !== selectedSubcategory) return false;
|
||||||
if (activeTab === 'new' && course.badge !== 'Nuevo') return false;
|
}
|
||||||
|
} else {
|
||||||
|
// Main Page Popular Area Filter (active only when selectedCategory === 'Todos')
|
||||||
|
if (selectedPopularArea !== 'Todos') {
|
||||||
|
const cat = getCourseCategory(course);
|
||||||
|
if (cat !== selectedPopularArea) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@@ -72,184 +208,403 @@ export const CourseGrid: React.FC<CourseGridProps> = ({
|
|||||||
if (sortBy === 'price-desc') return b.price - a.price;
|
if (sortBy === 'price-desc') return b.price - a.price;
|
||||||
return b.studentsCount - a.studentsCount; // default: popular
|
return b.studentsCount - a.studentsCount; // default: popular
|
||||||
});
|
});
|
||||||
}, [courses, searchQuery, selectedCategory, selectedLevel, activeTab, sortBy]);
|
}, [courses, searchQuery, localSearch, selectedCategory, selectedSubcategory, selectedPopularArea, selectedLevel, sortBy]);
|
||||||
|
|
||||||
|
// Filtered courses for general list when selectedCategory === 'Todos'
|
||||||
|
const generalCourses = useMemo<Course[]>(() => {
|
||||||
|
return courses
|
||||||
|
.filter((course) => {
|
||||||
|
// Search query filter
|
||||||
|
const activeSearch = localSearch.trim() || searchQuery.trim();
|
||||||
|
if (activeSearch) {
|
||||||
|
const q = activeSearch.toLowerCase();
|
||||||
|
const matchTitle = course.title.toLowerCase().includes(q);
|
||||||
|
const matchSub = course.subtitle.toLowerCase().includes(q);
|
||||||
|
const matchCat = course.category.toLowerCase().includes(q);
|
||||||
|
const matchInst = course.instructor.name.toLowerCase().includes(q);
|
||||||
|
if (!matchTitle && !matchSub && !matchCat && !matchInst) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popular area filter
|
||||||
|
if (selectedPopularArea !== 'Todos') {
|
||||||
|
const cat = getCourseCategory(course);
|
||||||
|
if (cat !== selectedPopularArea) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.studentsCount - a.studentsCount);
|
||||||
|
}, [courses, searchQuery, localSearch, selectedPopularArea]);
|
||||||
|
|
||||||
const resetAllFilters = () => {
|
const resetAllFilters = () => {
|
||||||
setActiveTab('all');
|
|
||||||
setSelectedCategory('Todos');
|
setSelectedCategory('Todos');
|
||||||
|
setSelectedSubcategory('Todos');
|
||||||
|
setSelectedPopularArea('Todos');
|
||||||
setSelectedLevel('Todos');
|
setSelectedLevel('Todos');
|
||||||
setSortBy('students');
|
setSortBy('students');
|
||||||
|
setLocalSearch('');
|
||||||
|
setShowAll(false);
|
||||||
onClearSearch();
|
onClearSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Dynamic Icon Mapper Helper for category pills
|
||||||
|
const getCategoryIcon = (catName: string) => {
|
||||||
|
const name = catName.toLowerCase();
|
||||||
|
if (name.includes('informática') || name.includes('computación') || name.includes('programación') || name.includes('código')) {
|
||||||
|
return <FiCode className="text-xs shrink-0 text-cyan-400" />;
|
||||||
|
}
|
||||||
|
if (name.includes('tecnolog') || name.includes('redes') || name.includes('sistemas') || name.includes('monitor')) {
|
||||||
|
return <FiMonitor className="text-xs shrink-0 text-cyan-400" />;
|
||||||
|
}
|
||||||
|
if (name.includes('datos') || name.includes('inteligencia') || name.includes('ia') || name.includes('analytics') || name.includes('trending')) {
|
||||||
|
return <FiTrendingUp className="text-xs shrink-0" />;
|
||||||
|
}
|
||||||
|
if (name.includes('ingenier') || name.includes('física') || name.includes('química') || name.includes('actividad')) {
|
||||||
|
return <FiActivity className="text-xs shrink-0 text-cyan-400" />;
|
||||||
|
}
|
||||||
|
if (name.includes('matemát') || name.includes('lógica') || name.includes('cálculo') || name.includes('grid')) {
|
||||||
|
return <FiGrid className="text-xs shrink-0" />;
|
||||||
|
}
|
||||||
|
if (name.includes('empresa') || name.includes('negocio') || name.includes('finanzas') || name.includes('briefcase')) {
|
||||||
|
return <FiBriefcase className="text-xs shrink-0" />;
|
||||||
|
}
|
||||||
|
if (name.includes('arte') || name.includes('humanidad') || name.includes('diseño') || name.includes('feather') || name.includes('fotografía')) {
|
||||||
|
return <FiFeather className="text-xs shrink-0" />;
|
||||||
|
}
|
||||||
|
if (name.includes('idioma') || name.includes('lengua') || name.includes('globe') || name.includes('inglés')) {
|
||||||
|
return <FiGlobe className="text-xs shrink-0" />;
|
||||||
|
}
|
||||||
|
if (name.includes('social') || name.includes('comunidad') || name.includes('users') || name.includes('psicología')) {
|
||||||
|
return <FiUsers className="text-xs shrink-0" />;
|
||||||
|
}
|
||||||
|
if (name.includes('personal') || name.includes('liderazgo') || name.includes('zap') || name.includes('crecimiento')) {
|
||||||
|
return <FiZap className="text-xs shrink-0 text-cyan-400" />;
|
||||||
|
}
|
||||||
|
if (name.includes('salud') || name.includes('shield') || name.includes('medicina') || name.includes('bienestar')) {
|
||||||
|
return <FiShield className="text-xs shrink-0 text-cyan-400" />;
|
||||||
|
}
|
||||||
|
return <FiLayers className="text-xs shrink-0" />; // generic fallback
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all unique category names
|
||||||
|
const allCategories = useMemo(() => {
|
||||||
|
return Array.from(new Set(courses.map(getCourseCategory))).sort();
|
||||||
|
}, [courses]);
|
||||||
|
|
||||||
|
// Filter categories by categorySearch query
|
||||||
|
const filteredCategories = useMemo(() => {
|
||||||
|
const query = categorySearch.trim().toLowerCase();
|
||||||
|
return allCategories.filter(cat => cat.toLowerCase().includes(query));
|
||||||
|
}, [allCategories, categorySearch]);
|
||||||
|
|
||||||
|
// Popular Areas matching "Más populares" pills from Coursera screenshot
|
||||||
|
const popularAreasList = [
|
||||||
|
{ name: 'Empresas' },
|
||||||
|
{ name: 'Ciencia de datos' },
|
||||||
|
{ name: 'Tecnologías de la información' },
|
||||||
|
{ name: 'Informática' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="catalog-section" className="py-12 bg-slate-950/60">
|
<section id="catalog-section" className="py-6 bg-slate-950/60">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-8">
|
||||||
|
|
||||||
{/* Section Header */}
|
{/* VIEW TYPE A: GENERAL EXPLORE PAGE */}
|
||||||
<div className="flex flex-col md:flex-row md:items-end justify-between mb-8 gap-4">
|
{selectedCategory === 'Todos' ? (
|
||||||
<div>
|
<div className="space-y-8">
|
||||||
<div className="flex items-center gap-2 text-cyan-400 text-xs font-bold uppercase tracking-wider mb-2">
|
|
||||||
<FiLayers />
|
{/* Section Header & Search Bar (Side-by-Side) */}
|
||||||
<span>Catálogo Completo</span>
|
<div className="border-b border-slate-800/80 pb-6 flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-cyan-400 text-xs font-bold">
|
||||||
|
<FiLayers />
|
||||||
|
<span>Catálogo de recursos</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-extrabold text-white tracking-tight">
|
||||||
|
Explorar por categoría
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Selecciona una categoría para ver los cursos y certificaciones disponibles.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Bar compact on the right */}
|
||||||
|
<div className="relative w-full md:w-80 lg:w-96 shrink-0">
|
||||||
|
<FiSearch className="absolute left-4 top-3.5 text-slate-400 text-base" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={localSearch}
|
||||||
|
onChange={(e) => setLocalSearch(e.target.value)}
|
||||||
|
placeholder="Buscar cursos..."
|
||||||
|
className="w-full pl-11 pr-4 py-3 bg-slate-900 border border-slate-800 rounded-2xl text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:border-cyan-500 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl sm:text-3xl font-extrabold text-white tracking-tight">
|
|
||||||
Explora Cursos Destacados
|
|
||||||
</h2>
|
|
||||||
<p className="text-sm text-slate-400 mt-1">
|
|
||||||
Filtra por tu área de interés, nivel o aprovecha promociones de temporada.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick Tabs */}
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5 p-1 bg-slate-900 border border-slate-800 rounded-xl">
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('all')}
|
|
||||||
className={`px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${
|
|
||||||
activeTab === 'all'
|
|
||||||
? 'bg-cyan-500/20 text-cyan-300 border border-cyan-500/40'
|
|
||||||
: 'text-slate-400 hover:text-white'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Todos ({courses.length})
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
{/* Explore Categories Directory */}
|
||||||
onClick={() => setActiveTab('popular')}
|
<div className="space-y-4 pt-2">
|
||||||
className={`flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${
|
{/* Category Search Filter Input */}
|
||||||
activeTab === 'popular'
|
{allCategories.length > 5 && (
|
||||||
? 'bg-amber-500/20 text-amber-300 border border-amber-500/40'
|
<div className="flex justify-end">
|
||||||
: 'text-slate-400 hover:text-white'
|
<div className="relative w-full sm:w-64 shrink-0">
|
||||||
}`}
|
<FiFilter className="absolute left-3.5 top-2.5 text-slate-500 text-xs" />
|
||||||
>
|
<input
|
||||||
<FiFlag className="text-amber-400" /> Populares
|
type="text"
|
||||||
</button>
|
value={categorySearch}
|
||||||
|
onChange={(e) => setCategorySearch(e.target.value)}
|
||||||
|
placeholder="Filtrar categorías por nombre..."
|
||||||
|
className="w-full pl-9 pr-4 py-2 bg-slate-900 border border-slate-800 rounded-full text-xs text-slate-200 placeholder-slate-505 focus:outline-none focus:border-cyan-500 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
{filteredCategories.length > 0 ? (
|
||||||
onClick={() => setActiveTab('top-rated')}
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
|
||||||
className={`flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${
|
{filteredCategories.map((catName) => (
|
||||||
activeTab === 'top-rated'
|
<button
|
||||||
? 'bg-blue-500/20 text-blue-300 border border-blue-500/40'
|
key={catName}
|
||||||
: 'text-slate-400 hover:text-white'
|
onClick={() => setSelectedCategory(catName)}
|
||||||
}`}
|
className="flex items-center gap-3 pl-3 pr-4 py-2.5 bg-slate-900/30 hover:bg-slate-900 border border-slate-850 hover:border-cyan-500/30 text-slate-300 hover:text-white rounded-full text-xs font-semibold hover:-translate-y-0.5 hover:shadow-lg hover:shadow-cyan-500/5 transition-all duration-200 cursor-pointer group"
|
||||||
>
|
>
|
||||||
<FiStar className="text-blue-400" /> Top Calificados
|
{/* Circle icon wrapper */}
|
||||||
</button>
|
<div className="w-6 h-6 rounded-full bg-slate-950 border border-slate-850 flex items-center justify-center text-slate-400 group-hover:text-cyan-400 transition-colors shrink-0">
|
||||||
|
{getCategoryIcon(catName)}
|
||||||
<button
|
</div>
|
||||||
onClick={() => setActiveTab('offers')}
|
<span className="truncate">{catName}</span>
|
||||||
className={`flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${
|
</button>
|
||||||
activeTab === 'offers'
|
))}
|
||||||
? 'bg-rose-500/20 text-rose-300 border border-rose-500/40'
|
</div>
|
||||||
: 'text-slate-400 hover:text-white'
|
) : (
|
||||||
}`}
|
<div className="py-8 text-center bg-slate-900/10 rounded-2xl border border-slate-900/80 p-6">
|
||||||
>
|
<p className="text-xs text-slate-400">
|
||||||
<FiPercent className="text-rose-400" /> Ofertas
|
No se encontraron categorías que coincidan con "{categorySearch}".
|
||||||
</button>
|
</p>
|
||||||
|
</div>
|
||||||
<button
|
)}
|
||||||
onClick={() => setActiveTab('new')}
|
|
||||||
className={`flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${
|
|
||||||
activeTab === 'new'
|
|
||||||
? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/40'
|
|
||||||
: 'text-slate-400 hover:text-white'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<FiSpeaker className="text-emerald-400" /> Nuevos
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Categories Bar */}
|
|
||||||
<div className="mb-6">
|
|
||||||
<CategoryPills
|
|
||||||
selectedCategory={selectedCategory}
|
|
||||||
onSelectCategory={setSelectedCategory}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Secondary Filter & Sorting Controls */}
|
|
||||||
<div className="flex flex-wrap items-center justify-between gap-4 p-4 rounded-xl bg-slate-900/50 border border-slate-800/80 mb-8">
|
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
|
||||||
<span className="flex items-center gap-1 text-xs font-semibold text-slate-400">
|
|
||||||
<FiFilter className="text-cyan-400" /> Filtros:
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Level Filter */}
|
|
||||||
<select
|
|
||||||
value={selectedLevel}
|
|
||||||
onChange={(e) => setSelectedLevel(e.target.value)}
|
|
||||||
className="bg-slate-800 text-slate-200 border border-slate-700 rounded-lg px-3 py-1.5 text-xs font-medium focus:outline-none focus:border-cyan-500"
|
|
||||||
>
|
|
||||||
<option value="Todos">Nivel: Todos</option>
|
|
||||||
<option value="Principiante">Principiante</option>
|
|
||||||
<option value="Intermedio">Intermedio</option>
|
|
||||||
<option value="Avanzado">Avanzado</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
{/* Clear Filters if active */}
|
|
||||||
{(selectedCategory !== 'Todos' || selectedLevel !== 'Todos' || activeTab !== 'all' || searchQuery.trim()) && (
|
|
||||||
<button
|
|
||||||
onClick={resetAllFilters}
|
|
||||||
className="flex items-center gap-1 text-xs text-rose-400 hover:text-rose-300 font-medium px-2 py-1 rounded bg-rose-500/10 border border-rose-500/20 transition"
|
|
||||||
>
|
|
||||||
<FiRefreshCw className="text-[10px]" /> Limpiar filtros
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sort By Dropdown & Results Counter */}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-xs text-slate-400">
|
|
||||||
Mostrando <strong className="text-white font-mono">{filteredCourses.length}</strong> cursos
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-slate-400">Ordenar por:</span>
|
|
||||||
<select
|
|
||||||
value={sortBy}
|
|
||||||
onChange={(e) => setSortBy(e.target.value as any)}
|
|
||||||
className="bg-slate-800 text-slate-200 border border-slate-700 rounded-lg px-3 py-1.5 text-xs font-medium focus:outline-none focus:border-cyan-500"
|
|
||||||
>
|
|
||||||
<option value="students">Más populares (Alumnos)</option>
|
|
||||||
<option value="rating">Mejor valorados</option>
|
|
||||||
<option value="price-asc">Precio: Menor a Mayor</option>
|
|
||||||
<option value="price-desc">Precio: Mayor a Menor</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Courses Grid */}
|
|
||||||
{filteredCourses.length > 0 ? (
|
{/* 2. Áreas más populares (Sentence case, descriptive subtitle) */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
<div className="space-y-3 pt-4 border-t border-slate-900/60">
|
||||||
{filteredCourses.map((course) => (
|
<h3 className="text-xl font-bold text-white tracking-tight">
|
||||||
<CourseCard
|
Áreas más populares
|
||||||
key={course.id}
|
</h3>
|
||||||
course={course}
|
<p className="text-xs text-slate-400">
|
||||||
isInCart={cartCourseIds.includes(course.id)}
|
Descubre los temas de mayor demanda laboral en la actualidad.
|
||||||
onAddToCart={onAddToCart}
|
</p>
|
||||||
onOpenDetails={onOpenDetails}
|
<div className="flex flex-wrap gap-2.5">
|
||||||
/>
|
{/* Todos Pill */}
|
||||||
))}
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedPopularArea('Todos');
|
||||||
|
setShowAll(false);
|
||||||
|
}}
|
||||||
|
className={`px-4 py-2 rounded-full text-xs font-semibold border transition-all cursor-pointer ${selectedPopularArea === 'Todos' && !showAll
|
||||||
|
? 'bg-slate-200 border-slate-200 text-slate-950 font-bold shadow-md'
|
||||||
|
: 'bg-slate-900/30 hover:bg-slate-900 border-slate-800/80 hover:border-slate-700 text-slate-300 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Todos
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Popular Area Pills */}
|
||||||
|
{popularAreasList.map((area) => {
|
||||||
|
const isSelected = selectedPopularArea === area.name;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={area.name}
|
||||||
|
onClick={() => setSelectedPopularArea((prev) => (prev === area.name ? 'Todos' : area.name))}
|
||||||
|
className={`px-4 py-2 rounded-full text-xs font-semibold border transition-all cursor-pointer ${isSelected
|
||||||
|
? 'bg-slate-200 border-slate-200 text-slate-950 font-bold shadow-md'
|
||||||
|
: 'bg-slate-900/30 hover:bg-slate-900 border-slate-800/80 hover:border-slate-700 text-slate-300 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{area.name}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* General Courses Grid */}
|
||||||
|
<div className="pt-6 border-t border-slate-900/80 space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="text-xl font-bold text-white tracking-tight">
|
||||||
|
Listado de cursos
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-slate-400">
|
||||||
|
Todos los cursos incluyen material de aprendizaje de alta calidad y acceso permanente.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{generalCourses.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||||
|
{generalCourses.map((course) => (
|
||||||
|
<CourseCard
|
||||||
|
key={course.id}
|
||||||
|
course={course}
|
||||||
|
isInCart={cartCourseIds.includes(course.id)}
|
||||||
|
onAddToCart={onAddToCart}
|
||||||
|
onOpenDetails={onOpenDetails}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-20 bg-slate-900/40 rounded-2xl border border-slate-800 p-8 space-y-4">
|
||||||
|
<div className="w-16 h-16 rounded-full bg-slate-800 text-slate-400 flex items-center justify-center mx-auto text-2xl">
|
||||||
|
<FiFilter />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-bold text-white">No se encontraron cursos</h3>
|
||||||
|
<button
|
||||||
|
onClick={resetAllFilters}
|
||||||
|
className="px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-cyan-500/20"
|
||||||
|
>
|
||||||
|
Restablecer filtros
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-20 bg-slate-900/40 rounded-2xl border border-slate-800 p-8 space-y-4">
|
/* VIEW TYPE B: DEDICATED CATEGORY PAGE */
|
||||||
<div className="w-16 h-16 rounded-full bg-slate-800 text-slate-400 flex items-center justify-center mx-auto text-2xl">
|
<div className="space-y-8">
|
||||||
<FiFilter />
|
|
||||||
|
{/* Breadcrumbs Navigation */}
|
||||||
|
<div className="flex items-center gap-2 text-xs text-slate-400 select-none">
|
||||||
|
<span
|
||||||
|
className="hover:text-cyan-400 cursor-pointer transition-colors"
|
||||||
|
onClick={resetAllFilters}
|
||||||
|
>
|
||||||
|
Categorías
|
||||||
|
</span>
|
||||||
|
<FiChevronRight className="text-[10px] text-slate-650" />
|
||||||
|
<span className="text-slate-200 font-medium select-text">
|
||||||
|
{selectedCategory}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Category Page Header Block (Title, Description, and Metrics side-by-side) */}
|
||||||
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 pb-6 border-b border-slate-800">
|
||||||
|
<div className="space-y-3 max-w-3xl">
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-extrabold text-white tracking-tight">
|
||||||
|
{selectedCategory}
|
||||||
|
</h1>
|
||||||
|
{categoryDetails && (
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
{categoryDetails.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Metrics block on the right */}
|
||||||
|
{categoryDetails && (
|
||||||
|
<div className="flex items-center gap-8 lg:pl-8 lg:border-l border-slate-800 shrink-0 select-none">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-3xl font-extrabold text-white">{categoryDetails.credentials}</div>
|
||||||
|
<div className="text-xs text-slate-400 font-medium">credenciales</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-xs text-slate-400 font-medium">cursos</div>
|
||||||
|
<div className="text-3xl font-extrabold text-white">de {categoryDetails.totalCourses}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Subcategories - visible only when category has multiple topics */}
|
||||||
|
{subcategories.length > 2 && (
|
||||||
|
<div className="space-y-3 pt-1">
|
||||||
|
<div className="text-xs text-slate-400 font-semibold">Temas de {selectedCategory}</div>
|
||||||
|
<div className="flex flex-wrap gap-2.5">
|
||||||
|
{subcategories.map((sub) => {
|
||||||
|
const isSubSelected = selectedSubcategory === sub;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={sub}
|
||||||
|
onClick={() => setSelectedSubcategory(sub)}
|
||||||
|
className={`flex items-center gap-1.5 px-4 py-2 rounded-full text-xs font-semibold transition-all border cursor-pointer ${isSubSelected
|
||||||
|
? 'bg-cyan-500/10 border-cyan-500/40 text-cyan-300 shadow-md shadow-cyan-500/5'
|
||||||
|
: 'bg-slate-900/60 hover:bg-slate-900 border-slate-800 hover:border-slate-700 text-slate-300 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>{sub === 'Todos' ? `Todos en ${selectedCategory}` : sub}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* "Filtrar por nivel" (Sentence case, descriptive subtitle, capsules format) */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="text-xl font-bold text-white tracking-tight">
|
||||||
|
Filtrar por nivel
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-slate-400">
|
||||||
|
Selecciona la dificultad adecuada para tu ritmo de aprendizaje.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2.5">
|
||||||
|
{['Todos', 'Principiante', 'Intermedio', 'Avanzado'].map((lvl) => {
|
||||||
|
const isLvlSelected = selectedLevel === lvl;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={lvl}
|
||||||
|
onClick={() => setSelectedLevel(lvl)}
|
||||||
|
className={`px-4 py-2 rounded-full text-xs font-semibold border transition-all cursor-pointer ${isLvlSelected
|
||||||
|
? 'bg-slate-200 border-slate-200 text-slate-950 font-bold shadow-md'
|
||||||
|
: 'bg-slate-900/30 hover:bg-slate-900 border-slate-800/80 hover:border-slate-700 text-slate-300 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{lvl === 'Todos' ? 'Todos los niveles' : lvl}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section: Listado de cursos (Descriptive title & subtitle) */}
|
||||||
|
<div className="pt-6 border-t border-slate-900/80 space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="text-xl font-bold text-white tracking-tight">
|
||||||
|
Listado de cursos de {selectedCategory}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-slate-400">
|
||||||
|
Encuentra el material didáctico necesario para avanzar en tu formación profesional.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredCourses.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||||
|
{filteredCourses.map((course) => (
|
||||||
|
<CourseCard
|
||||||
|
key={course.id}
|
||||||
|
course={course}
|
||||||
|
isInCart={cartCourseIds.includes(course.id)}
|
||||||
|
onAddToCart={onAddToCart}
|
||||||
|
onOpenDetails={onOpenDetails}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-20 bg-slate-900/40 rounded-2xl border border-slate-800 p-8 space-y-4">
|
||||||
|
<div className="w-16 h-16 rounded-full bg-slate-800 text-slate-400 flex items-center justify-center mx-auto text-2xl">
|
||||||
|
<FiFilter />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-bold text-white">No se encontraron cursos en este nivel</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedLevel('Todos')}
|
||||||
|
className="px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-cyan-500/20"
|
||||||
|
>
|
||||||
|
Ver todos los niveles
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-bold text-white">No se encontraron cursos con estos filtros</h3>
|
|
||||||
<p className="text-xs text-slate-400 max-w-md mx-auto">
|
|
||||||
Intenta cambiar los términos de búsqueda, la categoría o restablecer los filtros para ver todos los cursos disponibles.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={resetAllFilters}
|
|
||||||
className="px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-cyan-500/20"
|
|
||||||
>
|
|
||||||
Restablecer todos los filtros
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FiMail,
|
|
||||||
FiCheckCircle,
|
|
||||||
FiGlobe,
|
FiGlobe,
|
||||||
FiShield,
|
FiShield,
|
||||||
FiAward,
|
|
||||||
FiHeart
|
FiHeart
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import { FaTwitter, FaYoutube, FaGithub, FaLinkedin, FaDiscord } from 'react-icons/fa';
|
import { FaTwitter, FaYoutube, FaGithub, FaLinkedin, FaDiscord } from 'react-icons/fa';
|
||||||
@@ -12,17 +9,6 @@ import { FaTwitter, FaYoutube, FaGithub, FaLinkedin, FaDiscord } from 'react-ico
|
|||||||
import { BrandLogo } from '../common/BrandLogo';
|
import { BrandLogo } from '../common/BrandLogo';
|
||||||
|
|
||||||
export const Footer: React.FC = () => {
|
export const Footer: React.FC = () => {
|
||||||
const [email, setEmail] = useState('');
|
|
||||||
const [subscribed, setSubscribed] = useState(false);
|
|
||||||
|
|
||||||
const handleSubscribe = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (email.trim()) {
|
|
||||||
setSubscribed(true);
|
|
||||||
setTimeout(() => setSubscribed(false), 4000);
|
|
||||||
setEmail('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="bg-slate-950 border-t border-slate-800 text-slate-400 text-sm">
|
<footer className="bg-slate-950 border-t border-slate-800 text-slate-400 text-sm">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router';
|
import { useNavigate, useLocation } from 'react-router';
|
||||||
import {
|
import {
|
||||||
@@ -88,8 +89,8 @@ export const Navbar: React.FC<NavbarProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold transition-all ${currentPath === '/'
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold transition-all ${currentPath === '/'
|
||||||
? 'bg-slate-800 text-cyan-400 border border-cyan-500/30'
|
? 'bg-slate-800 text-cyan-400 border border-cyan-500/30'
|
||||||
: 'text-slate-300 hover:text-white hover:bg-slate-900'
|
: 'text-slate-300 hover:text-white hover:bg-slate-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FiHome />
|
<FiHome />
|
||||||
@@ -99,8 +100,8 @@ export const Navbar: React.FC<NavbarProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate('/courses')}
|
onClick={() => navigate('/courses')}
|
||||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold transition-all ${currentPath === '/courses'
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold transition-all ${currentPath === '/courses'
|
||||||
? 'bg-slate-800 text-cyan-400 border border-cyan-500/30'
|
? 'bg-slate-800 text-cyan-400 border border-cyan-500/30'
|
||||||
: 'text-slate-300 hover:text-white hover:bg-slate-900'
|
: 'text-slate-300 hover:text-white hover:bg-slate-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FiGrid />
|
<FiGrid />
|
||||||
@@ -110,8 +111,8 @@ export const Navbar: React.FC<NavbarProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin')}
|
onClick={() => navigate('/admin')}
|
||||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold transition-all ${currentPath === '/admin'
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold transition-all ${currentPath === '/admin'
|
||||||
? 'bg-slate-800 text-cyan-400 border border-cyan-500/30'
|
? 'bg-slate-800 text-cyan-400 border border-cyan-500/30'
|
||||||
: 'text-slate-300 hover:text-white hover:bg-slate-900'
|
: 'text-slate-300 hover:text-white hover:bg-slate-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FiShield />
|
<FiShield />
|
||||||
@@ -120,29 +121,6 @@ export const Navbar: React.FC<NavbarProps> = ({
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 2. Spacious Center Search Bar */}
|
|
||||||
{!isSellerMode && (
|
|
||||||
<div className="hidden md:flex flex-1 max-w-xl mx-2">
|
|
||||||
<div className="relative w-full">
|
|
||||||
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
|
|
||||||
<FiSearch className="text-base" />
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={handleSearchInputChange}
|
|
||||||
placeholder="¿Qué quieres aprender hoy? (React, Python, Figma...)"
|
|
||||||
className="w-full pl-10 pr-12 py-2.5 bg-slate-900/90 border border-slate-700/80 rounded-xl text-xs text-slate-100 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 transition-all shadow-inner"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
|
||||||
<kbd className="hidden xl:inline-block px-1.5 py-0.5 text-[10px] font-semibold text-slate-400 bg-slate-800 border border-slate-700 rounded">
|
|
||||||
⌘K
|
|
||||||
</kbd>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 3. Right Actions & Role Switcher */}
|
{/* 3. Right Actions & Role Switcher */}
|
||||||
<div className="flex items-center gap-2.5 shrink-0">
|
<div className="flex items-center gap-2.5 shrink-0">
|
||||||
|
|
||||||
@@ -151,8 +129,8 @@ export const Navbar: React.FC<NavbarProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate('/courses')}
|
onClick={() => navigate('/courses')}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${!isSellerMode
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${!isSellerMode
|
||||||
? 'bg-gradient-to-r from-blue-600 to-cyan-500 text-white shadow-md shadow-cyan-500/20'
|
? 'bg-gradient-to-r from-blue-600 to-cyan-500 text-white shadow-md shadow-cyan-500/20'
|
||||||
: 'text-slate-400 hover:text-slate-200'
|
: 'text-slate-400 hover:text-slate-200'
|
||||||
}`}
|
}`}
|
||||||
title="Modo Estudiante - Explorar y Comprar cursos"
|
title="Modo Estudiante - Explorar y Comprar cursos"
|
||||||
>
|
>
|
||||||
@@ -163,8 +141,8 @@ export const Navbar: React.FC<NavbarProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate('/seller')}
|
onClick={() => navigate('/seller')}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${isSellerMode
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${isSellerMode
|
||||||
? 'bg-gradient-to-r from-indigo-600 to-violet-500 text-white shadow-md shadow-violet-500/20'
|
? 'bg-gradient-to-r from-indigo-600 to-violet-500 text-white shadow-md shadow-violet-500/20'
|
||||||
: 'text-slate-400 hover:text-slate-200'
|
: 'text-slate-400 hover:text-slate-200'
|
||||||
}`}
|
}`}
|
||||||
title="Modo Instructor - Vender y Gestionar cursos"
|
title="Modo Instructor - Vender y Gestionar cursos"
|
||||||
>
|
>
|
||||||
|
|||||||
+56
-5
@@ -412,15 +412,66 @@ export const mockCourses: Course[] = [
|
|||||||
isPublished: true,
|
isPublished: true,
|
||||||
salesCount: 510,
|
salesCount: 510,
|
||||||
totalRevenue: 16824.90
|
totalRevenue: 16824.90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'course-7',
|
||||||
|
title: 'Fotografía Profesional y Edición Digital con Lightroom',
|
||||||
|
subtitle: 'Aprende composición, manejo de cámara en modo manual, iluminación de estudio y revelado digital avanzado.',
|
||||||
|
category: 'Fotografía',
|
||||||
|
level: 'Principiante',
|
||||||
|
price: 21.99,
|
||||||
|
originalPrice: 79.99,
|
||||||
|
rating: 4.87,
|
||||||
|
reviewsCount: 420,
|
||||||
|
studentsCount: 3120,
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=800&auto=format&fit=crop&q=80',
|
||||||
|
duration: '20h 15min',
|
||||||
|
lessonsCount: 85,
|
||||||
|
badge: 'Nuevo',
|
||||||
|
instructor: mockInstructors.lucia,
|
||||||
|
updatedAt: 'Febrero 2026',
|
||||||
|
language: 'Español',
|
||||||
|
whatYouWillLearn: [
|
||||||
|
'Dominar el triángulo de exposición: obturación, diafragma e ISO',
|
||||||
|
'Reglas de composición aplicadas a retrato, paisaje y fotografía urbana',
|
||||||
|
'Revelado básico y avanzado en Adobe Lightroom'
|
||||||
|
],
|
||||||
|
requirements: [
|
||||||
|
'Cualquier cámara fotográfica (DSLR, Mirrorless o incluso un smartphone con modo manual)'
|
||||||
|
],
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
id: 'm1',
|
||||||
|
title: 'Módulo 1: Manejo de Cámara e Iluminación',
|
||||||
|
lessons: [
|
||||||
|
{ id: 'l1', title: 'El Triángulo de Exposición paso a paso', duration: '18:10', isFreePreview: true },
|
||||||
|
{ id: 'l2', title: 'Leyes de la Composición Visual', duration: '22:40' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
reviews: [
|
||||||
|
{
|
||||||
|
id: 'r8',
|
||||||
|
userName: 'Sofía Castro',
|
||||||
|
userAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',
|
||||||
|
rating: 5,
|
||||||
|
date: 'Hace 2 semanas',
|
||||||
|
comment: 'Las explicaciones sobre Lightroom son fantásticas, mi flujo de trabajo ahora es mucho más rápido.'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
isPublished: true,
|
||||||
|
salesCount: 220,
|
||||||
|
totalRevenue: 4837.80
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockCategories = [
|
export const mockCategories = [
|
||||||
{ id: 'all', name: 'Todos los cursos', icon: 'FiGrid', count: 120 },
|
{ id: 'all', name: 'Todos los cursos', icon: 'FiGrid', count: 120 },
|
||||||
{ id: 'dev', name: 'Desarrollo Web', icon: 'FiCode', count: 48 },
|
{ id: 'dev', name: 'Desarrollo', icon: 'FiCode', count: 48 },
|
||||||
{ id: 'ai', name: 'Inteligencia Artificial', icon: 'FiCpu', count: 26 },
|
{ id: 'design', name: 'Diseño', icon: 'FiLayout', count: 22 },
|
||||||
{ id: 'design', name: 'Diseño UI/UX', icon: 'FiLayout', count: 22 },
|
{ id: 'photo', name: 'Fotografía', icon: 'FiCamera', count: 12 },
|
||||||
|
{ id: 'ai', name: 'Inteligencia artificial', icon: 'FiCpu', count: 26 },
|
||||||
{ id: 'security', name: 'Ciberseguridad', icon: 'FiShield', count: 14 },
|
{ id: 'security', name: 'Ciberseguridad', icon: 'FiShield', count: 14 },
|
||||||
{ id: 'marketing', name: 'Marketing Digital', icon: 'FiTrendingUp', count: 18 },
|
{ id: 'marketing', name: 'Marketing', icon: 'FiTrendingUp', count: 18 },
|
||||||
{ id: 'business', name: 'Negocios & Finanzas', icon: 'FiDollarSign', count: 15 }
|
{ id: 'business', name: 'Negocios', icon: 'FiDollarSign', count: 15 }
|
||||||
];
|
];
|
||||||
+1
-1
@@ -46,6 +46,7 @@ export interface Course {
|
|||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
category: string;
|
category: string;
|
||||||
|
subcategory?: string;
|
||||||
level: CourseLevel;
|
level: CourseLevel;
|
||||||
price: number;
|
price: number;
|
||||||
originalPrice: number;
|
originalPrice: number;
|
||||||
@@ -73,5 +74,4 @@ export interface CartItem {
|
|||||||
addedAt: string;
|
addedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ActiveTabFilter = 'all' | 'popular' | 'top-rated' | 'offers' | 'new';
|
|
||||||
export type ViewMode = 'buyer' | 'seller';
|
export type ViewMode = 'buyer' | 'seller';
|
||||||
+2
-13
@@ -22,19 +22,7 @@ export const BuyerView: React.FC<BuyerViewProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="py-6 space-y-12">
|
<div className="py-6 space-y-12">
|
||||||
{/* Header section for Catalog View */}
|
{/* Course Grid & Filtering with integrated header, search, and banners layout */}
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="bg-gradient-to-r from-slate-900 via-indigo-950/60 to-slate-900 border border-slate-800/80 rounded-2xl p-6 sm:p-8 shadow-xl">
|
|
||||||
<h1 className="text-2xl sm:text-4xl font-extrabold text-white">
|
|
||||||
Catálogo de Cursos
|
|
||||||
</h1>
|
|
||||||
<p className="text-slate-400 text-xs sm:text-sm mt-2 max-w-2xl">
|
|
||||||
Explora nuestra biblioteca de cursos diseñados para ayudarte a dominar las mejores tecnologías y potenciar tu carrera profesional.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Course Grid & Filtering */}
|
|
||||||
<CourseGrid
|
<CourseGrid
|
||||||
courses={courses}
|
courses={courses}
|
||||||
cartCourseIds={cartCourseIds}
|
cartCourseIds={cartCourseIds}
|
||||||
@@ -42,6 +30,7 @@ export const BuyerView: React.FC<BuyerViewProps> = ({
|
|||||||
onAddToCart={onAddToCart}
|
onAddToCart={onAddToCart}
|
||||||
onOpenDetails={onOpenDetails}
|
onOpenDetails={onOpenDetails}
|
||||||
onClearSearch={onClearSearch}
|
onClearSearch={onClearSearch}
|
||||||
|
showBanners={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Features Banner */}
|
{/* Features Banner */}
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ export const HomeView: React.FC<HomeViewProps> = ({
|
|||||||
onAddToCart={onAddToCart}
|
onAddToCart={onAddToCart}
|
||||||
onOpenDetails={onOpenDetails}
|
onOpenDetails={onOpenDetails}
|
||||||
onClearSearch={onClearSearch}
|
onClearSearch={onClearSearch}
|
||||||
|
showBanners={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user