Skip to content

Commit e8b47e5

Browse files
Merge pull request Roshansuthar1105#205 from Vanshika814/feature/amazing-feature
enhanced courses page ui
2 parents c7f07ce + 9ec616f commit e8b47e5

6 files changed

Lines changed: 153 additions & 133 deletions

File tree

client/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<link rel="manifest" href="/manifest.json" />
8+
<meta name="mobile-web-app-capable" content="yes" />
89
<meta name="apple-mobile-web-app-capable" content="yes" />
910
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
1011
<meta name="apple-mobile-web-app-title" content="Codify" />

client/src/components/CardBody.jsx

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import React from 'react';
1+
2+
import { motion } from "framer-motion";
23
import { useAuth } from '../store/auth';
34
import { useTheme } from '../context/ThemeContext';
45
import { toast } from 'react-toastify';
56
import { MdBookmarkBorder, MdBookmarkAdded, MdEdit, MdPlayCircleOutline } from "react-icons/md";
67
import { Link, useNavigate } from 'react-router-dom';
78
import { useLoading } from './loadingContext';
8-
import { getYouTubeUrlType } from '../utils/youtubeUtils';
9+
import PropTypes from 'prop-types';
10+
911

1012
const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
1113
const { setIsLoading } = useLoading();
@@ -16,8 +18,7 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
1618
const token = localStorage.getItem('token');
1719
const navigate = useNavigate();
1820

19-
// Analyze YouTube link type
20-
const youtubeUrlInfo = getYouTubeUrlType(creator_youtube_link);
21+
2122

2223
// Handle card click
2324
const handleCardClick = (e) => {
@@ -63,8 +64,7 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
6364
throw new Error(`HTTP error! status: ${response.status}, message: ${errorMessage}`);
6465
}
6566

66-
const result = await response.json();
67-
//console.log("Watchlist update success:", result);
67+
await response.json();
6868

