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
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.