From 8d3508590876bedb3a82c0af1ffdb54b5cd6bd6d Mon Sep 17 00:00:00 2001 From: swve Date: Wed, 13 Dec 2023 09:56:11 +0100 Subject: [PATCH] feat: init new edit course page --- apps/api/src/db/activities.py | 9 -- apps/api/src/db/courses.py | 2 +- apps/api/src/routers/courses/chapters.py | 1 - .../services/courses/activities/activities.py | 4 +- .../src/services/courses/activities/video.py | 2 +- apps/api/src/services/courses/chapters.py | 42 +++++-- .../course/[courseuuid]/[subpage]/page.tsx | 59 ++++++++++ .../app/orgs/[orgslug]/dash/courses/page.tsx | 22 ++++ apps/web/app/orgs/[orgslug]/dash/layout.tsx | 20 ++++ apps/web/app/orgs/[orgslug]/dash/page.tsx | 9 ++ apps/web/app/orgs/[orgslug]/layout.tsx | 1 - apps/web/app/orgs/[orgslug]/template.tsx | 25 ---- .../components/Dashboard/CourseContext.tsx | 62 ++++++++++ .../Buttons/NewActivityButton.tsx | 93 +++++++++++++++ .../DraggableElements/ActivityElement.tsx | 96 +++++++++++++++ .../DraggableElements/ChapterElement.tsx | 104 ++++++++++++++++ .../EditCourseStructure.tsx | 111 ++++++++++++++++++ .../components/Dashboard/UI/BreadCrumbs.tsx | 30 +++++ .../Dashboard/UI/CourseOverviewTop.tsx | 31 +++++ apps/web/components/Dashboard/UI/LeftMenu.tsx | 22 ++++ .../web/components/Dashboard/UI/SaveState.tsx | 100 ++++++++++++++++ apps/web/next.config.js | 21 +--- apps/web/package-lock.json | 89 +++++++------- apps/web/package.json | 2 +- apps/web/services/courses/activities.ts | 2 +- apps/web/services/courses/chapters.ts | 7 ++ apps/web/tsconfig.json | 7 +- pnpm-lock.yaml | 77 ++++++------ 28 files changed, 891 insertions(+), 159 deletions(-) create mode 100644 apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx create mode 100644 apps/web/app/orgs/[orgslug]/dash/courses/page.tsx create mode 100644 apps/web/app/orgs/[orgslug]/dash/layout.tsx create mode 100644 apps/web/app/orgs/[orgslug]/dash/page.tsx delete mode 100644 apps/web/app/orgs/[orgslug]/template.tsx create mode 100644 apps/web/components/Dashboard/CourseContext.tsx create mode 100644 apps/web/components/Dashboard/EditCourseStructure/Buttons/NewActivityButton.tsx create mode 100644 apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ActivityElement.tsx create mode 100644 apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ChapterElement.tsx create mode 100644 apps/web/components/Dashboard/EditCourseStructure/EditCourseStructure.tsx create mode 100644 apps/web/components/Dashboard/UI/BreadCrumbs.tsx create mode 100644 apps/web/components/Dashboard/UI/CourseOverviewTop.tsx create mode 100644 apps/web/components/Dashboard/UI/LeftMenu.tsx create mode 100644 apps/web/components/Dashboard/UI/SaveState.tsx diff --git a/apps/api/src/db/activities.py b/apps/api/src/db/activities.py index ada0d56d..95b6ecef 100644 --- a/apps/api/src/db/activities.py +++ b/apps/api/src/db/activities.py @@ -34,12 +34,6 @@ class ActivityBase(SQLModel): content: dict = Field(default={}, sa_column=Column(JSON)) published_version: int version: int - course_id: int = Field( - default=None, - sa_column=Column( - BigInteger, ForeignKey("course.id", ondelete="CASCADE") - ), - ) class Activity(ActivityBase, table=True): @@ -57,9 +51,6 @@ class Activity(ActivityBase, table=True): class ActivityCreate(ActivityBase): - course_id: int = Field( - sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE")) - ) chapter_id: int pass diff --git a/apps/api/src/db/courses.py b/apps/api/src/db/courses.py index 686538a2..a6a4199d 100644 --- a/apps/api/src/db/courses.py +++ b/apps/api/src/db/courses.py @@ -1,6 +1,6 @@ from typing import List, Optional 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.chapters import ChapterRead diff --git a/apps/api/src/routers/courses/chapters.py b/apps/api/src/routers/courses/chapters.py index be87247d..f6cf42db 100644 --- a/apps/api/src/routers/courses/chapters.py +++ b/apps/api/src/routers/courses/chapters.py @@ -6,7 +6,6 @@ from src.db.chapters import ( ChapterRead, ChapterUpdate, ChapterUpdateOrder, - DepreceatedChaptersRead, ) from src.services.courses.chapters import ( DEPRECEATED_get_course_chapters, diff --git a/apps/api/src/services/courses/activities/activities.py b/apps/api/src/services/courses/activities/activities.py index 0912937d..ba5ff82e 100644 --- a/apps/api/src/services/courses/activities/activities.py +++ b/apps/api/src/services/courses/activities/activities.py @@ -5,7 +5,6 @@ from src.security.rbac.rbac import ( authorization_verify_based_on_roles_and_authorship, authorization_verify_if_user_is_anon, ) -from src.db.organizations import Organization from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate from src.db.chapter_activities import ChapterActivity from src.db.users import AnonymousUser, PublicUser @@ -44,6 +43,7 @@ async def create_activity( activity.creation_date = str(datetime.now()) activity.update_date = str(datetime.now()) activity.org_id = chapter.org_id + activity.course_id = chapter.course_id # Insert Activity in DB db_session.add(activity) @@ -65,7 +65,7 @@ async def create_activity( activity_chapter = ChapterActivity( chapter_id=activity_object.chapter_id, 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, creation_date=str(datetime.now()), update_date=str(datetime.now()), diff --git a/apps/api/src/services/courses/activities/video.py b/apps/api/src/services/courses/activities/video.py index 8e25b9de..d73a311b 100644 --- a/apps/api/src/services/courses/activities/video.py +++ b/apps/api/src/services/courses/activities/video.py @@ -194,7 +194,7 @@ async def create_external_video_activity( # update chapter chapter_activity_object = ChapterActivity( - chapter_id=coursechapter.id, # type: ignore + chapter_id=coursechapter.chapter_id, # type: ignore activity_id=activity.id, # type: ignore course_id=coursechapter.course_id, org_id=coursechapter.org_id, diff --git a/apps/api/src/services/courses/chapters.py b/apps/api/src/services/courses/chapters.py index f610d6fe..1e166d74 100644 --- a/apps/api/src/services/courses/chapters.py +++ b/apps/api/src/services/courses/chapters.py @@ -16,7 +16,6 @@ from src.db.chapters import ( ChapterRead, ChapterUpdate, ChapterUpdateOrder, - DepreceatedChaptersRead, ) from src.services.courses.courses import Course 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 # activities - chapter_activityIdsGlobal = [] # chapters chapters = {} @@ -437,7 +435,7 @@ async def reorder_chapters_and_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 = ( select(ChapterActivity) .where( @@ -448,20 +446,36 @@ async def reorder_chapters_and_activities( ) chapter_activities = db_session.exec(statement).all() - activity_ids_to_keep = [ - activity_order.activity_id - for chapter_order in chapters_order.chapter_order_by_ids - for activity_order in chapter_order.activities_order_by_ids - ] - + activity_ids_to_delete = [] for chapter_activity in chapter_activities: - if chapter_activity.activity_id not in activity_ids_to_keep: - db_session.delete(chapter_activity) - db_session.commit() + if ( + chapter_activity.chapter_id not in chapter_ids_to_keep + 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 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 = ( select(ChapterActivity) .where( @@ -488,6 +502,8 @@ async def reorder_chapters_and_activities( db_session.add(chapter_activity) db_session.commit() + chapter_activity_map[activity_order.activity_id] = chapter_order.chapter_id + # Update order of activities for chapter_order in chapters_order.chapter_order_by_ids: for activity_order in chapter_order.activities_order_by_ids: diff --git a/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx new file mode 100644 index 00000000..d3a811f3 --- /dev/null +++ b/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx @@ -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 ( +
+ +
+ +
+ +
General
+ + +
Structure
+ +
+
+ + +
+ {params.subpage == 'structure' ? : ''} + +
+
+ ) +} + + + + + +export default CourseOverviewPage \ No newline at end of file diff --git a/apps/web/app/orgs/[orgslug]/dash/courses/page.tsx b/apps/web/app/orgs/[orgslug]/dash/courses/page.tsx new file mode 100644 index 00000000..8f422f1e --- /dev/null +++ b/apps/web/app/orgs/[orgslug]/dash/courses/page.tsx @@ -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 ( +
+
+
+ +
+
Courses
+
+
+ +
+
+ ) +} + +export default CoursesHome \ No newline at end of file diff --git a/apps/web/app/orgs/[orgslug]/dash/layout.tsx b/apps/web/app/orgs/[orgslug]/dash/layout.tsx new file mode 100644 index 00000000..b651d773 --- /dev/null +++ b/apps/web/app/orgs/[orgslug]/dash/layout.tsx @@ -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 ( + <> + +
+ +
+ {children} +
+
+
+ + ) +} + +export default DashboardLayout \ No newline at end of file diff --git a/apps/web/app/orgs/[orgslug]/dash/page.tsx b/apps/web/app/orgs/[orgslug]/dash/page.tsx new file mode 100644 index 00000000..ce5f585b --- /dev/null +++ b/apps/web/app/orgs/[orgslug]/dash/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +function DashboardHome() { + return ( +
DashboardHome
+ ) +} + +export default DashboardHome \ No newline at end of file diff --git a/apps/web/app/orgs/[orgslug]/layout.tsx b/apps/web/app/orgs/[orgslug]/layout.tsx index d9d693ce..548477a0 100644 --- a/apps/web/app/orgs/[orgslug]/layout.tsx +++ b/apps/web/app/orgs/[orgslug]/layout.tsx @@ -1,4 +1,3 @@ - import "@styles/globals.css"; export default function RootLayout({ children, params }: { children: React.ReactNode , params:any}) { diff --git a/apps/web/app/orgs/[orgslug]/template.tsx b/apps/web/app/orgs/[orgslug]/template.tsx deleted file mode 100644 index 6fca4b95..00000000 --- a/apps/web/app/orgs/[orgslug]/template.tsx +++ /dev/null @@ -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 ( -
- - {children} - -
- ); -} diff --git a/apps/web/components/Dashboard/CourseContext.tsx b/apps/web/components/Dashboard/CourseContext.tsx new file mode 100644 index 00000000..a37d2a5e --- /dev/null +++ b/apps/web/components/Dashboard/CourseContext.tsx @@ -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
Loading...
+ + + return ( + + + {children} + + + ) +} + +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}`); + } +} \ No newline at end of file diff --git a/apps/web/components/Dashboard/EditCourseStructure/Buttons/NewActivityButton.tsx b/apps/web/components/Dashboard/EditCourseStructure/Buttons/NewActivityButton.tsx new file mode 100644 index 00000000..74974cc5 --- /dev/null +++ b/apps/web/components/Dashboard/EditCourseStructure/Buttons/NewActivityButton.tsx @@ -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 ( +
+ } + dialogTitle="Create Activity" + dialogDescription="Choose between types of activities to add to the course" + + /> +
{ + 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"> + +
Add Activity +
+
+
+ ) +} + +export default NewActivityButton \ No newline at end of file diff --git a/apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ActivityElement.tsx b/apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ActivityElement.tsx new file mode 100644 index 00000000..a31fe3ac --- /dev/null +++ b/apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ActivityElement.tsx @@ -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 ( + + {(provided, snapshot) => ( +
+ + {/* Activity Type Icon */} +
+ {props.activity.activity_type === "video" && + <> +
+
+ } +
+ + + {/* Centered Activity Name */} +
+ {(

{props.activity.name}

)} + +
+ {/* Edit and View Button */} +
+ {props.activity.activity_type === "TYPE_DYNAMIC" && <> + +
Edit
+ + } + + + +
+ {/* Delete Button */} +
+ + + +
} + functionToExecute={() => deleteActivityUI()} + status='warning' + >
+ + )} +
+ ) +} + +export default ActivityElement \ No newline at end of file diff --git a/apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ChapterElement.tsx b/apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ChapterElement.tsx new file mode 100644 index 00000000..94ad67ad --- /dev/null +++ b/apps/web/components/Dashboard/EditCourseStructure/DraggableElements/ChapterElement.tsx @@ -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 ( + + {(provided, snapshot) => ( +
+
+
+
+ +
+
+

{props.chapter.name}

+ +
+
+ + + +

Delete Chapter

+
} + functionToExecute={() => deleteChapterUI()} + status='warning' + > +
+ + {(provided) => ( +
+
+ {activities.map((activity: any, index: any) => { + return ( +
+ +
+ ) + })} + {provided.placeholder} +
+
+ + )} +
+ +
+
+
+ + )} +
+ ) +} + +export default ChapterElement \ No newline at end of file diff --git a/apps/web/components/Dashboard/EditCourseStructure/EditCourseStructure.tsx b/apps/web/components/Dashboard/EditCourseStructure/EditCourseStructure.tsx new file mode 100644 index 00000000..658acb3b --- /dev/null +++ b/apps/web/components/Dashboard/EditCourseStructure/EditCourseStructure.tsx @@ -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(); + 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 + + return ( +
+ {winReady ? + + + {(provided) => ( +
+ {course_structure.chapters && course_structure.chapters.map((chapter: any, index: any) => { + return ( + + + ) + })} + {provided.placeholder} +
+ )} +
+
+ + : <>} +
+ + + ) +} + +export default EditCourseStructure \ No newline at end of file diff --git a/apps/web/components/Dashboard/UI/BreadCrumbs.tsx b/apps/web/components/Dashboard/UI/BreadCrumbs.tsx new file mode 100644 index 00000000..2df5b8f9 --- /dev/null +++ b/apps/web/components/Dashboard/UI/BreadCrumbs.tsx @@ -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 ( +
+
+
+
+ {props.type == 'courses' ?
Courses
: ''} + {props.type == 'users' ?
Users
: ''} +
+ {props.last_breadcrumb ? : ''} +
{props.last_breadcrumb}
+
+ +
+ ) +} + +export default BreadCrumbs \ No newline at end of file diff --git a/apps/web/components/Dashboard/UI/CourseOverviewTop.tsx b/apps/web/components/Dashboard/UI/CourseOverviewTop.tsx new file mode 100644 index 00000000..58cb5579 --- /dev/null +++ b/apps/web/components/Dashboard/UI/CourseOverviewTop.tsx @@ -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 ( + <> + +
+
+
+
+
Course
+
{course.courseStructure.name}
+
+
+
+ +
+
+ + ) + +} \ No newline at end of file diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/LeftMenu.tsx new file mode 100644 index 00000000..a2b501a7 --- /dev/null +++ b/apps/web/components/Dashboard/UI/LeftMenu.tsx @@ -0,0 +1,22 @@ + +import { Book } from 'lucide-react' +import Link from 'next/link' +import React from 'react' + +function LeftMenu() { + return ( +
+ +
+ +
+ + +
+ ) +} + +export default LeftMenu + diff --git a/apps/web/components/Dashboard/UI/SaveState.tsx b/apps/web/components/Dashboard/UI/SaveState.tsx new file mode 100644 index 00000000..603b08e2 --- /dev/null +++ b/apps/web/components/Dashboard/UI/SaveState.tsx @@ -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 ( +
+ {saved ? <> :
+ +
+ Unsaved changes +
+ +
} +
+ + {saved ? : } + {saved ?
Saved
:
Save
} +
+
+ + ) +} + +export default SaveState \ No newline at end of file diff --git a/apps/web/next.config.js b/apps/web/next.config.js index e1035183..efc12988 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -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} */ const nextConfig = { - reactStrictMode: false, - compiler: { - styledComponents: true, - }, -}; + reactStrictMode: false, +} -module.exports = nextConfig; - -module.exports = withSentryConfig( - module.exports, - { silent: true }, - { hideSourcemaps: true }, -); +module.exports = nextConfig diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index 9168a0d6..89c843f0 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -28,7 +28,7 @@ "framer-motion": "^10.16.1", "lowlight": "^3.0.0", "lucide-react": "^0.268.0", - "next": "^14.0.3", + "next": "^14.0.4", "re-resizable": "^6.9.9", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", @@ -2266,9 +2266,9 @@ } }, "node_modules/@next/env": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz", - "integrity": "sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==" + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", + "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.5.4", @@ -2300,9 +2300,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz", - "integrity": "sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", + "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", "cpu": [ "arm64" ], @@ -2315,9 +2315,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz", - "integrity": "sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", + "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==", "cpu": [ "x64" ], @@ -2330,9 +2330,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz", - "integrity": "sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", + "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", "cpu": [ "arm64" ], @@ -2345,9 +2345,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz", - "integrity": "sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", + "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", "cpu": [ "arm64" ], @@ -2360,9 +2360,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz", - "integrity": "sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", + "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", "cpu": [ "x64" ], @@ -2375,9 +2375,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz", - "integrity": "sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", + "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", "cpu": [ "x64" ], @@ -2390,9 +2390,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz", - "integrity": "sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", + "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", "cpu": [ "arm64" ], @@ -2405,9 +2405,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz", - "integrity": "sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", + "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", "cpu": [ "ia32" ], @@ -2420,9 +2420,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz", - "integrity": "sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", + "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", "cpu": [ "x64" ], @@ -7117,14 +7117,15 @@ "dev": true }, "node_modules/next": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.3.tgz", - "integrity": "sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", + "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==", "dependencies": { - "@next/env": "14.0.3", + "@next/env": "14.0.4", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", + "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1", "watchpack": "2.4.0" @@ -7136,15 +7137,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.3", - "@next/swc-darwin-x64": "14.0.3", - "@next/swc-linux-arm64-gnu": "14.0.3", - "@next/swc-linux-arm64-musl": "14.0.3", - "@next/swc-linux-x64-gnu": "14.0.3", - "@next/swc-linux-x64-musl": "14.0.3", - "@next/swc-win32-arm64-msvc": "14.0.3", - "@next/swc-win32-ia32-msvc": "14.0.3", - "@next/swc-win32-x64-msvc": "14.0.3" + "@next/swc-darwin-arm64": "14.0.4", + "@next/swc-darwin-x64": "14.0.4", + "@next/swc-linux-arm64-gnu": "14.0.4", + "@next/swc-linux-arm64-musl": "14.0.4", + "@next/swc-linux-x64-gnu": "14.0.4", + "@next/swc-linux-x64-musl": "14.0.4", + "@next/swc-win32-arm64-msvc": "14.0.4", + "@next/swc-win32-ia32-msvc": "14.0.4", + "@next/swc-win32-x64-msvc": "14.0.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", diff --git a/apps/web/package.json b/apps/web/package.json index 4844a0fe..5e2668af 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -29,7 +29,7 @@ "framer-motion": "^10.16.1", "lowlight": "^3.0.0", "lucide-react": "^0.268.0", - "next": "^14.0.3", + "next": "14.0.4", "re-resizable": "^6.9.9", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", diff --git a/apps/web/services/courses/activities.ts b/apps/web/services/courses/activities.ts index 573ad7f3..bb07488c 100644 --- a/apps/web/services/courses/activities.ts +++ b/apps/web/services/courses/activities.ts @@ -40,7 +40,7 @@ export async function createExternalVideoActivity(data: any, activity: any, chap data.chapter_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 result = await fetch(`${getAPIUrl()}activities/external_video`, RequestBody("POST", data, null)); const res = await result.json(); return res; } diff --git a/apps/web/services/courses/chapters.ts b/apps/web/services/courses/chapters.ts index 71e70678..26c54c81 100644 --- a/apps/web/services/courses/chapters.ts +++ b/apps/web/services/courses/chapters.ts @@ -1,3 +1,4 @@ +import { OrderPayload } from "@components/Dashboard/EditCourseStructure/EditCourseStructure"; import { getAPIUrl } from "@services/config/config"; import { RequestBody, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests"; @@ -25,6 +26,12 @@ export async function updateChapter(coursechapter_id: any, data: any) { 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) { const result: any = await fetch(`${getAPIUrl()}chapters/`, RequestBody("POST", data, null)); const res = await errorHandling(result); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 0bc0b60d..6546f796 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -5,11 +5,10 @@ "allowJs": true, "skipLibCheck": true, "strict": true, - "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", @@ -29,7 +28,7 @@ "@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"] -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41a46e2f..d1668b45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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) '@sentry/nextjs': 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': specifier: ^1.2.8 version: 1.2.8(react@18.2.0) @@ -81,8 +81,8 @@ importers: specifier: ^0.268.0 version: 0.268.0(react@18.2.0) next: - specifier: ^13.5.4 - version: 13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) + specifier: 14.0.4 + version: 14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) re-resizable: specifier: ^6.9.9 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/sourcemap-codec': 1.4.15 - /@next/env@13.5.4: - resolution: {integrity: sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ==} + /@next/env@14.0.4: + resolution: {integrity: sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==} dev: false /@next/eslint-plugin-next@13.5.4: @@ -1657,8 +1657,8 @@ packages: glob: 7.1.7 dev: true - /@next/swc-darwin-arm64@13.5.4: - resolution: {integrity: sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==} + /@next/swc-darwin-arm64@14.0.4: + resolution: {integrity: sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1666,8 +1666,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.5.4: - resolution: {integrity: sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==} + /@next/swc-darwin-x64@14.0.4: + resolution: {integrity: sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1675,8 +1675,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.5.4: - resolution: {integrity: sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==} + /@next/swc-linux-arm64-gnu@14.0.4: + resolution: {integrity: sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1684,8 +1684,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.5.4: - resolution: {integrity: sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==} + /@next/swc-linux-arm64-musl@14.0.4: + resolution: {integrity: sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1693,8 +1693,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.5.4: - resolution: {integrity: sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==} + /@next/swc-linux-x64-gnu@14.0.4: + resolution: {integrity: sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1702,8 +1702,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.5.4: - resolution: {integrity: sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==} + /@next/swc-linux-x64-musl@14.0.4: + resolution: {integrity: sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1711,8 +1711,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.5.4: - resolution: {integrity: sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==} + /@next/swc-win32-arm64-msvc@14.0.4: + resolution: {integrity: sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1720,8 +1720,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.5.4: - resolution: {integrity: sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==} + /@next/swc-win32-ia32-msvc@14.0.4: + resolution: {integrity: sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1729,8 +1729,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.5.4: - resolution: {integrity: sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==} + /@next/swc-win32-x64-msvc@14.0.4: + resolution: {integrity: sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2442,7 +2442,7 @@ packages: tslib: 2.6.2 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==} engines: {node: '>=8'} peerDependencies: @@ -2463,7 +2463,7 @@ packages: '@sentry/vercel-edge': 7.74.0 '@sentry/webpack-plugin': 1.20.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 rollup: 2.78.0 stacktrace-parser: 0.1.10 @@ -4901,9 +4901,9 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /next@13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==} - engines: {node: '>=16.14.0'} + /next@14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==} + engines: {node: '>=18.17.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -4916,25 +4916,26 @@ packages: sass: optional: true dependencies: - '@next/env': 13.5.4 + '@next/env': 14.0.4 '@swc/helpers': 0.5.2 busboy: 1.6.0 caniuse-lite: 1.0.30001547 + graceful-fs: 4.2.11 postcss: 8.4.31 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) watchpack: 2.4.0 optionalDependencies: - '@next/swc-darwin-arm64': 13.5.4 - '@next/swc-darwin-x64': 13.5.4 - '@next/swc-linux-arm64-gnu': 13.5.4 - '@next/swc-linux-arm64-musl': 13.5.4 - '@next/swc-linux-x64-gnu': 13.5.4 - '@next/swc-linux-x64-musl': 13.5.4 - '@next/swc-win32-arm64-msvc': 13.5.4 - '@next/swc-win32-ia32-msvc': 13.5.4 - '@next/swc-win32-x64-msvc': 13.5.4 + '@next/swc-darwin-arm64': 14.0.4 + '@next/swc-darwin-x64': 14.0.4 + '@next/swc-linux-arm64-gnu': 14.0.4 + '@next/swc-linux-arm64-musl': 14.0.4 + '@next/swc-linux-x64-gnu': 14.0.4 + '@next/swc-linux-x64-musl': 14.0.4 + '@next/swc-win32-arm64-msvc': 14.0.4 + '@next/swc-win32-ia32-msvc': 14.0.4 + '@next/swc-win32-x64-msvc': 14.0.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros