Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fahim/Tony #39 #47

Merged
merged 7 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@

const Button = ({ label, isOutline, onClick }) => {
const Button = ({ label, isOutline, onClick, className = "" }) => {
return (
<button
className={`px-4 py-2 rounded-lg transition-colors duration-300 ${isOutline
? 'border border-cerulean text-black-500 bg-white'
: 'bg-cerulean text-white bg-dark-blue-800'
}`}
className={`
px-6
py-2.5
rounded-lg
font-medium
transition-all
duration-300
${isOutline
? 'border border-dark-blue-800 text-dark-blue-800 bg-white hover:bg-dark-blue-50'
: 'bg-dark-blue-800 text-white hover:bg-dark-blue-700'
}
${className}
`}
onClick={onClick}
>
{label}
</button>
);
};

export default Button;

export default Button;
50 changes: 40 additions & 10 deletions src/components/Class.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import Button from '@/components/Button'
import Button from '@/components/Button';
import { IoTimeOutline, IoCalendarOutline } from "react-icons/io5";

const Class = ({ classObj }) => {
const ageGroup = classObj.ageGroup.toString()
const ageGroup = classObj.ageGroup.toString();

return (
<div className="bg-white px-4 py-3 rounded-lg w-1/4 h-1/4 shadow-md">
<p>{ageGroup.charAt(0).toUpperCase() + ageGroup.slice(1)}'s Class</p>
<p>with {classObj.instructor}</p>
{classObj.schedule.map((schedule, index) => (
<p key={index}>{schedule.day} {schedule.time}</p>
))}
<Button label={"Register"} />
<div className="bg-white rounded-lg border border-neutral-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6">
<div className="flex flex-col gap-4">
{/* Header */}
<div>
<h3 className="text-xl font-bold text-dark-blue-800 mb-1">
{ageGroup.charAt(0).toUpperCase() + ageGroup.slice(1)}'s Class
</h3>
<span className="text-sm text-neutral-400">w/ {classObj.instructor}</span>
</div>

{/* Schedule */}
<div className="space-y-3">
{classObj.schedule.map((schedule, index) => (
<div key={index} className="flex items-center text-neutral-400 text-sm">
<div className="flex items-center gap-2 w-1/2">
<IoCalendarOutline className="text-lg" />
<span>{schedule.day}</span>
</div>
<div className="flex items-center gap-2">
<IoTimeOutline className="text-lg" />
<span>{schedule.time}</span>
</div>
</div>
))}
</div>

{/* Register Button */}
<Button
label="Register"
isOutline={false}
className="w-full bg-dark-blue-800 text-white hover:bg-dark-blue-700 py-2.5"
/>
</div>
</div>
</div>
)
);
};

