mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: courses dashboard
This commit is contained in:
parent
8d35085908
commit
c39d9d5340
22 changed files with 611 additions and 67 deletions
109
apps/web/app/orgs/[orgslug]/dash/courses/client.tsx
Normal file
109
apps/web/app/orgs/[orgslug]/dash/courses/client.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
'use client';
|
||||
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
|
||||
import CreateCourseModal from '@components/Objects/Modals/Course/Create/CreateCourse';
|
||||
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail';
|
||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
||||
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import Link from 'next/link'
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import React from 'react'
|
||||
|
||||
type CourseProps = {
|
||||
orgslug: string;
|
||||
courses: any;
|
||||
org_id: string;
|
||||
}
|
||||
|
||||
function CoursesHome(params: CourseProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const isCreatingCourse = searchParams.get('new') ? true : false;
|
||||
const [newCourseModal, setNewCourseModal] = React.useState(isCreatingCourse);
|
||||
const orgslug = params.orgslug;
|
||||
const courses = params.courses;
|
||||
|
||||
|
||||
async function closeNewCourseModal() {
|
||||
setNewCourseModal(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full w-full bg-[#f8f8f8]'>
|
||||
<div >
|
||||
<div className='pl-10 mr-10 tracking-tighter'>
|
||||
<BreadCrumbs type='courses' />
|
||||
|
||||
<div className='w-100 flex justify-between'>
|
||||
<div className='flex font-bold text-4xl'>Courses</div>
|
||||
<AuthenticatedClientElement checkMethod='roles'
|
||||
action='create'
|
||||
ressourceType='course'
|
||||
orgId={params.org_id}>
|
||||
<Modal
|
||||
isDialogOpen={newCourseModal}
|
||||
onOpenChange={setNewCourseModal}
|
||||
minHeight="md"
|
||||
dialogContent={<CreateCourseModal
|
||||
closeModal={closeNewCourseModal}
|
||||
orgslug={orgslug}
|
||||
></CreateCourseModal>}
|
||||
dialogTitle="Create Course"
|
||||
dialogDescription="Create a new course"
|
||||
dialogTrigger={
|
||||
|
||||
<button>
|
||||
<NewCourseButton />
|
||||
</button>}
|
||||
/>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap mx-8 mt-7">
|
||||
{courses.map((course: any) => (
|
||||
<div className="px-3" key={course.course_uuid}>
|
||||
<CourseThumbnail course={course} orgslug={orgslug} />
|
||||
</div>
|
||||
))}
|
||||
{courses.length == 0 &&
|
||||
<div className="flex mx-auto h-[400px]">
|
||||
<div className="flex flex-col justify-center text-center items-center space-y-5">
|
||||
<div className='mx-auto'>
|
||||
<svg width="120" height="120" viewBox="0 0 295 295" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.51" x="10" y="10" width="275" height="275" rx="75" stroke="#4B5564" strokeOpacity="0.15" strokeWidth="20" />
|
||||
<path d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z" fill="#4B5564" fillOpacity="0.08" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="space-y-0">
|
||||
<h1 className="text-3xl font-bold text-gray-600">No courses yet</h1>
|
||||
<p className="text-lg text-gray-400">Create a course to add content</p>
|
||||
</div>
|
||||
<AuthenticatedClientElement
|
||||
action='create'
|
||||
ressourceType='course'
|
||||
checkMethod='roles' orgId={params.org_id}>
|
||||
<Modal
|
||||
isDialogOpen={newCourseModal}
|
||||
onOpenChange={setNewCourseModal}
|
||||
minHeight="md"
|
||||
dialogContent={<CreateCourseModal
|
||||
closeModal={closeNewCourseModal}
|
||||
orgslug={orgslug}
|
||||
></CreateCourseModal>}
|
||||
dialogTitle="Create Course"
|
||||
dialogDescription="Create a new course"
|
||||
dialogTrigger={
|
||||
<button>
|
||||
<NewCourseButton />
|
||||
</button>}
|
||||
/>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoursesHome
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
'use client';
|
||||
import EditCourseStructure from '../../../../../../../../components/Dashboard/EditCourseStructure/EditCourseStructure'
|
||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||
import EditCourseStructure from '../../../../../../../../components/DashboardPages/EditCourseStructure/EditCourseStructure'
|
||||
import BreadCrumbs from '@components/DashboardPages/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 { CourseProvider, useCourse } from '../../../../../../../../components/DashboardPages/CourseContext';
|
||||
import SaveState from '@components/DashboardPages/UI/SaveState';
|
||||
import Link from 'next/link';
|
||||
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop';
|
||||
import { CourseOverviewTop } from '@components/DashboardPages/UI/CourseOverviewTop';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import { motion } from 'framer-motion';
|
||||
import EditCourseGeneral from '@components/DashboardPages/EditCourseGeneral/EditCourseGeneral';
|
||||
import { GalleryVertical, GalleryVerticalEnd, Info } from 'lucide-react';
|
||||
|
||||
export type CourseOverviewParams = {
|
||||
orgslug: string,
|
||||
|
|
@ -20,7 +24,6 @@ export type CourseOverviewParams = {
|
|||
|
||||
export const CourseStructureContext = createContext({}) as any;
|
||||
|
||||
|
||||
function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
|
||||
|
||||
function getEntireCourseUUID(courseuuid: string) {
|
||||
|
|
@ -30,23 +33,41 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
|
|||
|
||||
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>
|
||||
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<Info size={16} />
|
||||
<div>General</div>
|
||||
</div>
|
||||
</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 href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/content`}>
|
||||
<div className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'content' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<GalleryVerticalEnd size={16} />
|
||||
<div>Content</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='h-6'></div>
|
||||
{params.subpage == 'structure' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.10, type: "spring", stiffness: 80 }}
|
||||
>
|
||||
{params.subpage == 'content' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
|
||||
{params.subpage == 'general' ? <EditCourseGeneral orgslug={params.orgslug} /> : ''}
|
||||
</motion.div>
|
||||
</CourseProvider>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,57 @@
|
|||
'use client';
|
||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||
import Link from 'next/link'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth';
|
||||
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses';
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs';
|
||||
import { Metadata } from 'next';
|
||||
import { cookies } from 'next/headers';
|
||||
import React from 'react'
|
||||
import CoursesHome from './client';
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
};
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: MetadataProps,
|
||||
): Promise<Metadata> {
|
||||
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
|
||||
|
||||
// SEO
|
||||
return {
|
||||
title: "Courses — " + org.name,
|
||||
description: org.description,
|
||||
keywords: `${org.name}, ${org.description}, courses, learning, education, online learning, edu, online courses, ${org.name} courses`,
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
nocache: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-image-preview": "large",
|
||||
}
|
||||
},
|
||||
openGraph: {
|
||||
title: "Courses — " + org.name,
|
||||
description: org.description,
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function CoursesPage(params: any) {
|
||||
const orgslug = params.params.orgslug;
|
||||
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
|
||||
const cookieStore = cookies();
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null);
|
||||
|
||||
|
||||
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>
|
||||
<CoursesHome org_id={org.org_id} orgslug={orgslug} courses={courses} />
|
||||
)
|
||||
}
|
||||
|
||||
export default CoursesHome
|
||||
export default CoursesPage
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
|
||||
import LeftMenu from '@components/DashboardPages/UI/LeftMenu'
|
||||
import AuthProvider from '@components/Security/AuthProvider'
|
||||
import React from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
'use client';
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import React, { createContext, useContext, useEffect, useReducer } from 'react'
|
||||
|
|
@ -26,7 +27,7 @@ export function CourseProvider({ children, courseuuid }: { children: React.React
|
|||
}, [courseStructureData]);
|
||||
|
||||
|
||||
if (!courseStructureData) return <div>Loading...</div>
|
||||
if (!courseStructureData) return <PageLoading></PageLoading>
|
||||
|
||||
|
||||
return (
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import FormLayout, { FormField, FormLabelAndMessage, Input, Textarea } from '@components/StyledElements/Form/Form';
|
||||
import { useFormik } from 'formik';
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import * as Switch from '@radix-ui/react-switch';
|
||||
import * as Form from '@radix-ui/react-form';
|
||||
import React from 'react'
|
||||
import { useCourse, useCourseDispatch } from '../CourseContext';
|
||||
|
||||
|
||||
type EditCourseStructureProps = {
|
||||
orgslug: string,
|
||||
course_uuid?: string,
|
||||
}
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {};
|
||||
|
||||
if (!values.name) {
|
||||
errors.name = 'Required';
|
||||
}
|
||||
|
||||
if (values.name.length > 100) {
|
||||
errors.name = 'Must be 100 characters or less';
|
||||
}
|
||||
|
||||
|
||||
if (!values.description) {
|
||||
errors.description = 'Required';
|
||||
|
||||
}
|
||||
|
||||
if (values.description.length > 1000) {
|
||||
errors.description = 'Must be 1000 characters or less';
|
||||
}
|
||||
|
||||
|
||||
if (!values.learnings) {
|
||||
errors.learnings = 'Required';
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
|
||||
function EditCourseGeneral(props: EditCourseStructureProps) {
|
||||
const [error, setError] = React.useState('');
|
||||
const course = useCourse() as any;
|
||||
const dispatchCourse = useCourseDispatch() as any;
|
||||
|
||||
const courseStructure = course.courseStructure;
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
name: String(courseStructure.name),
|
||||
description: String(courseStructure.description),
|
||||
about: String(courseStructure.about),
|
||||
learnings: String(courseStructure.learnings),
|
||||
tags: String(courseStructure.tags),
|
||||
public: String(courseStructure.public),
|
||||
},
|
||||
validate,
|
||||
onSubmit: async values => {
|
||||
|
||||
},
|
||||
enableReinitialize: true,
|
||||
});
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
// This code will run whenever form values are updated
|
||||
if (formik.values !== formik.initialValues) {
|
||||
dispatchCourse({ type: 'setIsNotSaved' });
|
||||
const updatedCourse = {
|
||||
...courseStructure,
|
||||
name: formik.values.name,
|
||||
description: formik.values.description,
|
||||
about: formik.values.about,
|
||||
learnings: formik.values.learnings,
|
||||
tags: formik.values.tags,
|
||||
public: formik.values.public,
|
||||
}
|
||||
dispatchCourse({ type: 'setCourseStructure', payload: updatedCourse });
|
||||
}
|
||||
|
||||
}, [course, formik.values, formik.initialValues]);
|
||||
|
||||
return (
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'>
|
||||
|
||||
{course.courseStructure && (
|
||||
<div className="editcourse-form">
|
||||
{error && (
|
||||
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-4 transition-all shadow-sm">
|
||||
<AlertTriangle size={18} />
|
||||
<div className="font-bold text-sm">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
<FormLayout onSubmit={formik.handleSubmit}>
|
||||
<FormField name="name">
|
||||
<FormLabelAndMessage label='Name' message={formik.errors.name} />
|
||||
<Form.Control asChild>
|
||||
<Input style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.name} type="text" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="description">
|
||||
<FormLabelAndMessage label='Description' message={formik.errors.description} />
|
||||
<Form.Control asChild>
|
||||
<Textarea style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.description} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="about">
|
||||
<FormLabelAndMessage label='About' message={formik.errors.about} />
|
||||
<Form.Control asChild>
|
||||
<Textarea style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.about} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="learnings">
|
||||
<FormLabelAndMessage label='Learnings' message={formik.errors.learnings} />
|
||||
<Form.Control asChild>
|
||||
<Textarea style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.learnings} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="tags">
|
||||
<FormLabelAndMessage label='Tags' message={formik.errors.tags} />
|
||||
<Form.Control asChild>
|
||||
<Textarea style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.tags} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField className="flex items-center h-10" name="public">
|
||||
<div className='flex my-auto items-center'>
|
||||
<label className="text-black text-[15px] leading-none pr-[15px]" htmlFor="public-course">
|
||||
Public Course
|
||||
</label>
|
||||
<Switch.Root
|
||||
className="w-[42px] h-[25px] bg-neutral-200 rounded-full relative data-[state=checked]:bg-neutral-500 outline-none cursor-default"
|
||||
id="public-course"
|
||||
onCheckedChange={checked => formik.setFieldValue('public', checked)}
|
||||
checked={formik.values.public === 'true'}
|
||||
>
|
||||
<Switch.Thumb className="block w-[21px] h-[21px] bg-white rounded-full shadow-[0_2px_2px] shadow-neutral-300 transition-transform duration-100 translate-x-0.5 will-change-transform data-[state=checked]:translate-x-[19px]" />
|
||||
</Switch.Root>
|
||||
</div>
|
||||
</FormField>
|
||||
|
||||
</FormLayout>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditCourseGeneral
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { useCourse } from '@components/Dashboard/CourseContext';
|
||||
import { useCourse } from '@components/DashboardPages/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 { Layers, Sparkles } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { use, useEffect } from 'react'
|
||||
import { mutate } from 'swr';
|
||||
|
|
@ -62,7 +62,7 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
, [course])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-center'>
|
||||
<Modal
|
||||
isDialogOpen={newActivityModal}
|
||||
onOpenChange={setNewActivityModal}
|
||||
|
|
@ -82,9 +82,9 @@ function NewActivityButton(props: NewActivityButtonProps) {
|
|||
/>
|
||||
<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>
|
||||
}} className="flex w-44 h-10 space-x-2 items-center py-2 my-3 rounded-xl justify-center text-white bg-black hover:cursor-pointer">
|
||||
<Layers className="" size={17} />
|
||||
<div className="text-sm mx-auto my-auto items-center font-bold">Add Activity</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
|
||||
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
|
||||
import { deleteActivity } from '@services/courses/activities'
|
||||
import { deleteActivity, updateActivity } 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'
|
||||
|
|
@ -16,8 +16,15 @@ type ActivitiyElementProps = {
|
|||
course_uuid: string
|
||||
}
|
||||
|
||||
interface ModifiedActivityInterface {
|
||||
activityId: string;
|
||||
activityName: string;
|
||||
}
|
||||
|
||||
function ActivityElement(props: ActivitiyElementProps) {
|
||||
const router = useRouter();
|
||||
const [modifiedActivity, setModifiedActivity] = React.useState<ModifiedActivityInterface | undefined>(undefined);
|
||||
const [selectedActivity, setSelectedActivity] = React.useState<string | undefined>(undefined);
|
||||
|
||||
async function deleteActivityUI() {
|
||||
await deleteActivity(props.activity.id);
|
||||
|
|
@ -26,6 +33,23 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
router.refresh();
|
||||
}
|
||||
|
||||
async function updateActivityName(activityId: string) {
|
||||
if ((modifiedActivity?.activityId === activityId) && selectedActivity !== undefined) {
|
||||
setSelectedActivity(undefined);
|
||||
let modifiedActivityCopy = {
|
||||
name: modifiedActivity.activityName,
|
||||
description: '',
|
||||
type: props.activity.type,
|
||||
content: props.activity.content,
|
||||
}
|
||||
|
||||
await updateActivity(modifiedActivityCopy, activityId)
|
||||
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) => (
|
||||
|
|
@ -38,21 +62,19 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
>
|
||||
|
||||
{/* 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>
|
||||
|
||||
<ActivityTypeIndicator activityType={props.activity.activity_type} />
|
||||
|
||||
{/* 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" />
|
||||
{selectedActivity === props.activity.id ?
|
||||
(<div className="chapter-modification-zone text-[7px] text-gray-600 shadow-inner bg-gray-200/60 py-1 px-4 rounded-lg space-x-3">
|
||||
<input type="text" className="bg-transparent outline-none text-xs text-gray-500" placeholder="Activity name" value={modifiedActivity ? modifiedActivity?.activityName : props.activity.name} onChange={(e) => setModifiedActivity({ activityId: props.activity.id, activityName: e.target.value })} />
|
||||
<button onClick={() => updateActivityName(props.activity.id)} className="bg-transparent text-neutral-700 hover:cursor-pointer hover:text-neutral-900">
|
||||
<Save size={11} onClick={() => updateActivityName(props.activity.id)} />
|
||||
</button>
|
||||
</div>) : (<p className="first-letter:uppercase"> {props.activity.name} </p>)}
|
||||
<Pencil onClick={() => setSelectedActivity(props.activity.id)}
|
||||
size={12} className="text-neutral-400 hover:cursor-pointer" />
|
||||
</div>
|
||||
{/* Edit and View Button */}
|
||||
<div className="flex flex-row space-x-2">
|
||||
|
|
@ -93,4 +115,18 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
const ActivityTypeIndicator = (props: { activityType: string }) => {
|
||||
return (
|
||||
<div className="px-3 text-gray-300 space-x-1 w-28" >
|
||||
|
||||
|
||||
{props.activityType === "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></>}
|
||||
{props.activityType === "TYPE_DOCUMENT" && <><div className="flex space-x-2 items-center"><div className="w-[30px]"><File size={16} /> </div><div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Document</div> </div></>}
|
||||
{props.activityType === "TYPE_DYNAMIC" && <><div className="flex space-x-2 items-center"><Sparkles size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Dynamic</div> </div></>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ActivityElement
|
||||
|
|
@ -6,7 +6,7 @@ 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 { deleteChapter, updateChapter } from '@services/courses/chapters';
|
||||
import { revalidateTags } from '@services/utils/ts/requests';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
|
|
@ -19,8 +19,16 @@ type ChapterElementProps = {
|
|||
course_uuid: string
|
||||
}
|
||||
|
||||
interface ModifiedChapterInterface {
|
||||
chapterId: string;
|
||||
chapterName: string;
|
||||
}
|
||||
|
||||
function ChapterElement(props: ChapterElementProps) {
|
||||
const activities = props.chapter.activities || [];
|
||||
const [modifiedChapter, setModifiedChapter] = React.useState<ModifiedChapterInterface | undefined>(undefined);
|
||||
const [selectedChapter, setSelectedChapter] = React.useState<string | undefined>(undefined);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const deleteChapterUI = async () => {
|
||||
|
|
@ -30,6 +38,19 @@ function ChapterElement(props: ChapterElementProps) {
|
|||
router.refresh();
|
||||
};
|
||||
|
||||
async function updateChapterName(chapterId: string) {
|
||||
if (modifiedChapter?.chapterId === chapterId) {
|
||||
setSelectedChapter(undefined);
|
||||
let modifiedChapterCopy = {
|
||||
name: modifiedChapter.chapterName,
|
||||
}
|
||||
await updateChapter(chapterId, modifiedChapterCopy)
|
||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`);
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={props.chapter.chapter_uuid}
|
||||
|
|
@ -38,7 +59,7 @@ function ChapterElement(props: ChapterElementProps) {
|
|||
>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="max-w-screen-2xl mx-auto bg-white rounded-xl shadow-sm px-6 pt-6"
|
||||
className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 pt-6"
|
||||
key={props.chapter.chapter_uuid}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
|
|
@ -50,8 +71,14 @@ function ChapterElement(props: ChapterElementProps) {
|
|||
<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" />
|
||||
{selectedChapter === props.chapter.id ?
|
||||
(<div className="chapter-modification-zone bg-neutral-100 py-1 px-4 rounded-lg space-x-3">
|
||||
<input type="text" className="bg-transparent outline-none text-sm text-neutral-700" placeholder="Chapter name" value={modifiedChapter ? modifiedChapter?.chapterName : props.chapter.name} onChange={(e) => setModifiedChapter({ chapterId: props.chapter.id, chapterName: e.target.value })} />
|
||||
<button onClick={() => updateChapterName(props.chapter.id)} className="bg-transparent text-neutral-700 hover:cursor-pointer hover:text-neutral-900">
|
||||
<Save size={15} onClick={() => updateChapterName(props.chapter.id)} />
|
||||
</button>
|
||||
</div>) : (<p className="text-neutral-700 first-letter:uppercase">{props.chapter.name}</p>)}
|
||||
<Pencil size={15} onClick={() => setSelectedChapter(props.chapter.id)} className="text-neutral-600 hover:cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
<MoreVertical size={15} className="text-gray-300" />
|
||||
|
|
@ -6,10 +6,13 @@ 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 { createChapter, 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';
|
||||
import { useCourse, useCourseDispatch } from '@components/DashboardPages/CourseContext';
|
||||
import { Hexagon } from 'lucide-react';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import NewChapterModal from '@components/Objects/Modals/Chapters/NewChapter';
|
||||
|
||||
type EditCourseStructureProps = {
|
||||
orgslug: string,
|
||||
|
|
@ -41,7 +44,21 @@ const EditCourseStructure = (props: EditCourseStructureProps) => {
|
|||
const course_structure = course ? course.courseStructure : {};
|
||||
const course_uuid = course ? course.courseStructure.course_uuid : '';
|
||||
|
||||
// New Chapter creation
|
||||
const [newChapterModal, setNewChapterModal] = useState(false);
|
||||
|
||||
const closeNewChapterModal = async () => {
|
||||
setNewChapterModal(false);
|
||||
};
|
||||
|
||||
// Submit new chapter
|
||||
const submitChapter = async (chapter: any) => {
|
||||
await createChapter(chapter);
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
|
||||
await revalidateTags(['courses'], props.orgslug);
|
||||
router.refresh();
|
||||
setNewChapterModal(false);
|
||||
};
|
||||
|
||||
const updateStructure = (result: any) => {
|
||||
const { destination, source, draggableId, type } = result;
|
||||
|
|
@ -99,6 +116,27 @@ const EditCourseStructure = (props: EditCourseStructureProps) => {
|
|||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
{/* New Chapter Modal */}
|
||||
<Modal
|
||||
isDialogOpen={newChapterModal}
|
||||
onOpenChange={setNewChapterModal}
|
||||
minHeight="sm"
|
||||
dialogContent={<NewChapterModal
|
||||
course={course ? course.courseStructure : null}
|
||||
closeModal={closeNewChapterModal}
|
||||
submitChapter={submitChapter}
|
||||
></NewChapterModal>}
|
||||
dialogTitle="Create chapter"
|
||||
dialogDescription="Add a new chapter to the course"
|
||||
dialogTrigger={
|
||||
<div className="mt-4 w-44 max-w-screen-2xl mx-auto bg-cyan-800 text-white rounded-xl shadow-sm px-6 items-center flex flex-row h-10">
|
||||
<div className='mx-auto flex space-x-2 items-center hover:cursor-pointer'>
|
||||
<Hexagon strokeWidth={3} size={16} className="text-white text-sm " />
|
||||
<div className='font-bold text-sm'>Add Chapter</div></div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</DragDropContext>
|
||||
|
||||
: <></>}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useCourse } from '@components/Dashboard/CourseContext'
|
||||
import { useCourse } from '@components/DashboardPages/CourseContext'
|
||||
import { Book, ChevronRight, User } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import React, { use, useEffect } from 'react'
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useCourse } from "@components/Dashboard/CourseContext";
|
||||
import { useCourse } from "@components/DashboardPages/CourseContext";
|
||||
import { useEffect } from "react";
|
||||
import BreadCrumbs from "./BreadCrumbs";
|
||||
import SaveState from "./SaveState";
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
|
||||
import { Book } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
|
@ -10,7 +11,9 @@ function LeftMenu() {
|
|||
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>
|
||||
<ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><Book size={18} /></Link>
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -2,11 +2,12 @@
|
|||
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 { useCourse, useCourseDispatch } from '@components/DashboardPages/CourseContext'
|
||||
import { Check, SaveAllIcon, Timer } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useEffect } from 'react'
|
||||
import { mutate } from 'swr';
|
||||
import { updateCourse } from '@services/courses/courses';
|
||||
|
||||
function SaveState(props: { orgslug: string }) {
|
||||
const course = useCourse() as any;
|
||||
|
|
@ -16,10 +17,14 @@ function SaveState(props: { orgslug: string }) {
|
|||
const course_structure = course.courseStructure;
|
||||
|
||||
const saveCourseState = async () => {
|
||||
// Course structure & order
|
||||
// Course order
|
||||
if (saved) return;
|
||||
await changeOrderBackend();
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
|
||||
// Course metadata
|
||||
await changeMetadataBackend();
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
dispatchCourse({ type: 'setIsSaved' })
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +39,15 @@ function SaveState(props: { orgslug: string }) {
|
|||
dispatchCourse({ type: 'setIsSaved' })
|
||||
}
|
||||
|
||||
// Course metadata
|
||||
const changeMetadataBackend = async () => {
|
||||
mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta`);
|
||||
await updateCourse(course.courseStructure.course_uuid, course.courseStructure);
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh();
|
||||
dispatchCourse({ type: 'setIsSaved' })
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleCourseOrder = (course_structure: any) => {
|
||||
|
|
@ -14,7 +14,7 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
const [name, setName] = React.useState("");
|
||||
const [description, setDescription] = React.useState("");
|
||||
const [learnings, setLearnings] = React.useState("");
|
||||
const [visibility, setVisibility] = React.useState("");
|
||||
const [visibility, setVisibility] = React.useState(true);
|
||||
const [tags, setTags] = React.useState("");
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [thumbnail, setThumbnail] = React.useState(null) as any;
|
||||
|
|
@ -45,7 +45,7 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
|
||||
const handleVisibilityChange = (event: React.ChangeEvent<any>) => {
|
||||
setVisibility(event.target.value);
|
||||
console.log(event.target.value);
|
||||
console.log(visibility);
|
||||
}
|
||||
|
||||
const handleTagsChange = (event: React.ChangeEvent<any>) => {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any,
|
|||
ressourceType="course"
|
||||
checkMethod='roles' orgId={props.course.org_id}>
|
||||
<div className="flex space-x-1 absolute justify-center mx-auto z-20 bottom-14 left-1/2 transform -translate-x-1/2">
|
||||
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
|
||||
<Link href={getUriWithOrg(props.orgSlug, "/dash/courses/course/" + removeCoursePrefix(props.courseId) + "/general")}>
|
||||
<div
|
||||
className=" hover:cursor-pointer p-1 px-4 bg-orange-600 rounded-xl items-center justify-center flex shadow-xl"
|
||||
rel="noopener noreferrer">
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ type TooltipProps = {
|
|||
sideOffset?: number;
|
||||
content: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
side?: 'top' | 'right' | 'bottom' | 'left'; // default is bottom
|
||||
slateBlack?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ const ToolTip = (props: TooltipProps) => {
|
|||
{props.children}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<TooltipContent slateBlack={props.slateBlack} side="bottom" sideOffset={props.sideOffset}>
|
||||
<TooltipContent slateBlack={props.slateBlack} side={props.side ? props.side : 'bottom'} sideOffset={props.sideOffset}>
|
||||
{props.content}
|
||||
</TooltipContent>
|
||||
</Tooltip.Portal>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-form": "^0.0.2",
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.5",
|
||||
"@sentry/nextjs": "^7.47.0",
|
||||
"@stitches/react": "^1.2.8",
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
"react-hot-toast": "^2.4.1",
|
||||
"react-katex": "^3.0.1",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-youtube": "^10.1.0",
|
||||
"styled-components": "^6.0.0-beta.9",
|
||||
"swr": "^2.2.4",
|
||||
|
|
@ -53,6 +55,7 @@
|
|||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"@types/react-katex": "^3.0.0",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { OrderPayload } from "@components/Dashboard/EditCourseStructure/EditCourseStructure";
|
||||
import { OrderPayload } from "@components/DashboardPages/EditCourseStructure/EditCourseStructure";
|
||||
import { getAPIUrl } from "@services/config/config";
|
||||
import { RequestBody, RequestBodyWithAuthHeader, errorHandling } from "@services/utils/ts/requests";
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export async function getCourseMetadataWithAuthHeader(course_uuid: any, next: an
|
|||
}
|
||||
|
||||
export async function updateCourse(course_uuid: any, data: any) {
|
||||
const result: any = await fetch(`${getAPIUrl()}courses/course_${course_uuid}`, RequestBody("PUT", data, null));
|
||||
const result: any = await fetch(`${getAPIUrl()}courses/${course_uuid}`, RequestBody("PUT", data, null));
|
||||
const res = await errorHandling(result);
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,3 +67,26 @@ a {
|
|||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.fade-enter {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: opacity 300ms, transform 300ms;
|
||||
}
|
||||
|
||||
.fade-exit {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.fade-exit-active {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transition: opacity 300ms, transform 300ms;
|
||||
}
|
||||
|
|
|
|||
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
|
|
@ -35,6 +35,9 @@ importers:
|
|||
'@radix-ui/react-icons':
|
||||
specifier: ^1.1.1
|
||||
version: 1.3.0(react@18.2.0)
|
||||
'@radix-ui/react-switch':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.7(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0)
|
||||
|
|
@ -107,6 +110,9 @@ importers:
|
|||
react-spinners:
|
||||
specifier: ^0.13.8
|
||||
version: 0.13.8(react-dom@18.2.0)(react@18.2.0)
|
||||
react-transition-group:
|
||||
specifier: ^4.4.5
|
||||
version: 4.4.5(react-dom@18.2.0)(react@18.2.0)
|
||||
react-youtube:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(react@18.2.0)
|
||||
|
|
@ -147,6 +153,9 @@ importers:
|
|||
'@types/react-katex':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.1
|
||||
'@types/react-transition-group':
|
||||
specifier: ^4.4.10
|
||||
version: 4.4.10
|
||||
'@types/styled-components':
|
||||
specifier: ^5.1.26
|
||||
version: 5.1.28
|
||||
|
|
@ -2162,6 +2171,33 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-switch@1.0.3(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.8)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.8)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.8)(react@18.2.0)
|
||||
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.8)(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.8)(react@18.2.0)
|
||||
'@types/react': 18.2.8
|
||||
'@types/react-dom': 18.0.6
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.0.6)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
|
||||
peerDependencies:
|
||||
|
|
@ -2261,6 +2297,20 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-previous@1.0.1(@types/react@18.2.8)(react@18.2.0):
|
||||
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
'@types/react': 18.2.8
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-rect@1.0.1(@types/react@18.2.8)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
|
||||
peerDependencies:
|
||||
|
|
@ -2923,6 +2973,12 @@ packages:
|
|||
redux: 4.2.1
|
||||
dev: false
|
||||
|
||||
/@types/react-transition-group@4.4.10:
|
||||
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
||||
dependencies:
|
||||
'@types/react': 18.2.8
|
||||
dev: true
|
||||
|
||||
/@types/react@18.2.8:
|
||||
resolution: {integrity: sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==}
|
||||
dependencies:
|
||||
|
|
@ -3606,6 +3662,13 @@ packages:
|
|||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dom-helpers@5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
csstype: 3.1.2
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.553:
|
||||
resolution: {integrity: sha512-HiRdtyKS2+VhiXvjhMvvxiMC33FJJqTA5EB2YHgFZW6v7HkK4Q9Ahv2V7O2ZPgAjw+MyCJVMQvigj13H8t+wvA==}
|
||||
|
||||
|
|
@ -5561,6 +5624,20 @@ packages:
|
|||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-youtube@10.1.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==}
|
||||
engines: {node: '>= 14.x'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue