diff --git a/front/app/editor/course/[courseid]/activity/[activityid]/edit/loading.tsx b/front/app/editor/course/[courseid]/activity/[activityid]/edit/loading.tsx new file mode 100644 index 00000000..b3fd3bc3 --- /dev/null +++ b/front/app/editor/course/[courseid]/activity/[activityid]/edit/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/editor/course/[courseid]/activity/[activityid]/edit/page.tsx b/front/app/editor/course/[courseid]/activity/[activityid]/edit/page.tsx index 8e6e2b82..2be18fae 100644 --- a/front/app/editor/course/[courseid]/activity/[activityid]/edit/page.tsx +++ b/front/app/editor/course/[courseid]/activity/[activityid]/edit/page.tsx @@ -1,34 +1,52 @@ - -"use client"; -import { default as React, useEffect, useRef } from "react"; - - +import { default as React, } from "react"; import { useRouter } from "next/navigation"; -import { getActivity } from "@services/courses/activities"; import AuthProvider from "@components/Security/AuthProvider"; import EditorWrapper from "@components/Editor/EditorWrapper"; -import useSWR, { mutate } from "swr"; -import { getAPIUrl, getOrgFromUri } from "@services/config/config"; +import { getAPIUrl } from "@services/config/config"; import { swrFetcher } from "@services/utils/ts/requests"; +import { getOrganizationContextInfo } from "@services/organizations/orgs"; +import { getCourseMetadataWithAuthHeader } from "@services/courses/courses"; +import { cookies } from "next/headers"; +import { Metadata } from "next"; +import { getActivityWithAuthHeader } from "@services/courses/activities"; +type MetadataProps = { + params: { orgslug: string, courseid: string, activityid: string }; + searchParams: { [key: string]: string | string[] | undefined }; +}; -function EditActivity(params: any) { - const router = useRouter(); +export async function generateMetadata( + { params }: MetadataProps, +): Promise { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + + // Get Org context information + const course_meta = await getCourseMetadataWithAuthHeader(params.courseid, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null) + + return { + title: `Edit - ${course_meta.course.name} Activity`, + description: course_meta.course.mini_description, + }; +} + +const EditActivity = async (params: any) => { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); const activityid = params.params.activityid; const courseid = params.params.courseid; const orgslug = params.params.orgslug; - const { data: courseInfo, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); - const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}activities/activity_${activityid}`, swrFetcher); - + const courseInfo = await getCourseMetadataWithAuthHeader(courseid, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null) + const activity = await getActivityWithAuthHeader(activityid, { revalidate: 0, tags: ['activities'] }, access_token_cookie ? access_token_cookie.value : null) - - return ( - - {!courseInfo || !activity ?
Loading...
: } -
+
+ + + +
); } diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx new file mode 100644 index 00000000..bf498799 --- /dev/null +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx @@ -0,0 +1,220 @@ +"use client"; +import Link from "next/link"; +import React, { useMemo } from "react"; +import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; +import Canva from "@components/Pages/Activities/DynamicCanva/DynamicCanva"; +import styled from "styled-components"; +import VideoActivity from "@components/Pages/Activities/Video/Video"; +import useSWR, { mutate } from "swr"; +import { Check } from "lucide-react"; +import { markActivityAsComplete } from "@services/courses/activity"; +import ToolTip from "@components/UI/Tooltip/Tooltip"; +import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf"; + +interface ActivityClientProps { + activityid: string; + courseid: string; + orgslug: string; + activity: any; + course: any; +} + +function ActivityClient(props: ActivityClientProps) { + const activityid = props.activityid; + const courseid = props.courseid; + const orgslug = props.orgslug; + const activity = props.activity; + const course = props.course; + + + async function markActivityAsCompleteFront() { + const trail = await markActivityAsComplete(orgslug, courseid, activityid); + mutate(`${getAPIUrl()}activities/activity_${activityid}`); + mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); + } + + return ( + <> + +
{JSON.stringify(activity, null, 2)}
+ + + + + + + +

Course

+

{course.course.name}

