Skip to content

Commit

Permalink
redesign literally everything to be cool
Browse files Browse the repository at this point in the history
AmirAgassi committed Nov 24, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent fcfbbc5 commit b185857
Showing 9 changed files with 844 additions and 474 deletions.
2 changes: 1 addition & 1 deletion backend/utils/grok.py
Original file line number Diff line number Diff line change
@@ -105,7 +105,7 @@ async def query_grok(content: str) -> str:
"model": "llama3-groq-8b-8192-tool-use-preview",
"messages": messages,
"temperature": 0.7,
"max_tokens": 5
"max_tokens": 4096
}

headers = {
4 changes: 2 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -54,13 +54,13 @@ const courseManageRoute = new Route({

const studentDashboardRoute = new Route({
getParentRoute: () => rootRoute,
path: 'dashboard/student',
path: 'dashboard',
component: StudentDashboard,
})

const professorDashboardRoute = new Route({
getParentRoute: () => rootRoute,
path: 'dashboard/professor',
path: 'dashboard',
component: ProfessorDashboard,
})

202 changes: 163 additions & 39 deletions frontend/src/components/CourseView.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState, useEffect } from 'react'
import { useParams } from '@tanstack/react-router'
import { useAuth } from '../contexts/AuthContext'
import { motion, AnimatePresence } from 'framer-motion'
import { FiHelpCircle, FiAlertTriangle, FiBook, FiArrowRight, FiArrowLeft, FiCheckCircle } from 'react-icons/fi'
import toast from 'react-hot-toast'
import ReactMarkdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
@@ -10,8 +12,21 @@ import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css'
import QuizModal from './QuizModal'
import QuizResults from './QuizResults'
import { FiHelpCircle, FiAlertTriangle } from 'react-icons/fi'
import { motion, AnimatePresence } from 'framer-motion'

const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
}

const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}

export default function CourseView() {
const { courseId } = useParams({ from: '/course/$courseId' })
@@ -127,90 +142,199 @@ export default function CourseView() {
}
}

if (!course) return <div>Loading...</div>
if (!course) return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-gray-50 to-blue-50 flex justify-center items-center">
<div className="w-16 h-16 relative">
<div className="absolute inset-0 rounded-full border-4 border-blue-100"></div>
<div className="absolute inset-0 rounded-full border-4 border-blue-500 border-t-transparent animate-spin"></div>
</div>
</div>
)

