Merge pull request #7 from learnhouse/feat/coursepage_improvements

Feat/coursepage improvements
This commit is contained in:
Badr B 2022-11-15 00:18:54 +01:00 committed by GitHub
commit 23c4224b2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 220 additions and 22 deletions

View file

@ -66,16 +66,16 @@ function CourseEdit() {
// Submit new chapter // Submit new chapter
const submitChapter = async (chapter: any) => { const submitChapter = async (chapter: any) => {
await createChapter(chapter, courseid); await createChapter(chapter, courseid);
getCourseChapters(); await getCourseChapters();
setNewChapterModal(false); setNewChapterModal(false);
}; };
// Submit new element // Submit new element
const submitElement = async (element: any) => { const submitElement = async (element: any) => {
console.log("submitElement", element); console.log("submitElement", element);
updateChaptersMetadata(courseid, data); await updateChaptersMetadata(courseid, data);
await createElement(element, element.chapterId); await createElement(element, element.chapterId);
getCourseChapters(); await getCourseChapters();
setNewElementModal(false); setNewElementModal(false);
}; };

View file

@ -31,9 +31,25 @@ function ElementPage() {
const output = useMemo(() => { const output = useMemo(() => {
if (router.isReady) { if (router.isReady) {
console.log("element", element.content); console.log( "el",element.content);
return generateHTML(element.content, [Document, StarterKit, Paragraph, Text, Bold]); let content = Object.keys(element.content).length > 0 ? element.content : {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Hello world, this is a example Canva ⚡️"
}
]
}
]
}
console.log("element", content);
return generateHTML(content, [Document, StarterKit, Paragraph, Text, Bold]);
} }
}, [element.content]); }, [element.content]);
@ -44,7 +60,7 @@ function ElementPage() {
) : ( ) : (
<div> <div>
<p>element</p> <p>element</p>
<h1>{element.name}</h1> <h1>{element.name} </h1>
<hr /> <hr />
<div dangerouslySetInnerHTML={{ __html: output } as any}></div> <div dangerouslySetInnerHTML={{ __html: output } as any}></div>
</div> </div>

View file

@ -1,23 +1,25 @@
import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import Layout from "../../../../../components/ui/Layout"; import Layout from "../../../../../components/ui/Layout";
import { getAPIUrl, getBackendUrl } from "../../../../../services/config"; import { getAPIUrl, getBackendUrl } from "../../../../../services/config";
import { getCourse } from "../../../../../services/courses/courses"; import { getCourse, getCourseMetadata } from "../../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs"; import { getOrganizationContextInfo } from "../../../../../services/orgs";
const CourseIdPage = () => { const CourseIdPage = () => {
const router = useRouter(); const router = useRouter();
const { courseid } = router.query; const { courseid, orgslug } = router.query;
const [isLoading, setIsLoading] = React.useState(true); const [isLoading, setIsLoading] = React.useState(true);
const [courseInfo, setCourseInfo] = React.useState({}) as any; const [courseInfo, setCourseInfo] = React.useState({}) as any;
async function fetchCourseInfo() { async function fetchCourseInfo() {
const course = await getCourse("course_" + courseid); const course = await getCourseMetadata("course_" + courseid);
setCourseInfo(course); setCourseInfo(course);
console.log("courseinfo" , courseInfo);
setIsLoading(false); setIsLoading(false);
} }
@ -27,7 +29,7 @@ const CourseIdPage = () => {
fetchCourseInfo(); fetchCourseInfo();
} }
return () => {}; return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]); }, [router.isReady]);
return ( return (
@ -35,22 +37,82 @@ const CourseIdPage = () => {
{isLoading ? ( {isLoading ? (
<div>Loading...</div> <div>Loading...</div>
) : ( ) : (
<div> <CoursePageLayout>
<br></br> <br></br>
<h1>{courseInfo.name}</h1> <p>Course</p>
<CourseWrapper> <h1>
<img src={`${getBackendUrl()}content/uploads/img/${courseInfo.thumbnail}`} alt="" /> {courseInfo.course.name}{" "}
</CourseWrapper> <Link href={`/org/${orgslug}/course/${courseid}/edit`}>
</div> <a target="_blank" rel="noopener noreferrer">
&nbsp; <Pencil2Icon />
</a>
</Link>{" "}
</h1>
<br />
<ChaptersWrapper>
{courseInfo.chapters.map((chapter: any) => {
return (
<>
{chapter.elements.map((element: any) => {
return (
<>
<Link href={`/org/${orgslug}/course/${courseid}/element/${element.id.replace("element_", "")}`}>
<a target="_blank" rel="noopener noreferrer">
<ChapterIndicator />
</a>
</Link>{" "}
</>
);
})}
&nbsp;&nbsp;&nbsp;&nbsp;
</>
);
})}
</ChaptersWrapper>
<CourseThumbnailWrapper>
<img src={`${getBackendUrl()}content/uploads/img/${courseInfo.course.thumbnail}`} alt="" />
</CourseThumbnailWrapper>
<h2>Description</h2>
<p>{courseInfo.course.description}</p>
<h2>What you will learn</h2>
<p>{courseInfo.course.learnings == ![] ? "no data" : courseInfo.course.learnings}</p>
<h2>Course Lessons</h2>
{courseInfo.chapters.map((chapter: any) => {
return (
<>
<h3>Chapter : {chapter.name}</h3>
{chapter.elements.map((element: any) => {
return (
<>
<p>
Element {element.name}
<Link href={`/org/${orgslug}/course/${courseid}/element/${element.id.replace("element_", "")}`}>
<a target="_blank" rel="noopener noreferrer">
&nbsp; <EyeOpenIcon />
</a>
</Link>{" "}
</p>
</>
);
})}
&nbsp;&nbsp;&nbsp;&nbsp;
</>
);
})}
</CoursePageLayout>
)} )}
</Layout> </Layout>
); );
}; };
const CourseWrapper = styled.div` const CourseThumbnailWrapper = styled.div`
display: flex; display: flex;
img { img {
position: absolute;
width: 794px; width: 794px;
height: 224.28px; height: 224.28px;
object-fit: cover; object-fit: cover;
@ -62,5 +124,29 @@ const CourseWrapper = styled.div`
border-radius: 7px; border-radius: 7px;
} }
`; `;
const CoursePageLayout = styled.div`
margin-left: 40px;
margin-right: 40px;
`;
const ChaptersWrapper = styled.div`
display: flex;
`;
const ChapterIndicator = styled.div`
border-radius: 20px;
height: 5px;
background: #151515;
border-radius: 3px;
width: 40px;
background-color: black;
margin: 10px;
margin-left: 0px;
transition: all 0.2s ease;
&:hover {
width: 50px;
cursor: pointer;
}
`;
export default CourseIdPage; export default CourseIdPage;

View file

@ -15,7 +15,7 @@ export async function getOrgCourses(org_id: number) {
.catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
} }
export async function getCourse(course_id: any) { export async function getCourse(course_id: string) {
const HeadersConfig = new Headers({ "Content-Type": "application/json" }); const HeadersConfig = new Headers({ "Content-Type": "application/json" });
const requestOptions: any = { const requestOptions: any = {
@ -31,6 +31,23 @@ export async function getCourse(course_id: any) {
.catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
} }
export async function getCourseMetadata(course_id: string) {
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
const requestOptions: any = {
method: "GET",
headers: HeadersConfig,
redirect: "follow",
credentials: "include",
};
// todo : add course id to url
return fetch(`${getAPIUrl()}courses/meta/${course_id}`, requestOptions)
.then((result) => result.json())
.catch((error) => console.log("error", error));
}
export async function createNewCourse(org_id: string, course_body: any, thumbnail: any) { export async function createNewCourse(org_id: string, course_body: any, thumbnail: any) {
const HeadersConfig = new Headers(); const HeadersConfig = new Headers();

View file

@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, UploadFile, Form from fastapi import APIRouter, Depends, UploadFile, Form
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
from src.services.courses.courses import Course, create_course, get_course, get_courses, update_course, delete_course, update_course_thumbnail from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, update_course, delete_course, update_course_thumbnail
from src.services.users import PublicUser from src.services.users import PublicUser
@ -34,6 +34,14 @@ async def api_get_course(course_id: str, current_user: PublicUser = Depends(get
return await get_course(course_id, current_user=current_user) return await get_course(course_id, current_user=current_user)
@router.get("/meta/{course_id}")
async def api_get_course_meta(course_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Get single Course Metadata (chapters, elements) by course_id
"""
return await get_course_meta(course_id, current_user=current_user)
@router.get("/{org_id}/page/{page}/limit/{limit}") @router.get("/{org_id}/page/{page}/limit/{limit}")
async def api_get_course_by(page: int, limit: int, org_id: str): async def api_get_course_by(page: int, limit: int, org_id: str):
""" """

View file

@ -3,6 +3,7 @@ import os
from typing import List from typing import List
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.courses.elements import ElementInDB
from src.services.uploads import upload_thumbnail from src.services.uploads import upload_thumbnail
from src.services.users import PublicUser, User from src.services.users import PublicUser, User
from src.services.database import create_config_collection, check_database, create_database, learnhouseDB from src.services.database import create_config_collection, check_database, create_database, learnhouseDB
@ -30,6 +31,21 @@ class CourseInDB(Course):
updateDate: str updateDate: str
authors: List[str] authors: List[str]
# TODO : wow terrible, fix this
# those models need to be available only in the chapters service
class CourseChapter(BaseModel):
name: str
description: str
elements: list
class CourseChapterInDB(CourseChapter):
coursechapter_id: str
course_id: str
creationDate: str
updateDate: str
#### Classes #################################################### #### Classes ####################################################
# TODO : Add courses photo & cover upload and delete # TODO : Add courses photo & cover upload and delete
@ -56,6 +72,61 @@ async def get_course(course_id: str, current_user: PublicUser):
return course return course
async def get_course_meta(course_id: str, current_user: PublicUser):
await check_database()
courses = learnhouseDB["courses"]
coursechapters = learnhouseDB["coursechapters"]
course = courses.find_one({"course_id": course_id})
elements = learnhouseDB["elements"]
# verify course rights
await verify_rights(course_id, current_user, "read")
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
coursechapters = coursechapters.find(
{"course_id": course_id}).sort("name", 1)
# elements
coursechapter_elementIds_global = []
# chapters
chapters = {}
for coursechapter in coursechapters:
coursechapter = CourseChapterInDB(**coursechapter)
coursechapter_elementIds = []
for element in coursechapter.elements:
coursechapter_elementIds.append(element)
coursechapter_elementIds_global.append(element)
chapters[coursechapter.coursechapter_id] = {
"id": coursechapter.coursechapter_id, "name": coursechapter.name, "elementIds": coursechapter_elementIds
}
# elements
elements_list = {}
for element in elements.find({"element_id": {"$in": coursechapter_elementIds_global}}):
element = ElementInDB(**element)
elements_list[element.element_id] = {
"id": element.element_id, "name": element.name, "type": element.type, "content": element.content
}
chapters_list_with_elements = []
for chapter in chapters:
chapters_list_with_elements.append(
{"id": chapters[chapter]["id"], "name": chapters[chapter]["name"], "elements": [elements_list[element] for element in chapters[chapter]["elementIds"]]})
course = Course(**course)
return {
"course": course,
"chapters": chapters_list_with_elements,
}
async def create_course(course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None): async def create_course(course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
await check_database() await check_database()
courses = learnhouseDB["courses"] courses = learnhouseDB["courses"]