6969
// Show appropriate toast message
7070
if (isInWatchlist) {
@@ -88,28 +88,50 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
8888
return (
8989
<div
9090
className={`
91-
w-[300px] border rounded-md overflow-hidden shadow-md transition-all duration-200
91+
group relative w-[330px] rounded-xl shadow-lg flex flex-col
92+
hover:border-b-2 hover:border-r-2 transition-all duration-300 overflow-hidden
9293
${isDark
93-
? 'bg-dark-bg-secondary border-dark-border'
94-
: 'bg-light-bg-secondary border-light-border'}
95-
hover:scale-[1.02] hover:shadow-lg cursor-pointer
94+
? 'bg-gradient-to-br from-gray-800 to-secondary-1000 backdrop-blur-xl'
95+
: 'bg-gradient-to-br from-blue-50 to-indigo-50 backdrop-blur-xl'}
96+
hover:shadow-xl cursor-pointer
9697
`}
9798
onClick={handleCardClick}
9899
>
100+
{/* Animated borders */}
101+
<motion.div
102+
className="absolute top-0 right-0 w-0 h-full bg-primary rounded-r-2xl"
103+
whileHover={{
104+
width: "3px",
105+
transition: { duration: 0.3, ease: "easeOut" }
106+
}}
107+
/>
108+
<motion.div
109+
className="absolute bottom-0 left-0 w-full h-0 bg-primary rounded-b-2xl"
110+
whileHover={{
111+
height: "3px",
112+
transition: { duration: 0.3, ease: "easeOut", delay: 0.05 }
113+
}}
114+
/>
115+
99116
<div className="relative">
100117
<img
101118
src={course_image}
102119
alt={course_title}
103-
className="w-full h-[180px] object-cover"
120+
className="w-full h-[180px] object-cover rounded-lg"
104121
/>
105-
<div className="absolute inset-0 bg-black/30 opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center">
106-
<MdPlayCircleOutline className="text-white text-5xl" />
122+
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center rounded-xl">
123+
<motion.div
124+
whileHover={{ scale: 1.1 }}
125+
className="transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300"
126+
>
127+
<MdPlayCircleOutline className="text-white text-6xl" />
128+
</motion.div>
107129
</div>
108130
</div>
109131

110-
<div className="p-4 flex flex-col gap-5 relative">
111-
<h3 className={`text-lg font-medium ${isDark ? 'text-dark-text-primary' : 'text-light-text-primary'}`}>
112-
{course_title.length > 30 ? `${course_title.slice(0, 30)}...` : course_title}
132+
<div className="px-4 sm:px-6 pb-3 pt-4 flex flex-col gap-5 relative">
133+
<h3 className={`text-lg font-medium leading-normal ${isDark ? 'text-dark-text-primary' : 'text-light-text-primary'} group-hover:text-primary transition-colors duration-300 line-clamp-3 min-h-[4.5rem]`}>
134+
{course_title}
113135
</h3>
114136

115137
<div className="flex items-center gap-2">
@@ -134,10 +156,9 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
134156
e.stopPropagation();
135157
navigate(`/courses/${course._id}`);
136158
}}
137-
className={`
138-
py-2 px-5 rounded-full flex items-center justify-center text-white transition-colors
139-
${isDark ? 'bg-primary hover:bg-primary-dark' : 'bg-primary hover:bg-primary-dark'}
140-
`}
159+
className="py-2.5 px-6 rounded-full flex items-center justify-center text-white font-medium
160+
bg-primary hover:bg-primary-dark transition-all duration-300 transform hover:scale-[1.02]
161+
shadow-md hover:shadow-lg shadow-primary/20"
141162
>
142163
Watch Now
143164
</button>
@@ -146,10 +167,15 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
146167
<button
147168
onClick={handleWatchlist}
148169
className={`
149-
h-10 w-10 rounded-full flex items-center justify-center transition-colors
170+
h-11 w-11 rounded-full flex items-center justify-center transition-all duration-300
150171
${isInWatchlist
151-
? 'bg-primary text-white'
152-
: `${isDark ? 'bg-dark-bg-tertiary text-dark-text-primary border border-dark-border' : 'bg-light-bg-tertiary text-light-text-primary border border-light-border'}`}
172+
? 'bg-primary text-white shadow-md shadow-primary/20'
173+
: `${isDark
174+
? 'bg-dark-bg-tertiary/80 text-dark-text-primary hover:bg-dark-bg-tertiary'
175+
: 'bg-light-bg-tertiary/80 text-light-text-primary hover:bg-light-bg-tertiary'}
176+
backdrop-blur-sm border border-current/10`
177+
}
178+
hover:scale-[1.05]
153179
`}
154180
aria-label={isInWatchlist ? "Remove from watchlist" : "Add to watchlist"}
155181
>
@@ -163,8 +189,11 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
163189
{userdata.isAdmin && (
164190
<Link
165191
className={`
166-
h-10 w-10 rounded-full flex items-center justify-center transition-colors
167-
${isDark ? 'bg-dark-bg-tertiary text-primary hover:bg-dark-bg-primary' : 'bg-light-bg-tertiary text-primary hover:bg-light-bg-primary'}
192+
h-11 w-11 rounded-full flex items-center justify-center transition-all duration-300
193+
${isDark
194+
? 'bg-dark-bg-tertiary/80 text-primary hover:bg-dark-bg-tertiary'
195+
: 'bg-light-bg-tertiary/80 text-primary hover:bg-light-bg-tertiary'}
196+
backdrop-blur-sm border border-current/10 hover:scale-[1.05]
168197
`}
169198
to={`/admin/courses/update/${course._id}`}
170199
aria-label="Edit course"
@@ -179,4 +208,20 @@ const CardBody = ({ course, watchlist = [], updateWatchlist, onClick }) => {
179208
);
180209
};
181210

211+
CardBody.propTypes = {
212+
course: PropTypes.shape({
213+
_id: PropTypes.string.isRequired,
214+
course_title: PropTypes.string.isRequired,
215+
creator_youtube_link: PropTypes.string.isRequired,
216+
creator_name: PropTypes.string.isRequired,
217+
creator_image: PropTypes.string.isRequired,
218+
course_image: PropTypes.string.isRequired
219+
}).isRequired,
220+
watchlist: PropTypes.arrayOf(PropTypes.shape({
221+
_id: PropTypes.string.isRequired
222+
})),
223+
updateWatchlist: PropTypes.func,
224+
onClick: PropTypes.func
225+
};
226+
182227
export default CardBody;

client/src/components/CoursesList.jsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import { useState, useEffect } from 'react';
22
import { useAuth } from '../store/auth.jsx';
33
import CardBodyd from './CardBodyd';
44
import SearchBard from './Searchbard.jsx'; // Import the SearchBar component
@@ -24,27 +24,35 @@ const CoursesList = () => {
2424
);
2525

2626
return (
27-
<div className="courses-list">
28-
<h2>All Courses</h2>
29-
<SearchBard searchTerm={searchTerm} setSearchTerm={setSearchTerm} />
30-
<div className="categories">
31-
{categories.map((category, index) => (
32-
<button
33-
key={index}
34-
className={`category-button ${selectedCategory === category ? 'active' : ''}`}
35-
onClick={() => setSelectedCategory(category)}
36-
>
37-
{category}
38-
</button>
39-
))}
27+
<div className="max-w-[1400px] mx-auto px-6 py-8">
28+
<div className="flex flex-col gap-8 mb-8">
29+
<h2 className="text-3xl font-bold">All Courses</h2>
30+
<SearchBard searchTerm={searchTerm} setSearchTerm={setSearchTerm} />
31+
<div className="flex flex-wrap gap-3">
32+
{categories.map((category, index) => (
33+
<button
34+
key={index}
35+
className={`px-4 py-2 rounded-full transition-all duration-300 ${
36+
selectedCategory === category
37+
? 'bg-primary text-white shadow-md'
38+
: 'bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700'
39+
}`}
40+
onClick={() => setSelectedCategory(category)}
41+
>
42+
{category}
43+
</button>
44+
))}
45+
</div>
4046
</div>
41-
<div className="courses">
47+
<div className="flex flex-wrap justify-between">
4248
{filteredCourses.length > 0 ? (
4349
filteredCourses.map(course => (
4450
<CardBodyd key={course._id} course={course} />
4551
))
4652
) : (
47-
<div className="no-courses">No courses found matching your search.</div>
53+
<div className="col-span-full text-center text-lg text-gray-500">
54+
No courses found matching your search.
55+
</div>
4856
)}
4957
</div>
5058
</div>

client/src/components/Loader.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
22
import { useTheme } from '../context/ThemeContext';
33

44
function Loader() {
@@ -67,7 +67,7 @@ function Loader() {
6767
</div>
6868
</div>
6969

70-
<style jsx>{`
70+
<style>{`
7171
@keyframes float {
7272
0%, 100% { transform: translateY(0px) rotate(0deg); opacity: 0.7; }
7373
33% { transform: translateY(-10px) rotate(5deg); opacity: 1; }

client/src/pages/Courses.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {
1+
import {
22
useState,
33
useEffect,
44
lazy,
@@ -387,7 +387,7 @@ const Courses = () => {
387387
animate={{ opacity: 1 }}
388388
className={`text-sm ${isDark ? 'text-dark-text-secondary' : 'text-light-text-secondary'}`}
389389
>
390-
Found {filteredCourses.length} course(s) matching "{searchTerm}"
390+
Found {filteredCourses.length} course(s) matching &quot;{searchTerm}&quot;
391391
</motion.p>
392392
)}
393393
</div>
@@ -402,7 +402,7 @@ const Courses = () => {
402402
className="mb-16"
403403
>
404404
{currentCourses.length > 0 ? (
405-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
405+
<div className="max-w-full flex flex-wrap justify-around gap-6">
406406
{currentCourses.map((course) => (
407407
<motion.div
408408
key={course._id}

0 commit comments

Comments
 (0)