diff --git a/.github/workflows/azure-static-web-apps-thankful-glacier-09f37ef03.yml b/.github/workflows/azure-static-web-apps-thankful-glacier-09f37ef03.yml index 0e90d04..37d3347 100644 --- a/.github/workflows/azure-static-web-apps-thankful-glacier-09f37ef03.yml +++ b/.github/workflows/azure-static-web-apps-thankful-glacier-09f37ef03.yml @@ -1,46 +1,46 @@ -name: Azure Static Web Apps CI/CD - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened, closed] - branches: - - main - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v3 - with: - submodules: true - lfs: false - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_GLACIER_09F37EF03 }} - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "/" # App source code path - api_location: "" # Api source code path - optional - output_location: "dist" # Built app content directory - optional - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_GLACIER_09F37EF03 }} - action: "close" +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + - uses: actions/checkout@v3 + with: + submodules: true + lfs: false + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_GLACIER_09F37EF03 }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "/" # App source code path + api_location: "" # Api source code path - optional + output_location: "dist" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_GLACIER_09F37EF03 }} + action: "close" diff --git a/eslint.config.js b/eslint.config.js index ec2b712..46c5909 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,33 +1,33 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; export default [ - { ignores: ['dist'] }, + { ignores: ["dist"] }, { - files: ['**/*.{js,jsx}'], + files: ["**/*.{js,jsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, parserOptions: { - ecmaVersion: 'latest', + ecmaVersion: "latest", ecmaFeatures: { jsx: true }, - sourceType: 'module', + sourceType: "module", }, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...js.configs.recommended.rules, ...reactHooks.configs.recommended.rules, - 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], - 'react-refresh/only-export-components': [ - 'warn', + "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }], + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, }, -] +]; diff --git a/index.html b/index.html index e22d842..53cb5e2 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + StudyHub diff --git a/package-lock.json b/package-lock.json index 8c4f0ef..a5c24b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", "postcss": "^8.5.3", + "prettier": "^3.0.0", "tailwindcss": "^3.4.17", "vite": "^6.3.5" } @@ -3788,6 +3789,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", diff --git a/package.json b/package.json index d3becca..f339e32 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", + "format": "prettier --write .", "preview": "vite preview" }, "dependencies": { @@ -29,6 +30,7 @@ "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", "postcss": "^8.5.3", + "prettier": "^3.0.0", "tailwindcss": "^3.4.17", "vite": "^6.3.5" } diff --git a/postcss.config.js b/postcss.config.js index 2e7af2b..2aa7205 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/src/App.css b/src/App.css index a68ab69..8872199 100644 --- a/src/App.css +++ b/src/App.css @@ -2,4 +2,4 @@ /* font-family: Arial, sans-serif;*/ /* margin: 0;*/ /* padding: 0;*/ -/*}*/ \ No newline at end of file +/*}*/ diff --git a/src/App.jsx b/src/App.jsx index f606a38..30b87fd 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,119 +1,189 @@ -import React from 'react'; -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import Header from './components/Header'; -import Sidebar from './components/Sidebar'; -import Login from './pages/Login'; -import Signup from './pages/Signup'; -import Dashboard from './pages/Dashboard'; -import Profile from './pages/Profile'; -import CourseGrid from './pages/courses/CourseGrid.jsx'; -import { themeConfig } from './themeConfig'; -import CourseHome from './pages/courses/course/CourseHome.jsx'; -import CourseModules from './pages/courses/course/CourseModules.jsx'; -import CourseAssignment from './pages/courses/course/CourseAssignment.jsx'; -import AssignmentSubmission from './pages/courses/course/AssignmentSubmission.jsx'; -import ModuleDetails from './pages/courses/course/ModuleDetails'; -import ModulesCreate from './pages/courses/course/ModulesCreate.jsx'; -import CourseCreate from './pages/courses/course/CourseCreate.jsx'; -import CourseEdit from './pages/courses/course/CourseEdit.jsx'; +import React from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { useSelector } from "react-redux"; +import Header from "./components/Header"; +import Sidebar from "./components/Sidebar"; +import Login from "./pages/Login"; +import Signup from "./pages/Signup"; +import Dashboard from "./pages/Dashboard"; +import Profile from "./pages/Profile"; +import CourseGrid from "./pages/courses/CourseGrid.jsx"; +import { themeConfig } from "./themeConfig"; +import CourseHome from "./pages/courses/course/CourseHome.jsx"; +import CourseModules from "./pages/courses/course/CourseModules.jsx"; +import CourseAssignment from "./pages/courses/course/CourseAssignment.jsx"; +import AssignmentSubmission from "./pages/courses/course/AssignmentSubmission.jsx"; +import ModuleDetails from "./pages/courses/course/ModuleDetails"; +import ModulesCreate from "./pages/courses/course/ModulesCreate.jsx"; +import CourseCreate from "./pages/courses/course/CourseCreate.jsx"; +import CourseEdit from "./pages/courses/course/CourseEdit.jsx"; import ResourceCreate from "./pages/courses/course/ResourceCreate.jsx"; -import ModulesEdit from './pages/courses/course/ModulesEdit.jsx'; +import ModulesEdit from "./pages/courses/course/ModulesEdit.jsx"; +import ResourceUpdate from "./pages/courses/course/ResourceUpdate.jsx"; const LoggedInLayout = ({ children }) => { - const { theme } = useSelector((state) => state.theme); - const { bg, text } = themeConfig[theme]; - return ( -
-
-
- -
- {children} -
-
-
- ); + const { theme } = useSelector((state) => state.theme); + const { bg, text } = themeConfig[theme]; + return ( +
+
+
+ +
+ {children} +
+
+
+ ); }; function App() { - const { theme } = useSelector((state) => state.theme); - const { bg, text } = themeConfig[theme]; + const { theme } = useSelector((state) => state.theme); + const { bg, text } = themeConfig[theme]; - return ( -
- - } /> - } /> - } /> - } - /> - } - /> - } - /> - } - /> - } - - /> -
Schedule Page (TBD)
} - /> -
Groups Page (TBD)
} - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> -
-
- ); + return ( +
+ + } /> + } /> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + +
Schedule Page (TBD)
+ + } + /> + +
Groups Page (TBD)
+ + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> +
+
+ ); } export default function AppWrapper() { - return ( - - - - ); -} \ No newline at end of file + return ( + + + + ); +} diff --git a/src/assets/profile-placeholder.jpg b/src/assets/profile-placeholder.jpg new file mode 100644 index 0000000..04d29ea Binary files /dev/null and b/src/assets/profile-placeholder.jpg differ diff --git a/src/components/DashboardCard.jsx b/src/components/DashboardCard.jsx index fbc683b..169e5f9 100644 --- a/src/components/DashboardCard.jsx +++ b/src/components/DashboardCard.jsx @@ -1,25 +1,86 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import {themeConfig} from "../themeConfig.js"; +import React from "react"; +import axios from "axios"; +import { useSelector } from "react-redux"; +import { themeConfig } from "../themeConfig.js"; const DashboardCard = () => { - const { theme } = useSelector((state) => state.theme); - const { bg, text, border } = themeConfig[theme]; - const subjects = [ - { title: "APP200", endDate: "2025-06-12" }, - { title: "DAT2000", endDate: "2025-05-31" }, - { title: "OBJ2100", endDate: "2025-06-23" }, - ]; + const { theme } = useSelector((state) => state.theme); + const { bg, text, border } = themeConfig[theme]; + const [courses, setCourses] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const [error, setError] = React.useState(null); + const user = JSON.parse(localStorage.getItem("user")); + const userId = user?.id; + const userRole = user?.role; + const formatDate = (dateString) => { + if (!dateString) return ""; + const date = new Date(dateString); + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are 0-based + const year = date.getFullYear(); + return `${day}/${month}/${year}`; + }; + + React.useEffect(() => { + if (!userId) { + setError("No user ID found"); + setLoading(false); + return; + } + + let endpoint; + if (userRole === "STUDENT") { + endpoint = `http://localhost:8080/api/courses/student/${userId}`; + } else if (userRole === "INSTRUCTOR") { + endpoint = `http://localhost:8080/api/courses/instructor/${userId}`; + } else { + setError("Invalid user role"); + setLoading(false); + return; + } + + axios + .get(endpoint) + .then((response) => { + setCourses(response.data); + setLoading(false); + }) + .catch((err) => { + console.error("Error fetching courses:", err); + setError("Error fetching courses"); + setLoading(false); + }); + }, [userId, userRole]); + + if (loading) return

Loading...

; + if (error) return ( -
- {subjects.map((subject) => ( -
-

{subject.title}

-

End Date: {subject.endDate}

-
- ))} -
+

+ {error} +

); + + return ( +
+ {courses.length === 0 && ( +

No enrolled courses.

+ )} + {courses.map((course, index) => ( +
+

{course.title}

+

+ End Date:{" "} + {formatDate(course.endDate)} +

+
+ ))} +
+ ); }; -export default DashboardCard; \ No newline at end of file +export default DashboardCard; diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index ff3edfa..622f60e 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -1,11 +1,13 @@ -import { useSelector } from 'react-redux'; +import { useSelector } from "react-redux"; export function Footer() { - return ( - - ); + return ( + + ); } diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 759a0c9..e4d8a24 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,47 +1,46 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useSelector, useDispatch } from 'react-redux'; -import { setTheme } from '../store/themeSlice'; -import { themeConfig } from '../themeConfig'; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { useSelector, useDispatch } from "react-redux"; +import { setTheme } from "../store/themeSlice"; +import { themeConfig } from "../themeConfig"; +import profilePlaceholder from "../assets/profile-placeholder.jpg"; const Header = () => { - const navigate = useNavigate(); - const { theme } = useSelector((state) => state.theme); - const dispatch = useDispatch(); - const { bg, text, border } = themeConfig[theme]; + const navigate = useNavigate(); + const { theme } = useSelector((state) => state.theme); + const dispatch = useDispatch(); + const { bg, text, border } = themeConfig[theme]; - const handleThemeToggle = () => { - dispatch(setTheme(theme === 'light' ? 'dark' : 'light')); - }; + const handleThemeToggle = () => { + dispatch(setTheme(theme === "light" ? "dark" : "light")); + }; - return ( -
-
navigate('/dashboard')} - > -

StudyHub

-
-
- -
navigate('/profile')} - > - Profile -
-
-
- ); + return ( +
+
navigate("/dashboard")}> +