+
+
+ + {course.chapters.map((chapter: any) => { + return ( + <> +
+ {chapter.activities.map((activity: any) => { + return ( + + + + + + ); + })} +
+      + + ); + })} +
+ + {activity ? ( + + {activity.type == "dynamic" && } + {/* todo : use apis & streams instead of this */} + {activity.type == "video" && } + + {activity.type == "documentpdf" && } + + + + {course.trail.activities_marked_complete && + course.trail.activities_marked_complete.includes("activity_" + activityid) && + course.trail.status == "ongoing" ? ( + + ) : ( + + )} + + + ) : (
)} + {
} +
+ + ); +} + +const ActivityLayout = styled.div``; + +const ActivityThumbnail = styled.div` + padding-right: 30px; + justify-self: center; + img { + box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); + border-radius: 7px; + width: 100px; + height: 57px; + } +`; +const ActivityInfo = styled.div` + h1 { + margin-top: 0px; + } + + p { + margin-top: 0; + margin-bottom: 0; + font-weight: 700; + } +`; + +const ChaptersWrapper = styled.div` + display: flex; + // row + flex-direction: row; + width: 100%; + width: 1300px; + margin: 0 auto; +`; + +const ChapterIndicator = styled.div < { active?: boolean, done?: boolean } > ` + border-radius: 20px; + height: 5px; + background: #151515; + border-radius: 3px; + + width: 35px; + background-color: ${props => props.done ? "green" : (props.active ? "#9d9d9d" : "black")}; + margin: 10px; + margin-bottom: 0px; + margin-left: 0px; + + &:hover { + cursor: pointer; + background-color: #9d9d9d; + } +`; + +const ActivityTopWrapper = styled.div` + width: 1300px; + padding-top: 50px; + margin: 0 auto; + display: flex; +`; + +const CourseContent = styled.div` + display: flex; + flex-direction: column; + background-color: white; + min-height: 600px; +`; + +const ActivityMarkerWrapper = styled.div` + display: block; + width: 1300px; + justify-content: flex-end; + margin: 0 auto; + align-items: center; + + button { + background-color: #151515; + border: none; + padding: 18px; + border-radius: 15px; + margin: 15px; + margin-left: 20px; + margin-top: 20px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + margin: auto; + color: white; + font-weight: 700; + font-family: "DM Sans"; + font-size: 16px; + letter-spacing: -0.05em; + box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); + + i { + margin-right: 5px; + + // center the icon + display: flex; + align-items: center; + justify-content: center; + } + + &:hover { + background-color: #000000; + } + } +`; + +export default ActivityClient; diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/loading.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/loading.tsx new file mode 100644 index 00000000..b3fd3bc3 --- /dev/null +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/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]/activity/[activityid]/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx index 8f9bb906..140a0f1a 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx @@ -1,221 +1,52 @@ -"use client"; -import { useRouter } from "next/navigation"; -import Link from "next/link"; -import React, { useMemo } from "react"; -import { getActivity } from "@services/courses/activities"; -import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; -import Canva from "@components/Pages/Activities/DynamicCanva/DynamicCanva"; -import styled from "styled-components"; -import { getCourse } from "@services/courses/courses"; -import VideoActivity from "@components/Pages/Activities/Video/Video"; -import useSWR, { mutate } from "swr"; -import { Check } from "lucide-react"; -import { swrFetcher } from "@services/utils/ts/requests"; -import { markActivityAsComplete } from "@services/courses/activity"; -import ToolTip from "@components/UI/Tooltip/Tooltip"; -import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf"; - -function ActivityPage(params: any) { - const activityid = params.params.activityid; - const courseid = params.params.courseid; - const orgslug = params.params.orgslug; - - const { data: course, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); - const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}activities/activity_${activityid}`, swrFetcher); +import { getActivityWithAuthHeader } from "@services/courses/activities"; +import { getCourseMetadataWithAuthHeader } from "@services/courses/courses"; +import { cookies } from "next/headers"; +import ActivityClient from "./activity"; +import { getOrganizationContextInfo } from "@services/organizations/orgs"; +import { Metadata } from "next"; - async function markActivityAsCompleteFront() { - const trail = await markActivityAsComplete(orgslug, courseid, activityid); - mutate(`${getAPIUrl()}activities/activity_${activityid}`); - mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); - } +type MetadataProps = { + params: { orgslug: string, courseid: string, activityid: string }; + searchParams: { [key: string]: string | string[] | undefined }; +}; - return ( - <> - {error_course &&

