diff --git a/src/hooks/useAuth.js b/src/hooks/useAuth.js
new file mode 100644
index 0000000..20b1395
--- /dev/null
+++ b/src/hooks/useAuth.js
@@ -0,0 +1,7 @@
+// src/hooks/useAuth.js
+import { useContext } from "react";
+import { AuthContext } from "../contexts/AuthContext";
+
+export const useAuth = () => {
+ return useContext(AuthContext);
+};
diff --git a/src/pages/AssignmentPage.jsx b/src/pages/AssignmentPage.jsx
new file mode 100644
index 0000000..2f24da7
--- /dev/null
+++ b/src/pages/AssignmentPage.jsx
@@ -0,0 +1,119 @@
+import React, { useState } from "react";
+import "../scss/components/_assignment.scss";
+
+const AssignmentPage = () => {
+ const [title, setTitle] = useState("");
+ const [description, setDescription] = useState("");
+ const [file, setFile] = useState(null);
+ const [projects, setProjects] = useState([]);
+ const [showModal, setShowModal] = useState(false);
+ const [editingIndex, setEditingIndex] = useState(null);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ const formData = new FormData();
+ formData.append("title", title);
+ formData.append("description", description);
+ formData.append("file", file);
+
+ try {
+ const res = await fetch("/api/assignments", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (res.ok) {
+ alert("Successfully Uploaded!");
+ setProjects([...projects, { title, description }]);
+ setTitle("");
+ setDescription("");
+ setFile(null);
+ setShowModal(false);
+ } else {
+ alert("Fail Uploading!");
+ }
+ } catch (err) {
+ console.error(err);
+ alert("Server Error!");
+ }
+ };
+
+ const handleDelete = (index) => {
+ const updated = projects.filter((_, i) => i !== index);
+ setProjects(updated);
+ };
+
+ return (
+
+
π Assignments
+
+
+ {showModal && (
+
+
+
{editingIndex !== null ? "Edit Assignment" : "New Assignment"}
+
+
+
+ )}
+
+
+
π Projects
+ {projects.map((project, index) => (
+
+
{project.title}
+
{project.description}
+
+
+
+ ))}
+
+
+ );
+};
+
+export default AssignmentPage;
diff --git a/src/routers/AppRouter.jsx b/src/routers/AppRouter.jsx
index f1b5ffa..0ca9c86 100644
--- a/src/routers/AppRouter.jsx
+++ b/src/routers/AppRouter.jsx
@@ -14,6 +14,7 @@ import PageNotFound from "../pages/PageNotFound";
import Hero from "../components/Hero";
import Navbar from "../components/Navbar";
import Services from "../components/Services";
+import AssignmentPage from "../pages/AssignmentPage";
const AppRouter = () => {
return (
@@ -32,6 +33,14 @@ const AppRouter = () => {
>
}
/>
+
+
+ //
+ }
+ />
} />
{
}
/>
+
} />
diff --git a/src/scss/components/ProtectedRoute.jsx b/src/scss/components/ProtectedRoute.jsx
new file mode 100644
index 0000000..0f5769c
--- /dev/null
+++ b/src/scss/components/ProtectedRoute.jsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { Navigate } from "react-router-dom";
+import { useAuth } from "../hooks/useAuth"; // μμ ν
: λμ νλ‘μ νΈμ λ§κ² μμ !
+
+const ProtectedRoute = ({ children, role }) => {
+ const { user, isLoading } = useAuth();
+
+ if (isLoading) {
+ return loading...
;
+ }
+
+ if (!user) {
+ return ;
+ }
+
+ if (role && user.role !== role) {
+ return ;
+ }
+
+ return children;
+};
+
+export default ProtectedRoute;
diff --git a/src/scss/components/_assignment.scss b/src/scss/components/_assignment.scss
new file mode 100644
index 0000000..42a5298
--- /dev/null
+++ b/src/scss/components/_assignment.scss
@@ -0,0 +1,150 @@
+.assignment-page {
+ max-width: 600px;
+ margin: auto;
+ padding: 20px;
+
+ form {
+ margin-bottom: 20px;
+
+ div {
+ margin-bottom: 15px;
+
+ label {
+ display: block;
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+
+ input[type="text"],
+ textarea,
+ input[type="file"] {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ }
+
+ textarea {
+ height: 80px;
+ resize: vertical;
+ }
+ }
+
+ button {
+ padding: 8px 16px;
+ background-color: #4285f4;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #3367d6;
+ }
+ }
+ }
+
+ .project-list {
+ margin-top: 30px;
+
+ .project-item {
+ border: 1px solid #ddd;
+ padding: 12px;
+ margin-bottom: 10px;
+ border-radius: 6px;
+ background-color: #f9f9f9;
+
+ h4 {
+ margin: 0 0 5px 0;
+ }
+
+ button {
+ margin-right: 10px;
+ padding: 6px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+
+ &:first-of-type {
+ background-color: #ffa500;
+ color: white;
+
+ &:hover {
+ background-color: #e59400;
+ }
+ }
+
+ &:last-of-type {
+ background-color: #e74c3c;
+ color: white;
+
+ &:hover {
+ background-color: #c0392b;
+ }
+ }
+ }
+ }
+ }
+
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .modal {
+ background: white;
+ padding: 20px;
+ border-radius: 10px;
+ width: 300px;
+
+ h3 {
+ margin-bottom: 10px;
+ }
+
+ input,
+ textarea {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 8px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ }
+
+ .modal-buttons {
+ display: flex;
+ justify-content: space-between;
+
+ button {
+ padding: 6px 12px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+
+ &:first-of-type {
+ background-color: #28a745;
+ color: white;
+
+ &:hover {
+ background-color: #218838;
+ }
+ }
+
+ &:last-of-type {
+ background-color: #6c757d;
+ color: white;
+
+ &:hover {
+ background-color: #5a6268;
+ }
+ }
+ }
+ }
+ }
+ }
+}