From 9b224a43316fa318a505c29b965ba42adeeafbf5 Mon Sep 17 00:00:00 2001 From: swve Date: Mon, 22 May 2023 17:34:04 +0000 Subject: [PATCH] feat: migrate course page to server component --- .../(withmenu)/course/[courseid]/course.tsx | 232 ++++++++++++++ .../(withmenu)/course/[courseid]/loading.tsx | 9 + .../(withmenu)/course/[courseid]/page.tsx | 287 +++--------------- .../[orgslug]/(withmenu)/courses/page.tsx | 2 +- front/services/courses/chapters.ts | 6 +- front/services/courses/courses.ts | 5 + 6 files changed, 286 insertions(+), 255 deletions(-) create mode 100644 front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx create mode 100644 front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/loading.tsx diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx new file mode 100644 index 00000000..aa1678d4 --- /dev/null +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx @@ -0,0 +1,232 @@ +"use client"; +import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons"; +import { removeCourse, startCourse } from "@services/courses/activity"; +import Link from "next/link"; +import React from "react"; +import styled from "styled-components"; +import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; +import useSWR, { mutate } from "swr"; +import ToolTip from "@components/UI/Tooltip/Tooltip"; +import PageLoading from "@components/Pages/PageLoading"; +import { revalidateTags } from "@services/utils/ts/requests"; + +const CourseClient = (props: any) => { + const courseid = props.courseid; + const orgslug = props.orgslug; + const course = props.course; + + async function startCourseUI() { + // Create activity + await startCourse("course_" + courseid, orgslug); + + revalidateTags(['courses']); + } + + async function quitCourse() { + + // Close activity + let activity = await removeCourse("course_" + courseid, orgslug); + console.log(activity); + + // Mutate course + revalidateTags(['courses']); + } + + console.log(course); + return ( + <> + {!course ? ( + + ) : ( + +

Course

+

+ {course.course.name}{" "} + + + {" "} +

+ + + + + + + +

Description

+ + +

{course.course.description}

+
+ +

What you will learn

+ +

{course.learnings == ![] ? "no data" : course.learnings}

+
+ +

Course Lessons