+ StudyHub +

+
+
+ +
navigate("/profile")}> + Profile +
+
+
+ ); }; -export default Header; \ No newline at end of file +export default Header; diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 45a7698..22e434d 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -1,50 +1,55 @@ -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { Home, BookOpen, Calendar, Users } from 'lucide-react'; -import { themeConfig } from '../themeConfig'; -import { iconColors } from '../utils/styles'; +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { Home, BookOpen, Calendar, Users } from "lucide-react"; +import { themeConfig } from "../themeConfig"; +import { iconColors } from "../utils/styles"; const Sidebar = () => { - const navigate = useNavigate(); - const { theme } = useSelector((state) => state.theme); - const [isCollapsed, setIsCollapsed] = useState(false); - const { bg, text, border, accentBg } = themeConfig[theme]; + const navigate = useNavigate(); + const { theme } = useSelector((state) => state.theme); + const [isCollapsed, setIsCollapsed] = useState(false); + const { bg, text, border, accentBg } = themeConfig[theme]; - const navItems = [ - { icon: , label: 'Dashboard', path: '/dashboard' }, - { icon: , label: 'Courses', path: '/courses' }, - { icon: , label: 'Schedule', path: '/schedule' }, - { icon: , label: 'Groups', path: '/groups' }, - ]; + const navItems = [ + { icon: , label: "Dashboard", path: "/dashboard" }, + { icon: , label: "Courses", path: "/courses" }, + { icon: , label: "Schedule", path: "/schedule" }, + { icon: , label: "Groups", path: "/groups" }, + ]; - return ( - + ); }; -export default Sidebar; \ No newline at end of file +export default Sidebar; diff --git a/src/components/ThemeSwitcher.jsx b/src/components/ThemeSwitcher.jsx index 6efe0b2..453bc21 100644 --- a/src/components/ThemeSwitcher.jsx +++ b/src/components/ThemeSwitcher.jsx @@ -1,36 +1,38 @@ -import React from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { setTheme } from '../store/themeSlice'; -import { themeConfig } from '../themeConfig'; +import React from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { setTheme } from "../store/themeSlice"; +import { themeConfig } from "../themeConfig"; const themes = [ - { name: 'light', label: 'Light ☀️' }, - { name: 'dark', label: 'Dark 🌙' }, - { name: 'ocean', label: 'Ocean 🌊' }, - { name: 'forest', label: 'Forest 🌲' }, - { name: 'sunset', label: 'Sunset 🌅' }, + { name: "light", label: "Light ☀️" }, + { name: "dark", label: "Dark 🌙" }, + { name: "ocean", label: "Ocean 🌊" }, + { name: "forest", label: "Forest 🌲" }, + { name: "sunset", label: "Sunset 🌅" }, ]; const ThemeSwitcher = ({ className }) => { - const { theme } = useSelector((state) => state.theme); - const dispatch = useDispatch(); - const { bg, text, border } = themeConfig[theme]; + const { theme } = useSelector((state) => state.theme); + const dispatch = useDispatch(); + const { bg, text, border } = themeConfig[theme]; - return ( -
- -
- ); + return ( +
+ +
+ ); }; -export default ThemeSwitcher; \ No newline at end of file +export default ThemeSwitcher; diff --git a/src/config.js b/src/config.js index b0593b7..b512432 100644 --- a/src/config.js +++ b/src/config.js @@ -1 +1 @@ -export const API_BASE_URL = 'http://localhost:8080/api/users'; \ No newline at end of file +// export const API_BASE_URL = 'http://localhost:8080/api/users'; diff --git a/src/index.css b/src/index.css index f65fcba..6ff7657 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Roboto+Condensed:wght@400;700&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Roboto+Condensed:wght@400;700&display=swap"); @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; @@ -7,7 +7,12 @@ html { @apply font-quicksand text-[16px] leading-6 min-h-screen; } - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { @apply font-roboto; } } @@ -37,4 +42,4 @@ top: 2rem; right: 2rem; } -} \ No newline at end of file +} diff --git a/src/main.jsx b/src/main.jsx index febba96..214b892 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,12 +1,12 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import { Provider } from 'react-redux'; -import './index.css'; -import App from './App.jsx'; -import { store } from './store/store.js'; +import React from "react"; +import { createRoot } from "react-dom/client"; +import { Provider } from "react-redux"; +import "./index.css"; +import App from "./App.jsx"; +import { store } from "./store/store.js"; -createRoot(document.getElementById('root')).render( - - - -); \ No newline at end of file +createRoot(document.getElementById("root")).render( + + + , +); diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index f25a752..565eaa0 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -1,18 +1,23 @@ -import React from 'react'; +import React from "react"; import DashboardCard from "../components/DashboardCard.jsx"; -import { useSelector } from 'react-redux'; -import {themeConfig} from "../themeConfig.js"; +import { useSelector } from "react-redux"; +import { themeConfig } from "../themeConfig.js"; const Dashboard = () => { - const { theme } = useSelector((state) => state.theme); - const { bg, text } = themeConfig[theme]; - return ( -
-

