feat: init new edit course page

This commit is contained in:
swve 2023-12-13 09:56:11 +01:00
parent 187f75e583
commit 8d35085908
28 changed files with 891 additions and 159 deletions

View file

@ -34,12 +34,6 @@ class ActivityBase(SQLModel):
content: dict = Field(default={}, sa_column=Column(JSON)) content: dict = Field(default={}, sa_column=Column(JSON))
published_version: int published_version: int
version: int version: int
course_id: int = Field(
default=None,
sa_column=Column(
BigInteger, ForeignKey("course.id", ondelete="CASCADE")
),
)
class Activity(ActivityBase, table=True): class Activity(ActivityBase, table=True):
@ -57,9 +51,6 @@ class Activity(ActivityBase, table=True):
class ActivityCreate(ActivityBase): class ActivityCreate(ActivityBase):
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int chapter_id: int
pass pass

View file

@ -1,6 +1,6 @@
from typing import List, Optional from typing import List, Optional
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
from src.db.users import User, UserRead from src.db.users import UserRead
from src.db.trails import TrailRead from src.db.trails import TrailRead
from src.db.chapters import ChapterRead from src.db.chapters import ChapterRead

View file

@ -6,7 +6,6 @@ from src.db.chapters import (
ChapterRead, ChapterRead,
ChapterUpdate, ChapterUpdate,
ChapterUpdateOrder, ChapterUpdateOrder,
DepreceatedChaptersRead,
) )
from src.services.courses.chapters import ( from src.services.courses.chapters import (
DEPRECEATED_get_course_chapters, DEPRECEATED_get_course_chapters,

View file

@ -5,7 +5,6 @@ from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship, authorization_verify_based_on_roles_and_authorship,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.db.organizations import Organization
from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate
from src.db.chapter_activities import ChapterActivity from src.db.chapter_activities import ChapterActivity
from src.db.users import AnonymousUser, PublicUser from src.db.users import AnonymousUser, PublicUser
@ -44,6 +43,7 @@ async def create_activity(
activity.creation_date = str(datetime.now()) activity.creation_date = str(datetime.now())
activity.update_date = str(datetime.now()) activity.update_date = str(datetime.now())
activity.org_id = chapter.org_id activity.org_id = chapter.org_id
activity.course_id = chapter.course_id
# Insert Activity in DB # Insert Activity in DB
db_session.add(activity) db_session.add(activity)
@ -65,7 +65,7 @@ async def create_activity(
activity_chapter = ChapterActivity( activity_chapter = ChapterActivity(
chapter_id=activity_object.chapter_id, chapter_id=activity_object.chapter_id,
activity_id=activity.id if activity.id else 0, activity_id=activity.id if activity.id else 0,
course_id=activity_object.course_id, course_id=chapter.course_id,
org_id=chapter.org_id, org_id=chapter.org_id,
creation_date=str(datetime.now()), creation_date=str(datetime.now()),
update_date=str(datetime.now()), update_date=str(datetime.now()),

View file

@ -194,7 +194,7 @@ async def create_external_video_activity(
# update chapter # update chapter
chapter_activity_object = ChapterActivity( chapter_activity_object = ChapterActivity(
chapter_id=coursechapter.id, # type: ignore chapter_id=coursechapter.chapter_id, # type: ignore
activity_id=activity.id, # type: ignore activity_id=activity.id, # type: ignore
course_id=coursechapter.course_id, course_id=coursechapter.course_id,
org_id=coursechapter.org_id, org_id=coursechapter.org_id,

View file

@ -16,7 +16,6 @@ from src.db.chapters import (
ChapterRead, ChapterRead,
ChapterUpdate, ChapterUpdate,
ChapterUpdateOrder, ChapterUpdateOrder,
DepreceatedChaptersRead,
) )
from src.services.courses.courses import Course from src.services.courses.courses import Course
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
@ -270,7 +269,6 @@ async def DEPRECEATED_get_course_chapters(
chapters_in_db = await get_course_chapters(request, course.id, db_session, current_user) # type: ignore chapters_in_db = await get_course_chapters(request, course.id, db_session, current_user) # type: ignore
# activities # activities
chapter_activityIdsGlobal = []
# chapters # chapters
chapters = {} chapters = {}
@ -437,7 +435,7 @@ async def reorder_chapters_and_activities(
# Activities # Activities
########### ###########
# Delete ChapterActivities that are not linked to chapter_id and activity_id and org_id and course_id # Delete ChapterActivities that are no longer part of the new order
statement = ( statement = (
select(ChapterActivity) select(ChapterActivity)
.where( .where(
@ -448,20 +446,36 @@ async def reorder_chapters_and_activities(
) )
chapter_activities = db_session.exec(statement).all() chapter_activities = db_session.exec(statement).all()
activity_ids_to_keep = [ activity_ids_to_delete = []
activity_order.activity_id
for chapter_order in chapters_order.chapter_order_by_ids
for activity_order in chapter_order.activities_order_by_ids
]
for chapter_activity in chapter_activities: for chapter_activity in chapter_activities:
if chapter_activity.activity_id not in activity_ids_to_keep: if (
db_session.delete(chapter_activity) chapter_activity.chapter_id not in chapter_ids_to_keep
db_session.commit() or chapter_activity.activity_id not in activity_ids_to_delete
):
activity_ids_to_delete.append(chapter_activity.activity_id)
# If links do not exists, create them for activity_id in activity_ids_to_delete:
statement = (
select(ChapterActivity)
.where(
ChapterActivity.activity_id == activity_id,
ChapterActivity.course_id == course.id,
)
.order_by(ChapterActivity.order)
)
chapter_activity = db_session.exec(statement).first()
db_session.delete(chapter_activity)
db_session.commit()
# If links do not exist, create them
chapter_activity_map = {}
for chapter_order in chapters_order.chapter_order_by_ids: for chapter_order in chapters_order.chapter_order_by_ids:
for activity_order in chapter_order.activities_order_by_ids: for activity_order in chapter_order.activities_order_by_ids:
if activity_order.activity_id in chapter_activity_map and chapter_activity_map[activity_order.activity_id] != chapter_order.chapter_id:
continue
statement = ( statement = (
select(ChapterActivity) select(ChapterActivity)
.where( .where(
@ -488,6 +502,8 @@ async def reorder_chapters_and_activities(
db_session.add(chapter_activity) db_session.add(chapter_activity)
db_session.commit() db_session.commit()
chapter_activity_map[activity_order.activity_id] = chapter_order.chapter_id
# Update order of activities # Update order of activities
for chapter_order in chapters_order.chapter_order_by_ids: for chapter_order in chapters_order.chapter_order_by_ids:
for activity_order in chapter_order.activities_order_by_ids: for activity_order in chapter_order.activities_order_by_ids:

View file

@ -0,0 +1,59 @@
'use client';
import EditCourseStructure from '../../../../../../../../components/Dashboard/EditCourseStructure/EditCourseStructure'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import PageLoading from '@components/Objects/Loaders/PageLoading';
import ClientComponentSkeleton from '@components/Utils/ClientComp';
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
import { swrFetcher } from '@services/utils/ts/requests';
import React, { createContext, use, useEffect, useState } from 'react'
import useSWR from 'swr';
import { CourseProvider, useCourse } from '../../../../../../../../components/Dashboard/CourseContext';
import SaveState from '@components/Dashboard/UI/SaveState';
import Link from 'next/link';
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop';
export type CourseOverviewParams = {
orgslug: string,
courseuuid: string,
subpage: string
}
export const CourseStructureContext = createContext({}) as any;
function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
function getEntireCourseUUID(courseuuid: string) {
// add course_ to uuid
return `course_${courseuuid}`
}
return (
<div className='h-full w-full bg-[#f8f8f8]'>
<CourseProvider courseuuid={getEntireCourseUUID(params.courseuuid)}>
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
<CourseOverviewTop params={params} />
<div className='flex space-x-5 font-black text-sm'>
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/general`}>
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>General</div>
</Link>
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/structure`}>
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${params.subpage.toString() === 'structure' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>Structure</div>
</Link>
</div>
</div>
<div className='h-6'></div>
{params.subpage == 'structure' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
</CourseProvider>
</div>
)
}
export default CourseOverviewPage

View file

@ -0,0 +1,22 @@
'use client';
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import Link from 'next/link'
import React from 'react'
function CoursesHome() {
return (
<div>
<div className='h-full w-fullbg-white'>
<div className='pl-10 tracking-tighter'>
<BreadCrumbs type='courses' />
<div className='flex pt-2'>
<div className='font-bold text-4xl'>Courses</div>
</div>
</div>
</div>
</div>
)
}
export default CoursesHome

View file

@ -0,0 +1,20 @@
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import AuthProvider from '@components/Security/AuthProvider'
import React from 'react'
function DashboardLayout({ children, params }: { children: React.ReactNode, params: any }) {
return (
<>
<AuthProvider>
<div className='flex'>
<LeftMenu/>
<div className='flex w-full'>
{children}
</div>
</div>
</AuthProvider>
</>
)
}
export default DashboardLayout

View file

@ -0,0 +1,9 @@
import React from 'react'
function DashboardHome() {
return (
<div>DashboardHome</div>
)
}
export default DashboardHome

View file

@ -1,4 +1,3 @@
import "@styles/globals.css"; import "@styles/globals.css";
export default function RootLayout({ children, params }: { children: React.ReactNode , params:any}) { export default function RootLayout({ children, params }: { children: React.ReactNode , params:any}) {

View file

@ -1,25 +0,0 @@
"use client";
import { motion } from "framer-motion";
export default function Template({ children }: { children: React.ReactNode }) {
const variants = {
hidden: { opacity: 0, x: 0, y: 0 },
enter: { opacity: 1, x: 0, y: 0 },
exit: { opacity: 0, x: 0, y: 0 },
};
return (
<div>
<motion.main
variants={variants} // Pass the variant object into Framer Motion
initial="hidden" // Set the initial state to variants.hidden
animate="enter" // Animated state to variants.enter
exit="exit" // Exit state (used later) to variants.exit
transition={{ type: "linear" }} // Set the transition to linear
className=""
>
{children}
</motion.main>
</div>
);
}

View file

@ -0,0 +1,62 @@
'use client';
import { getAPIUrl } from '@services/config/config';
import { swrFetcher } from '@services/utils/ts/requests';
import React, { createContext, useContext, useEffect, useReducer } from 'react'
import useSWR, { mutate } from 'swr';
export const CourseContext = createContext(null) as any;
export const CourseDispatchContext = createContext(null) as any;
export function CourseProvider({ children, courseuuid }: { children: React.ReactNode, courseuuid: string }) {
const { data: courseStructureData } = useSWR(`${getAPIUrl()}courses/${courseuuid}/meta`, swrFetcher);
const [courseStructure, dispatchCourseStructure] = useReducer(courseReducer,
{
courseStructure: courseStructureData ? courseStructureData : {},
courseOrder: {},
isSaved: true
}
);
// When courseStructureData is loaded, update the state
useEffect(() => {
if (courseStructureData) {
dispatchCourseStructure({ type: 'setCourseStructure', payload: courseStructureData });
}
}, [courseStructureData]);
if (!courseStructureData) return <div>Loading...</div>
return (
<CourseContext.Provider value={courseStructure}>
<CourseDispatchContext.Provider value={dispatchCourseStructure}>
{children}
</CourseDispatchContext.Provider>
</CourseContext.Provider>
)
}
export function useCourse() {
return useContext(CourseContext);
}
export function useCourseDispatch() {
return useContext(CourseDispatchContext);
}
function courseReducer(state: any, action: any) {
switch (action.type) {
case 'setCourseStructure':
return { ...state, courseStructure: action.payload };
case 'setCourseOrder':
return { ...state, courseOrder: action.payload };
case 'setIsSaved':
return { ...state, isSaved: true };
case 'setIsNotSaved':
return { ...state, isSaved: false };
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}

View file

@ -0,0 +1,93 @@
import { useCourse } from '@components/Dashboard/CourseContext';
import NewActivityModal from '@components/Objects/Modals/Activities/Create/NewActivity';
import Modal from '@components/StyledElements/Modal/Modal';
import { getAPIUrl } from '@services/config/config';
import { createActivity, createExternalVideoActivity, createFileActivity } from '@services/courses/activities';
import { getOrganizationContextInfoWithoutCredentials } from '@services/organizations/orgs';
import { revalidateTags } from '@services/utils/ts/requests';
import { Sparkles } from 'lucide-react'
import { useRouter } from 'next/navigation';
import React, { use, useEffect } from 'react'
import { mutate } from 'swr';
type NewActivityButtonProps = {
chapterId: string,
orgslug: string
}
function NewActivityButton(props: NewActivityButtonProps) {
const [newActivityModal, setNewActivityModal] = React.useState(false);
const router = useRouter();
const course = useCourse() as any;
const openNewActivityModal = async (chapterId: any) => {
setNewActivityModal(true);
};
const closeNewActivityModal = async () => {
setNewActivityModal(false);
};
// Submit new activity
const submitActivity = async (activity: any) => {
let org = await getOrganizationContextInfoWithoutCredentials(props.orgslug, { revalidate: 1800 });
await createActivity(activity, props.chapterId, org.org_id);
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
setNewActivityModal(false);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
};
// Submit File Upload
const submitFileActivity = async (file: any, type: any, activity: any, chapterId: string) => {
await createFileActivity(file, type, activity, chapterId);
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
setNewActivityModal(false);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
};
// Submit YouTube Video Upload
const submitExternalVideo = async (external_video_data: any, activity: any, chapterId: string) => {
await createExternalVideoActivity(external_video_data, activity, props.chapterId);
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
setNewActivityModal(false);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
};
useEffect(() => { }
, [course])
return (
<div>
<Modal
isDialogOpen={newActivityModal}
onOpenChange={setNewActivityModal}
minHeight="no-min"
addDefCloseButton={false}
dialogContent={<NewActivityModal
closeModal={closeNewActivityModal}
submitFileActivity={submitFileActivity}
submitExternalVideo={submitExternalVideo}
submitActivity={submitActivity}
chapterId={props.chapterId}
course={course}
></NewActivityModal>}
dialogTitle="Create Activity"
dialogDescription="Choose between types of activities to add to the course"
/>
<div onClick={() => {
openNewActivityModal(props.chapterId)
}} className="flex space-x-2 items-center py-2 my-3 rounded-md justify-center text-white bg-black hover:cursor-pointer">
<Sparkles className="" size={17} />
<div className="text-sm mx-auto my-auto items-center font-bold">Add Activity + </div>
</div>
</div>
)
}
export default NewActivityButton

View file

@ -0,0 +1,96 @@
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
import { deleteActivity } from '@services/courses/activities'
import { revalidateTags } from '@services/utils/ts/requests'
import { Eye, File, MoreVertical, Pencil, Save, Sparkles, Video, X } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React from 'react'
import { Draggable } from 'react-beautiful-dnd'
import { mutate } from 'swr'
type ActivitiyElementProps = {
orgslug: string,
activity: any,
activityIndex: any,
course_uuid: string
}
function ActivityElement(props: ActivitiyElementProps) {
const router = useRouter();
async function deleteActivityUI() {
await deleteActivity(props.activity.id);
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
}
return (
<Draggable key={props.activity.activity_uuid} draggableId={props.activity.activity_uuid} index={props.activityIndex}>
{(provided, snapshot) => (
<div
className="flex flex-row py-2 my-2 w-full rounded-md bg-gray-50 text-gray-500 hover:bg-gray-100 hover:scale-102 hover:shadow space-x-1 items-center ring-1 ring-inset ring-gray-400/10 shadow-sm transition-all delay-100 duration-75 ease-linear"
key={props.activity.id}
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
{/* Activity Type Icon */}
<div className="px-3 text-gray-300 space-x-1 w-28" >
{props.activity.activity_type === "video" &&
<>
<div className="flex space-x-2 items-center">
<Video size={16} />
<div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full mx-auto justify-center align-middle">Video</div>
</div>
</>}
</div>
{/* Centered Activity Name */}
<div className="grow items-center space-x-2 flex mx-auto justify-center">
{(<p className="first-letter:uppercase"> {props.activity.name} </p>)}
<Pencil size={12} className="text-neutral-400 hover:cursor-pointer" />
</div>
{/* Edit and View Button */}
<div className="flex flex-row space-x-2">
{props.activity.activity_type === "TYPE_DYNAMIC" && <>
<Link
href={''}
className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center"
rel="noopener noreferrer">
<div className="text-sky-100 font-bold text-xs" >Edit </div>
</Link>
</>}
<Link
href={''}
className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md"
rel="noopener noreferrer">
<Eye strokeWidth={2} size={15} className="text-gray-600" />
</Link>
</div>
{/* Delete Button */}
<div className="flex flex-row pr-3 space-x-1 items-center">
<MoreVertical size={15} className="text-gray-300" />
<ConfirmationModal
confirmationMessage="Are you sure you want to delete this activity ?"
confirmationButtonText="Delete Activity"
dialogTitle={"Delete " + props.activity.name + " ?"}
dialogTrigger={
<div
className=" hover:cursor-pointer p-1 px-5 bg-red-600 rounded-md"
rel="noopener noreferrer">
<X size={15} className="text-rose-200 font-bold" />
</div>}
functionToExecute={() => deleteActivityUI()}
status='warning'
></ConfirmationModal></div>
</div>
)}
</Draggable>
)
}
export default ActivityElement

View file

@ -0,0 +1,104 @@
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
import { Activity, Hexagon, MoreHorizontal, MoreVertical, Pencil, Save, Sparkles, X } from 'lucide-react';
import React from 'react'
import ActivitiyElement from './ActivityElement';
import { Draggable, Droppable } from 'react-beautiful-dnd';
import ActivityElement from './ActivityElement';
import NewActivity from '../Buttons/NewActivityButton';
import NewActivityButton from '../Buttons/NewActivityButton';
import { deleteChapter } from '@services/courses/chapters';
import { revalidateTags } from '@services/utils/ts/requests';
import { useRouter } from 'next/navigation';
import { getAPIUrl } from '@services/config/config';
import { mutate } from 'swr';
type ChapterElementProps = {
chapter: any,
chapterIndex: number,
orgslug: string
course_uuid: string
}
function ChapterElement(props: ChapterElementProps) {
const activities = props.chapter.activities || [];
const router = useRouter();
const deleteChapterUI = async () => {
await deleteChapter(props.chapter.id);
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
};
return (
<Draggable
key={props.chapter.chapter_uuid}
draggableId={props.chapter.chapter_uuid}
index={props.chapterIndex}
>
{(provided, snapshot) => (
<div
className="max-w-screen-2xl mx-auto bg-white rounded-xl shadow-sm px-6 pt-6"
key={props.chapter.chapter_uuid}
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div className="flex font-bold text-md items-center space-x-2 pb-3" >
<div className="flex grow text-lg space-x-3 items-center rounded-md ">
<div className="bg-neutral-100 rounded-md p-2">
<Hexagon strokeWidth={3} size={16} className="text-neutral-600 " />
</div>
<div className="flex space-x-2 items-center">
<p className="text-neutral-700 first-letter:uppercase">{props.chapter.name} </p>
<Pencil size={15} className="text-neutral-600 hover:cursor-pointer" />
</div>
</div>
<MoreVertical size={15} className="text-gray-300" />
<ConfirmationModal
confirmationButtonText="Delete Chapter"
confirmationMessage="Are you sure you want to delete this chapter?"
dialogTitle={"Delete " + props.chapter.name + " ?"}
dialogTrigger={
<div
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md shadow flex space-x-1 items-center text-rose-100 text-sm"
rel="noopener noreferrer">
<X size={15} className="text-rose-200 font-bold" />
<p>Delete Chapter</p>
</div>}
functionToExecute={() => deleteChapterUI()}
status='warning'
></ConfirmationModal>
</div>
<Droppable key={props.chapter.chapter_uuid} droppableId={props.chapter.chapter_uuid} type="activity">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
<div className="flex flex-col">
{activities.map((activity: any, index: any) => {
return (
<div key={index} className="flex items-center ">
<ActivityElement
orgslug={props.orgslug}
course_uuid={props.course_uuid}
activityIndex={index}
activity={activity} />
</div>
)
})}
{provided.placeholder}
</div>
</div>
)}
</Droppable>
<NewActivityButton orgslug={props.orgslug} chapterId={props.chapter.id} />
<div className='h-6'>
<div className='flex items-center'><MoreHorizontal size={19} className="text-gray-300 mx-auto" /></div>
</div>
</div>
)}
</Draggable>
)
}
export default ChapterElement

View file

@ -0,0 +1,111 @@
'use client';
import { getAPIUrl } from '@services/config/config';
import { revalidateTags, swrFetcher } from '@services/utils/ts/requests';
import React, { useContext, useEffect, useState } from 'react'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import useSWR, { mutate } from 'swr';
import ChapterElement from './DraggableElements/ChapterElement';
import PageLoading from '@components/Objects/Loaders/PageLoading';
import { updateCourseOrderStructure } from '@services/courses/chapters';
import { useRouter } from 'next/navigation';
import { CourseStructureContext } from 'app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page';
import { useCourse, useCourseDispatch } from '@components/Dashboard/CourseContext';
type EditCourseStructureProps = {
orgslug: string,
course_uuid?: string,
}
export type OrderPayload = {
chapter_order_by_ids: [
{
chapter_id: string,
activities_order_by_ids: [
{
activity_id: string
}
]
}
],
} | undefined
const EditCourseStructure = (props: EditCourseStructureProps) => {
const router = useRouter();
// Check window availability
const [winReady, setwinReady] = useState(false);
const dispatchCourse = useCourseDispatch() as any;
const [order, setOrder] = useState<OrderPayload>();
const course = useCourse() as any;
const course_structure = course ? course.courseStructure : {};
const course_uuid = course ? course.courseStructure.course_uuid : '';
const updateStructure = (result: any) => {
const { destination, source, draggableId, type } = result;
if (!destination) return;
if (destination.droppableId === source.droppableId && destination.index === source.index) return;
if (type === 'chapter') {
const newChapterOrder = Array.from(course_structure.chapters);
newChapterOrder.splice(source.index, 1);
newChapterOrder.splice(destination.index, 0, course_structure.chapters[source.index]);
dispatchCourse({ type: 'setCourseStructure', payload: { ...course_structure, chapters: newChapterOrder } })
dispatchCourse({ type: 'setIsNotSaved' })
}
if (type === 'activity') {
const newChapterOrder = Array.from(course_structure.chapters);
const sourceChapter = newChapterOrder.find((chapter: any) => chapter.chapter_uuid === source.droppableId) as any;
const destinationChapter = newChapterOrder.find((chapter: any) => chapter.chapter_uuid === destination.droppableId) ? newChapterOrder.find((chapter: any) => chapter.chapter_uuid === destination.droppableId) : sourceChapter;
const activity = sourceChapter.activities.find((activity: any) => activity.activity_uuid === draggableId);
sourceChapter.activities.splice(source.index, 1);
destinationChapter.activities.splice(destination.index, 0, activity);
dispatchCourse({ type: 'setCourseStructure', payload: { ...course_structure, chapters: newChapterOrder } })
dispatchCourse({ type: 'setIsNotSaved' })
}
}
useEffect(() => {
setwinReady(true);
}, [props.course_uuid, course_structure, course]);
if (!course) return <PageLoading></PageLoading>
return (
<div className='flex flex-col'>
{winReady ?
<DragDropContext onDragEnd={updateStructure}>
<Droppable type='chapter' droppableId='chapters'>
{(provided) => (
<div
className='space-y-4'
{...provided.droppableProps}
ref={provided.innerRef}>
{course_structure.chapters && course_structure.chapters.map((chapter: any, index: any) => {
return (
<ChapterElement
key={chapter.chapter_uuid}
chapterIndex={index}
orgslug={props.orgslug}
course_uuid={course_uuid}
chapter={chapter} />
)
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
: <></>}
</div>
)
}
export default EditCourseStructure

View file

@ -0,0 +1,30 @@
import { useCourse } from '@components/Dashboard/CourseContext'
import { Book, ChevronRight, User } from 'lucide-react'
import Link from 'next/link'
import React, { use, useEffect } from 'react'
type BreadCrumbsProps = {
type: 'courses' | 'users'
last_breadcrumb?: string
}
function BreadCrumbs(props: BreadCrumbsProps) {
const course = useCourse() as any;
return (
<div>
<div className='h-7'></div>
<div className='text-gray-400 tracking-tight font-medium text-sm flex space-x-1'>
<div className='flex items-center space-x-1'>
{props.type == 'courses' ? <div className='flex space-x-2 items-center'> <Book className='text-gray' size={14}></Book><Link href='/dash/courses'>Courses</Link></div> : ''}
{props.type == 'users' ? <div> <User size={14}></User><Link href='/dash/users'>Users</Link></div> : ''}
<div className='flex items-center space-x-1 first-letter:uppercase'>
{props.last_breadcrumb ? <ChevronRight size={17} /> : ''}
<div className='first-letter:uppercase'> {props.last_breadcrumb}</div>
</div></div></div>
</div>
)
}
export default BreadCrumbs

View file

@ -0,0 +1,31 @@
import { useCourse } from "@components/Dashboard/CourseContext";
import { useEffect } from "react";
import BreadCrumbs from "./BreadCrumbs";
import SaveState from "./SaveState";
import { CourseOverviewParams } from "app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page";
export function CourseOverviewTop({ params }: { params: CourseOverviewParams }) {
const course = useCourse() as any;
useEffect(() => { }
, [course])
return (
<>
<BreadCrumbs type='courses' last_breadcrumb={course.courseStructure.name} ></BreadCrumbs>
<div className='flex'>
<div className='flex py-5 grow items-center'>
<div className="image rounded-lg shadow-md bg-gray-900 w-28 h-14"></div>
<div className="flex flex-col course_metadata justify-center pl-5">
<div className='text-gray-400 font-semibold text-sm'>Course</div>
<div className='text-black font-bold text-xl -mt-1 first-letter:uppercase'>{course.courseStructure.name}</div>
</div>
</div>
<div className='flex items-center'>
<SaveState orgslug={params.orgslug} />
</div>
</div>
</>
)
}

View file

@ -0,0 +1,22 @@
import { Book } from 'lucide-react'
import Link from 'next/link'
import React from 'react'
function LeftMenu() {
return (
<div
style={{ background: "linear-gradient(0deg, rgba(0, 0, 0, 0.20) 0%, rgba(0, 0, 0, 0.20) 100%), radial-gradient(271.56% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0.00) 100%), #2E2D2D" }}
className='flex flex-col w-20 justifiy-center bg-black h-screen justify-center text-white'>
<div className='flex items-center mx-auto'>
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><Book size={18}/></Link>
</div>
</div>
)
}
export default LeftMenu

View file

@ -0,0 +1,100 @@
'use client';
import { getAPIUrl } from '@services/config/config';
import { updateCourseOrderStructure } from '@services/courses/chapters';
import { revalidateTags } from '@services/utils/ts/requests';
import { useCourse, useCourseDispatch } from '@components/Dashboard/CourseContext'
import { Check, SaveAllIcon, Timer } from 'lucide-react'
import { useRouter } from 'next/navigation';
import React, { useEffect } from 'react'
import { mutate } from 'swr';
function SaveState(props: { orgslug: string }) {
const course = useCourse() as any;
const router = useRouter();
const saved = course ? course.isSaved : true;
const dispatchCourse = useCourseDispatch() as any;
const course_structure = course.courseStructure;
const saveCourseState = async () => {
// Course structure & order
if (saved) return;
await changeOrderBackend();
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
dispatchCourse({ type: 'setIsSaved' })
}
//
// Course Order
const changeOrderBackend = async () => {
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
await updateCourseOrderStructure(course.courseStructure.course_uuid, course.courseOrder);
await revalidateTags(['courses'], props.orgslug)
router.refresh();
dispatchCourse({ type: 'setIsSaved' })
}
const handleCourseOrder = (course_structure: any) => {
const chapters = course_structure.chapters;
const chapter_order_by_ids = chapters.map((chapter: any) => {
return {
chapter_id: chapter.id,
activities_order_by_ids: chapter.activities.map((activity: any) => {
return {
activity_id: activity.id
}
})
}
})
dispatchCourse({ type: 'setCourseOrder', payload: { chapter_order_by_ids: chapter_order_by_ids } })
dispatchCourse({ type: 'setIsNotSaved' })
}
const initOrderPayload = () => {
if (course_structure && course_structure.chapters) {
handleCourseOrder(course_structure);
dispatchCourse({ type: 'setIsSaved' })
}
}
const changeOrderPayload = () => {
if (course_structure && course_structure.chapters) {
handleCourseOrder(course_structure);
dispatchCourse({ type: 'setIsNotSaved' })
}
}
useEffect(() => {
if (course_structure?.chapters) {
initOrderPayload();
}
if (course_structure?.chapters && !saved) {
changeOrderPayload();
}
}, [course_structure]); // This effect depends on the `course_structure` variable
return (
<div className='flex space-x-4'>
{saved ? <></> : <div className='text-gray-600 flex space-x-2 items-center antialiased'>
<Timer size={15} />
<div>
Unsaved changes
</div>
</div>}
<div className={`px-4 py-2 rounded-lg drop-shadow-md cursor-pointer flex space-x-2 items-center font-bold antialiased transition-all ease-linear ` + (saved ? 'bg-gray-600 text-white' : 'bg-black text-white border hover:bg-gray-900 ')
} onClick={saveCourseState}>
{saved ? <Check size={20} /> : <SaveAllIcon size={20} />}
{saved ? <div className=''>Saved</div> : <div className=''>Save</div>}
</div>
</div>
)
}
export default SaveState

View file

@ -1,21 +1,6 @@
// This file sets a custom webpack configuration to use your Next.js app
// with Sentry.
// https://nextjs.org/docs/api-reference/next.config.js/introduction
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
const { withSentryConfig } = require('@sentry/nextjs');
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: false, reactStrictMode: false,
compiler: { }
styledComponents: true,
},
};
module.exports = nextConfig; module.exports = nextConfig
module.exports = withSentryConfig(
module.exports,
{ silent: true },
{ hideSourcemaps: true },
);

View file

@ -28,7 +28,7 @@
"framer-motion": "^10.16.1", "framer-motion": "^10.16.1",
"lowlight": "^3.0.0", "lowlight": "^3.0.0",
"lucide-react": "^0.268.0", "lucide-react": "^0.268.0",
"next": "^14.0.3", "next": "^14.0.4",
"re-resizable": "^6.9.9", "re-resizable": "^6.9.9",
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
@ -2266,9 +2266,9 @@
} }
}, },
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz",
"integrity": "sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==" "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ=="
}, },
"node_modules/@next/eslint-plugin-next": { "node_modules/@next/eslint-plugin-next": {
"version": "13.5.4", "version": "13.5.4",
@ -2300,9 +2300,9 @@
} }
}, },
"node_modules/@next/swc-darwin-arm64": { "node_modules/@next/swc-darwin-arm64": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz",
"integrity": "sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==", "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2315,9 +2315,9 @@
} }
}, },
"node_modules/@next/swc-darwin-x64": { "node_modules/@next/swc-darwin-x64": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz",
"integrity": "sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==", "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2330,9 +2330,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-gnu": { "node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz",
"integrity": "sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==", "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2345,9 +2345,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-musl": { "node_modules/@next/swc-linux-arm64-musl": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz",
"integrity": "sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==", "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2360,9 +2360,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-gnu": { "node_modules/@next/swc-linux-x64-gnu": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz",
"integrity": "sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==", "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2375,9 +2375,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-musl": { "node_modules/@next/swc-linux-x64-musl": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz",
"integrity": "sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==", "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2390,9 +2390,9 @@
} }
}, },
"node_modules/@next/swc-win32-arm64-msvc": { "node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz",
"integrity": "sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==", "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2405,9 +2405,9 @@
} }
}, },
"node_modules/@next/swc-win32-ia32-msvc": { "node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz",
"integrity": "sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==", "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -2420,9 +2420,9 @@
} }
}, },
"node_modules/@next/swc-win32-x64-msvc": { "node_modules/@next/swc-win32-x64-msvc": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz",
"integrity": "sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==", "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -7117,14 +7117,15 @@
"dev": true "dev": true
}, },
"node_modules/next": { "node_modules/next": {
"version": "14.0.3", "version": "14.0.4",
"resolved": "https://registry.npmjs.org/next/-/next-14.0.3.tgz", "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz",
"integrity": "sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==", "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==",
"dependencies": { "dependencies": {
"@next/env": "14.0.3", "@next/env": "14.0.4",
"@swc/helpers": "0.5.2", "@swc/helpers": "0.5.2",
"busboy": "1.6.0", "busboy": "1.6.0",
"caniuse-lite": "^1.0.30001406", "caniuse-lite": "^1.0.30001406",
"graceful-fs": "^4.2.11",
"postcss": "8.4.31", "postcss": "8.4.31",
"styled-jsx": "5.1.1", "styled-jsx": "5.1.1",
"watchpack": "2.4.0" "watchpack": "2.4.0"
@ -7136,15 +7137,15 @@
"node": ">=18.17.0" "node": ">=18.17.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@next/swc-darwin-arm64": "14.0.3", "@next/swc-darwin-arm64": "14.0.4",
"@next/swc-darwin-x64": "14.0.3", "@next/swc-darwin-x64": "14.0.4",
"@next/swc-linux-arm64-gnu": "14.0.3", "@next/swc-linux-arm64-gnu": "14.0.4",
"@next/swc-linux-arm64-musl": "14.0.3", "@next/swc-linux-arm64-musl": "14.0.4",
"@next/swc-linux-x64-gnu": "14.0.3", "@next/swc-linux-x64-gnu": "14.0.4",
"@next/swc-linux-x64-musl": "14.0.3", "@next/swc-linux-x64-musl": "14.0.4",
"@next/swc-win32-arm64-msvc": "14.0.3", "@next/swc-win32-arm64-msvc": "14.0.4",
"@next/swc-win32-ia32-msvc": "14.0.3", "@next/swc-win32-ia32-msvc": "14.0.4",
"@next/swc-win32-x64-msvc": "14.0.3" "@next/swc-win32-x64-msvc": "14.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",

View file

@ -29,7 +29,7 @@
"framer-motion": "^10.16.1", "framer-motion": "^10.16.1",
"lowlight": "^3.0.0", "lowlight": "^3.0.0",
"lucide-react": "^0.268.0", "lucide-react": "^0.268.0",
"next": "^14.0.3", "next": "14.0.4",
"re-resizable": "^6.9.9", "re-resizable": "^6.9.9",
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",

View file

@ -40,7 +40,7 @@ export async function createExternalVideoActivity(data: any, activity: any, chap
data.chapter_id = chapter_id; data.chapter_id = chapter_id;
data.activity_id = activity.id; data.activity_id = activity.id;
const result = await fetch(`${getAPIUrl()}activities/external_video?coursechapter_id=${chapter_id}`, RequestBody("POST", data, null)); const result = await fetch(`${getAPIUrl()}activities/external_video`, RequestBody("POST", data, null));
const res = await result.json(); const res = await result.json();
return res; return res;
} }

View file

@ -1,3 +1,4 @@
import { OrderPayload } from "@components/Dashboard/EditCourseStructure/EditCourseStructure";
import { getAPIUrl } from "@services/config/config"; import { getAPIUrl } from "@services/config/config";
import { RequestBody, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests"; import { RequestBody, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests";
@ -25,6 +26,12 @@ export async function updateChapter(coursechapter_id: any, data: any) {
return res; return res;
} }
export async function updateCourseOrderStructure(course_uuid: any, data: OrderPayload) {
const result: any = await fetch(`${getAPIUrl()}chapters/course/${course_uuid}/order`, RequestBody("PUT", data, null));
const res = await errorHandling(result);
return res;
}
export async function createChapter(data: any) { export async function createChapter(data: any) {
const result: any = await fetch(`${getAPIUrl()}chapters/`, RequestBody("POST", data, null)); const result: any = await fetch(`${getAPIUrl()}chapters/`, RequestBody("POST", data, null));
const res = await errorHandling(result); const res = await errorHandling(result);

View file

@ -5,11 +5,10 @@
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
@ -29,7 +28,7 @@
"@editor/*": ["components/Objects/Editor/*"] "@editor/*": ["components/Objects/Editor/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx","**/**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

77
pnpm-lock.yaml generated
View file

@ -40,7 +40,7 @@ importers:
version: 1.0.7(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0)
'@sentry/nextjs': '@sentry/nextjs':
specifier: ^7.47.0 specifier: ^7.47.0
version: 7.74.0(next@13.5.4)(react@18.2.0) version: 7.74.0(next@14.0.4)(react@18.2.0)
'@stitches/react': '@stitches/react':
specifier: ^1.2.8 specifier: ^1.2.8
version: 1.2.8(react@18.2.0) version: 1.2.8(react@18.2.0)
@ -81,8 +81,8 @@ importers:
specifier: ^0.268.0 specifier: ^0.268.0
version: 0.268.0(react@18.2.0) version: 0.268.0(react@18.2.0)
next: next:
specifier: ^13.5.4 specifier: 14.0.4
version: 13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) version: 14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)
re-resizable: re-resizable:
specifier: ^6.9.9 specifier: ^6.9.9
version: 6.9.11(react-dom@18.2.0)(react@18.2.0) version: 6.9.11(react-dom@18.2.0)(react@18.2.0)
@ -1647,8 +1647,8 @@ packages:
'@jridgewell/resolve-uri': 3.1.1 '@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
/@next/env@13.5.4: /@next/env@14.0.4:
resolution: {integrity: sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ==} resolution: {integrity: sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==}
dev: false dev: false
/@next/eslint-plugin-next@13.5.4: /@next/eslint-plugin-next@13.5.4:
@ -1657,8 +1657,8 @@ packages:
glob: 7.1.7 glob: 7.1.7
dev: true dev: true
/@next/swc-darwin-arm64@13.5.4: /@next/swc-darwin-arm64@14.0.4:
resolution: {integrity: sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==} resolution: {integrity: sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -1666,8 +1666,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64@13.5.4: /@next/swc-darwin-x64@14.0.4:
resolution: {integrity: sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==} resolution: {integrity: sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -1675,8 +1675,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu@13.5.4: /@next/swc-linux-arm64-gnu@14.0.4:
resolution: {integrity: sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==} resolution: {integrity: sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1684,8 +1684,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl@13.5.4: /@next/swc-linux-arm64-musl@14.0.4:
resolution: {integrity: sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==} resolution: {integrity: sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1693,8 +1693,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu@13.5.4: /@next/swc-linux-x64-gnu@14.0.4:
resolution: {integrity: sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==} resolution: {integrity: sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1702,8 +1702,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl@13.5.4: /@next/swc-linux-x64-musl@14.0.4:
resolution: {integrity: sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==} resolution: {integrity: sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1711,8 +1711,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc@13.5.4: /@next/swc-win32-arm64-msvc@14.0.4:
resolution: {integrity: sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==} resolution: {integrity: sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -1720,8 +1720,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc@13.5.4: /@next/swc-win32-ia32-msvc@14.0.4:
resolution: {integrity: sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==} resolution: {integrity: sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -1729,8 +1729,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc@13.5.4: /@next/swc-win32-x64-msvc@14.0.4:
resolution: {integrity: sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==} resolution: {integrity: sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -2442,7 +2442,7 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/@sentry/nextjs@7.74.0(next@13.5.4)(react@18.2.0): /@sentry/nextjs@7.74.0(next@14.0.4)(react@18.2.0):
resolution: {integrity: sha512-ZxhlBKRV8To3NgNblJ+8RL1urDIzsdV9+8k9GZqNG5BFxLN2tJvbgHaxI7iV4E4qYpJAae379dWpvCzGWUXWkw==} resolution: {integrity: sha512-ZxhlBKRV8To3NgNblJ+8RL1urDIzsdV9+8k9GZqNG5BFxLN2tJvbgHaxI7iV4E4qYpJAae379dWpvCzGWUXWkw==}
engines: {node: '>=8'} engines: {node: '>=8'}
peerDependencies: peerDependencies:
@ -2463,7 +2463,7 @@ packages:
'@sentry/vercel-edge': 7.74.0 '@sentry/vercel-edge': 7.74.0
'@sentry/webpack-plugin': 1.20.0 '@sentry/webpack-plugin': 1.20.0
chalk: 3.0.0 chalk: 3.0.0
next: 13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) next: 14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
rollup: 2.78.0 rollup: 2.78.0
stacktrace-parser: 0.1.10 stacktrace-parser: 0.1.10
@ -4901,9 +4901,9 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true dev: true
/next@13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0): /next@14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==} resolution: {integrity: sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==}
engines: {node: '>=16.14.0'} engines: {node: '>=18.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.1.0 '@opentelemetry/api': ^1.1.0
@ -4916,25 +4916,26 @@ packages:
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 13.5.4 '@next/env': 14.0.4
'@swc/helpers': 0.5.2 '@swc/helpers': 0.5.2
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001547 caniuse-lite: 1.0.30001547
graceful-fs: 4.2.11
postcss: 8.4.31 postcss: 8.4.31
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
styled-jsx: 5.1.1(@babel/core@7.23.2)(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.23.2)(react@18.2.0)
watchpack: 2.4.0 watchpack: 2.4.0
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 13.5.4 '@next/swc-darwin-arm64': 14.0.4
'@next/swc-darwin-x64': 13.5.4 '@next/swc-darwin-x64': 14.0.4
'@next/swc-linux-arm64-gnu': 13.5.4 '@next/swc-linux-arm64-gnu': 14.0.4
'@next/swc-linux-arm64-musl': 13.5.4 '@next/swc-linux-arm64-musl': 14.0.4
'@next/swc-linux-x64-gnu': 13.5.4 '@next/swc-linux-x64-gnu': 14.0.4
'@next/swc-linux-x64-musl': 13.5.4 '@next/swc-linux-x64-musl': 14.0.4
'@next/swc-win32-arm64-msvc': 13.5.4 '@next/swc-win32-arm64-msvc': 14.0.4
'@next/swc-win32-ia32-msvc': 13.5.4 '@next/swc-win32-ia32-msvc': 14.0.4
'@next/swc-win32-x64-msvc': 13.5.4 '@next/swc-win32-x64-msvc': 14.0.4
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros