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))
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

View file

@ -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

View file

@ -6,7 +6,6 @@ from src.db.chapters import (
ChapterRead,
ChapterUpdate,
ChapterUpdateOrder,
DepreceatedChaptersRead,
)
from src.services.courses.chapters import (
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_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()),

View file

@ -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,

View file

@ -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:
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)
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 exists, create them
# 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:

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";
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} */
const nextConfig = {
reactStrictMode: false,
compiler: {
styledComponents: true,
},
};
}
module.exports = nextConfig;
module.exports = withSentryConfig(
module.exports,
{ silent: true },
{ hideSourcemaps: true },
);
module.exports = nextConfig

View file

@ -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",

View file

@ -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",

View file

@ -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;
}

View file

@ -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);

View file

@ -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"]
}

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)
'@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