Dashboard

-

Your enrolled classes for the semester

- -
- ); + const { theme } = useSelector((state) => state.theme); + const { bg, text } = themeConfig[theme]; + + return ( +
+
+

+ Dashboard +

+

Your semester courses

+ +
+
+ ); }; -export default Dashboard; \ No newline at end of file +export default Dashboard; diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 910bbcb..d1813d3 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,113 +1,154 @@ -import React, { useState } from 'react'; -import axios from 'axios'; -import { useNavigate, Link } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { Eye, EyeOff } from 'lucide-react'; -import { API_BASE_URL } from '../config'; -import {themeConfig} from "../themeConfig.js"; -import {iconColors} from "../utils/styles.js"; +import React, { useState } from "react"; +import axios from "axios"; +import { useNavigate, Link } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { Eye, EyeOff } from "lucide-react"; +import { themeConfig } from "../themeConfig.js"; +import { iconColors } from "../utils/styles.js"; const Login = () => { - const [formData, setFormData] = useState({ email: '', password: '' }); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(''); - const navigate = useNavigate(); - const { theme } = useSelector((state) => state.theme); - const { bg, text, accentBg, border } = themeConfig[theme]; + const [formData, setFormData] = useState({ email: "", password: "" }); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(""); + const navigate = useNavigate(); + const { theme } = useSelector((state) => state.theme); + const { bg, text, accentBg, border } = themeConfig[theme]; - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prev) => ({ ...prev, [name]: value })); - setError(''); - }; + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + setError(""); + }; - const handleSubmit = async (e) => { - e.preventDefault(); - setError(''); - try { - const response = await axios.post(`${API_BASE_URL}/login`, formData); - localStorage.setItem('user', JSON.stringify(response.data)); - navigate('/dashboard'); - } catch (err) { - setError(err.response?.data || 'Login failed'); - } - }; + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); + try { + const response = await axios.post( + `http://localhost:8080/api/users/login`, + formData, + ); + localStorage.setItem("user", JSON.stringify(response.data)); + navigate("/dashboard"); + } catch (err) { + setError(err.response?.data || "Login failed"); + } + }; - const handleForgotPasswordClick = (e) => { - e.preventDefault(); - alert('Password reset not implemented. Contact support.'); - }; + const handleForgotPasswordClick = (e) => { + e.preventDefault(); + alert("Password reset not implemented. Contact support."); + }; - return ( -
-
-
-
-

Login to StudyHub

- {error &&

{error}

} -
-
- - -
-
- -
- - -
-
-
-
- - -
- Forgot password? -
- -
-

- Don't have an account? Register -

-
+ return ( +
+
+
+
+

+ Login to StudyHub +

+ {error && ( +

+ {error} +

+ )} +
+
+ +
+
+ +
+ + +
+
+
+
+ + +
+ + Forgot password? + +
+ +
+

+ Don't have an account?{" "} + + Register + +

- ); +
+
+ ); }; -export default Login; \ No newline at end of file +export default Login; diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index 2c4f870..b227de9 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -1,57 +1,60 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import {themeConfig} from "../themeConfig.js"; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { themeConfig } from "../themeConfig.js"; const Profile = () => { - const user = JSON.parse(localStorage.getItem('user')); - const navigate = useNavigate(); - const { theme } = useSelector((state) => state.theme); - const { bg, text, border, accentBg } = themeConfig[theme]; - const [profilePic, setProfilePic] = React.useState('/profile-placeholder.jpg'); + const user = JSON.parse(localStorage.getItem("user")); + const navigate = useNavigate(); + const { theme } = useSelector((state) => state.theme); + const { bg, text, border, accentBg } = themeConfig[theme]; - if (!user) { - navigate('/login'); - return null; - } + if (!user) { + navigate("/login"); + return null; + } - const handleLogout = () => { localStorage.removeItem('user'); navigate('/login'); }; - const handleImageChange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onloadend = () => setProfilePic(reader.result); - reader.readAsDataURL(file); - } - }; + const handleLogout = () => { + localStorage.removeItem("user"); + navigate("/login"); + }; - return ( -
-
-

User Profile

-
- Profile - -
-
-

Username: {user.username}

-

Email: {user.email}

-

First Name: {user.firstName}

-

Last Name: {user.lastName}

-

Role: {user.role}

-
- -
+ return ( +
+
+

+ User Profile +

+ +
+

+ Email: {user.email} +

+

+ First Name: {user.firstName} +

+

+ Last Name: {user.lastName} +

+

+ Role: {user.role} +

- ); + +
+
+ ); }; -export default Profile; \ No newline at end of file +export default Profile; diff --git a/src/pages/Signup.jsx b/src/pages/Signup.jsx index 1c95667..63c8d0b 100644 --- a/src/pages/Signup.jsx +++ b/src/pages/Signup.jsx @@ -1,181 +1,231 @@ -import React from 'react'; -import axios from 'axios'; -import { useNavigate, Link } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { Eye, EyeOff } from 'lucide-react'; -import { iconColors } from '../utils/styles'; -import { API_BASE_URL } from '../config'; -import {themeConfig} from "../themeConfig.js"; +import React from "react"; +import axios from "axios"; +import { useNavigate, Link } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { Eye, EyeOff } from "lucide-react"; +import { iconColors } from "../utils/styles"; +import { themeConfig } from "../themeConfig.js"; const Signup = () => { - const [formData, setFormData] = React.useState({ - email: '', firstName: '', lastName: '', role: 'STUDENT', password: '', passwordConfirm: '', - }); - const [showPassword, setShowPassword] = React.useState(false); - const [error, setError] = React.useState(''); - const navigate = useNavigate(); - const { theme } = useSelector((state) => state.theme); - const { bg, text, accentBg, border } = themeConfig[theme]; + const [formData, setFormData] = React.useState({ + email: "", + firstName: "", + lastName: "", + role: "STUDENT", + password: "", + passwordConfirm: "", + }); + const [showPassword, setShowPassword] = React.useState(false); + const [error, setError] = React.useState(""); + const navigate = useNavigate(); + const { theme } = useSelector((state) => state.theme); + const { bg, text, accentBg, border } = themeConfig[theme]; - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prev) => ({ ...prev, [name]: value })); - setError(''); - }; + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + setError(""); + }; - const handleSubmit = async (e) => { - e.preventDefault(); - if (formData.password !== formData.passwordConfirm) { - setError('Passwords do not match'); - return; - } - try { - await axios.post(`${API_BASE_URL}/register`, { - email: formData.email, - firstName: formData.firstName, - lastName: formData.lastName, - role: formData.role, - password: formData.password, - }); - navigate('/dashboard'); - } catch (err) { - setError(err.response?.data || 'Registration failed'); - } - }; + const handleSubmit = async (e) => { + e.preventDefault(); + if (formData.password !== formData.passwordConfirm) { + setError("Passwords do not match"); + return; + } + try { + await axios.post(`http://localhost:8080/api/users/register`, { + email: formData.email, + firstName: formData.firstName, + lastName: formData.lastName, + role: formData.role, + password: formData.password, + }); + navigate("/dashboard"); + } catch (err) { + setError(err.response?.data || "Registration failed"); + } + }; - const handleReset = () => { - setFormData({ email: '', firstName: '', lastName: '', role: 'STUDENT', password: '', passwordConfirm: '' }); - setError(''); - }; + const handleReset = () => { + setFormData({ + email: "", + firstName: "", + lastName: "", + role: "STUDENT", + password: "", + passwordConfirm: "", + }); + setError(""); + }; - return ( -
-
-
-
-

Join StudyHub

- {error &&

{error}

} -
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- -
- - -
-
-
- -
- - -
-
-
- - -
-
-

- Already have an account? Login -

-
+ return ( +
+
+
+
+

+ Join StudyHub +

+ {error && ( +

+ {error} +

+ )} +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+

+ Already have an account?{" "} + + Login + +

- ); +
+
+ ); }; -export default Signup; \ No newline at end of file +export default Signup; diff --git a/src/pages/courses/CourseCard.jsx b/src/pages/courses/CourseCard.jsx index bc4451e..ae32e58 100644 --- a/src/pages/courses/CourseCard.jsx +++ b/src/pages/courses/CourseCard.jsx @@ -1,49 +1,53 @@ import { BookOpen } from "lucide-react"; import { useNavigate } from "react-router-dom"; -import { useSelector } from 'react-redux'; -import { themeConfig } from '../../themeConfig'; -import {iconColors} from "../../utils/styles.js"; +import { useSelector } from "react-redux"; +import { themeConfig } from "../../themeConfig"; +import { iconColors } from "../../utils/styles.js"; import React from "react"; const CourseCard = ({ id, title, department, credits }) => { - const navigate = useNavigate(); - const { theme } = useSelector((state) => state.theme); - const { bg, text, border, hoverBg } = themeConfig[theme]; - - const handleCourseClick = () => navigate(`/course/${id}`); - - const handleCourseClick2 = () => navigate(`/courses/${id}/CourseEdit`); - - return ( -
- - - - -
- - - - - ); + )} + +
+ ); }; -export default CourseCard; \ No newline at end of file +export default CourseCard; diff --git a/src/pages/courses/CourseGrid.jsx b/src/pages/courses/CourseGrid.jsx index efc93e6..dfe2661 100644 --- a/src/pages/courses/CourseGrid.jsx +++ b/src/pages/courses/CourseGrid.jsx @@ -1,97 +1,88 @@ import CourseCard from "./CourseCard.jsx"; import React from "react"; import axios from "axios"; -import {useSelector} from 'react-redux'; -import {themeConfig} from '../../themeConfig'; -import {useNavigate} from "react-router-dom"; +import { useSelector } from "react-redux"; +import { themeConfig } from "../../themeConfig"; +import { useNavigate } from "react-router-dom"; const CourseGrid = () => { - const [courses, setCourses] = React.useState([]); - const [loading, setLoading] = React.useState(true); - const [error, setError] = React.useState(null); - const user = JSON.parse(localStorage.getItem('user')); - //const studentId = user?.id; - const userId = user?.id; - const userRole = user?.role; - const {theme} = useSelector((state) => state.theme); - const {bg, text} = themeConfig[theme]; - const navigate = useNavigate(); - - React.useEffect(() => { - if (!userId) { - setError("No user ID found"); - setLoading(false); - return; - } - let endpoint; - - if (userRole === 'STUDENT') { - endpoint = `http://localhost:8080/api/courses/student/${userId}/summary`; - } else if (userRole === 'INSTRUCTOR') { - endpoint = `http://localhost:8080/api/courses/instructor/${userId}/summary`; - } else { - setError("Invalid user role"); - setLoading(false); - return; - } - - - axios.get(endpoint) - .then((response) => { - setCourses(response.data); - setLoading(false); - }) - .catch(err => { - console.error('Error fetching courses:', err); - setError('Error fetching courses'); - setLoading(false); - }); - }, [userId, userRole]); - - - if (loading) return

Loading...

; - if (error) return

{error}

; - - return ( - -
- - + const [courses, setCourses] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const [error, setError] = React.useState(null); + const user = JSON.parse(localStorage.getItem("user")); + const userId = user?.id; + const userRole = user?.role; + const { theme } = useSelector((state) => state.theme); + const { bg, text, accentBg } = themeConfig[theme]; + const navigate = useNavigate(); + + React.useEffect(() => { + if (!userId) { + setError("No user ID found"); + setLoading(false); + return; + } + let endpoint; + + if (userRole === "STUDENT") { + endpoint = `http://localhost:8080/api/courses/student/${userId}/summary`; + } else if (userRole === "INSTRUCTOR") { + endpoint = `http://localhost:8080/api/courses/instructor/${userId}/summary`; + } else { + setError("Invalid user role"); + setLoading(false); + return; + } + + axios + .get(endpoint) + .then((response) => { + setCourses(response.data); + setLoading(false); + }) + .catch((err) => { + console.error("Error fetching courses:", err); + setError("Error fetching courses"); + setLoading(false); + }); + }, [userId, userRole]); + + if (loading) return

Loading...

; + if (error) return

{error}

; + + return ( +
+ {(userRole === "INSTRUCTOR" || userRole === "ADMIN") && ( - -
- - -

Your Courses

- -
- -
- - -
- {courses.length === 0 &&

No enrolled courses.

} - {courses.map((course, index) => ( - - ))} - -
-
+ )} + +
+

+ Your Courses +

+ +
+ +
+ {courses.length === 0 &&

No enrolled courses.

} + {courses.map((course, index) => ( + + ))} +
- - - ); +
+ ); }; -export default CourseGrid; \ No newline at end of file +export default CourseGrid; diff --git a/src/pages/courses/course/AssignmentSubmission.jsx b/src/pages/courses/course/AssignmentSubmission.jsx index f0ef106..875cbf7 100644 --- a/src/pages/courses/course/AssignmentSubmission.jsx +++ b/src/pages/courses/course/AssignmentSubmission.jsx @@ -1,922 +1,1123 @@ import React, { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; -import {ArrowLeft, Upload, X, Calendar, CheckCircle, Plus} from "lucide-react"; +import { + ArrowLeft, + Upload, + X, + Calendar, + CheckCircle, + Plus, +} from "lucide-react"; import axios from "axios"; import { useSelector } from "react-redux"; import { themeConfig } from "../../../themeConfig"; import { iconColors } from "../../../utils/styles"; -import {getAssignmentsByCourse, createAssignment, submitAssignment, getSubmissionsByStudent, getSubmissionsByAssignment, updateAssignment, deleteAssignment} from '../../../services/api'; const AssignmentSubmission = () => { - const {courseId, assignmentId} = useParams(); - const navigate = useNavigate(); - const {theme} = useSelector((state) => state.theme); - const {bg, text, accentBg, border} = themeConfig[theme]; - - // Get user - const user = JSON.parse(localStorage.getItem("user")); - const studentId = user?.id; - const userRole = user?.role || 'STUDENT'; - - // Form state for submission - const [submissionForm, setSubmissionForm] = useState({ - text: "", - fileName: "No file chosen" - }); + const { courseId, assignmentId } = useParams(); + const navigate = useNavigate(); + const { theme } = useSelector((state) => state.theme); + const { bg, text, accentBg, border } = themeConfig[theme]; - // Assignment and loading states - const [assignment, setAssignment] = useState(null); - const [assignments, setAssignments] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - // Submission data state for display (grades/feedback) - const [submissionData, setSubmissionData] = useState({ - grade: null, - feedback: null, - isSubmitted: false, - submittedFileName: null - }); + // Get user + const user = JSON.parse(localStorage.getItem("user")); + const studentId = user?.id; + const userRole = user?.role || "STUDENT"; - // Instructor states - const [courseSubmissions, setCourseSubmissions] = useState([]); - const [editAssignment, setEditAssignment] = useState(null); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(null); - const [submissions, setSubmissions] = useState([]); - const [selectedSubmission, setSelectedSubmission] = useState(null); - const [showGrade, setShowGrade] = useState(false); - const [gradeForm, setGradeForm] = useState({ - grade: '', - feedback: '' - }); + // Form state for submission + const [submissionForm, setSubmissionForm] = useState({ + text: "", + fileName: "No file chosen", + }); - const [newAssignment, setNewAssignment] = useState({ - courseId: courseId, - title: '', - description: '', - dueDate: '' - }); + // Assignment and loading states + const [assignment, setAssignment] = useState(null); + const [assignments, setAssignments] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - useEffect(() => { - if (courseId) { - fetchAssignments(); - } - }, [courseId]); + // Submission data state for display (grades/feedback) + const [submissionData, setSubmissionData] = useState({ + grade: null, + feedback: null, + isSubmitted: false, + submittedFileName: null, + }); - useEffect(() => { - if (userRole === 'STUDENT' && assignmentId && studentId && courseId) { - fetchSubmissions(); - } - }, [assignmentId, userRole, studentId, courseId]); + // Instructor states + const [courseSubmissions, setCourseSubmissions] = useState([]); + const [editAssignment, setEditAssignment] = useState(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(null); + const [submissions, setSubmissions] = useState([]); + const [selectedSubmission, setSelectedSubmission] = useState(null); + const [showGrade, setShowGrade] = useState(false); + const [gradeForm, setGradeForm] = useState({ + grade: "", + feedback: "", + }); - useEffect(() => { - if (assignmentId && assignments.length > 0) { - const foundAssignment = assignments.find(a => a.id.toString() === assignmentId); - setAssignment(foundAssignment); - } - }, [assignmentId, assignments]); - - // Fallback useEffect for assignment fetching - useEffect(() => { - if (!assignment && assignmentId) { - setLoading(true); - axios - .get(`http://localhost:8080/api/courses/${courseId}/assignments/${assignmentId}`) - .then((response) => { - setAssignment(response.data); - setLoading(false); - }) - .catch((err) => { - setError(err.message || "Assignment not found"); - setLoading(false); - }); - } - }, [courseId, assignmentId, assignment]); - - const fetchAssignments = async () => { - setLoading(true); - try { - const response = await getAssignmentsByCourse(courseId); - setAssignments(response.data); - - if (userRole === 'STUDENT' && studentId) { - fetchSubmissions(); - } else if (userRole === 'INSTRUCTOR') { - fetchCourseSubmissions(); - } - setLoading(false); - } catch (err) { - setError(err.message || "Assignment not found"); - setLoading(false); - } - }; - - const fetchSubmissions = async () => { - try { - const response = await getSubmissionsByStudent(courseId, studentId); - setSubmissions(response.data); - - if (assignmentId) { - const currentSubmission = response.data.find(s => - s.assignmentId && s.assignmentId.toString() === assignmentId - ); - - if (currentSubmission) { - setSubmissionData({ - grade: currentSubmission.grade, - feedback: currentSubmission.feedback, - isSubmitted: !!currentSubmission.submittedFileName, - submittedFileName: currentSubmission.submittedFileName - }); - } else { - setSubmissionData({ - grade: null, - feedback: null, - isSubmitted: false, - submittedFileName: null - }); - } - } - } catch (err) { - if (err.response?.status !== 404) { - console.error('Error fetching submissions:', err); - } - } - }; - - const fetchCourseSubmissions = async () => { - try { - if (assignmentId) { - const response = await getSubmissionsByAssignment(courseId, assignmentId); - setCourseSubmissions(response.data); - } else { - setCourseSubmissions([]); - } - } catch (err) { - if (err.response?.status === 404) { - setCourseSubmissions([]); - } else { - console.error('Error fetching assignment submissions:', err); - setCourseSubmissions([]); - } - } - }; - - const handleFileChange = (e) => { - const file = e.target.files[0]; - if (file) { - setSubmissionForm((prev) => ({ - ...prev, - fileName: file.name - })); - } - }; + const [newAssignment, setNewAssignment] = useState({ + courseId: courseId, + title: "", + description: "", + dueDate: "", + }); - const clearFile = () => { - setSubmissionForm((prev) => ({ ...prev, fileName: "No file chosen" })); - const fileInput = document.getElementById('assignmentFile'); - if (fileInput) { - fileInput.value = ''; - } - }; + useEffect(() => { + if (courseId) { + fetchAssignments(); + } + }, [courseId]); - const handleSubmit = async (e) => { - e.preventDefault(); + useEffect(() => { + if (userRole === "STUDENT" && assignmentId && studentId && courseId) { + fetchSubmissions(); + } + }, [assignmentId, userRole, studentId, courseId]); - try { - const fileInput = document.getElementById('assignmentFile'); - const file = fileInput.files[0]; + useEffect(() => { + if (assignmentId && assignments.length > 0) { + const foundAssignment = assignments.find( + (a) => a.id.toString() === assignmentId, + ); + setAssignment(foundAssignment); + } + }, [assignmentId, assignments]); - if (!file) { - alert('Please select a file to submit'); - return; - } + // Fallback useEffect for assignment fetching + useEffect(() => { + if (!assignment && assignmentId) { + setLoading(true); + axios + .get( + `http://localhost:8080/api/courses/${courseId}/assignments/${assignmentId}`, + ) + .then((response) => { + setAssignment(response.data); + setLoading(false); + }) + .catch((err) => { + setError(err.message || "Assignment not found"); + setLoading(false); + }); + } + }, [courseId, assignmentId, assignment]); - await submitAssignment(courseId, assignmentId, studentId, file); - alert('Assignment submitted successfully!'); + const fetchAssignments = async () => { + setLoading(true); + try { + const response = await axios.get( + `http://localhost:8080/api/courses/${courseId}/assignments`, + ); + setAssignments(response.data); - setSubmissionData(prev => ({ - ...prev, - isSubmitted: true, - submittedFileName: file.name - })); + if (userRole === "STUDENT" && studentId) { + await fetchSubmissions(); + } else if (userRole === "INSTRUCTOR") { + await fetchCourseSubmissions(); + } + setLoading(false); + } catch (err) { + setError(err.message || "Assignment not found"); + setLoading(false); + } + }; - await fetchSubmissions(); - setSubmissionForm({text: '', fileName: 'No file chosen'}); - fileInput.value = ''; + const fetchSubmissions = async () => { + try { + const response = await axios.get( + `http://localhost:8080/api/courses/${courseId}/submissions/student/${studentId}`, + ); + setSubmissions(response.data); - } catch (err) { - console.error("Submission error:", err); - alert("Failed to submit assignment"); - } - }; - - const handleDownload = async () => { - try { - const currentSubmission = submissions.find(s => - s && s.assignmentId && s.assignmentId.toString() === assignmentId - ); - - if (!currentSubmission) { - alert('Submission not found'); - return; - } - - const response = await axios.get( - `http://localhost:8080/api/courses/${courseId}/submissions/${assignmentId}/student/${studentId}/download`, - { responseType: 'blob' } - ); - - const url = window.URL.createObjectURL(new Blob([response.data])); - const a = document.createElement('a'); - a.href = url; - a.setAttribute('download', submissionData.submittedFileName || currentSubmission.fileName || 'download'); - document.body.appendChild(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - } catch (err) { - console.error('Download error:', err); - alert('Failed to download file'); - } - }; - - const handleCreateAssignment = async (e) => { - e.preventDefault(); - - try { - await createAssignment(courseId, newAssignment); - alert('Assignment created successfully!'); - setNewAssignment({courseId: courseId, title: '', description: '', dueDate: ''}); - fetchAssignments(); - } catch (err) { - console.error('Error creating assignment:', err); - alert('Error creating assignment: ' + (err.response?.data?.message || err.message)); - } - }; - - const handleEditAssignment = (assignmentToEdit) => { - setEditAssignment(assignmentToEdit); - setNewAssignment({ - courseId: courseId, - title: assignmentToEdit.title, - description: assignmentToEdit.description, - dueDate: assignmentToEdit.dueDate.slice(0, 16) - }); - }; - - const cancelEdit = () => { - setEditAssignment(null); - setNewAssignment({courseId: courseId, title: '', description: '', dueDate: ''}); - }; - - const handleUpdateAssignment = async (e) => { - e.preventDefault(); - - try { - await updateAssignment(editAssignment.courseId, editAssignment.id, newAssignment); - alert('Assignment updated successfully!'); - setNewAssignment({courseId: courseId, title: '', description: '', dueDate: ''}); - setEditAssignment(null); - fetchAssignments(); - } catch (err) { - console.error('Error updating assignment:', err); - alert('Error updating assignment: ' + (err.response?.data?.message || err.message)); - } - }; - - const handleDeleteAssignment = async (assignmentId) => { - try { - await deleteAssignment(courseId, assignmentId); - alert('Assignment deleted successfully!'); - setShowDeleteConfirm(null); - fetchAssignments(); - } catch (err) { - console.error('Error deleting assignment:', err); - alert('Error deleting assignment: ' + (err.response?.data?.message || err.message)); - } - }; + if (assignmentId) { + const currentSubmission = response.data.find( + (s) => s.assignmentId && s.assignmentId.toString() === assignmentId, + ); - const handleGradeSubmission = (submission) => { - setSelectedSubmission(submission); - setGradeForm({ - grade: submission.grade || '', - feedback: submission.feedback || '' - }); - setShowGrade(true); - }; - - const handleGrade = async () => { - try { - await axios.put( - `http://localhost:8080/api/courses/${courseId}/submissions/${assignmentId}/student/${selectedSubmission.studentId}`, - { - grade: gradeForm.grade, - feedback: gradeForm.feedback - } - ); - - alert('Grade and feedback saved successfully!'); - setShowGrade(false); - fetchCourseSubmissions(); - - } catch (err) { - console.error('Error saving grade:', err); - alert('Failed to save grade: ' + (err.response?.data?.message || err.message)); + if (currentSubmission) { + setSubmissionData({ + grade: currentSubmission.grade, + feedback: currentSubmission.feedback, + isSubmitted: !!currentSubmission.submittedFileName, + submittedFileName: currentSubmission.submittedFileName, + }); + } else { + setSubmissionData({ + grade: null, + feedback: null, + isSubmitted: false, + submittedFileName: null, + }); } - }; - - const handleDownloadSubmission = async (submission) => { - try { - const response = await axios.get( - `http://localhost:8080/api/courses/${courseId}/submissions/${assignmentId}/student/${submission.studentId}/download`, - { responseType: 'blob' } - ); - - const url = window.URL.createObjectURL(new Blob([response.data])); - const a = document.createElement('a'); - a.href = url; - a.setAttribute('download', submission.submittedFileName || 'submission'); - document.body.appendChild(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - } catch (err) { - console.error('Download error:', err); - alert('Failed to download file'); - } - }; + } + } catch (err) { + if (err.response?.status !== 404) { + console.error("Error fetching submissions:", err); + } + } + }; - const handleGoBack = () => navigate(`/course/${courseId}/assignments`); + const fetchCourseSubmissions = async () => { + try { + if (assignmentId) { + const response = await axios.get( + `http://localhost:8080/api/courses/${courseId}/submissions/${assignmentId}/submissions`, + ); + setCourseSubmissions(response.data); + } else { + setCourseSubmissions([]); + } + } catch (err) { + if (err.response?.status === 404) { + setCourseSubmissions([]); + } else { + console.error("Error fetching assignment submissions:", err); + setCourseSubmissions([]); + } + } + }; - if (loading) return
Loading assignment...
; - if (error && userRole === 'STUDENT') return
Error: {error}
; - if (!assignment && assignmentId && userRole === 'STUDENT') return
Assignment not found!
; + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file) { + setSubmissionForm((prev) => ({ + ...prev, + fileName: file.name, + })); + } + }; - return ( -
-
- + + {/* Student Section */} + {userRole === "STUDENT" && assignmentId && assignment && ( +
+
+

+ {assignment.title} +

+

+ {assignment.description || "No description available"} +

+
+
+ + + + + Due:{" "} + {assignment.dueDate + ? new Date(assignment.dueDate).toLocaleDateString() + : "N/A"} + +
+
+
+
+
+
+ +
+ + + + {submissionForm.fileName} - Back to Assignments - + {submissionForm.fileName !== "No file chosen" && ( + + )} +
+

+ Accepted formats: PDF, DOC, DOCX, ZIP, TXT (Max 10MB) +

+
+
+ {/**/} + {/* Cancel*/} + {/**/} + +
+
- {/* Student Section */} - {userRole === 'STUDENT' && assignmentId && assignment && ( -
-
+
+ + + +

+ Assignment Submitted Successfully +

+
+
+ {/*

Your submission has been recorded:

*/} +
+
+ -

- {assignment.title} -

-

{assignment.description || "No description available"}

-
-
- - - - - Due: {assignment.dueDate ? new Date(assignment.dueDate).toLocaleDateString() : "N/A"} - -
-
-
-
-
-
- -