Skip to content

Commit 46d55c8

Browse files
authored
Merge pull request #16 from zachspang/dashboard
[Feature] Create course button
2 parents 44e48d3 + d1f6b91 commit 46d55c8

File tree

3 files changed

+156
-7
lines changed

3 files changed

+156
-7
lines changed

backend/internal/handlers/courses.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ func Courses(c *fiber.Ctx) error {
2121

2222
var courses []entity.Course
2323
// Get all courses that user is enrolled in
24-
if err := database.DB.Model(&entity.Course{}).Where("id IN (?)", database.DB.Table("enrollment").Select("course_id").Where("account_id = ?", user_id)).Find(&courses).Error; err != nil {
25-
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error retrieving courses"})
24+
if user.Role == "educator" {
25+
if err := database.DB.Model(&entity.Course{}).Where("creator_id = ?", user_id).Find(&courses).Error; err != nil {
26+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error retrieving courses"})
27+
}
28+
} else {
29+
if err := database.DB.Model(&entity.Course{}).Where("id IN (?)", database.DB.Table("enrollment").Select("course_id").Where("account_id = ?", user_id)).Find(&courses).Error; err != nil {
30+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error retrieving courses"})
31+
}
2632
}
2733

2834
if len(courses) == 0 {

frontend/src/components/Modal.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, {useState} from 'react';
2+
3+
/**
4+
* Modal component
5+
* @param {string} title - A string which is the title at the top of the modal.
6+
* @param {React.JSX.Element} trigger - React.JSX.Element that when clicked will open the modal.
7+
* @param {Map} inputFields - {var : "Input Var Here", ...} A map of variables where things typed into a text box will be stored to the string that the user will see above that text box.
8+
* @param {(e: any) => void} changeHandler - The handler that handles changes in text and assigns them to a variable.
9+
* @param {(e: any) => Promise<void>} confirmHandler - The handler that calls an API upon clicking confirm
10+
*/
11+
export const Modal = ({title, trigger, inputFields, changeHandler, confirmHandler}) => {
12+
13+
const [showModal, setShowModal] = useState(false);
14+
15+
const close = () => {
16+
setShowModal(false);
17+
};
18+
19+
const open = () => {
20+
setShowModal(true);
21+
};
22+
23+
var fieldList = []
24+
Object.entries(inputFields).map(([name, text]) => (
25+
fieldList.push(
26+
<div className="p-2">
27+
<label>{text+ ": "} </label>
28+
<input className="border-2 border-slate-300 rounded p-2 size-11/12"
29+
type="text"
30+
name={name}
31+
onChange={changeHandler}
32+
/>
33+
</div>
34+
)
35+
));
36+
37+
return (
38+
<div>
39+
<div onClick={open}>{trigger}</div>
40+
{showModal ?
41+
<div>
42+
<div className='opacity-50 bg-black fixed top-0 left-0 h-full w-full' onClick={close}/>
43+
<div className="flex-auto bg-white rounded-lg border-2 border-slate-300 absolute min-w-80 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 p-3">
44+
<h3 className="font-semibold">
45+
{title}
46+
</h3>
47+
{fieldList}
48+
<div className="grid grid-rows-1 grid-cols-2 p-2">
49+
<button className="justify-self-start border-2 border-slate-300 p-1 rounded" onClick=
50+
{() => close()}>
51+
Cancel
52+
</button>
53+
<button className="justify-self-end border-2 border-slate-300 p-1 rounded bg-green-400" onClick={confirmHandler}>
54+
Confirm
55+
</button>
56+
</div>
57+
</div>
58+
</div>:null}
59+
</div>
60+
);
61+
}

frontend/src/components/pages/CourseDashboard.js

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
11
import React, { useEffect, useState } from 'react';
22
import Cookies from 'js-cookie';
3+
import Notiflix from 'notiflix';
4+
import {Modal} from '../Modal.js';
35

46
export function CourseDashboard() {
57
const [courseInfo, setCourseInfo] = useState(null);
8+
const [userInfo, setUserInfo] = useState(null);
9+
const [newCourse, setNewCourse] = useState({
10+
title: "",
11+
description: "",
12+
});
613
const [error, setError] = useState(null);
714

15+
const handleChange = (e) => {
16+
const { name, value } = e.target;
17+
setNewCourse({ ...newCourse, [name]: value });
18+
};
19+
20+
const handleCreateCourse = async (e) =>{
21+
if (newCourse.title === "" || newCourse.description === ""){
22+
return
23+
}
24+
25+
const userId = Cookies.get('userId');
26+
try {
27+
const response = await fetch(`http://localhost:4000/create-course/${userId}`, {
28+
method: "POST",
29+
headers: {
30+
"Content-Type": "application/json",
31+
},
32+
body: JSON.stringify({
33+
title: newCourse.title,
34+
description: newCourse.description
35+
}),
36+
});
37+
38+
const data = await response.json();
39+
if (response.ok) {
40+
Notiflix.Notify.success("Course creation successful!");
41+
setTimeout(() => {
42+
window.location.reload();
43+
}, 500);
44+
} else {
45+
Notiflix.Notify.failure(data.message || "Course creation failed");
46+
}
47+
} catch (error) {
48+
Notiflix.Notify.failure("Error occurred during course creation");
49+
}
50+
};
51+
852
useEffect(() => {
953
const userId = Cookies.get('userId');
1054

@@ -13,7 +57,8 @@ export function CourseDashboard() {
1357
return;
1458
}
1559

16-
fetch(`http://localhost:4000/courses/${userId}`)
60+
async function fetchCourses() {
61+
await fetch(`http://localhost:4000/courses/${userId}`)
1762
.then((response) => {
1863
if (!response.ok) {
1964
throw new Error('Network response was not ok');
@@ -22,27 +67,64 @@ export function CourseDashboard() {
2267
})
2368
.then((data) => setCourseInfo(data.courses))
2469
.catch((error) => setError(error.message));
70+
}
71+
72+
async function fetchUser() {
73+
await fetch(`http://localhost:4000/user/${userId}`)
74+
.then((response) => {
75+
if (!response.ok) {
76+
throw new Error('Network response was not ok');
77+
}
78+
return response.json();
79+
})
80+
.then((data) => setUserInfo(data.user))
81+
.catch((error) => setError(error.message));
82+
}
83+
84+
fetchCourses();
85+
fetchUser();
2586
}, []);
2687

2788
if (error) return <p>Error: {error}</p>;
28-
if (!courseInfo) return <p>Loading...</p>;
89+
if (!courseInfo || !userInfo) return <p>Loading...</p>;
90+
91+
let createButton = null;
92+
if (userInfo.role === "educator"){
93+
createButton = <Modal
94+
title={"Create New Course"}
95+
trigger={
96+
<div className="bg-blue-500 p-2 rounded shadow hover:bg-blue-700 m-auto text-center text-sm text-white font-semibold hover:cursor-pointer" >
97+
<div>+ New Course</div>
98+
</div>
99+
}
100+
inputFields={{
101+
title : "Course Name",
102+
description : "Course Description"
103+
}}
104+
changeHandler={handleChange}
105+
confirmHandler={handleCreateCourse}
106+
/>
107+
}
29108

30109
var courseList = [];
31110
//Push all courses into courseList
32111
courseInfo.forEach(course => {
33112
courseList.push(
34113
<a href={`/courses/${course.ID}`}>
35114
<div className="bg-gray-100 p-4 rounded shadow hover:bg-gray-300" >
36-
<h3 className="text-xl font-semibold" >{course.title}</h3>
115+
<h3 className="text-xl font-semibold truncate overflow-hidden" >{course.title}</h3>
37116
<p className="mt-2">{course.description}</p>
38117
</div>
39118
</a>
40119
)
41120
});
42-
121+
43122
return (
44123
<div className="p-6">
45-
<h1 className="text-2xl font-bold mb-4">Courses</h1>
124+
<div className="flex justify-between flex-wrap mb-5">
125+
<h1 className="text-2xl font-bold">Courses</h1>
126+
{createButton}
127+
</div>
46128
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
47129
{courseList}
48130
</div>

0 commit comments

Comments
 (0)