+ + + {course.chapters.map((chapter: any) => { + return ( +
+

{chapter.name}

+
{chapter.activities.map((activity: any) => { + return ( + <> +

+ {activity.name} + + + {" "} +

+ + ); + })}
+
+ ); + })} +
+
+ + {course.trail.status == "ongoing" ? ( + + ) : ( + + )} + +
+
+ )} + + ); +}; + +const CourseThumbnailWrapper = styled.div` + display: flex; + padding-bottom: 20px; + img { + width: 100%; + height: 300px; + object-fit: cover; + object-position: center; + background: url(), #d9d9d9; + border: 1px solid rgba(255, 255, 255, 0.19); + box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); + border-radius: 7px; + } +`; +const CoursePageLayout = styled.div` + width: 1300px; + margin: 0 auto; + p { + margin-bottom: 0px; + letter-spacing: -0.05em; + + color: #727272; + font-weight: 700; + } + h1 { + margin-top: 5px; + letter-spacing: -0.05em; + margin-bottom: 10px; + } +`; + +const ChaptersWrapper = styled.div` + display: flex; + width: 100%; +`; +const CourseIndicator = styled.div< { active?: boolean, done?: boolean } >` + border-radius: 20px; + height: 5px; + background: #151515; + border-radius: 3px; + + background-color: ${props => props.done ? "green" : "black"}; + + width: 40px; + margin: 10px; + margin-bottom: 20px; + margin-left: 0px; + + &:hover { + cursor: pointer; + background-color: #9d9d9d; + } +`; + +const ChapterSeparator = styled.div` + display: flex; + flex-direction: row; + padding-right: 7px; +`; + +const BoxWrapper = styled.div` + background: #ffffff; + box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03); + border-radius: 7px; + padding: 20px; + padding-top: 7px; + padding-left: 30px; + + p { + font-family: "DM Sans"; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: -0.02em; + + color: #9d9d9d; + } +`; + +const CourseMetaWrapper = styled.div` + display: flex; + justify-content: space-between; +`; + +const CourseMetaLeft = styled.div` + width: 80%; +`; + +const CourseMetaRight = styled.div` + background: #ffffff; + box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03); + border-radius: 7px; + padding: 20px; + width: 30%; + display: flex; + height: 100%; + justify-content: center; + margin-left: 50px; + margin-top: 20px; + button { + width: 100%; + height: 50px; + background: #151515; + border-radius: 15px; + border: none; + color: white; + font-weight: 700; + font-family: "DM Sans"; + font-size: 16px; + letter-spacing: -0.05em; + transition: all 0.2s ease; + box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); + + &:hover { + cursor: pointer; + background: #000000; + } + } +`; + +export default CourseClient; diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/loading.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/loading.tsx new file mode 100644 index 00000000..b3fd3bc3 --- /dev/null +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/loading.tsx @@ -0,0 +1,9 @@ +import PageLoading from "@components/Pages/PageLoading"; + +export default function Loading() { + // Or a custom loading skeleton component + return ( + + ) + +} \ No newline at end of file diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx index 8a6214c4..3617db18 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx @@ -1,261 +1,44 @@ -"use client"; -import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons"; -import { removeCourse, startCourse } from "@services/courses/activity"; -import Link from "next/link"; -import React from "react"; -import styled from "styled-components"; -import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; -import useSWR, { mutate } from "swr"; -import { swrFetcher } from "@services/utils/ts/requests"; -import { useRouter } from "next/navigation"; -import ToolTip from "@components/UI/Tooltip/Tooltip"; -import PageLoading from "@components/Pages/PageLoading"; +import React from 'react' +import CourseClient from './course' +import { cookies } from 'next/headers'; +import { getCourseMetadataWithAuthHeader } from '@services/courses/courses'; +import { getOrganizationContextInfo } from '@services/organizations/orgs'; +import { Metadata } from 'next'; -const CourseIdPage = (params: any) => { - const courseid = params.params.courseid; - const orgslug = params.params.orgslug; - const router = useRouter(); - - const { data: course, error: error } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, - (url: string, body: any) => swrFetcher(url, body, router) - ); - - async function startCourseUI() { - // Create activity - await startCourse("course_" + courseid, orgslug); - - // Mutate course - mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); - } - - async function quitCourse() { - - // Close activity - let activity = await removeCourse("course_" + courseid, orgslug); - console.log(activity); - - // Mutate course - mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); - } - - return ( - <> - {error &&

Failed to load

} - {!course ? ( - - ) : ( - -

-

Course

-

- {course.course.name}{" "} - - - {" "} -

- - {course.chapters.map((chapter: any) => { - return ( - - {chapter.activities.map((activity: any) => { - return ( - <> - - - - - - - - ); - })} - - ); - })} - - - - - - - - -

Description

- - -

{course.course.description}

-
- -

What you will learn

- -

{course.course.learnings == ![] ? "no data" : course.course.learnings}

-
- -

Course Lessons

- - - {course.chapters.map((chapter: any) => { - return ( -
-

{chapter.name}

-
{chapter.activities.map((activity: any) => { - return ( - <> -

- {activity.name} - - - {" "} -

- - ); - })}
-
- ); - })} -
-
- - {course.trail.status == "ongoing" ? ( - - ) : ( - - )} - -
-
- )} - - ); +type MetadataProps = { + params: { orgslug: string, courseid: string }; + searchParams: { [key: string]: string | string[] | undefined }; }; -const CourseThumbnailWrapper = styled.div` - display: flex; - padding-bottom: 20px; - img { - width: 100%; - height: 300px; - object-fit: cover; - object-position: center; - background: url(), #d9d9d9; - border: 1px solid rgba(255, 255, 255, 0.19); - box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); - border-radius: 7px; - } -`; -const CoursePageLayout = styled.div` - width: 1300px; - margin: 0 auto; - p { - margin-bottom: 0px; - letter-spacing: -0.05em; +export async function generateMetadata( + { params }: MetadataProps, +): Promise { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); - color: #727272; - font-weight: 700; - } - h1 { - margin-top: 5px; - letter-spacing: -0.05em; - margin-bottom: 10px; - } -`; + // Get Org context information + const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); + const course_meta = await getCourseMetadataWithAuthHeader(params.courseid, { revalidate: 360, tags: ['courses'] }, access_token_cookie.value) -const ChaptersWrapper = styled.div` - display: flex; - width: 100%; -`; -const CourseIndicator = styled.div< { active?: boolean, done?: boolean } >` - border-radius: 20px; - height: 5px; - background: #151515; - border-radius: 3px; + return { + title: course_meta.course.name + ` — ${org.name}`, + description: course_meta.course.mini_description, + }; +} - background-color: ${props => props.done ? "green" : "black"}; - width: 40px; - margin: 10px; - margin-bottom: 20px; - margin-left: 0px; - &:hover { - cursor: pointer; - background-color: #9d9d9d; - } -`; +const CoursePage = async (params: any) => { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + const courseid = params.params.courseid + const orgslug = params.params.orgslug; + const course_meta = await getCourseMetadataWithAuthHeader(courseid, { revalidate: 360, tags: ['courses'] }, access_token_cookie.value) + return ( +
+ +
+ ) +} -const ChapterSeparator = styled.div` - display: flex; - flex-direction: row; - padding-right: 7px; -`; - -const BoxWrapper = styled.div` - background: #ffffff; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03); - border-radius: 7px; - padding: 20px; - padding-top: 7px; - padding-left: 30px; - - p { - font-family: "DM Sans"; - font-style: normal; - font-weight: 500; - line-height: 16px; - letter-spacing: -0.02em; - - color: #9d9d9d; - } -`; - -const CourseMetaWrapper = styled.div` - display: flex; - justify-content: space-between; -`; - -const CourseMetaLeft = styled.div` - width: 80%; -`; - -const CourseMetaRight = styled.div` - background: #ffffff; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03); - border-radius: 7px; - padding: 20px; - width: 30%; - display: flex; - height: 100%; - justify-content: center; - margin-left: 50px; - margin-top: 20px; - button { - width: 100%; - height: 50px; - background: #151515; - border-radius: 15px; - border: none; - color: white; - font-weight: 700; - font-family: "DM Sans"; - font-size: 16px; - letter-spacing: -0.05em; - transition: all 0.2s ease; - box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); - - &:hover { - cursor: pointer; - background: #000000; - } - } -`; - -export default CourseIdPage; +export default CoursePage \ No newline at end of file diff --git a/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx index 17dd3580..ef2615db 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx @@ -17,7 +17,7 @@ export async function generateMetadata( // Get Org context information const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); return { - title: org.name + " — Courses", + title: "Courses — " + org.name, description: org.description, }; } diff --git a/front/services/courses/chapters.ts b/front/services/courses/chapters.ts index eecaca41..35a8887c 100644 --- a/front/services/courses/chapters.ts +++ b/front/services/courses/chapters.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, errorHandling } from "@services/utils/ts/requests"; +import { RequestBody, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -8,11 +8,13 @@ import { RequestBody, errorHandling } from "@services/utils/ts/requests"; //TODO : depreciate this function export async function getCourseChaptersMetadata(course_id: any, next: any) { - const result = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("GET", null,next)); + const result = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("GET", null, next)); const res = await errorHandling(result); return res; } + + export async function updateChaptersMetadata(course_id: any, data: any) { const result: any = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("PUT", data, null)); const res = await errorHandling(result); diff --git a/front/services/courses/courses.ts b/front/services/courses/courses.ts index 1d6b75a6..62e29b3f 100644 --- a/front/services/courses/courses.ts +++ b/front/services/courses/courses.ts @@ -19,6 +19,11 @@ export async function getOrgCoursesWithAuthHeader(org_id: number, next: any, acc return res; } +export async function getCourseMetadataWithAuthHeader(course_id: any, next: any, access_token: string) { + const result = await fetch(`${getAPIUrl()}courses/meta/course_${course_id}`, RequestBodyWithAuthHeader("GET", null, next, access_token)); + const res = await errorHandling(result); + return res; +} export async function getCourse(course_id: string, next: any) { const result: any = await fetch(`${getAPIUrl()}courses/${course_id}`, RequestBody("GET", null, next));