Failed to load

} - {!course || !activity ? ( -
Loading...
- ) : ( - - - - - - - - -

Course

-

{course.course.name}

-
-
- - {course.chapters.map((chapter: any) => { - return ( - <> -
- {chapter.activities.map((activity: any) => { - return ( - - - - - - ); - })} -
-      - - ); - })} -
+export async function generateMetadata( + { params }: MetadataProps, +): Promise { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); - {activity ? ( - - {activity.type == "dynamic" && } - {/* todo : use apis & streams instead of this */} - {activity.type == "video" && } + // Get Org context information + const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); + const course_meta = await getCourseMetadataWithAuthHeader(params.courseid, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null ) + const activity = await getActivityWithAuthHeader(params.activityid, { revalidate: 0, tags: ['activities'] }, access_token_cookie ? access_token_cookie.value : null) - {activity.type == "documentpdf" && } - - - - {course.trail.activities_marked_complete && - course.trail.activities_marked_complete.includes("activity_" + activityid) && - course.trail.status == "ongoing" ? ( - - ) : ( - - )} - - - ) : (
)} - {error_activity &&

Failed to load {error_activity.message}

} -
- )} - - ); + return { + title: activity.name + ` — ${course_meta.course.name} Course`, + description: course_meta.course.mini_description, + }; } -const ActivityLayout = styled.div``; +const ActivityPage = async (params: any) => { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + const activityid = params.params.activityid; + const courseid = params.params.courseid; + const orgslug = params.params.orgslug; -const ActivityThumbnail = styled.div` - padding-right: 30px; - justify-self: center; - img { - box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); - border-radius: 7px; - width: 100px; - height: 57px; - } -`; -const ActivityInfo = styled.div` - h1 { - margin-top: 0px; - } + const course_meta = await getCourseMetadataWithAuthHeader(courseid, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null) + const activity = await getActivityWithAuthHeader(activityid, { revalidate: 0, tags: ['activities'] }, access_token_cookie ? access_token_cookie.value : null) + return ( + <> + + ) +} - p { - margin-top: 0; - margin-bottom: 0; - font-weight: 700; - } -`; - -const ChaptersWrapper = styled.div` - display: flex; - // row - flex-direction: row; - width: 100%; - width: 1300px; - margin: 0 auto; -`; - -const ChapterIndicator = styled.div < { active?: boolean, done?: boolean } > ` - border-radius: 20px; - height: 5px; - background: #151515; - border-radius: 3px; - - width: 35px; - background-color: ${props => props.done ? "green" : (props.active ? "#9d9d9d" : "black")}; - margin: 10px; - margin-bottom: 0px; - margin-left: 0px; - - &:hover { - cursor: pointer; - background-color: #9d9d9d; - } -`; - -const ActivityTopWrapper = styled.div` - width: 1300px; - padding-top: 50px; - margin: 0 auto; - display: flex; -`; - -const CourseContent = styled.div` - display: flex; - flex-direction: column; - background-color: white; - min-height: 600px; -`; - -const ActivityMarkerWrapper = styled.div` - display: block; - width: 1300px; - justify-content: flex-end; - margin: 0 auto; - align-items: center; - - button { - background-color: #151515; - border: none; - padding: 18px; - border-radius: 15px; - margin: 15px; - margin-left: 20px; - margin-top: 20px; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; - margin: auto; - color: white; - font-weight: 700; - font-family: "DM Sans"; - font-size: 16px; - letter-spacing: -0.05em; - box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42); - - i { - margin-right: 5px; - - // center the icon - display: flex; - align-items: center; - justify-content: center; - } - - &:hover { - background-color: #000000; - } - } -`; - -export default ActivityPage; +export default ActivityPage \ No newline at end of file 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..e5494c84 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx @@ -1,261 +1,45 @@ -"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: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null) -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: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null) + + 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/app/orgs/[orgslug]/(withmenu)/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/page.tsx index ee1d2d7f..4c5def42 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/page.tsx @@ -1,17 +1,16 @@ export const dynamic = 'force-dynamic'; import { Metadata, ResolvingMetadata } from 'next'; -import { Menu } from "@components/UI/Elements/Menu"; import { getBackendUrl, getUriWithOrg } from "@services/config/config"; -import { getOrgCourses } from "@services/courses/courses"; +import { getCourse, getOrgCourses, getOrgCoursesWithAuthHeader } from "@services/courses/courses"; import CoursesLogo from "public/svg/courses.svg"; import CollectionsLogo from "public/svg/collections.svg"; import Link from "next/link"; import Image from "next/image"; -import { log } from "console"; -import AuthProvider from "@components/Security/AuthProvider"; -import { getOrgCollections } from "@services/courses/collections"; +import { getOrgCollections, getOrgCollectionsWithAuthHeader } from "@services/courses/collections"; import { getOrganizationContextInfo } from '@services/organizations/orgs'; +import { cookies } from 'next/headers'; + type MetadataProps = { params: { orgslug: string }; searchParams: { [key: string]: string | string[] | undefined }; @@ -21,18 +20,23 @@ export async function generateMetadata( { params }: MetadataProps, ): Promise { + // Get Org context information const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); return { - title: org.name + " — Home", + title: `Home — ${org.name}`, description: org.description, }; } const OrgHomePage = async (params: any) => { const orgslug = params.params.orgslug; - const courses = await getOrgCourses(orgslug, { revalidate: 360 , tags: ['courses'] }); - const collections = await getOrgCollections(); + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + + const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null); + const collections = await getOrgCollectionsWithAuthHeader(access_token_cookie ? access_token_cookie.value : null); + // function to remove "course_" from the course_id function removeCoursePrefix(course_id: string) { diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index 34f12a21..19cfee84 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -1,3 +1,4 @@ +'use client'; import React from "react"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; diff --git a/front/components/Editor/EditorWrapper.tsx b/front/components/Editor/EditorWrapper.tsx index 6b5bc38e..3119a71e 100644 --- a/front/components/Editor/EditorWrapper.tsx +++ b/front/components/Editor/EditorWrapper.tsx @@ -1,3 +1,4 @@ +'use client'; import { default as React, } from "react"; import * as Y from "yjs"; import { WebrtcProvider } from "y-webrtc"; diff --git a/front/components/UI/Modal/Modal.tsx b/front/components/UI/Modal/Modal.tsx index 6b234cdc..dab976b9 100644 --- a/front/components/UI/Modal/Modal.tsx +++ b/front/components/UI/Modal/Modal.tsx @@ -1,3 +1,4 @@ +'use client'; import React from 'react'; import * as Dialog from '@radix-ui/react-dialog'; import { styled, keyframes } from '@stitches/react'; @@ -44,7 +45,7 @@ const Modal = (params: ModalParams) => ( {params.addDefCloseButton ? ( - Close + Close ) : null} diff --git a/front/components/UI/Toast/Toast.tsx b/front/components/UI/Toast/Toast.tsx index b297ec45..e7d43ffe 100644 --- a/front/components/UI/Toast/Toast.tsx +++ b/front/components/UI/Toast/Toast.tsx @@ -1,3 +1,4 @@ +'use client'; import React from 'react' import { Toaster } from 'react-hot-toast'; diff --git a/front/components/UI/Tooltip/Tooltip.tsx b/front/components/UI/Tooltip/Tooltip.tsx index c2291a3d..8a8cf0fe 100644 --- a/front/components/UI/Tooltip/Tooltip.tsx +++ b/front/components/UI/Tooltip/Tooltip.tsx @@ -1,3 +1,4 @@ +'use client'; import React from 'react'; import * as Tooltip from '@radix-ui/react-tooltip'; import { styled, keyframes } from '@stitches/react'; @@ -6,14 +7,14 @@ import { PlusIcon } from '@radix-ui/react-icons'; type TooltipProps = { - sideOffset?: number; - content: React.ReactNode; - children: React.ReactNode; - slateBlack?: boolean; + sideOffset?: number; + content: React.ReactNode; + children: React.ReactNode; + slateBlack?: boolean; }; const ToolTip = (props: TooltipProps) => { - + return ( @@ -58,10 +59,10 @@ const closeAndFade = keyframes({ const TooltipContent = styled(Tooltip.Content, { - variants : { + variants: { slateBlack: { true: { - backgroundColor:" #5a5a5a", + backgroundColor: " #5a5a5a", color: 'white', }, }, @@ -71,7 +72,7 @@ const TooltipContent = styled(Tooltip.Content, { padding: '5px 10px', fontSize: 12, lineHeight: 1, - color:"black", + color: "black", backgroundColor: 'rgba(217, 217, 217, 0.50)', zIndex: 4, boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px', @@ -97,7 +98,7 @@ const TooltipContent = styled(Tooltip.Content, { const TooltipArrow = styled(Tooltip.Arrow, { fill: 'white', - + }); const IconButton = styled('button', { diff --git a/front/package-lock.json b/front/package-lock.json index a51e4c21..f71c3d35 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -26,7 +26,7 @@ "formik": "^2.2.9", "framer-motion": "^7.3.6", "lucide-react": "^0.104.1", - "next": "^13.4.2", + "next": "^13.4.3", "re-resizable": "^6.9.9", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", @@ -2123,9 +2123,9 @@ } }, "node_modules/@next/env": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.2.tgz", - "integrity": "sha512-Wqvo7lDeS0KGwtwg9TT9wKQ8raelmUxt+TQKWvG/xKfcmDXNOtCuaszcfCF8JzlBG1q0VhpI6CKaRMbVPMDWgw==" + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.3.tgz", + "integrity": "sha512-pa1ErjyFensznttAk3EIv77vFbfSYT6cLzVRK5jx4uiRuCQo+m2wCFAREaHKIy63dlgvOyMlzh6R8Inu8H3KrQ==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.0.6", @@ -2137,9 +2137,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.2.tgz", - "integrity": "sha512-6BBlqGu3ewgJflv9iLCwO1v1hqlecaIH2AotpKfVUEzUxuuDNJQZ2a4KLb4MBl8T9/vca1YuWhSqtbF6ZuUJJw==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz", + "integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==", "cpu": [ "arm64" ], @@ -2152,9 +2152,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.2.tgz", - "integrity": "sha512-iZuYr7ZvGLPjPmfhhMl0ISm+z8EiyLBC1bLyFwGBxkWmPXqdJ60mzuTaDSr5WezDwv0fz32HB7JHmRC6JVHSZg==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz", + "integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==", "cpu": [ "x64" ], @@ -2167,9 +2167,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.2.tgz", - "integrity": "sha512-2xVabFtIge6BJTcJrW8YuUnYTuQjh4jEuRuS2mscyNVOj6zUZkom3CQg+egKOoS+zh2rrro66ffSKIS+ztFJTg==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz", + "integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==", "cpu": [ "arm64" ], @@ -2182,9 +2182,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.2.tgz", - "integrity": "sha512-wKRCQ27xCUJx5d6IivfjYGq8oVngqIhlhSAJntgXLt7Uo9sRT/3EppMHqUZRfyuNBTbykEre1s5166z+pvRB5A==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz", + "integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==", "cpu": [ "arm64" ], @@ -2197,9 +2197,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.2.tgz", - "integrity": "sha512-NpCa+UVhhuNeaFVUP1Bftm0uqtvLWq2JTm7+Ta48+2Uqj2mNXrDIvyn1DY/ZEfmW/1yvGBRaUAv9zkMkMRixQA==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz", + "integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==", "cpu": [ "x64" ], @@ -2212,9 +2212,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.2.tgz", - "integrity": "sha512-ZWVC72x0lW4aj44e3khvBrj2oSYj1bD0jESmyah3zG/3DplEy/FOtYkMzbMjHTdDSheso7zH8GIlW6CDQnKhmQ==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz", + "integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==", "cpu": [ "x64" ], @@ -2227,9 +2227,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.2.tgz", - "integrity": "sha512-pLT+OWYpzJig5K4VKhLttlIfBcVZfr2+Xbjra0Tjs83NQSkFS+y7xx+YhCwvpEmXYLIvaggj2ONPyjbiigOvHQ==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz", + "integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==", "cpu": [ "arm64" ], @@ -2242,9 +2242,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.2.tgz", - "integrity": "sha512-dhpiksQCyGca4WY0fJyzK3FxMDFoqMb0Cn+uDB+9GYjpU2K5//UGPQlCwiK4JHxuhg8oLMag5Nf3/IPSJNG8jw==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz", + "integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==", "cpu": [ "ia32" ], @@ -2257,9 +2257,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.2.tgz", - "integrity": "sha512-O7bort1Vld00cu8g0jHZq3cbSTUNMohOEvYqsqE10+yfohhdPHzvzO+ziJRz4Dyyr/fYKREwS7gR4JC0soSOMw==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz", + "integrity": "sha512-jglUk/x7ZWeOJWlVoKyIAkHLTI+qEkOriOOV+3hr1GyiywzcqfI7TpFSiwC7kk1scOiH7NTFKp8mA3XPNO9bDw==", "cpu": [ "x64" ], @@ -6371,11 +6371,11 @@ "dev": true }, "node_modules/next": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.2.tgz", - "integrity": "sha512-aNFqLs3a3nTGvLWlO9SUhCuMUHVPSFQC0+tDNGAsDXqx+WJDFSbvc233gOJ5H19SBc7nw36A9LwQepOJ2u/8Kg==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.3.tgz", + "integrity": "sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==", "dependencies": { - "@next/env": "13.4.2", + "@next/env": "13.4.3", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -6390,15 +6390,15 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.2", - "@next/swc-darwin-x64": "13.4.2", - "@next/swc-linux-arm64-gnu": "13.4.2", - "@next/swc-linux-arm64-musl": "13.4.2", - "@next/swc-linux-x64-gnu": "13.4.2", - "@next/swc-linux-x64-musl": "13.4.2", - "@next/swc-win32-arm64-msvc": "13.4.2", - "@next/swc-win32-ia32-msvc": "13.4.2", - "@next/swc-win32-x64-msvc": "13.4.2" + "@next/swc-darwin-arm64": "13.4.3", + "@next/swc-darwin-x64": "13.4.3", + "@next/swc-linux-arm64-gnu": "13.4.3", + "@next/swc-linux-arm64-musl": "13.4.3", + "@next/swc-linux-x64-gnu": "13.4.3", + "@next/swc-linux-x64-musl": "13.4.3", + "@next/swc-win32-arm64-msvc": "13.4.3", + "@next/swc-win32-ia32-msvc": "13.4.3", + "@next/swc-win32-x64-msvc": "13.4.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -10043,9 +10043,9 @@ } }, "@next/env": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.2.tgz", - "integrity": "sha512-Wqvo7lDeS0KGwtwg9TT9wKQ8raelmUxt+TQKWvG/xKfcmDXNOtCuaszcfCF8JzlBG1q0VhpI6CKaRMbVPMDWgw==" + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.3.tgz", + "integrity": "sha512-pa1ErjyFensznttAk3EIv77vFbfSYT6cLzVRK5jx4uiRuCQo+m2wCFAREaHKIy63dlgvOyMlzh6R8Inu8H3KrQ==" }, "@next/eslint-plugin-next": { "version": "13.0.6", @@ -10057,57 +10057,57 @@ } }, "@next/swc-darwin-arm64": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.2.tgz", - "integrity": "sha512-6BBlqGu3ewgJflv9iLCwO1v1hqlecaIH2AotpKfVUEzUxuuDNJQZ2a4KLb4MBl8T9/vca1YuWhSqtbF6ZuUJJw==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz", + "integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==", "optional": true }, "@next/swc-darwin-x64": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.2.tgz", - "integrity": "sha512-iZuYr7ZvGLPjPmfhhMl0ISm+z8EiyLBC1bLyFwGBxkWmPXqdJ60mzuTaDSr5WezDwv0fz32HB7JHmRC6JVHSZg==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz", + "integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.2.tgz", - "integrity": "sha512-2xVabFtIge6BJTcJrW8YuUnYTuQjh4jEuRuS2mscyNVOj6zUZkom3CQg+egKOoS+zh2rrro66ffSKIS+ztFJTg==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz", + "integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.2.tgz", - "integrity": "sha512-wKRCQ27xCUJx5d6IivfjYGq8oVngqIhlhSAJntgXLt7Uo9sRT/3EppMHqUZRfyuNBTbykEre1s5166z+pvRB5A==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz", + "integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.2.tgz", - "integrity": "sha512-NpCa+UVhhuNeaFVUP1Bftm0uqtvLWq2JTm7+Ta48+2Uqj2mNXrDIvyn1DY/ZEfmW/1yvGBRaUAv9zkMkMRixQA==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz", + "integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.2.tgz", - "integrity": "sha512-ZWVC72x0lW4aj44e3khvBrj2oSYj1bD0jESmyah3zG/3DplEy/FOtYkMzbMjHTdDSheso7zH8GIlW6CDQnKhmQ==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz", + "integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.2.tgz", - "integrity": "sha512-pLT+OWYpzJig5K4VKhLttlIfBcVZfr2+Xbjra0Tjs83NQSkFS+y7xx+YhCwvpEmXYLIvaggj2ONPyjbiigOvHQ==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz", + "integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.2.tgz", - "integrity": "sha512-dhpiksQCyGca4WY0fJyzK3FxMDFoqMb0Cn+uDB+9GYjpU2K5//UGPQlCwiK4JHxuhg8oLMag5Nf3/IPSJNG8jw==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz", + "integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.2.tgz", - "integrity": "sha512-O7bort1Vld00cu8g0jHZq3cbSTUNMohOEvYqsqE10+yfohhdPHzvzO+ziJRz4Dyyr/fYKREwS7gR4JC0soSOMw==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz", + "integrity": "sha512-jglUk/x7ZWeOJWlVoKyIAkHLTI+qEkOriOOV+3hr1GyiywzcqfI7TpFSiwC7kk1scOiH7NTFKp8mA3XPNO9bDw==", "optional": true }, "@nicolo-ribaudo/chokidar-2": { @@ -13076,20 +13076,20 @@ "dev": true }, "next": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.2.tgz", - "integrity": "sha512-aNFqLs3a3nTGvLWlO9SUhCuMUHVPSFQC0+tDNGAsDXqx+WJDFSbvc233gOJ5H19SBc7nw36A9LwQepOJ2u/8Kg==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.3.tgz", + "integrity": "sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==", "requires": { - "@next/env": "13.4.2", - "@next/swc-darwin-arm64": "13.4.2", - "@next/swc-darwin-x64": "13.4.2", - "@next/swc-linux-arm64-gnu": "13.4.2", - "@next/swc-linux-arm64-musl": "13.4.2", - "@next/swc-linux-x64-gnu": "13.4.2", - "@next/swc-linux-x64-musl": "13.4.2", - "@next/swc-win32-arm64-msvc": "13.4.2", - "@next/swc-win32-ia32-msvc": "13.4.2", - "@next/swc-win32-x64-msvc": "13.4.2", + "@next/env": "13.4.3", + "@next/swc-darwin-arm64": "13.4.3", + "@next/swc-darwin-x64": "13.4.3", + "@next/swc-linux-arm64-gnu": "13.4.3", + "@next/swc-linux-arm64-musl": "13.4.3", + "@next/swc-linux-x64-gnu": "13.4.3", + "@next/swc-linux-x64-musl": "13.4.3", + "@next/swc-win32-arm64-msvc": "13.4.3", + "@next/swc-win32-ia32-msvc": "13.4.3", + "@next/swc-win32-x64-msvc": "13.4.3", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", diff --git a/front/package.json b/front/package.json index 0066e5ec..55ed4d6b 100644 --- a/front/package.json +++ b/front/package.json @@ -27,7 +27,7 @@ "formik": "^2.2.9", "framer-motion": "^7.3.6", "lucide-react": "^0.104.1", - "next": "^13.4.2", + "next": "^13.4.3", "re-resizable": "^6.9.9", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", diff --git a/front/services/auth/auth.ts b/front/services/auth/auth.ts index c952b2f8..5c16970c 100644 --- a/front/services/auth/auth.ts +++ b/front/services/auth/auth.ts @@ -12,8 +12,7 @@ export async function loginAndGetToken(username: string, password: string): Prom // Request Config // get origin - const origin = window.location.origin; - const HeadersConfig = new Headers({ "Content-Type": "application/x-www-form-urlencoded" , Origin: origin }); + const HeadersConfig = new Headers({ "Content-Type": "application/x-www-form-urlencoded" }); const urlencoded = new URLSearchParams({ username: username, password: password }); const requestOptions: any = { diff --git a/front/services/courses/activities.ts b/front/services/courses/activities.ts index a87b398e..d3c4e160 100644 --- a/front/services/courses/activities.ts +++ b/front/services/courses/activities.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/ts/requests"; +import { RequestBody, RequestBodyForm, RequestBodyWithAuthHeader } from "@services/utils/ts/requests"; export async function createActivity(data: any, chapter_id: any, org_id: any) { data.content = {}; @@ -40,14 +40,20 @@ export async function createExternalVideoActivity(data: any, activity: any, chap // add coursechapter_id to data data.coursechapter_id = chapter_id; data.activity_id = activity.id; - + const result = await fetch(`${getAPIUrl()}activities/external_video?coursechapter_id=${chapter_id}`, RequestBody("POST", data, null)); const res = await result.json(); return res; } export async function getActivity(activity_id: any, next: any) { - const result = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("GET", null,next)); + const result = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("GET", null, next)); + const res = await result.json(); + return res; +} + +export async function getActivityWithAuthHeader(activity_id: any, next: any, access_token: string) { + const result = await fetch(`${getAPIUrl()}activities/activity_${activity_id}`, RequestBodyWithAuthHeader("GET", null, next, access_token)); const res = await result.json(); return res; } 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/collections.ts b/front/services/courses/collections.ts index b3e4c027..73805141 100644 --- a/front/services/courses/collections.ts +++ b/front/services/courses/collections.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "../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 @@ -19,7 +19,7 @@ export async function createCollection(collection: any) { return res; } -// Get collections +// Get collections // TODO : add per org filter export async function getOrgCollections() { const result: any = await fetch(`${getAPIUrl()}collections/page/1/limit/10`, { next: { revalidate: 10 } }); @@ -27,3 +27,8 @@ export async function getOrgCollections() { return res; } +export async function getOrgCollectionsWithAuthHeader(access_token: string) { + const result: any = await fetch(`${getAPIUrl()}collections/page/1/limit/10`, RequestBodyWithAuthHeader("GET", null, { revalidate: 10 }, access_token)); + const res = await errorHandling(result); + return res; +} diff --git a/front/services/courses/courses.ts b/front/services/courses/courses.ts index ea1b4941..62e29b3f 100644 --- a/front/services/courses/courses.ts +++ b/front/services/courses/courses.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm, errorHandling } from "@services/utils/ts/requests"; +import { RequestBody, RequestBodyForm, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -9,7 +9,19 @@ import { RequestBody, RequestBodyForm, errorHandling } from "@services/utils/ts/ export async function getOrgCourses(org_id: number, next: any) { const result: any = await fetch(`${getAPIUrl()}courses/org_slug/${org_id}/page/1/limit/10`, RequestBody("GET", null, next)); const res = await errorHandling(result); - + + return res; +} + +export async function getOrgCoursesWithAuthHeader(org_id: number, next: any, access_token: string) { + const result: any = await fetch(`${getAPIUrl()}courses/org_slug/${org_id}/page/1/limit/10`, RequestBodyWithAuthHeader("GET", null, next, access_token)); + const res = await errorHandling(result); + 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; } diff --git a/front/services/utils/ts/requests.ts b/front/services/utils/ts/requests.ts index 07ba1142..06c88816 100644 --- a/front/services/utils/ts/requests.ts +++ b/front/services/utils/ts/requests.ts @@ -18,6 +18,20 @@ export const RequestBody = (method: string, data: any, next: any) => { return options; }; +export const RequestBodyWithAuthHeader = (method: string, data: any, next: any, token: string) => { + let HeadersConfig = new Headers(token ? { "Content-Type": "application/json", Authorization: `Bearer ${token}` } : { "Content-Type": "application/json" }); + let options: any = { + method: method, + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + body: data, + // Next.js + next: next, + }; + return options; +}; + export const RequestBodyForm = (method: string, data: any, next: any) => { let HeadersConfig = new Headers({}); let options: any = { diff --git a/src/routers/auth.py b/src/routers/auth.py index b563212d..5f5eacb1 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -1,13 +1,14 @@ from urllib.request import Request -from fastapi import Depends, APIRouter, HTTPException, status, Request -from fastapi.security import OAuth2PasswordRequestForm +from fastapi import Depends, APIRouter, HTTPException, Response, status, Request +from fastapi.security import OAuth2PasswordRequestForm from src.security.auth import * from src.services.users.users import * router = APIRouter() -@router.post('/refresh') + +@router.post("/refresh") def refresh(Authorize: AuthJWT = Depends()): """ The jwt_refresh_token_required() function insures a valid refresh @@ -18,11 +19,17 @@ def refresh(Authorize: AuthJWT = Depends()): Authorize.jwt_refresh_token_required() current_user = Authorize.get_jwt_subject() - new_access_token = Authorize.create_access_token(subject=current_user) # type: ignore + new_access_token = Authorize.create_access_token(subject=current_user) # type: ignore return {"access_token": new_access_token} -@router.post('/login') -async def login(request: Request,Authorize: AuthJWT = Depends(), form_data: OAuth2PasswordRequestForm = Depends()): + +@router.post("/login") +async def login( + request: Request, + response: Response, + Authorize: AuthJWT = Depends(), + form_data: OAuth2PasswordRequestForm = Depends(), +): user = await authenticate_user(request, form_data.username, form_data.password) if not user: raise HTTPException( @@ -34,10 +41,17 @@ async def login(request: Request,Authorize: AuthJWT = Depends(), form_data: OAut access_token = Authorize.create_access_token(subject=form_data.username) refresh_token = Authorize.create_refresh_token(subject=form_data.username) Authorize.set_refresh_cookies(refresh_token) - Authorize.set_access_cookies(access_token) - return {"access_token": access_token , "refresh_token": refresh_token} + # set cookies using fastapi + response.set_cookie(key="access_token_cookie", value=access_token , httponly=False) -@router.delete('/logout') + result = { + "user": user, + "tokens": {"access_token": access_token, "refresh_token": refresh_token}, + } + return result + + +@router.delete("/logout") def logout(Authorize: AuthJWT = Depends()): """ Because the JWT are stored in an httponly cookie now, we cannot @@ -47,4 +61,4 @@ def logout(Authorize: AuthJWT = Depends()): Authorize.jwt_required() Authorize.unset_jwt_cookies() - return {"msg":"Successfully logout"} \ No newline at end of file + return {"msg": "Successfully logout"} diff --git a/src/security/auth.py b/src/security/auth.py index 7100a566..be7d37b1 100644 --- a/src/security/auth.py +++ b/src/security/auth.py @@ -10,26 +10,26 @@ from fastapi_jwt_auth import AuthJWT oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") + #### JWT Auth #################################################### class Settings(BaseModel): authjwt_secret_key: str = "secret" - authjwt_token_location = {"cookies"} + authjwt_token_location = {"cookies", "headers"} authjwt_cookie_csrf_protect = False - authjwt_access_token_expires = False # (pre-alpha only) # TODO: set to 1 hour - authjwt_cookie_samesite = "none" + authjwt_access_token_expires = False # (pre-alpha only) # TODO: set to 1 hour + authjwt_cookie_samesite = "lax" authjwt_cookie_secure = True - - -@AuthJWT.load_config # type: ignore + authjwt_cookie_domain = ".localhost" + + +@AuthJWT.load_config # type: ignore def get_config(): return Settings() - #### JWT Auth #################################################### - #### Classes #################################################### @@ -41,10 +41,11 @@ class Token(BaseModel): class TokenData(BaseModel): username: str | None = None + #### Classes #################################################### -async def authenticate_user(request: Request,email: str, password: str): +async def authenticate_user(request: Request, email: str, password: str): user = await security_get_user(request, email) if not user: return False @@ -64,29 +65,28 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None): return encoded_jwt - - async def get_current_user(request: Request, Authorize: AuthJWT = Depends()): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) - + try: Authorize.jwt_optional() username = Authorize.get_jwt_subject() or None token_data = TokenData(username=username) # type: ignore except JWTError: raise credentials_exception - if username: - user = await security_get_user(request, email=token_data.username) # type: ignore # treated as an email + if username: + user = await security_get_user(request, email=token_data.username) # type: ignore # treated as an email if user is None: raise credentials_exception return PublicUser(**user.dict()) else: return AnonymousUser() - -async def non_public_endpoint(current_user: PublicUser ): + + +async def non_public_endpoint(current_user: PublicUser): if isinstance(current_user, AnonymousUser): - raise HTTPException(status_code=401, detail="Not authenticated") \ No newline at end of file + raise HTTPException(status_code=401, detail="Not authenticated")