export default Class;
61 changes: 46 additions & 15 deletions src/components/Level.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,55 @@ const Level = ({ level, isSimplified }) => {
const params = new URLSearchParams()
params.set("level", level.level)

return (
<Link href={`/classes?${params.toString()}`} className="bg-white px-4 py-3 rounded-lg w-1/4 h-1/4 shadow-md">
{isSimplified ?
// TODO (Fahim & Tony): work on the simplified version of the Level component seen on the Class pages
<div></div>
:
<div>
<h3 className='text-2xl font-semibold'>Level: {level.level}</h3>
<p>{level.name}</p>
<div>
<p>Instructors:</p>
{level.instructors.map((instructor, instructorIndex) => (
<p key={instructorIndex}>{instructor}</p>
))}
if (isSimplified) {
return (
<div className="bg-white rounded-lg border border-neutral-100 overflow-hidden hover:shadow-md transition-all duration-300">
<div className="relative">
{/* Blue accent line on the left */}
<div className="absolute left-0 top-0 bottom-0 w-1 bg-blue-400"></div>

<div className="p-6">
<h3 className="text-lg font-semibold text-dark-blue-800 mb-2">
Level {level.level}
</h3>
<p className="text-neutral-600 text-sm mb-4">
{level.description || "New Concept Book 1 -- Ch. 1-72"}
</p>
<Link
href={`/classes?${params.toString()}`}
className="text-blue-500 text-sm font-medium hover:text-blue-600 flex items-center gap-1"
>
View Level
<span className="text-lg">→</span>
</Link>
</div>
</div>
}
</div>
)
}

return (
<Link
href={`/classes?${params.toString()}`}
className="bg-white px-6 py-4 rounded-lg shadow-sm hover:shadow-md transition-all duration-300"
>
<div>
<h3 className='text-2xl font-semibold text-dark-blue-800 mb-2'>
Level {level.level}
</h3>
<p className="text-neutral-600 mb-4">{level.name}</p>
<div>
<p className="text-sm text-neutral-500 mb-2">Instructors:</p>
{level.instructors.map((instructor, instructorIndex) => (
<p
key={instructorIndex}
className="text-sm text-neutral-600"
>
{instructor}
</p>
))}
</div>
</div>
</Link>
)
}
Expand Down
116 changes: 89 additions & 27 deletions src/pages/Classes.jsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,112 @@
import { useState, useEffect } from 'react';
import { useSearch, useLocation } from 'wouter';
import Class from '../components/Class';
import Level from '../components/Level';
import { getClasses, getLevels } from '../api/class-wrapper';

const Classes = () => {
const [classes, setClasses] = useState([]);
const [level, setLevel] = useState()
const [loading, setLoading] = useState(true)
const queryString = useSearch()
const [location, setLocation] = useLocation()
const classFilter = new URLSearchParams(queryString)
const [level, setLevel] = useState();
const [allLevels, setAllLevels] = useState([]);
const [loading, setLoading] = useState(true);
const queryString = useSearch();
const [location, setLocation] = useLocation();
const classFilter = new URLSearchParams(queryString);

useEffect(() => {
// make sure level query exists otherwise redirect to level page
if (queryString === "") {
setLocation("/levels")
setLocation("/levels");
}

const fetchData = async () => {
setLoading(true)
// fetch classes
const classData = await getClasses(classFilter.toString())
setLoading(true);
const classData = await getClasses(classFilter.toString());
setClasses(classData);
// fetch level
const levelData = await getLevels(classFilter.toString())
setLevel(levelData[0])
setLoading(false)
}
const levelData = await getLevels();
setAllLevels(levelData);
setLevel(levelData.find(l => l.level === parseInt(classFilter.get("level"))));
setLoading(false);
};
fetchData();
}, []);

if (loading || !level) {
return <></>
}
if (loading || !level) return <></>;

return (
<div className='px-8 py-6'>
<h3>Level {level.level}</h3>
<h1 className='text-4xl font-bold'>{level.name}</h1>
<div className='flex gap-3 mt-4'>
{classes.map((classObj, classIndex) => (
<Class key={classIndex} classObj={classObj} />
))}
<div className="min-h-screen bg-white">
{/* Banner Section */}
<div className="bg-gradient-to-r from-[#FFFFFF] from-5% via-[#D3EDFC] via-35% via-[#B2A0FA] via-75% via-[#8AC7F4] to-[#3F96EA] py-12">
<div className="max-w-7xl mx-auto px-8">
<p className="text-lg text-dark-blue-700 mb-2">Level {level.level}</p>
<h1 className='text-4xl font-bold text-dark-blue-800 mb-4'>{level.name}</h1>

<div className="flex items-center gap-2 mb-6">
<div className="flex -space-x-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-300 to-blue-400 border-2 border-white"></div>
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-200 to-blue-300 border-2 border-white"></div>
</div>
<span className="text-neutral-400">{level.enrolledCount || 46} students enrolled</span>
</div>

<p className="text-neutral-600 max-w-2xl mb-8">
This class is for those with little to no experience in English. It will be going over
the alphabet, basic vocabulary, and simple grammar rules.
</p>

<div className="flex gap-4">
<span className="px-6 py-2.5 bg-white rounded-full text-neutral-500 text-sm hover:bg-opacity-90 transition-colors">
Basic Conversations
</span>
<span className="px-6 py-2.5 bg-white rounded-full text-neutral-500 text-sm hover:bg-opacity-90 transition-colors">
The Alphabet
</span>
</div>
</div>
</div>

{/* Content Section */}
<div className="max-w-7xl mx-auto px-8 py-12">
{/* Open Classes */}
<div className="mb-20">
<h2 className="text-2xl font-bold text-dark-blue-800 mb-4">Open Classes</h2>
<p className="text-neutral-600 mb-8">
Here are the open classes in this level. More information will be given by the instructor after you sign up!
</p>
<div className='grid grid-cols-1 md:grid-cols-4 gap-6'>
{classes.map((classObj, classIndex) => (
<Class key={classIndex} classObj={classObj} />
))}
</div>
</div>

{/* Other Levels */}
<div>
<h2 className="text-2xl font-bold text-dark-blue-800 mb-4">Other Levels</h2>
<p className="text-neutral-600 mb-8">
If this level isn't a good fit for you, take a look at the other available levels!
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{allLevels
.filter(l => l.level !== level.level)
.map((level, index) => (
<div key={index} className="bg-white shadow-level rounded-lg border border-neutral-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6">
<h3 className="text-lg font-semibold text-dark-blue-800 mb-2">Level {level.level}</h3>
<p className="text-neutral-600 text-sm mb-4">{level.description || "New Concept Book 1 -- Ch. 1-72"}</p>
<button
onClick={() => setLocation(`/classes?level=${level.level}`)}
className="text-blue-500 text-sm font-medium hover:text-blue-600"
>
View Level →
</button>
</div>
</div>
myix765 marked this conversation as resolved.
Show resolved Hide resolved
))}
</div>
</div>
</div>
</div>
);
}
};

export default Classes
export default Classes;
3 changes: 3 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export default {
],
theme: {
extend: {
boxShadow:{
'level': 'inset 0.5em 0em #A5D3E9',
},
fontFamily: {
avenir: ["Avenir", "sans-serif"],
},
Expand Down