return (
<div className="flex min-h-screen">
{/* sidebar with section navigation */}
<div className="w-64 bg-gray-100 p-4 border-r">
<h2 className="text-xl font-bold mb-4">{course.title}</h2>
<div className="space-y-2">
{sections.map(section => (
<div
key={section.id}
onClick={() => handleSectionClick(section)}
className={`
p-2 rounded cursor-pointer
${currentSection?.id === section.id ? 'bg-blue-500 text-white' : 'hover:bg-gray-200'}
${progress[section.id]?.completed ? 'border-l-4 border-green-500' : ''}
`}
>
{section.title}
</div>
))}

{/* quiz button */}
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-gray-50 to-blue-50">
<div className="flex">
{/* sidebar with section navigation */}
<motion.div
initial={{ x: -50, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
className="w-80 bg-white shadow-lg p-6 min-h-screen sticky top-0"
>
<div className="mb-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2">{course.title}</h2>
<div className="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full"></div>
</div>

<div className="space-y-2">
{sections.map(section => (
<motion.div
key={section.id}
whileHover={{ x: 4 }}
onClick={() => handleSectionClick(section)}
className={`
p-4 rounded-xl cursor-pointer transition-all
${currentSection?.id === section.id
? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-lg'
: 'hover:bg-gray-50'
}
${progress[section.id]?.completed ? 'border-l-4 border-green-500' : ''}
`}
>
<div className="flex items-center justify-between">
<span className="font-medium">{section.title}</span>
{progress[section.id]?.completed && (
<FiCheckCircle className="text-green-500" />
)}
</div>
</motion.div>
))}
</div>

<motion.button
onClick={() => handleQuizButtonClick()}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="mt-6 w-full p-3 bg-gradient-to-r from-purple-500 to-indigo-600
text-white rounded-lg shadow-md hover:shadow-lg transition-all"
className="mt-8 w-full p-4 bg-gradient-to-r from-purple-500 to-pink-500
text-white rounded-xl shadow-md hover:shadow-lg transition-all
flex items-center justify-center group"
>
Take Final Quiz
<FiArrowRight className="ml-2 group-hover:translate-x-1 transition-transform" />
</motion.button>
</motion.div>

{/* main content area */}
<div className="flex-1 p-8 max-w-4xl mx-auto">
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
>
{currentSection ? (
<motion.div variants={itemVariants}>
<div className="bg-white rounded-2xl shadow-lg p-8 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-6">
{currentSection.title}
</h2>

{currentSection.pages && currentSection.pages[currentPage] && (
<div className="prose prose-slate max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeRaw, rehypeKatex]}
className="markdown-content"
>
{currentSection.pages[currentPage].content}
</ReactMarkdown>
</div>
)}
</div>

{/* pagination controls */}
<div className="flex justify-between items-center mt-8">
<motion.button
onClick={handlePrevPage}
disabled={currentPage === 0}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex items-center px-6 py-3 bg-white rounded-xl shadow-md
disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg
transition-all group"
>
<FiArrowLeft className="mr-2 group-hover:-translate-x-1 transition-transform" />
Previous
</motion.button>

<span className="text-gray-600 font-medium">
Page {currentPage + 1} of {currentSection.pages?.length || 1}
</span>

<motion.button
onClick={handleNextPage}
disabled={!currentSection.pages || currentPage >= currentSection.pages.length - 1}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex items-center px-6 py-3 bg-white rounded-xl shadow-md
disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg
transition-all group"
>
Next
<FiArrowRight className="ml-2 group-hover:translate-x-1 transition-transform" />
</motion.button>
</div>
</motion.div>
) : (
<motion.div
variants={itemVariants}
className="bg-white rounded-2xl shadow-lg p-12 text-center"
>
<FiBook className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-xl font-semibold text-gray-900 mb-2">
Select a section to begin
</h3>
<p className="text-gray-600">
Choose a section from the sidebar to start learning
</p>
</motion.div>
)}
</motion.div>
</div>
</div>

{/* confirmation modal */}
{/* quiz confirmation modal */}
<AnimatePresence>
{showQuizConfirm && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
className="bg-white rounded-xl p-6 max-w-md w-full shadow-xl"
className="bg-white rounded-2xl p-8 max-w-md w-full shadow-xl"
>
<div className="text-center mb-6">
<div className="bg-yellow-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<FiAlertTriangle className="w-8 h-8 text-yellow-500" />
</div>
<h3 className="text-xl font-bold text-gray-800">Are you sure?</h3>
<p className="text-gray-600 mt-2">
<h3 className="text-2xl font-bold text-gray-900 mb-2">Are you sure?</h3>
<p className="text-gray-600">
You haven't viewed all sections yet. It's recommended to review all material before taking the quiz.
</p>
</div>

<div className="flex gap-3">
<button
<div className="flex gap-4">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowQuizConfirm(false)}
className="flex-1 px-4 py-2 bg-purple-600 text-white rounded-lg
hover:bg-purple-700 transition-colors"
className="flex-1 px-6 py-3 bg-gradient-to-r from-purple-500 to-pink-500
text-white rounded-xl shadow-md hover:shadow-lg transition-all"
>
Review Material
</button>
<button
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => {
setShowQuizConfirm(false)
handleQuizStart(currentSection.id)
}}
className="flex-1 px-4 py-2 bg-gray-800 text-white rounded-lg
hover:bg-gray-900 transition-colors"
className="flex-1 px-6 py-3 bg-gradient-to-r from-gray-700 to-gray-900
text-white rounded-xl shadow-md hover:shadow-lg transition-all"
>
Take Quiz Anyway
</button>
</motion.button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>

{/* quiz modal */}
{/* main content area with pagination */}
<div className="flex-1 p-8">
{currentSection ? (
2 changes: 1 addition & 1 deletion frontend/src/components/Layout.jsx
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ export default function Layout() {
{isDropdownOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1">
<Link
to={`/dashboard/${user.role.toLowerCase()}`}
to={`/dashboard`}
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setIsDropdownOpen(false)}
>
246 changes: 140 additions & 106 deletions frontend/src/components/LoginPage.jsx
Original file line number Diff line number Diff line change
@@ -1,145 +1,179 @@
import { useState } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { Link } from '@tanstack/react-router'
import { useAuth } from '../contexts/AuthContext'
import { motion } from 'framer-motion'
import { FiLogIn, FiArrowRight } from 'react-icons/fi'
import toast from 'react-hot-toast'

const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
}

const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}

export default function LoginPage() {
const navigate = useNavigate()
const { login, signup } = useAuth()
const [isLogin, setIsLogin] = useState(true)
const { login } = useAuth()
const [isLoading, setIsLoading] = useState(false)
const [formData, setFormData] = useState({
email: '',
password: '',
username: '',
role: 'student'
})

const handleSubmit = async (e) => {
e.preventDefault()
setIsLoading(true)

const loadingToast = toast.loading(
isLogin ? 'Signing in...' : 'Creating account...'
)

try {
const success = await (isLogin
? login(formData.email, formData.password)
: signup(formData.email, formData.password, formData.username, formData.role)
)

toast.dismiss(loadingToast)
const success = await login(formData.email, formData.password)

if (success) {
toast.success(
isLogin
? 'Successfully signed in!'
: 'Account created successfully!'
)
await navigate({ to: '/dashboard', replace: true })
toast.success('Successfully signed in!')
navigate({ to: '/dashboard', replace: true })
} else {
toast.error(
isLogin
? 'Invalid email or password'
: 'Failed to create account'
)
toast.error('Invalid email or password')
}
} catch (error) {
toast.dismiss(loadingToast)
console.error('Login error:', error)
toast.error('Something went wrong. Please try again.')
console.error('Error:', error)
} finally {
setIsLoading(false)
}
}

return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
<div>
<h2 className="text-center text-3xl font-extrabold text-gray-900">
{isLogin ? 'Sign in to your account' : 'Create a new account'}
</h2>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="space-y-4">
{!isLogin && (
<div className="min-h-screen relative overflow-hidden">
{/* animated background */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50" />
<div className="absolute inset-0 bg-[url('/grid-pattern.svg')] opacity-[0.2]" />

{/* floating shapes */}
<div className="absolute inset-0 overflow-hidden">
{[...Array(3)].map((_, i) => (
<motion.div
key={i}
className="absolute w-72 h-72 bg-gradient-to-r from-blue-400/5 to-purple-400/5 rounded-full"
animate={{
y: [0, -50, 0],
scale: [1, 1.05, 1],
}}
transition={{
duration: 15 + i * 2,
repeat: Infinity,
ease: "easeInOut"
}}
style={{
left: `${20 + i * 30}%`,
top: `${20 + i * 20}%`,
}}
/>
))}
</div>

<div className="relative flex items-center justify-center min-h-screen py-12 px-4 sm:px-6 lg:px-8">
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="max-w-md w-full space-y-8"
>
<motion.div variants={itemVariants}>
<div className="flex justify-center mb-4">
<div className="p-4 bg-white rounded-full shadow-xl">
<FiLogIn className="w-8 h-8 text-blue-600" />
</div>
</div>
<h2 className="text-center text-4xl font-bold text-gray-900 drop-shadow-sm">
Welcome Back
</h2>
<p className="mt-3 text-center text-gray-600">
Don't have an account?{' '}
<Link to="/register" className="font-medium text-blue-600 hover:text-blue-500 transition-colors">
Sign up
</Link>
</p>
</motion.div>

<motion.form
variants={itemVariants}
onSubmit={handleSubmit}
className="mt-8 space-y-6 bg-white/80 backdrop-blur-lg p-8 rounded-2xl shadow-xl"
>
<div className="space-y-5">
<div>
<input
type="text"
name="username"
autoComplete="username"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
className="w-full rounded border p-2"
placeholder="Username"
type="email"
name="email"
autoComplete="email"
required
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
className="appearance-none rounded-xl relative block w-full px-4 py-3 border
border-gray-300 placeholder-gray-400 text-gray-900 focus:outline-none
focus:ring-2 focus:ring-blue-500 focus:border-transparent
transition-all duration-200 ease-in-out"
placeholder="Email address"
/>
</div>
)}
<div>
<input
type="email"
name="email"
autoComplete="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
className="w-full rounded border p-2"
placeholder="Email address"
required
/>
</div>
<div>
<input
type="password"
name="password"
autoComplete={isLogin ? "current-password" : "new-password"}
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
className="w-full rounded border p-2"
placeholder="Password"
required
/>
</div>
</div>

{!isLogin && (
<>
<div>
<label htmlFor="role" className="block text-sm font-medium text-gray-700">
I am a:
</label>
<select
id="role"
name="role"
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
value={formData.role}
onChange={(e) => setFormData({...formData, role: e.target.value})}
>
<option value="student">Student</option>
<option value="professor">Professor</option>
</select>
<input
type="password"
name="password"
autoComplete="current-password"
required
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
className="appearance-none rounded-xl relative block w-full px-4 py-3 border
border-gray-300 placeholder-gray-400 text-gray-900 focus:outline-none
focus:ring-2 focus:ring-blue-500 focus:border-transparent
transition-all duration-200 ease-in-out"
placeholder="Password"
/>
</div>
</>
)}
</div>

<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{isLogin ? 'Sign in' : 'Sign up'}
</button>
</div>
</form>
<div>
<button
type="submit"
disabled={isLoading}
className="group relative w-full flex justify-center items-center py-3 px-4
text-sm font-medium rounded-xl text-white bg-gradient-to-r from-blue-600 to-purple-600
hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 ease-in-out
disabled:from-blue-400 disabled:to-purple-400 shadow-lg"
>
{isLoading ? (
'Signing in...'
) : (
<>
Sign in
<FiArrowRight className="ml-2 group-hover:translate-x-1 transition-transform" />
</>
)}
</button>
</div>

<div className="text-center">
<button
onClick={() => setIsLogin(!isLogin)}
className="text-sm text-blue-600 hover:text-blue-500"
>
{isLogin
? "Don't have an account? Sign up"
: "Already have an account? Sign in"}
</button>
</div>
<div className="flex items-center justify-center">
<Link
to="/forgot-password"
className="text-sm text-blue-600 hover:text-blue-500 transition-colors"
>
Forgot your password?
</Link>
</div>
</motion.form>
</motion.div>
</div>
</div>
)
321 changes: 182 additions & 139 deletions frontend/src/components/ProfessorDashboard.jsx

Large diffs are not rendered by default.

280 changes: 179 additions & 101 deletions frontend/src/components/Register.jsx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion frontend/src/components/Root.jsx
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ export default function Root() {
{isDropdownOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1">
<Link
to={user.role === 'student' ? '/dashboard/student' : '/dashboard/professor'}
to={user.role === 'student' ? '/dashboard' : '/dashboard'}
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setIsDropdownOpen(false)}
>
259 changes: 175 additions & 84 deletions frontend/src/components/StudentDashboard.jsx

Large diffs are not rendered by default.

0 comments on commit b185857

Please sign in to comment.