mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #268 from learnhouse/feat/june-bugs-cleaning-season
June Bugs Cleaning 🪲
This commit is contained in:
commit
6390c56282
24 changed files with 446 additions and 403 deletions
|
|
@ -33,19 +33,18 @@ async def authorization_verify_if_element_is_public(
|
||||||
detail="User rights : You don't have the right to perform this action",
|
detail="User rights : You don't have the right to perform this action",
|
||||||
)
|
)
|
||||||
|
|
||||||
if element_nature == "collections" and action == "read":
|
if element_nature == "collections" and action == "read":
|
||||||
|
statement = select(Collection).where(
|
||||||
statement = select(Collection).where(
|
Collection.public == True, Collection.collection_uuid == element_uuid
|
||||||
Collection.public == True, Collection.collection_uuid == element_uuid
|
)
|
||||||
|
collection = db_session.exec(statement).first()
|
||||||
|
if collection:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="User rights : You don't have the right to perform this action",
|
||||||
)
|
)
|
||||||
collection = db_session.exec(statement).first()
|
|
||||||
if collection:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail="User rights : You don't have the right to perform this action",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from fastapi import HTTPException, status, Request
|
||||||
async def get_collection(
|
async def get_collection(
|
||||||
request: Request,
|
request: Request,
|
||||||
collection_uuid: str,
|
collection_uuid: str,
|
||||||
current_user: PublicUser,
|
current_user: PublicUser | AnonymousUser,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
) -> CollectionRead:
|
) -> CollectionRead:
|
||||||
statement = select(Collection).where(Collection.collection_uuid == collection_uuid)
|
statement = select(Collection).where(Collection.collection_uuid == collection_uuid)
|
||||||
|
|
@ -48,6 +48,7 @@ async def get_collection(
|
||||||
statement_all = (
|
statement_all = (
|
||||||
select(Course)
|
select(Course)
|
||||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||||
|
.where(CollectionCourse.org_id == collection.org_id)
|
||||||
.distinct(Course.id)
|
.distinct(Course.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ async def get_collection(
|
||||||
.where(CollectionCourse.org_id == collection.org_id, Course.public == True)
|
.where(CollectionCourse.org_id == collection.org_id, Course.public == True)
|
||||||
)
|
)
|
||||||
|
|
||||||
if current_user.id == 0:
|
if current_user.user_uuid == "user_anonymous":
|
||||||
statement = statement_public
|
statement = statement_public
|
||||||
else:
|
else:
|
||||||
statement = statement_all
|
statement = statement_all
|
||||||
|
|
@ -88,7 +89,6 @@ async def create_collection(
|
||||||
# Add collection to database
|
# Add collection to database
|
||||||
db_session.add(collection)
|
db_session.add(collection)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
db_session.refresh(collection)
|
db_session.refresh(collection)
|
||||||
|
|
||||||
# Link courses to collection
|
# Link courses to collection
|
||||||
|
|
@ -184,6 +184,7 @@ async def update_collection(
|
||||||
statement = (
|
statement = (
|
||||||
select(Course)
|
select(Course)
|
||||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||||
|
.where(Course.org_id == collection.org_id)
|
||||||
.distinct(Course.id)
|
.distinct(Course.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -255,6 +256,7 @@ async def get_collections(
|
||||||
statement_all = (
|
statement_all = (
|
||||||
select(Course)
|
select(Course)
|
||||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||||
|
.where(CollectionCourse.org_id == collection.org_id)
|
||||||
.distinct(Course.id)
|
.distinct(Course.id)
|
||||||
)
|
)
|
||||||
statement_public = (
|
statement_public = (
|
||||||
|
|
@ -297,8 +299,10 @@ async def rbac_check(
|
||||||
detail="User rights : You are not allowed to read this collection",
|
detail="User rights : You are not allowed to read this collection",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
res = await authorization_verify_based_on_roles_and_authorship_and_usergroups(
|
res = (
|
||||||
request, current_user.id, action, collection_uuid, db_session
|
await authorization_verify_based_on_roles_and_authorship_and_usergroups(
|
||||||
|
request, current_user.id, action, collection_uuid, db_session
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ function ForgotPasswordClient() {
|
||||||
email: ''
|
email: ''
|
||||||
},
|
},
|
||||||
validate,
|
validate,
|
||||||
|
validateOnBlur: true,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
let res = await sendResetLink(values.email, org?.id)
|
let res = await sendResetLink(values.email, org?.id)
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,17 @@ const LoginClient = (props: LoginClientProps) => {
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
validate,
|
validate,
|
||||||
onSubmit: async (values) => {
|
validateOnBlur: true,
|
||||||
|
validateOnChange: true,
|
||||||
|
onSubmit: async (values, {validateForm, setErrors, setSubmitting}) => {
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
const errors = await validateForm(values);
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
setErrors(errors);
|
||||||
|
setSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const res = await signIn('credentials', {
|
const res = await signIn('credentials', {
|
||||||
redirect: false,
|
redirect: false,
|
||||||
email: values.email,
|
email: values.email,
|
||||||
|
|
@ -139,7 +148,7 @@ const LoginClient = (props: LoginClientProps) => {
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
value={formik.values.email}
|
value={formik.values.email}
|
||||||
type="email"
|
type="email"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
@ -155,7 +164,7 @@ const LoginClient = (props: LoginClientProps) => {
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
value={formik.values.password}
|
value={formik.values.password}
|
||||||
type="password"
|
type="password"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
@ -170,7 +179,7 @@ const LoginClient = (props: LoginClientProps) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex py-4">
|
<div className="flex py-4">
|
||||||
<Form.Submit asChild>
|
<Form.Submit asChild>
|
||||||
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer">
|
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer">
|
||||||
{isSubmitting ? 'Loading...' : 'Login'}
|
{isSubmitting ? 'Loading...' : 'Login'}
|
||||||
</button>
|
</button>
|
||||||
</Form.Submit>
|
</Form.Submit>
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ const EditActivity = async (params: any) => {
|
||||||
{ revalidate: 0, tags: ['activities'] },
|
{ revalidate: 0, tags: ['activities'] },
|
||||||
access_token ? access_token : null
|
access_token ? access_token : null
|
||||||
)
|
)
|
||||||
|
|
||||||
const org = await getOrganizationContextInfoWithId(courseInfo.org_id, {
|
const org = await getOrganizationContextInfoWithId(courseInfo.org_id, {
|
||||||
revalidate: 180,
|
revalidate: 180,
|
||||||
tags: ['organizations'],
|
tags: ['organizations'],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import learnhousetextlogo from '../../../../public/learnhouse_logo.png'
|
import learnhousetextlogo from '../../../../public/learnhouse_logo.png'
|
||||||
import { BookCopy, School, Settings, Users } from 'lucide-react'
|
import { BookCopy, School, Settings, University, Users } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
||||||
|
|
||||||
|
|
@ -62,12 +62,13 @@ function DashboardHome() {
|
||||||
<div className="h-1 w-[100px] bg-neutral-200 rounded-full mx-auto"></div>
|
<div className="h-1 w-[100px] bg-neutral-200 rounded-full mx-auto"></div>
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
<Link
|
<Link
|
||||||
href={'https://learn.learnhouse.io/'}
|
href={'https://university.learnhouse.io/'}
|
||||||
|
target='_blank'
|
||||||
className="flex mt-[40px] bg-black space-x-2 items-center py-3 px-7 rounded-lg shadow-lg hover:scale-105 transition-all ease-linear cursor-pointer"
|
className="flex mt-[40px] bg-black space-x-2 items-center py-3 px-7 rounded-lg shadow-lg hover:scale-105 transition-all ease-linear cursor-pointer"
|
||||||
>
|
>
|
||||||
<BookCopy className=" text-gray-100" size={20}></BookCopy>
|
<University className=" text-gray-100" size={20}></University>
|
||||||
<div className=" text-sm font-bold text-gray-100">
|
<div className=" text-sm font-bold text-gray-100">
|
||||||
Learn LearnHouse
|
LearnHouse University
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
import { OrgProvider } from '@components/Contexts/OrgContext'
|
import { OrgProvider } from '@components/Contexts/OrgContext'
|
||||||
|
import NextTopLoader from 'nextjs-toploader';
|
||||||
import Toast from '@components/StyledElements/Toast/Toast'
|
import Toast from '@components/StyledElements/Toast/Toast'
|
||||||
import '@styles/globals.css'
|
import '@styles/globals.css'
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ export default function RootLayout({
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<OrgProvider orgslug={params.orgslug}>
|
<OrgProvider orgslug={params.orgslug}>
|
||||||
|
<NextTopLoader color="#2e2e2e" initialPosition={0.3} height={4} easing={'ease'} speed={500} showSpinner={false} />
|
||||||
<Toast />
|
<Toast />
|
||||||
{children}
|
{children}
|
||||||
</OrgProvider>
|
</OrgProvider>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { swrFetcher } from '@services/utils/ts/requests'
|
||||||
import React, { createContext, useContext, useEffect, useReducer } from 'react'
|
import React, { createContext, useContext, useEffect, useReducer } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
|
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||||
|
|
||||||
export const CourseContext = createContext(null)
|
export const CourseContext = createContext(null)
|
||||||
export const CourseDispatchContext = createContext(null)
|
export const CourseDispatchContext = createContext(null)
|
||||||
|
|
@ -33,15 +34,17 @@ export function CourseProvider({ children, courseuuid }: any) {
|
||||||
}, [courseStructureData]);
|
}, [courseStructureData]);
|
||||||
|
|
||||||
if (error) return <div>Failed to load course structure</div>;
|
if (error) return <div>Failed to load course structure</div>;
|
||||||
if (!courseStructureData) return <div>Loading...</div>;
|
if (!courseStructureData) return '';
|
||||||
|
|
||||||
return (
|
if (courseStructureData) {
|
||||||
<CourseContext.Provider value={state}>
|
return (
|
||||||
<CourseDispatchContext.Provider value={dispatch}>
|
<CourseContext.Provider value={state}>
|
||||||
{children}
|
<CourseDispatchContext.Provider value={dispatch}>
|
||||||
</CourseDispatchContext.Provider>
|
{children}
|
||||||
</CourseContext.Provider>
|
</CourseDispatchContext.Provider>
|
||||||
)
|
</CourseContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCourse() {
|
export function useCourse() {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function OrgProvider({ children, orgslug }: { children: React.ReactNode,
|
||||||
const isUserPartOfTheOrg = useMemo(() => orgs?.some((userOrg: any) => userOrg.id === org?.id), [orgs, org?.id])
|
const isUserPartOfTheOrg = useMemo(() => orgs?.some((userOrg: any) => userOrg.id === org?.id), [orgs, org?.id])
|
||||||
|
|
||||||
if (orgError || orgsError) return <ErrorUI message='An error occurred while fetching data' />
|
if (orgError || orgsError) return <ErrorUI message='An error occurred while fetching data' />
|
||||||
if (!org || !orgs || !session) return <div>Loading...</div>
|
if (!org || !orgs || !session) return <div></div>
|
||||||
if (!isOrgActive) return <ErrorUI message='This organization is no longer active' />
|
if (!isOrgActive) return <ErrorUI message='This organization is no longer active' />
|
||||||
if (!isUserPartOfTheOrg && session.status == 'authenticated' && !isAllowedPathname) {
|
if (!isUserPartOfTheOrg && session.status == 'authenticated' && !isAllowedPathname) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { unLinkResourcesToUserGroup } from '@services/usergroups/usergroups'
|
||||||
import { swrFetcher } from '@services/utils/ts/requests'
|
import { swrFetcher } from '@services/utils/ts/requests'
|
||||||
import { Globe, SquareUserRound, Users, X } from 'lucide-react'
|
import { Globe, SquareUserRound, Users, X } from 'lucide-react'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import useSWR, { mutate } from 'swr'
|
import useSWR, { mutate } from 'swr'
|
||||||
|
|
||||||
|
|
@ -17,132 +17,132 @@ type EditCourseAccessProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditCourseAccess(props: EditCourseAccessProps) {
|
function EditCourseAccess(props: EditCourseAccessProps) {
|
||||||
const [error, setError] = React.useState('')
|
|
||||||
const session = useLHSession() as any;
|
const session = useLHSession() as any;
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
|
|
||||||
const course = useCourse() as any;
|
const course = useCourse() as any;
|
||||||
const { isLoading, courseStructure } = course as any;
|
const { isLoading, courseStructure } = course as any;
|
||||||
const dispatchCourse = useCourseDispatch() as any
|
const dispatchCourse = useCourseDispatch() as any;
|
||||||
const { data: usergroups } = useSWR(
|
|
||||||
courseStructure ? `${getAPIUrl()}usergroups/resource/${courseStructure.course_uuid}` : null,
|
|
||||||
(url) => swrFetcher(url, access_token)
|
|
||||||
)
|
|
||||||
const [isPublic, setIsPublic] = React.useState(courseStructure.public)
|
|
||||||
|
|
||||||
|
const { data: usergroups } = useSWR(courseStructure ? `${getAPIUrl()}usergroups/resource/${courseStructure.course_uuid}` : null, (url) => swrFetcher(url, access_token));
|
||||||
|
const [isClientPublic, setIsClientPublic] = useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
// This code will run whenever form values are updated
|
if (!isLoading && courseStructure?.public !== undefined) {
|
||||||
if ((isPublic !== courseStructure.public) && isLoading) {
|
setIsClientPublic(courseStructure.public);
|
||||||
dispatchCourse({ type: 'setIsNotSaved' })
|
|
||||||
const updatedCourse = {
|
|
||||||
...courseStructure,
|
|
||||||
public: isPublic,
|
|
||||||
}
|
|
||||||
dispatchCourse({ type: 'setCourseStructure', payload: updatedCourse })
|
|
||||||
}
|
}
|
||||||
}, [course, isPublic])
|
}, [isLoading, courseStructure]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && courseStructure?.public !== undefined && isClientPublic !== undefined) {
|
||||||
|
if (isClientPublic !== courseStructure.public) {
|
||||||
|
dispatchCourse({ type: 'setIsNotSaved' });
|
||||||
|
const updatedCourse = {
|
||||||
|
...courseStructure,
|
||||||
|
public: isClientPublic,
|
||||||
|
};
|
||||||
|
dispatchCourse({ type: 'setCourseStructure', payload: updatedCourse });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isLoading, isClientPublic, courseStructure, dispatchCourse]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{' '}
|
{courseStructure && (
|
||||||
<div className="h-6"></div>
|
<div>
|
||||||
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-4 py-4">
|
<div className="h-6"></div>
|
||||||
<div className="flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mb-3 ">
|
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-4 py-4">
|
||||||
<h1 className="font-bold text-xl text-gray-800">Access to the course</h1>
|
<div className="flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mb-3">
|
||||||
<h2 className="text-gray-500 text-sm">
|
<h1 className="font-bold text-xl text-gray-800">Access to the course</h1>
|
||||||
{' '}
|
<h2 className="text-gray-500 text-sm">
|
||||||
Choose if want your course to be publicly available on the internet or only accessible to signed in users{' '}
|
Choose if you want your course to be publicly available on the internet or only accessible to signed in users
|
||||||
</h2>
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2 mx-auto mb-3">
|
||||||
|
<ConfirmationModal
|
||||||
|
confirmationButtonText="Change to Public"
|
||||||
|
confirmationMessage="Are you sure you want this course to be publicly available on the internet?"
|
||||||
|
dialogTitle="Change to Public?"
|
||||||
|
dialogTrigger={
|
||||||
|
<div className="w-full h-[200px] bg-slate-100 rounded-lg cursor-pointer hover:bg-slate-200 transition-all">
|
||||||
|
{isClientPublic && (
|
||||||
|
<div className="bg-green-200 text-green-600 font-bold w-fit my-3 mx-3 absolute text-sm px-3 py-1 rounded-lg">
|
||||||
|
Active
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col space-y-1 justify-center items-center h-full">
|
||||||
|
<Globe className="text-slate-400" size={40} />
|
||||||
|
<div className="text-2xl text-slate-700 font-bold">
|
||||||
|
Public
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-400 text-md tracking-tight w-[500px] leading-5 text-center">
|
||||||
|
The Course is publicly available on the internet, it is indexed by search engines and can be accessed by anyone
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
functionToExecute={() => setIsClientPublic(true)}
|
||||||
|
status="info"
|
||||||
|
/>
|
||||||
|
<ConfirmationModal
|
||||||
|
confirmationButtonText="Change to Users Only"
|
||||||
|
confirmationMessage="Are you sure you want this course to be only accessible to signed in users?"
|
||||||
|
dialogTitle="Change to Users Only?"
|
||||||
|
dialogTrigger={
|
||||||
|
<div className="w-full h-[200px] bg-slate-100 rounded-lg cursor-pointer hover:bg-slate-200 transition-all">
|
||||||
|
{!isClientPublic && (
|
||||||
|
<div className="bg-green-200 text-green-600 font-bold w-fit my-3 mx-3 absolute text-sm px-3 py-1 rounded-lg">
|
||||||
|
Active
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col space-y-1 justify-center items-center h-full">
|
||||||
|
<Users className="text-slate-400" size={40} />
|
||||||
|
<div className="text-2xl text-slate-700 font-bold">
|
||||||
|
Users Only
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-400 text-md tracking-tight w-[500px] leading-5 text-center">
|
||||||
|
The Course is only accessible to signed in users, additionally you can choose which UserGroups can access this course
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
functionToExecute={() => setIsClientPublic(false)}
|
||||||
|
status="info"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!isClientPublic && <UserGroupsSection usergroups={usergroups} />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2 mx-auto mb-3">
|
)}
|
||||||
<ConfirmationModal
|
|
||||||
confirmationButtonText="Change to Public"
|
|
||||||
confirmationMessage="Are you sure you want this course to be publicly available on the internet ?"
|
|
||||||
dialogTitle={'Change to Public ?'}
|
|
||||||
dialogTrigger={
|
|
||||||
<div className="w-full h-[200px] bg-slate-100 rounded-lg cursor-pointer hover:bg-slate-200 ease-linear transition-all">
|
|
||||||
{isPublic ? (
|
|
||||||
<div className="bg-green-200 text-green-600 font-bold w-fit my-3 mx-3 absolute text-sm px-3 py-1 rounded-lg">
|
|
||||||
Active
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="flex flex-col space-y-1 justify-center items-center h-full">
|
|
||||||
<Globe className="text-slate-400" size={40}></Globe>
|
|
||||||
<div className="text-2xl text-slate-700 font-bold">
|
|
||||||
Public
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-400 text-md tracking-tight w-[500px] leading-5 text-center">
|
|
||||||
The Course is publicly available on the internet, it is indexed by search engines and can be accessed by anyone
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
functionToExecute={() => {
|
|
||||||
setIsPublic(true)
|
|
||||||
}}
|
|
||||||
status="info"
|
|
||||||
></ConfirmationModal>
|
|
||||||
<ConfirmationModal
|
|
||||||
confirmationButtonText="Change to Users Only"
|
|
||||||
confirmationMessage="Are you sure you want this course to be only accessible to signed in users ?"
|
|
||||||
dialogTitle={'Change to Users Only ?'}
|
|
||||||
dialogTrigger={
|
|
||||||
<div className="w-full h-[200px] bg-slate-100 rounded-lg cursor-pointer hover:bg-slate-200 ease-linear transition-all">
|
|
||||||
{!isPublic ? (
|
|
||||||
<div className="bg-green-200 text-green-600 font-bold w-fit my-3 mx-3 absolute text-sm px-3 py-1 rounded-lg">
|
|
||||||
Active
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="flex flex-col space-y-1 justify-center items-center h-full">
|
|
||||||
<Users className="text-slate-400" size={40}></Users>
|
|
||||||
<div className="text-2xl text-slate-700 font-bold">
|
|
||||||
Users Only
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-400 text-md tracking-tight w-[500px] leading-5 text-center">
|
|
||||||
The Course is only accessible to signed in users, additionaly you can choose which UserGroups can access this course
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
functionToExecute={() => {
|
|
||||||
setIsPublic(false)
|
|
||||||
}}
|
|
||||||
status="info"
|
|
||||||
></ConfirmationModal>
|
|
||||||
</div>
|
|
||||||
{!isPublic ? (<UserGroupsSection usergroups={usergroups} />) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function UserGroupsSection({ usergroups }: { usergroups: any[] }) {
|
function UserGroupsSection({ usergroups }: { usergroups: any[] }) {
|
||||||
const course = useCourse() as any
|
const course = useCourse() as any;
|
||||||
const [userGroupModal, setUserGroupModal] = React.useState(false)
|
const [userGroupModal, setUserGroupModal] = useState(false);
|
||||||
const session = useLHSession() as any;
|
const session = useLHSession() as any;
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
|
|
||||||
const removeUserGroupLink = async (usergroup_id: number) => {
|
const removeUserGroupLink = async (usergroup_id: number) => {
|
||||||
const res = await unLinkResourcesToUserGroup(usergroup_id, course.courseStructure.course_uuid, access_token)
|
try {
|
||||||
if (res.status === 200) {
|
const res = await unLinkResourcesToUserGroup(usergroup_id, course.courseStructure.course_uuid, access_token);
|
||||||
toast.success('Successfully unliked from usergroup')
|
if (res.status === 200) {
|
||||||
mutate(`${getAPIUrl()}usergroups/resource/${course.courseStructure.course_uuid}`)
|
toast.success('Successfully unlinked from usergroup');
|
||||||
|
mutate(`${getAPIUrl()}usergroups/resource/${course.courseStructure.course_uuid}`);
|
||||||
|
} else {
|
||||||
|
toast.error(`Error ${res.status}: ${res.data.detail}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('An error occurred while unlinking the user group.');
|
||||||
}
|
}
|
||||||
else {
|
};
|
||||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mb-3 ">
|
<div className="flex flex-col bg-gray-50 -space-y-1 px-5 py-3 rounded-md mb-3">
|
||||||
<h1 className="font-bold text-xl text-gray-800">UserGroups</h1>
|
<h1 className="font-bold text-xl text-gray-800">UserGroups</h1>
|
||||||
<h2 className="text-gray-500 text-sm">
|
<h2 className="text-gray-500 text-sm">
|
||||||
{' '}
|
You can choose to give access to this course to specific groups of users only by linking it to a UserGroup
|
||||||
You can choose to give access to this course to specific groups of users only by linking it to a UserGroup{' '}
|
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<table className="table-auto w-full text-left whitespace-nowrap rounded-md overflow-hidden">
|
<table className="table-auto w-full text-left whitespace-nowrap rounded-md overflow-hidden">
|
||||||
|
|
@ -152,67 +152,48 @@ function UserGroupsSection({ usergroups }: { usergroups: any[] }) {
|
||||||
<th className="py-3 px-4">Actions</th>
|
<th className="py-3 px-4">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<>
|
<tbody className="mt-5 bg-white rounded-md">
|
||||||
<tbody className="mt-5 bg-white rounded-md">
|
{usergroups?.map((usergroup: any) => (
|
||||||
{usergroups?.map((usergroup: any) => (
|
<tr key={usergroup.invite_code_uuid} className="border-b border-gray-100 text-sm">
|
||||||
<tr
|
<td className="py-3 px-4">{usergroup.name}</td>
|
||||||
key={usergroup.invite_code_uuid}
|
<td className="py-3 px-4">
|
||||||
className="border-b border-gray-100 text-sm"
|
<ConfirmationModal
|
||||||
>
|
confirmationButtonText="Delete Link"
|
||||||
<td className="py-3 px-4">{usergroup.name}</td>
|
confirmationMessage="Users from this UserGroup will no longer have access to this course"
|
||||||
<td className="py-3 px-4">
|
dialogTitle="Unlink UserGroup?"
|
||||||
<ConfirmationModal
|
dialogTrigger={
|
||||||
confirmationButtonText="Delete Link"
|
<button className="mr-2 flex space-x-2 hover:cursor-pointer p-1 px-3 bg-rose-700 rounded-md font-bold items-center text-sm text-rose-100">
|
||||||
confirmationMessage="Users from this UserGroup will no longer have access to this course"
|
<X className="w-4 h-4" />
|
||||||
dialogTitle={'Unlink UserGroup ?'}
|
<span>Delete link</span>
|
||||||
dialogTrigger={
|
</button>
|
||||||
<button className="mr-2 flex space-x-2 hover:cursor-pointer p-1 px-3 bg-rose-700 rounded-md font-bold items-center text-sm text-rose-100">
|
}
|
||||||
<X className="w-4 h-4" />
|
functionToExecute={() => removeUserGroupLink(usergroup.id)}
|
||||||
<span> Delete link</span>
|
status="warning"
|
||||||
</button>
|
/>
|
||||||
}
|
</td>
|
||||||
functionToExecute={() => {
|
</tr>
|
||||||
removeUserGroupLink(usergroup.id)
|
))}
|
||||||
}}
|
</tbody>
|
||||||
status="warning"
|
|
||||||
></ConfirmationModal>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</>
|
|
||||||
</table>
|
</table>
|
||||||
<div className='flex flex-row-reverse mt-3 mr-2'>
|
<div className="flex flex-row-reverse mt-3 mr-2">
|
||||||
<Modal
|
<Modal
|
||||||
isDialogOpen={
|
isDialogOpen={userGroupModal}
|
||||||
userGroupModal
|
onOpenChange={() => setUserGroupModal(!userGroupModal)}
|
||||||
}
|
|
||||||
onOpenChange={() =>
|
|
||||||
setUserGroupModal(!userGroupModal)
|
|
||||||
}
|
|
||||||
minHeight="no-min"
|
minHeight="no-min"
|
||||||
minWidth='md'
|
minWidth="md"
|
||||||
dialogContent={
|
dialogContent={<LinkToUserGroup setUserGroupModal={setUserGroupModal} />}
|
||||||
<LinkToUserGroup setUserGroupModal={setUserGroupModal} />
|
|
||||||
|
|
||||||
}
|
|
||||||
dialogTitle="Link Course to a UserGroup"
|
dialogTitle="Link Course to a UserGroup"
|
||||||
dialogDescription={
|
dialogDescription="Choose a UserGroup to link this course to. Users from this UserGroup will have access to this course."
|
||||||
'Choose a UserGroup to link this course to, Users from this UserGroup will have access to this course.'
|
|
||||||
}
|
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<button
|
<button className="flex space-x-2 hover:cursor-pointer p-1 px-3 bg-green-700 rounded-md font-bold items-center text-sm text-green-100">
|
||||||
className=" flex space-x-2 hover:cursor-pointer p-1 px-3 bg-green-700 rounded-md font-bold items-center text-sm text-green-100"
|
|
||||||
>
|
|
||||||
<SquareUserRound className="w-4 h-4" />
|
<SquareUserRound className="w-4 h-4" />
|
||||||
<span>Link to a UserGroup</span>
|
<span>Link to a UserGroup</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditCourseAccess
|
export default EditCourseAccess;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { revalidateTags } from '@services/utils/ts/requests'
|
||||||
import {
|
import {
|
||||||
Eye,
|
Eye,
|
||||||
File,
|
File,
|
||||||
|
FilePenLine,
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
Pencil,
|
Pencil,
|
||||||
Save,
|
Save,
|
||||||
|
|
@ -44,7 +45,7 @@ function ActivityElement(props: ActivitiyElementProps) {
|
||||||
const activityUUID = props.activity.activity_uuid
|
const activityUUID = props.activity.activity_uuid
|
||||||
|
|
||||||
async function deleteActivityUI() {
|
async function deleteActivityUI() {
|
||||||
await deleteActivity(props.activity.activity_uuid,access_token)
|
await deleteActivity(props.activity.activity_uuid, access_token)
|
||||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||||
await revalidateTags(['courses'], props.orgslug)
|
await revalidateTags(['courses'], props.orgslug)
|
||||||
router.refresh()
|
router.refresh()
|
||||||
|
|
@ -63,7 +64,7 @@ function ActivityElement(props: ActivitiyElementProps) {
|
||||||
content: props.activity.content,
|
content: props.activity.content,
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateActivity(modifiedActivityCopy, activityUUID,access_token)
|
await updateActivity(modifiedActivityCopy, activityUUID, access_token)
|
||||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||||
await revalidateTags(['courses'], props.orgslug)
|
await revalidateTags(['courses'], props.orgslug)
|
||||||
router.refresh()
|
router.refresh()
|
||||||
|
|
@ -141,10 +142,13 @@ function ActivityElement(props: ActivitiyElementProps) {
|
||||||
''
|
''
|
||||||
)}/edit`
|
)}/edit`
|
||||||
}
|
}
|
||||||
|
prefetch
|
||||||
className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center"
|
className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center"
|
||||||
rel="noopener noreferrer"
|
target='_blank' // hotfix for an editor prosemirror bug
|
||||||
>
|
>
|
||||||
<div className="text-sky-100 font-bold text-xs">Edit </div>
|
<div className="text-sky-100 font-bold text-xs flex items-center space-x-1">
|
||||||
|
<FilePenLine size={12} /> <span>Edit Page</span>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -159,10 +163,12 @@ function ActivityElement(props: ActivitiyElementProps) {
|
||||||
''
|
''
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md"
|
prefetch
|
||||||
|
className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md font-bold text-xs flex items-center space-x-1"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Eye strokeWidth={2} size={15} className="text-gray-600" />
|
<Eye strokeWidth={2} size={12} className="text-gray-600" />
|
||||||
|
<span>Preview</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{/* Delete Button */}
|
{/* Delete Button */}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
|
||||||
import Collaboration from '@tiptap/extension-collaboration'
|
import Collaboration from '@tiptap/extension-collaboration'
|
||||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||||
import ActiveAvatars from './ActiveAvatars'
|
import ActiveAvatars from './ActiveAvatars'
|
||||||
|
import { getUriWithOrg } from '@services/config/config'
|
||||||
|
|
||||||
interface Editor {
|
interface Editor {
|
||||||
content: string
|
content: string
|
||||||
|
|
@ -182,11 +183,11 @@ function Editor(props: Editor) {
|
||||||
</Link>
|
</Link>
|
||||||
<Link target="_blank" href={`/course/${course_uuid}`}>
|
<Link target="_blank" href={`/course/${course_uuid}`}>
|
||||||
<EditorInfoThumbnail
|
<EditorInfoThumbnail
|
||||||
src={`${getCourseThumbnailMediaDirectory(
|
src={`${props.course.thumbnail_image ? getCourseThumbnailMediaDirectory(
|
||||||
props.org?.org_uuid,
|
props.org?.org_uuid,
|
||||||
props.course.course_uuid,
|
props.course.course_uuid,
|
||||||
props.course.thumbnail_image
|
props.course.thumbnail_image
|
||||||
)}`}
|
) : getUriWithOrg(props.org?.slug,'/empty_thumbnail.png')}`}
|
||||||
alt=""
|
alt=""
|
||||||
></EditorInfoThumbnail>
|
></EditorInfoThumbnail>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ interface EditorWrapperProps {
|
||||||
|
|
||||||
function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
||||||
const session = useLHSession() as any
|
const session = useLHSession() as any
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
// Define provider in the state
|
// Define provider in the state
|
||||||
const [provider, setProvider] = React.useState<HocuspocusProvider | null>(null);
|
const [provider, setProvider] = React.useState<HocuspocusProvider | null>(null);
|
||||||
const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string)
|
const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string)
|
||||||
|
|
@ -80,7 +80,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.promise(updateActivity(activity, activity.activity_uuid,access_token), {
|
toast.promise(updateActivity(activity, activity.activity_uuid, access_token), {
|
||||||
loading: 'Saving...',
|
loading: 'Saving...',
|
||||||
success: <b>Activity saved!</b>,
|
success: <b>Activity saved!</b>,
|
||||||
error: <b>Could not save.</b>,
|
error: <b>Could not save.</b>,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
|
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
|
||||||
import { getUriWithOrg } from '@services/config/config'
|
import { getUriWithOrg } from '@services/config/config'
|
||||||
|
import { BookCopy, Signpost, SquareLibrary } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
function MenuLinks(props: { orgslug: string }) {
|
function MenuLinks(props: { orgslug: string }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='pl-1'>
|
||||||
<ul className="flex space-x-4">
|
<ul className="flex space-x-5">
|
||||||
<LinkItem
|
<LinkItem
|
||||||
link="/courses"
|
link="/courses"
|
||||||
type="courses"
|
type="courses"
|
||||||
|
|
@ -33,57 +34,24 @@ const LinkItem = (props: any) => {
|
||||||
const orgslug = props.orgslug
|
const orgslug = props.orgslug
|
||||||
return (
|
return (
|
||||||
<Link href={getUriWithOrg(orgslug, link)}>
|
<Link href={getUriWithOrg(orgslug, link)}>
|
||||||
<li className="flex space-x-3 items-center text-[#909192] font-medium">
|
<li className="flex space-x-2 items-center text-[#909192] font-medium">
|
||||||
{props.type == 'courses' && (
|
{props.type == 'courses' && (
|
||||||
<>
|
<>
|
||||||
<svg
|
<BookCopy size={20} />{' '}
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M14.9987 1.66663H6.66536C5.78131 1.66663 4.93346 2.01782 4.30834 2.64294C3.68322 3.26806 3.33203 4.1159 3.33203 4.99996V15C3.33203 15.884 3.68322 16.7319 4.30834 17.357C4.93346 17.9821 5.78131 18.3333 6.66536 18.3333H14.9987C15.4407 18.3333 15.8646 18.1577 16.1772 17.8451C16.4898 17.5326 16.6654 17.1087 16.6654 16.6666V3.33329C16.6654 2.89127 16.4898 2.46734 16.1772 2.15478C15.8646 1.84222 15.4407 1.66663 14.9987 1.66663ZM4.9987 4.99996C4.9987 4.55793 5.17429 4.13401 5.48685 3.82145C5.79941 3.50889 6.22334 3.33329 6.66536 3.33329H14.9987V11.6666H6.66536C6.0779 11.6691 5.50203 11.8303 4.9987 12.1333V4.99996ZM6.66536 16.6666C6.22334 16.6666 5.79941 16.491 5.48685 16.1785C5.17429 15.8659 4.9987 15.442 4.9987 15C4.9987 14.5579 5.17429 14.134 5.48685 13.8214C5.79941 13.5089 6.22334 13.3333 6.66536 13.3333H14.9987V16.6666H6.66536ZM8.33203 6.66663H11.6654C11.8864 6.66663 12.0983 6.57883 12.2546 6.42255C12.4109 6.26627 12.4987 6.05431 12.4987 5.83329C12.4987 5.61228 12.4109 5.40032 12.2546 5.24404C12.0983 5.08776 11.8864 4.99996 11.6654 4.99996H8.33203C8.11102 4.99996 7.89906 5.08776 7.74278 5.24404C7.5865 5.40032 7.4987 5.61228 7.4987 5.83329C7.4987 6.05431 7.5865 6.26627 7.74278 6.42255C7.89906 6.57883 8.11102 6.66663 8.33203 6.66663V6.66663Z"
|
|
||||||
fill="#898A8B"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>Courses</span>
|
<span>Courses</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.type == 'collections' && (
|
{props.type == 'collections' && (
|
||||||
<>
|
<>
|
||||||
<svg
|
<SquareLibrary size={20} />{' '}
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M17.0567 6.14171C17.0567 6.14171 17.0567 6.14171 17.0567 6.07504L17.0067 5.95004C16.9893 5.92352 16.9698 5.89844 16.9483 5.87504C16.926 5.83976 16.901 5.80632 16.8733 5.77504L16.7983 5.71671L16.665 5.65004L10.415 1.79171C10.2826 1.70893 10.1295 1.66504 9.97333 1.66504C9.81715 1.66504 9.66411 1.70893 9.53166 1.79171L3.33166 5.65004L3.25666 5.71671L3.18166 5.77504C3.15404 5.80632 3.12896 5.83976 3.10666 5.87504C3.08524 5.89844 3.06573 5.92352 3.04833 5.95004L2.99833 6.07504C2.99833 6.07504 2.99833 6.07504 2.99833 6.14171C2.99014 6.2137 2.99014 6.28639 2.99833 6.35838V13.6417C2.99805 13.7833 3.03386 13.9227 3.10239 14.0466C3.17092 14.1706 3.2699 14.275 3.39 14.35L9.64 18.2084C9.67846 18.2321 9.72076 18.2491 9.765 18.2584C9.765 18.2584 9.80666 18.2584 9.83166 18.2584C9.97265 18.3031 10.124 18.3031 10.265 18.2584C10.265 18.2584 10.3067 18.2584 10.3317 18.2584C10.3759 18.2491 10.4182 18.2321 10.4567 18.2084L16.665 14.35C16.7851 14.275 16.8841 14.1706 16.9526 14.0466C17.0211 13.9227 17.0569 13.7833 17.0567 13.6417V6.35838C17.0649 6.28639 17.0649 6.2137 17.0567 6.14171ZM9.165 16.0084L4.58166 13.175V7.85838L9.165 10.6834V16.0084ZM9.99833 9.24171L5.33166 6.35838L9.99833 3.48337L14.665 6.35838L9.99833 9.24171ZM15.415 13.175L10.8317 16.0084V10.6834L15.415 7.85838V13.175Z"
|
|
||||||
fill="#898A8B"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>Collections</span>
|
<span>Collections</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.type == 'trail' && (
|
{props.type == 'trail' && (
|
||||||
<>
|
<>
|
||||||
<svg
|
<Signpost size={20} />{' '}
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M16.5751 7.95841C16.5059 7.82098 16.3999 7.70541 16.269 7.62451C16.1381 7.54361 15.9874 7.50054 15.8335 7.50008H11.6668V2.50008C11.6757 2.31731 11.6243 2.13669 11.5204 1.98608C11.4164 1.83547 11.2658 1.72325 11.0918 1.66674C10.9245 1.6117 10.744 1.61108 10.5763 1.66498C10.4087 1.71888 10.2624 1.82452 10.1585 1.96674L3.4918 11.1334C3.40827 11.2541 3.35811 11.3948 3.3464 11.5411C3.3347 11.6874 3.36186 11.8343 3.42513 11.9667C3.4834 12.1182 3.58462 12.2493 3.71637 12.3441C3.84812 12.4388 4.00467 12.493 4.1668 12.5001H8.33346V17.5001C8.33359 17.6758 8.38927 17.847 8.49254 17.9892C8.59581 18.1314 8.74139 18.2373 8.90846 18.2917C8.99219 18.3177 9.07915 18.3317 9.1668 18.3334C9.29828 18.3338 9.42799 18.303 9.5453 18.2436C9.66262 18.1842 9.76422 18.0979 9.8418 17.9917L16.5085 8.82508C16.5982 8.70074 16.652 8.55404 16.6637 8.40112C16.6755 8.24821 16.6448 8.09502 16.5751 7.95841ZM10.0001 14.9334V11.6667C10.0001 11.4457 9.91233 11.2338 9.75605 11.0775C9.59977 10.9212 9.38781 10.8334 9.1668 10.8334H5.83346L10.0001 5.06674V8.33341C10.0001 8.55442 10.0879 8.76638 10.2442 8.92267C10.4005 9.07895 10.6124 9.16674 10.8335 9.16674H14.1668L10.0001 14.9334Z"
|
|
||||||
fill="#909192"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>Trail</span>
|
<span>Trail</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ function DynamicCanvaModal({ submitActivity, chapterId, course }: any) {
|
||||||
</FormMessage>
|
</FormMessage>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Form.Control asChild>
|
<Form.Control asChild>
|
||||||
<Textarea onChange={handleActivityDescriptionChange} required />
|
<Textarea onChange={handleActivityDescriptionChange} />
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
import { useCourse } from '@components/Contexts/CourseContext';
|
import { useCourse } from '@components/Contexts/CourseContext';
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
import { useOrg } from '@components/Contexts/OrgContext';
|
import { useOrg } from '@components/Contexts/OrgContext';
|
||||||
import { getAPIUrl } from '@services/config/config';
|
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
|
||||||
import { linkResourcesToUserGroup } from '@services/usergroups/usergroups';
|
import { linkResourcesToUserGroup } from '@services/usergroups/usergroups';
|
||||||
import { swrFetcher } from '@services/utils/ts/requests';
|
import { swrFetcher } from '@services/utils/ts/requests';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR, { mutate } from 'swr'
|
import useSWR, { mutate } from 'swr'
|
||||||
|
|
@ -24,7 +25,7 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
|
||||||
|
|
||||||
const { data: usergroups } = useSWR(
|
const { data: usergroups } = useSWR(
|
||||||
courseStructure && org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
|
courseStructure && org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
|
||||||
swrFetcher
|
(url) => swrFetcher(url, access_token)
|
||||||
)
|
)
|
||||||
const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any
|
const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any
|
||||||
|
|
||||||
|
|
@ -55,19 +56,26 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {
|
||||||
<h1 className=' font-medium'>Users that are not part of the UserGroup will no longer have access to this course</h1>
|
<h1 className=' font-medium'>Users that are not part of the UserGroup will no longer have access to this course</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className='p-4 flex-row flex justify-between items-center'>
|
<div className='p-4 flex-row flex justify-between items-center'>
|
||||||
|
{usergroups?.length >= 1 &&
|
||||||
|
<div className='py-1'>
|
||||||
|
<span className='px-3 text-gray-400 font-bold rounded-full py-1 bg-gray-100 mx-3'>UserGroup Name </span>
|
||||||
|
|
||||||
<div className='py-1'>
|
<select
|
||||||
<span className='px-3 text-gray-400 font-bold rounded-full py-1 bg-gray-100 mx-3'>UserGroup Name </span>
|
onChange={(e) => setSelectedUserGroup(e.target.value)}
|
||||||
<select
|
defaultValue={selectedUserGroup}
|
||||||
onChange={(e) => setSelectedUserGroup(e.target.value)}
|
>
|
||||||
defaultValue={selectedUserGroup}
|
{usergroups && usergroups.map((group: any) => (
|
||||||
>
|
<option key={group.id} value={group.id}>{group.name}</option>
|
||||||
{usergroups && usergroups.map((group: any) => (
|
))}
|
||||||
<option key={group.id} value={group.id}>{group.name}</option>
|
|
||||||
))}
|
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
|
||||||
|
</div>}
|
||||||
|
{usergroups?.length == 0 &&
|
||||||
|
<div className='flex space-x-3 items-center'>
|
||||||
|
<span className='px-3 text-yellow-700 font-bold rounded-full py-1 mx-3'>No UserGroups available </span>
|
||||||
|
<Link className='px-3 text-blue-700 font-bold rounded-full py-1 bg-blue-100 mx-1' target='_blank' href={getUriWithOrg(org.slug, '/dash/users/settings/usergroups')}>Create a UserGroup</Link>
|
||||||
|
</div>}
|
||||||
<div className='py-3'>
|
<div className='py-3'>
|
||||||
<button onClick={() => { handleLink() }} className='bg-green-700 text-white font-bold px-4 py-2 rounded-md shadow'>Link</button>
|
<button onClick={() => { handleLink() }} className='bg-green-700 text-white font-bold px-4 py-2 rounded-md shadow'>Link</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useOrg } from '@components/Contexts/OrgContext'
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
import { getAPIUrl } from '@services/config/config'
|
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
|
||||||
import { createInviteCode, createInviteCodeWithUserGroup } from '@services/organizations/invites'
|
import { createInviteCode, createInviteCodeWithUserGroup } from '@services/organizations/invites'
|
||||||
import { swrFetcher } from '@services/utils/ts/requests'
|
import { swrFetcher } from '@services/utils/ts/requests'
|
||||||
import { Ticket } from 'lucide-react'
|
import { Ticket } from 'lucide-react'
|
||||||
|
|
@ -7,6 +7,7 @@ import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import useSWR, { mutate } from 'swr'
|
import useSWR, { mutate } from 'swr'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
type OrgInviteCodeGenerateProps = {
|
type OrgInviteCodeGenerateProps = {
|
||||||
setInvitesModal: any
|
setInvitesModal: any
|
||||||
|
|
@ -17,9 +18,10 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
|
||||||
const session = useLHSession() as any
|
const session = useLHSession() as any
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
const [usergroup_id, setUsergroup_id] = React.useState(0);
|
const [usergroup_id, setUsergroup_id] = React.useState(0);
|
||||||
|
|
||||||
const { data: usergroups } = useSWR(
|
const { data: usergroups } = useSWR(
|
||||||
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
|
org ? `${getAPIUrl()}usergroups/org/${org.id}` : null,
|
||||||
swrFetcher
|
(url) => swrFetcher(url, access_token)
|
||||||
)
|
)
|
||||||
|
|
||||||
async function createInviteWithUserGroup() {
|
async function createInviteWithUserGroup() {
|
||||||
|
|
@ -55,24 +57,34 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) {
|
||||||
<h1 className='mx-auto pt-4 text-gray-600 font-medium'>Invite Code linked to a UserGroup</h1>
|
<h1 className='mx-auto pt-4 text-gray-600 font-medium'>Invite Code linked to a UserGroup</h1>
|
||||||
<h2 className='mx-auto text-xs text-gray-600 font-medium'>On Signup, Users will be automatically linked to a UserGroup of your choice</h2>
|
<h2 className='mx-auto text-xs text-gray-600 font-medium'>On Signup, Users will be automatically linked to a UserGroup of your choice</h2>
|
||||||
<div className='flex items-center space-x-4 pt-3 mx-auto'>
|
<div className='flex items-center space-x-4 pt-3 mx-auto'>
|
||||||
<select
|
{usergroups?.length >= 1 &&
|
||||||
defaultValue={usergroup_id}
|
<div className='flex space-x-4 items-center'>
|
||||||
className='flex p-2 w-fit rounded-md text-sm bg-gray-100'>
|
<select
|
||||||
{usergroups?.map((usergroup: any) => (
|
defaultValue={usergroup_id}
|
||||||
<option key={usergroup.id} value={usergroup.id}>
|
className='flex p-2 w-fit rounded-md text-sm bg-gray-100'>
|
||||||
{usergroup.name}
|
{usergroups?.map((usergroup: any) => (
|
||||||
</option>
|
<option key={usergroup.id} value={usergroup.id}>
|
||||||
))}
|
{usergroup.name}
|
||||||
</select>
|
</option>
|
||||||
<div className=''>
|
))}
|
||||||
<button
|
|
||||||
onClick={createInviteWithUserGroup}
|
</select>
|
||||||
className="flex space-x-2 w-fit hover:cursor-pointer p-1 px-3 bg-green-700 rounded-md font-bold items-center text-sm text-green-100"
|
|
||||||
>
|
<div className=''>
|
||||||
<Ticket className="w-4 h-4" />
|
<button
|
||||||
<span> Generate </span>
|
onClick={createInviteWithUserGroup}
|
||||||
</button>
|
className="flex space-x-2 w-fit hover:cursor-pointer p-1 px-3 bg-green-700 rounded-md font-bold items-center text-sm text-green-100"
|
||||||
</div>
|
>
|
||||||
|
<Ticket className="w-4 h-4" />
|
||||||
|
<span> Generate </span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
{usergroups?.length == 0 &&
|
||||||
|
<div className='flex space-x-3 items-center text-xs pt-3'>
|
||||||
|
<span className='px-3 text-yellow-700 font-bold rounded-full py-1 mx-3'>No UserGroups available </span>
|
||||||
|
<Link className='px-3 text-blue-700 font-bold rounded-full py-1 bg-blue-100 mx-1' target='_blank' href={getUriWithOrg(org.slug, '/dash/users/settings/usergroups')}>Create a UserGroup </Link>
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import FormLayout, {
|
||||||
Flex,
|
Flex,
|
||||||
FormField,
|
FormField,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
|
FormLabelAndMessage,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
Input,
|
Input,
|
||||||
} from '@components/StyledElements/Form/Form'
|
} from '@components/StyledElements/Form/Form'
|
||||||
|
|
@ -15,86 +16,84 @@ import { createUserGroup } from '@services/usergroups/usergroups'
|
||||||
import { mutate } from 'swr'
|
import { mutate } from 'swr'
|
||||||
import { getAPIUrl } from '@services/config/config'
|
import { getAPIUrl } from '@services/config/config'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
|
||||||
type AddUserGroupProps = {
|
type AddUserGroupProps = {
|
||||||
setCreateUserGroupModal: any
|
setCreateUserGroupModal: any
|
||||||
}
|
}
|
||||||
|
const validate = (values: any) => {
|
||||||
|
const errors: any = {}
|
||||||
|
|
||||||
|
if (!values.name) {
|
||||||
|
errors.name = 'Name is Required'
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
function AddUserGroup(props: AddUserGroupProps) {
|
function AddUserGroup(props: AddUserGroupProps) {
|
||||||
const org = useOrg() as any;
|
const org = useOrg() as any;
|
||||||
const session = useLHSession() as any
|
const session = useLHSession() as any
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
const [userGroupName, setUserGroupName] = React.useState('')
|
|
||||||
const [userGroupDescription, setUserGroupDescription] = React.useState('')
|
|
||||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||||
|
|
||||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const formik = useFormik({
|
||||||
setUserGroupName(event.target.value)
|
initialValues: {
|
||||||
}
|
name: '',
|
||||||
|
description: '',
|
||||||
const handleDescriptionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setUserGroupDescription(event.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
event.preventDefault()
|
|
||||||
setIsSubmitting(true)
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
name: userGroupName,
|
|
||||||
description: userGroupDescription,
|
|
||||||
org_id: org.id
|
org_id: org.id
|
||||||
}
|
},
|
||||||
const res = await createUserGroup(obj, access_token)
|
validate,
|
||||||
if (res.status == 200) {
|
onSubmit: async (values) => {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(true)
|
||||||
mutate(`${getAPIUrl()}usergroups/org/${org.id}`)
|
const res = await createUserGroup(values, access_token)
|
||||||
props.setCreateUserGroupModal(false)
|
if (res.status == 200) {
|
||||||
|
setIsSubmitting(false)
|
||||||
|
mutate(`${getAPIUrl()}usergroups/org/${org.id}`)
|
||||||
|
props.setCreateUserGroupModal(false)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormLayout onSubmit={handleSubmit}>
|
<FormLayout onSubmit={formik.handleSubmit}>
|
||||||
<FormField name="name">
|
<FormField name="name">
|
||||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
<FormLabelAndMessage
|
||||||
<FormLabel>Name</FormLabel>
|
label="Name"
|
||||||
<FormMessage match="valueMissing">
|
message={formik.errors.name}
|
||||||
Please provide a ug name
|
/>
|
||||||
</FormMessage>
|
|
||||||
</Flex>
|
|
||||||
<Form.Control asChild>
|
<Form.Control asChild>
|
||||||
<Input onChange={handleNameChange} type="text" required />
|
<Input
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name}
|
||||||
|
type="name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField name="description">
|
<FormField name="description">
|
||||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
<FormLabelAndMessage
|
||||||
<FormLabel>Description</FormLabel>
|
label="Description"
|
||||||
<FormMessage match="valueMissing">
|
message={formik.errors.description}
|
||||||
Please provide a ug description
|
/>
|
||||||
</FormMessage>
|
|
||||||
</Flex>
|
|
||||||
<Form.Control asChild>
|
<Form.Control asChild>
|
||||||
<Input onChange={handleDescriptionChange} type="text" required />
|
<Input
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.description}
|
||||||
|
type="description"
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</FormField>
|
</FormField>
|
||||||
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
|
<div className="flex py-4">
|
||||||
<Form.Submit asChild>
|
<Form.Submit asChild>
|
||||||
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
|
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer">
|
||||||
{isSubmitting ? (
|
{isSubmitting ? 'Loading...' : 'Create a UserGroup'}
|
||||||
<BarLoader
|
</button>
|
||||||
cssOverride={{ borderRadius: 60 }}
|
|
||||||
width={60}
|
|
||||||
color="#ffffff"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
'Create UserGroup'
|
|
||||||
)}
|
|
||||||
</ButtonBlack>
|
|
||||||
</Form.Submit>
|
</Form.Submit>
|
||||||
</Flex>
|
</div>
|
||||||
</FormLayout>
|
</FormLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ type ManageUsersProps = {
|
||||||
function ManageUsers(props: ManageUsersProps) {
|
function ManageUsers(props: ManageUsersProps) {
|
||||||
const org = useOrg() as any
|
const org = useOrg() as any
|
||||||
const session = useLHSession() as any
|
const session = useLHSession() as any
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
const { data: OrgUsers } = useSWR(
|
const { data: OrgUsers } = useSWR(
|
||||||
org ? `${getAPIUrl()}orgs/${org.id}/users` : null,
|
org ? `${getAPIUrl()}orgs/${org.id}/users` : null,
|
||||||
swrFetcher
|
(url) => swrFetcher(url, access_token)
|
||||||
)
|
)
|
||||||
const { data: UGusers } = useSWR(
|
const { data: UGusers } = useSWR(
|
||||||
org ? `${getAPIUrl()}usergroups/${props.usergroup_id}/users` : null,
|
org ? `${getAPIUrl()}usergroups/${props.usergroup_id}/users` : null,
|
||||||
swrFetcher
|
(url) => swrFetcher(url, access_token)
|
||||||
)
|
)
|
||||||
|
|
||||||
const isUserPartOfGroup = (user_id: any) => {
|
const isUserPartOfGroup = (user_id: any) => {
|
||||||
|
|
@ -34,7 +34,7 @@ function ManageUsers(props: ManageUsersProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLinkUser = async (user_id: any) => {
|
const handleLinkUser = async (user_id: any) => {
|
||||||
const res = await linkUserToUserGroup(props.usergroup_id, user_id,access_token)
|
const res = await linkUserToUserGroup(props.usergroup_id, user_id, access_token)
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success('User linked successfully')
|
toast.success('User linked successfully')
|
||||||
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
||||||
|
|
@ -44,7 +44,7 @@ function ManageUsers(props: ManageUsersProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUnlinkUser = async (user_id: any) => {
|
const handleUnlinkUser = async (user_id: any) => {
|
||||||
const res = await unLinkUserToUserGroup(props.usergroup_id, user_id,access_token)
|
const res = await unLinkUserToUserGroup(props.usergroup_id, user_id, access_token)
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success('User unlinked successfully')
|
toast.success('User unlinked successfully')
|
||||||
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { getUriWithOrg } from '@services/config/config'
|
||||||
import { deleteCourseFromBackend } from '@services/courses/courses'
|
import { deleteCourseFromBackend } from '@services/courses/courses'
|
||||||
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
|
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
|
||||||
import { revalidateTags } from '@services/utils/ts/requests'
|
import { revalidateTags } from '@services/utils/ts/requests'
|
||||||
import { Settings, X } from 'lucide-react'
|
import { BookMinus, FilePenLine, Settings, Settings2, X, EllipsisVertical } from 'lucide-react'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
@ -71,7 +71,10 @@ function CourseThumbnail(props: PropsType) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<h2 className="font-bold text-lg w-[250px] py-2">{props.course.name}</h2>
|
<div className='flex flex-col w-[250px] pt-3 space-y-2'>
|
||||||
|
<h2 className="font-bold text-gray-800 max-h-[80px] h-fit line-clamp-2 leading-tight text-lg capitalize">{props.course.name}</h2>
|
||||||
|
<h3 className='text-sm text-gray-700 leading-normal line-clamp-3'>{props.course.description}</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +92,24 @@ const AdminEditsArea = (props: {
|
||||||
checkMethod="roles"
|
checkMethod="roles"
|
||||||
orgId={props.course.org_id}
|
orgId={props.course.org_id}
|
||||||
>
|
>
|
||||||
<div className="flex space-x-2 absolute z-20 bottom-14 right-[15px] transform">
|
<div
|
||||||
|
className="flex items-center space-x-2 absolute z-20 overflow-hidden rounded-xl pt-0 mx-auto justify-center transform w-full h-[60px] bg-gradient-to-t from-transparent from-10% to-gray-900/60">
|
||||||
|
<Link
|
||||||
|
href={getUriWithOrg(
|
||||||
|
props.orgSlug,
|
||||||
|
'/dash/courses/course/' +
|
||||||
|
removeCoursePrefix(props.courseId) +
|
||||||
|
'/content'
|
||||||
|
)}
|
||||||
|
prefetch
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="hover:cursor-pointer p-1 px-4 bg-blue-600 rounded-xl items-center flex shadow-2xl"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<FilePenLine size={14} className="text-blue-200 font-bold" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={getUriWithOrg(
|
href={getUriWithOrg(
|
||||||
props.orgSlug,
|
props.orgSlug,
|
||||||
|
|
@ -97,24 +117,26 @@ const AdminEditsArea = (props: {
|
||||||
removeCoursePrefix(props.courseId) +
|
removeCoursePrefix(props.courseId) +
|
||||||
'/general'
|
'/general'
|
||||||
)}
|
)}
|
||||||
|
prefetch
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className=" hover:cursor-pointer p-1 px-4 bg-slate-700 rounded-xl items-center flex shadow-xl"
|
className=" hover:cursor-pointer p-1 px-4 bg-gray-800 rounded-xl items-center flex shadow-2xl"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Settings size={14} className="text-slate-200 font-bold" />
|
<Settings2 size={14} className="text-gray-200 font-bold" />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
<EllipsisVertical size={14} className='text-gray-200 font-bold' />
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
confirmationButtonText="Delete Course"
|
confirmationButtonText="Delete Course"
|
||||||
confirmationMessage="Are you sure you want to delete this course?"
|
confirmationMessage="Are you sure you want to delete this course?"
|
||||||
dialogTitle={'Delete ' + props.course.name + ' ?'}
|
dialogTitle={'Delete ' + props.course.name + ' ?'}
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<div
|
<div
|
||||||
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-xl items-center justify-center flex shadow-xl"
|
className="hover:cursor-pointer p-1 px-4 bg-rose-600 h-fit rounded-xl items-center flex shadow-2xl"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<X size={14} className="text-rose-200 font-bold" />
|
<BookMinus size={14} className="text-rose-200 font-bold" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
functionToExecute={() => props.deleteCourses(props.courseId)}
|
functionToExecute={() => props.deleteCourses(props.courseId)}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export const HeaderProfileBox = () => {
|
||||||
<AccountArea className="space-x-0">
|
<AccountArea className="space-x-0">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className='flex items-center space-x-2' >
|
<div className='flex items-center space-x-2' >
|
||||||
<p className='text-sm'>{session.data.user.username}</p>
|
<p className='text-sm capitalize'>{session.data.user.username}</p>
|
||||||
{isUserAdmin.isAdmin && <div className="text-[10px] bg-rose-300 px-2 font-bold rounded-md shadow-inner py-1">ADMIN</div>}
|
{isUserAdmin.isAdmin && <div className="text-[10px] bg-rose-300 px-2 font-bold rounded-md shadow-inner py-1">ADMIN</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@
|
||||||
"katex": "^0.16.10",
|
"katex": "^0.16.10",
|
||||||
"lowlight": "^3.1.0",
|
"lowlight": "^3.1.0",
|
||||||
"lucide-react": "^0.363.0",
|
"lucide-react": "^0.363.0",
|
||||||
"next": "14.2.3",
|
"next": "14.2.4",
|
||||||
"next-auth": "^4.24.7",
|
"next-auth": "^4.24.7",
|
||||||
|
"nextjs-toploader": "^1.6.12",
|
||||||
"prosemirror-state": "^1.4.3",
|
"prosemirror-state": "^1.4.3",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
"re-resizable": "^6.9.17",
|
"re-resizable": "^6.9.17",
|
||||||
|
|
@ -59,7 +60,7 @@
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"y-indexeddb": "^9.0.12",
|
"y-indexeddb": "^9.0.12",
|
||||||
"y-prosemirror": "^1.2.6",
|
"y-prosemirror": "^1.2.8",
|
||||||
"y-webrtc": "^10.3.0",
|
"y-webrtc": "^10.3.0",
|
||||||
"yjs": "^13.6.16"
|
"yjs": "^13.6.16"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
147
apps/web/pnpm-lock.yaml
generated
147
apps/web/pnpm-lock.yaml
generated
|
|
@ -43,10 +43,10 @@ importers:
|
||||||
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/extension-code-block@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)
|
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/extension-code-block@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)
|
||||||
'@tiptap/extension-collaboration':
|
'@tiptap/extension-collaboration':
|
||||||
specifier: ^2.4.0
|
specifier: ^2.4.0
|
||||||
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)(y-prosemirror@1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))
|
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)(y-prosemirror@1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))
|
||||||
'@tiptap/extension-collaboration-cursor':
|
'@tiptap/extension-collaboration-cursor':
|
||||||
specifier: ^2.4.0
|
specifier: ^2.4.0
|
||||||
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(y-prosemirror@1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))
|
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(y-prosemirror@1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))
|
||||||
'@tiptap/extension-youtube':
|
'@tiptap/extension-youtube':
|
||||||
specifier: ^2.4.0
|
specifier: ^2.4.0
|
||||||
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))
|
version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))
|
||||||
|
|
@ -93,11 +93,14 @@ importers:
|
||||||
specifier: ^0.363.0
|
specifier: ^0.363.0
|
||||||
version: 0.363.0(react@18.3.1)
|
version: 0.363.0(react@18.3.1)
|
||||||
next:
|
next:
|
||||||
specifier: 14.2.3
|
specifier: 14.2.4
|
||||||
version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: ^4.24.7
|
specifier: ^4.24.7
|
||||||
version: 4.24.7(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 4.24.7(next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
nextjs-toploader:
|
||||||
|
specifier: ^1.6.12
|
||||||
|
version: 1.6.12(next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
prosemirror-state:
|
prosemirror-state:
|
||||||
specifier: ^1.4.3
|
specifier: ^1.4.3
|
||||||
version: 1.4.3
|
version: 1.4.3
|
||||||
|
|
@ -153,8 +156,8 @@ importers:
|
||||||
specifier: ^9.0.12
|
specifier: ^9.0.12
|
||||||
version: 9.0.12(yjs@13.6.16)
|
version: 9.0.12(yjs@13.6.16)
|
||||||
y-prosemirror:
|
y-prosemirror:
|
||||||
specifier: ^1.2.6
|
specifier: ^1.2.8
|
||||||
version: 1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16)
|
version: 1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16)
|
||||||
y-webrtc:
|
y-webrtc:
|
||||||
specifier: ^10.3.0
|
specifier: ^10.3.0
|
||||||
version: 10.3.0(yjs@13.6.16)
|
version: 10.3.0(yjs@13.6.16)
|
||||||
|
|
@ -427,62 +430,62 @@ packages:
|
||||||
'@lifeomic/attempt@3.1.0':
|
'@lifeomic/attempt@3.1.0':
|
||||||
resolution: {integrity: sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==}
|
resolution: {integrity: sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==}
|
||||||
|
|
||||||
'@next/env@14.2.3':
|
'@next/env@14.2.4':
|
||||||
resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==}
|
resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@14.2.3':
|
'@next/eslint-plugin-next@14.2.3':
|
||||||
resolution: {integrity: sha512-L3oDricIIjgj1AVnRdRor21gI7mShlSwU/1ZGHmqM3LzHhXXhdkrfeNY5zif25Bi5Dd7fiJHsbhoZCHfXYvlAw==}
|
resolution: {integrity: sha512-L3oDricIIjgj1AVnRdRor21gI7mShlSwU/1ZGHmqM3LzHhXXhdkrfeNY5zif25Bi5Dd7fiJHsbhoZCHfXYvlAw==}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@14.2.3':
|
'@next/swc-darwin-arm64@14.2.4':
|
||||||
resolution: {integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==}
|
resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-darwin-x64@14.2.3':
|
'@next/swc-darwin-x64@14.2.4':
|
||||||
resolution: {integrity: sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==}
|
resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@14.2.3':
|
'@next/swc-linux-arm64-gnu@14.2.4':
|
||||||
resolution: {integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==}
|
resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@14.2.3':
|
'@next/swc-linux-arm64-musl@14.2.4':
|
||||||
resolution: {integrity: sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==}
|
resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@14.2.3':
|
'@next/swc-linux-x64-gnu@14.2.4':
|
||||||
resolution: {integrity: sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==}
|
resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@14.2.3':
|
'@next/swc-linux-x64-musl@14.2.4':
|
||||||
resolution: {integrity: sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==}
|
resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@14.2.3':
|
'@next/swc-win32-arm64-msvc@14.2.4':
|
||||||
resolution: {integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==}
|
resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/swc-win32-ia32-msvc@14.2.3':
|
'@next/swc-win32-ia32-msvc@14.2.4':
|
||||||
resolution: {integrity: sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==}
|
resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@14.2.3':
|
'@next/swc-win32-x64-msvc@14.2.4':
|
||||||
resolution: {integrity: sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==}
|
resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
@ -2082,8 +2085,8 @@ packages:
|
||||||
nodemailer:
|
nodemailer:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
next@14.2.3:
|
next@14.2.4:
|
||||||
resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==}
|
resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==}
|
||||||
engines: {node: '>=18.17.0'}
|
engines: {node: '>=18.17.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -2100,6 +2103,13 @@ packages:
|
||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
nextjs-toploader@1.6.12:
|
||||||
|
resolution: {integrity: sha512-nbun5lvVjlKnxLQlahzZ55nELVEduqoEXT03KCHnsEYJnFpI/3BaIzpMyq/v8C7UGU2NfxQmjq6ldZ310rsDqA==}
|
||||||
|
peerDependencies:
|
||||||
|
next: '>= 6.0.0'
|
||||||
|
react: '>= 16.0.0'
|
||||||
|
react-dom: '>= 16.0.0'
|
||||||
|
|
||||||
node-releases@2.0.14:
|
node-releases@2.0.14:
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||||
|
|
||||||
|
|
@ -2111,6 +2121,9 @@ packages:
|
||||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
nprogress@0.2.0:
|
||||||
|
resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
|
||||||
|
|
||||||
oauth@0.9.15:
|
oauth@0.9.15:
|
||||||
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
||||||
|
|
||||||
|
|
@ -2891,8 +2904,8 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
yjs: ^13.0.0
|
yjs: ^13.0.0
|
||||||
|
|
||||||
y-prosemirror@1.2.6:
|
y-prosemirror@1.2.8:
|
||||||
resolution: {integrity: sha512-rGz8kX4v/uFJrLaqZvsezY1JGN/zTDSPMO76zRbNcpE63OEiw2PBCEQi9ZlfbEwgCMoeJLUT+otNyO/Oj73TGQ==}
|
resolution: {integrity: sha512-xNDOEe9ViBXck0qwcTvzGgj832ecoz8GQSppoh6PwUokbXoEBDbAH76Qs15HOiatjZkSODHRGdpYlLBBkJPiGA==}
|
||||||
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prosemirror-model: ^1.7.1
|
prosemirror-model: ^1.7.1
|
||||||
|
|
@ -3137,37 +3150,37 @@ snapshots:
|
||||||
|
|
||||||
'@lifeomic/attempt@3.1.0': {}
|
'@lifeomic/attempt@3.1.0': {}
|
||||||
|
|
||||||
'@next/env@14.2.3': {}
|
'@next/env@14.2.4': {}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@14.2.3':
|
'@next/eslint-plugin-next@14.2.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
glob: 10.3.10
|
glob: 10.3.10
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@14.2.3':
|
'@next/swc-darwin-arm64@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-darwin-x64@14.2.3':
|
'@next/swc-darwin-x64@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@14.2.3':
|
'@next/swc-linux-arm64-gnu@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@14.2.3':
|
'@next/swc-linux-arm64-musl@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@14.2.3':
|
'@next/swc-linux-x64-gnu@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@14.2.3':
|
'@next/swc-linux-x64-musl@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@14.2.3':
|
'@next/swc-win32-arm64-msvc@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-ia32-msvc@14.2.3':
|
'@next/swc-win32-ia32-msvc@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@14.2.3':
|
'@next/swc-win32-x64-msvc@14.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
|
|
@ -3536,16 +3549,16 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
|
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
|
||||||
|
|
||||||
'@tiptap/extension-collaboration-cursor@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(y-prosemirror@1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))':
|
'@tiptap/extension-collaboration-cursor@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(y-prosemirror@1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
|
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
|
||||||
y-prosemirror: 1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16)
|
y-prosemirror: 1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16)
|
||||||
|
|
||||||
'@tiptap/extension-collaboration@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)(y-prosemirror@1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))':
|
'@tiptap/extension-collaboration@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)(y-prosemirror@1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
|
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
|
||||||
'@tiptap/pm': 2.4.0
|
'@tiptap/pm': 2.4.0
|
||||||
y-prosemirror: 1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16)
|
y-prosemirror: 1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16)
|
||||||
|
|
||||||
'@tiptap/extension-document@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))':
|
'@tiptap/extension-document@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -4237,7 +4250,7 @@ snapshots:
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
|
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
|
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
|
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
|
||||||
eslint-plugin-react: 7.34.2(eslint@8.57.0)
|
eslint-plugin-react: 7.34.2(eslint@8.57.0)
|
||||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
|
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
|
||||||
|
|
@ -4261,7 +4274,7 @@ snapshots:
|
||||||
enhanced-resolve: 5.17.0
|
enhanced-resolve: 5.17.0
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
|
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||||
fast-glob: 3.3.2
|
fast-glob: 3.3.2
|
||||||
get-tsconfig: 4.7.5
|
get-tsconfig: 4.7.5
|
||||||
is-core-module: 2.13.1
|
is-core-module: 2.13.1
|
||||||
|
|
@ -4283,7 +4296,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
|
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.8
|
||||||
array.prototype.findlastindex: 1.2.5
|
array.prototype.findlastindex: 1.2.5
|
||||||
|
|
@ -4928,13 +4941,13 @@ snapshots:
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
next-auth@4.24.7(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next-auth@4.24.7(next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.24.7
|
'@babel/runtime': 7.24.7
|
||||||
'@panva/hkdf': 1.1.1
|
'@panva/hkdf': 1.1.1
|
||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
jose: 4.15.5
|
jose: 4.15.5
|
||||||
next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
oauth: 0.9.15
|
oauth: 0.9.15
|
||||||
openid-client: 5.6.5
|
openid-client: 5.6.5
|
||||||
preact: 10.22.0
|
preact: 10.22.0
|
||||||
|
|
@ -4943,9 +4956,9 @@ snapshots:
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
|
|
||||||
next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 14.2.3
|
'@next/env': 14.2.4
|
||||||
'@swc/helpers': 0.5.5
|
'@swc/helpers': 0.5.5
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
caniuse-lite: 1.0.30001632
|
caniuse-lite: 1.0.30001632
|
||||||
|
|
@ -4955,25 +4968,35 @@ snapshots:
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
styled-jsx: 5.1.1(react@18.3.1)
|
styled-jsx: 5.1.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 14.2.3
|
'@next/swc-darwin-arm64': 14.2.4
|
||||||
'@next/swc-darwin-x64': 14.2.3
|
'@next/swc-darwin-x64': 14.2.4
|
||||||
'@next/swc-linux-arm64-gnu': 14.2.3
|
'@next/swc-linux-arm64-gnu': 14.2.4
|
||||||
'@next/swc-linux-arm64-musl': 14.2.3
|
'@next/swc-linux-arm64-musl': 14.2.4
|
||||||
'@next/swc-linux-x64-gnu': 14.2.3
|
'@next/swc-linux-x64-gnu': 14.2.4
|
||||||
'@next/swc-linux-x64-musl': 14.2.3
|
'@next/swc-linux-x64-musl': 14.2.4
|
||||||
'@next/swc-win32-arm64-msvc': 14.2.3
|
'@next/swc-win32-arm64-msvc': 14.2.4
|
||||||
'@next/swc-win32-ia32-msvc': 14.2.3
|
'@next/swc-win32-ia32-msvc': 14.2.4
|
||||||
'@next/swc-win32-x64-msvc': 14.2.3
|
'@next/swc-win32-x64-msvc': 14.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
|
||||||
|
nextjs-toploader@1.6.12(next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
next: 14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
nprogress: 0.2.0
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
node-releases@2.0.14: {}
|
node-releases@2.0.14: {}
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
normalize-range@0.1.2: {}
|
normalize-range@0.1.2: {}
|
||||||
|
|
||||||
|
nprogress@0.2.0: {}
|
||||||
|
|
||||||
oauth@0.9.15: {}
|
oauth@0.9.15: {}
|
||||||
|
|
||||||
object-assign@4.1.1: {}
|
object-assign@4.1.1: {}
|
||||||
|
|
@ -5880,7 +5903,7 @@ snapshots:
|
||||||
lib0: 0.2.94
|
lib0: 0.2.94
|
||||||
yjs: 13.6.16
|
yjs: 13.6.16
|
||||||
|
|
||||||
y-prosemirror@1.2.6(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16):
|
y-prosemirror@1.2.8(prosemirror-model@1.21.1)(prosemirror-state@1.4.3)(prosemirror-view@1.33.7)(y-protocols@1.0.6(yjs@13.6.16))(yjs@13.6.16):
|
||||||
dependencies:
|
dependencies:
|
||||||
lib0: 0.2.94
|
lib0: 0.2.94
|
||||||
prosemirror-model: 1.21.1
|
prosemirror-model: 1.21.1
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,5 @@ export const getDefaultOrg = () => {
|
||||||
export const getCollaborationServerUrl = () => {
|
export const getCollaborationServerUrl = () => {
|
||||||
return `${LEARNHOUSE_COLLABORATION_WS_URL}`
|
return `${LEARNHOUSE_COLLABORATION_WS_URL}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue