From 866111aa4cec335c61f93196389c63e5a54aa797 Mon Sep 17 00:00:00 2001 From: swve Date: Thu, 13 Jun 2024 22:42:30 +0100 Subject: [PATCH 01/10] feat: usergroups bugs --- .../Dash/OrgAccess/OrgInviteCodeGenerate.tsx | 3 +- .../Dash/OrgUserGroups/AddUserGroup.tsx | 109 +++++++++--------- .../Modals/Dash/OrgUserGroups/ManageUsers.tsx | 10 +- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx b/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx index ce67f16a..d2b46a11 100644 --- a/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx +++ b/apps/web/components/Objects/Modals/Dash/OrgAccess/OrgInviteCodeGenerate.tsx @@ -17,9 +17,10 @@ function OrgInviteCodeGenerate(props: OrgInviteCodeGenerateProps) { const session = useLHSession() as any const access_token = session?.data?.tokens?.access_token; const [usergroup_id, setUsergroup_id] = React.useState(0); + const { data: usergroups } = useSWR( org ? `${getAPIUrl()}usergroups/org/${org.id}` : null, - swrFetcher + (url) => swrFetcher(url, access_token) ) async function createInviteWithUserGroup() { diff --git a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx index 9429cd08..9325e264 100644 --- a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx +++ b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/AddUserGroup.tsx @@ -4,6 +4,7 @@ import FormLayout, { Flex, FormField, FormLabel, + FormLabelAndMessage, FormMessage, Input, } from '@components/StyledElements/Form/Form' @@ -15,86 +16,84 @@ import { createUserGroup } from '@services/usergroups/usergroups' import { mutate } from 'swr' import { getAPIUrl } from '@services/config/config' import { useLHSession } from '@components/Contexts/LHSessionContext' +import { useFormik } from 'formik' type AddUserGroupProps = { setCreateUserGroupModal: any } +const validate = (values: any) => { + const errors: any = {} + + if (!values.name) { + errors.name = 'Name is Required' + } + + return errors +} function AddUserGroup(props: AddUserGroupProps) { const org = useOrg() as any; const session = useLHSession() as any 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 handleNameChange = (event: React.ChangeEvent) => { - setUserGroupName(event.target.value) - } - - const handleDescriptionChange = (event: React.ChangeEvent) => { - setUserGroupDescription(event.target.value) - } - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault() - setIsSubmitting(true) - - const obj = { - name: userGroupName, - description: userGroupDescription, + const formik = useFormik({ + initialValues: { + name: '', + description: '', org_id: org.id - } - const res = await createUserGroup(obj, access_token) - if (res.status == 200) { - setIsSubmitting(false) - mutate(`${getAPIUrl()}usergroups/org/${org.id}`) - props.setCreateUserGroupModal(false) + }, + validate, + onSubmit: async (values) => { + setIsSubmitting(true) + const res = await createUserGroup(values, access_token) + if (res.status == 200) { + setIsSubmitting(false) + mutate(`${getAPIUrl()}usergroups/org/${org.id}`) + props.setCreateUserGroupModal(false) - } else { - setIsSubmitting(false) - } - } + } else { + setIsSubmitting(false) + } + }, + }) return ( - + - - Name - - Please provide a ug name - - + - + - - Description - - Please provide a ug description - - + - + - +
- - {isSubmitting ? ( - - ) : ( - 'Create UserGroup' - )} - + - +
) } diff --git a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx index f47f9cf1..3e41c8de 100644 --- a/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx +++ b/apps/web/components/Objects/Modals/Dash/OrgUserGroups/ManageUsers.tsx @@ -16,14 +16,14 @@ type ManageUsersProps = { function ManageUsers(props: ManageUsersProps) { const org = useOrg() 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( org ? `${getAPIUrl()}orgs/${org.id}/users` : null, - swrFetcher + (url) => swrFetcher(url, access_token) ) const { data: UGusers } = useSWR( org ? `${getAPIUrl()}usergroups/${props.usergroup_id}/users` : null, - swrFetcher + (url) => swrFetcher(url, access_token) ) const isUserPartOfGroup = (user_id: any) => { @@ -34,7 +34,7 @@ function ManageUsers(props: ManageUsersProps) { } 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) { toast.success('User linked successfully') mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`) @@ -44,7 +44,7 @@ function ManageUsers(props: ManageUsersProps) { } 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) { toast.success('User unlinked successfully') mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`) From 29219391ea52c9fb017c6d6c5dbda43cf55f91e8 Mon Sep 17 00:00:00 2001 From: swve Date: Thu, 13 Jun 2024 23:00:33 +0100 Subject: [PATCH 02/10] feat: add loading indicator and improve loading screens --- apps/web/app/orgs/[orgslug]/layout.tsx | 2 ++ .../web/components/Contexts/CourseContext.tsx | 3 +- apps/web/components/Contexts/OrgContext.tsx | 2 +- apps/web/package.json | 1 + apps/web/pnpm-lock.yaml | 29 +++++++++++++++++-- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/apps/web/app/orgs/[orgslug]/layout.tsx b/apps/web/app/orgs/[orgslug]/layout.tsx index d722fb9f..de924c3d 100644 --- a/apps/web/app/orgs/[orgslug]/layout.tsx +++ b/apps/web/app/orgs/[orgslug]/layout.tsx @@ -1,5 +1,6 @@ 'use client' import { OrgProvider } from '@components/Contexts/OrgContext' +import NextTopLoader from 'nextjs-toploader'; import Toast from '@components/StyledElements/Toast/Toast' import '@styles/globals.css' @@ -13,6 +14,7 @@ export default function RootLayout({ return (
+ {children} diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx index 11d7d5df..4df1ac6b 100644 --- a/apps/web/components/Contexts/CourseContext.tsx +++ b/apps/web/components/Contexts/CourseContext.tsx @@ -4,6 +4,7 @@ import { swrFetcher } from '@services/utils/ts/requests' import React, { createContext, useContext, useEffect, useReducer } from 'react' import useSWR from 'swr' import { useLHSession } from '@components/Contexts/LHSessionContext' +import PageLoading from '@components/Objects/Loaders/PageLoading' export const CourseContext = createContext(null) export const CourseDispatchContext = createContext(null) @@ -33,7 +34,7 @@ export function CourseProvider({ children, courseuuid }: any) { }, [courseStructureData]); if (error) return
Failed to load course structure
; - if (!courseStructureData) return
Loading...
; + if (!courseStructureData) return ; return ( diff --git a/apps/web/components/Contexts/OrgContext.tsx b/apps/web/components/Contexts/OrgContext.tsx index f434b3f1..d9482fed 100644 --- a/apps/web/components/Contexts/OrgContext.tsx +++ b/apps/web/components/Contexts/OrgContext.tsx @@ -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]) if (orgError || orgsError) return - if (!org || !orgs || !session) return
Loading...
+ if (!org || !orgs || !session) return
if (!isOrgActive) return if (!isUserPartOfTheOrg && session.status == 'authenticated' && !isAllowedPathname) { return ( diff --git a/apps/web/package.json b/apps/web/package.json index 9d8dc990..a265be62 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -41,6 +41,7 @@ "lucide-react": "^0.363.0", "next": "14.2.3", "next-auth": "^4.24.7", + "nextjs-toploader": "^1.6.12", "prosemirror-state": "^1.4.3", "randomcolor": "^0.6.2", "re-resizable": "^6.9.17", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index e39fc0e6..81df49ee 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: next-auth: 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) + nextjs-toploader: + specifier: ^1.6.12 + version: 1.6.12(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) prosemirror-state: specifier: ^1.4.3 version: 1.4.3 @@ -2100,6 +2103,13 @@ packages: sass: 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: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} @@ -2111,6 +2121,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + oauth@0.9.15: resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} @@ -4237,7 +4250,7 @@ snapshots: eslint: 8.57.0 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-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-react: 7.34.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 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 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -4283,7 +4296,7 @@ snapshots: transitivePeerDependencies: - 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: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -4968,12 +4981,22 @@ snapshots: - '@babel/core' - babel-plugin-macros + nextjs-toploader@1.6.12(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): + dependencies: + next: 14.2.3(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: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} + nprogress@0.2.0: {} + oauth@0.9.15: {} object-assign@4.1.1: {} From f524ddb51ac119afaeed291504f4d3c9fa5fc4b8 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 14 Jun 2024 00:18:23 +0100 Subject: [PATCH 03/10] feat: various course edition improvements --- apps/web/app/orgs/[orgslug]/dash/page.tsx | 9 +- .../web/components/Contexts/CourseContext.tsx | 18 +- .../EditCourseAccess/EditCourseAccess.tsx | 287 ++++++++---------- .../DraggableElements/ActivityElement.tsx | 14 +- .../Dash/EditCourseAccess/LinkToUserGroup.tsx | 34 ++- 5 files changed, 179 insertions(+), 183 deletions(-) diff --git a/apps/web/app/orgs/[orgslug]/dash/page.tsx b/apps/web/app/orgs/[orgslug]/dash/page.tsx index 9f104e12..acc01033 100644 --- a/apps/web/app/orgs/[orgslug]/dash/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/page.tsx @@ -1,7 +1,7 @@ import Image from 'next/image' import React from 'react' 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 AdminAuthorization from '@components/Security/AdminAuthorization' @@ -62,12 +62,13 @@ function DashboardHome() {
- +
- Learn LearnHouse + LearnHouse University
diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx index 4df1ac6b..bc615cc3 100644 --- a/apps/web/components/Contexts/CourseContext.tsx +++ b/apps/web/components/Contexts/CourseContext.tsx @@ -34,15 +34,17 @@ export function CourseProvider({ children, courseuuid }: any) { }, [courseStructureData]); if (error) return
Failed to load course structure
; - if (!courseStructureData) return ; + if (!courseStructureData) return ; - return ( - - - {children} - - - ) + if (courseStructureData) { + return ( + + + {children} + + + ) + } } export function useCourse() { diff --git a/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx b/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx index 38ec8b3c..eac3c8db 100644 --- a/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx @@ -7,7 +7,7 @@ import { unLinkResourcesToUserGroup } from '@services/usergroups/usergroups' import { swrFetcher } from '@services/utils/ts/requests' import { Globe, SquareUserRound, Users, X } from 'lucide-react' import { useLHSession } from '@components/Contexts/LHSessionContext' -import React from 'react' +import React, { useEffect, useState } from 'react' import toast from 'react-hot-toast' import useSWR, { mutate } from 'swr' @@ -17,132 +17,132 @@ type EditCourseAccessProps = { } function EditCourseAccess(props: EditCourseAccessProps) { - const [error, setError] = React.useState('') const session = useLHSession() as any; const access_token = session?.data?.tokens?.access_token; - const course = useCourse() as any; const { isLoading, courseStructure } = course 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 dispatchCourse = useCourseDispatch() as any; + const { data: usergroups } = useSWR(courseStructure ? `${getAPIUrl()}usergroups/resource/${courseStructure.course_uuid}` : null, (url) => swrFetcher(url, access_token)); + const [isClientPublic, setIsClientPublic] = useState(undefined); - React.useEffect(() => { - // This code will run whenever form values are updated - if ((isPublic !== courseStructure.public) && isLoading) { - dispatchCourse({ type: 'setIsNotSaved' }) - const updatedCourse = { - ...courseStructure, - public: isPublic, - } - dispatchCourse({ type: 'setCourseStructure', payload: updatedCourse }) + useEffect(() => { + if (!isLoading && courseStructure?.public !== undefined) { + setIsClientPublic(courseStructure.public); } - }, [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 (
- {' '} -
-
-
-

Access to the course

-

- {' '} - Choose if want your course to be publicly available on the internet or only accessible to signed in users{' '} -

+ {courseStructure && ( +
+
+
+
+

Access to the course

+

+ Choose if you want your course to be publicly available on the internet or only accessible to signed in users +

+
+
+ + {isClientPublic && ( +
+ Active +
+ )} +
+ +
+ Public +
+
+ The Course is publicly available on the internet, it is indexed by search engines and can be accessed by anyone +
+
+
+ } + functionToExecute={() => setIsClientPublic(true)} + status="info" + /> + + {!isClientPublic && ( +
+ Active +
+ )} +
+ +
+ Users Only +
+
+ The Course is only accessible to signed in users, additionally you can choose which UserGroups can access this course +
+
+
+ } + functionToExecute={() => setIsClientPublic(false)} + status="info" + /> +
+ {!isClientPublic && } +
-
- - {isPublic ? ( -
- Active -
- ) : null} -
- -
- Public -
-
- The Course is publicly available on the internet, it is indexed by search engines and can be accessed by anyone -
-
- -
- } - functionToExecute={() => { - setIsPublic(true) - }} - status="info" - > - - {!isPublic ? ( -
- Active -
- ) : null} -
- -
- Users Only -
-
- The Course is only accessible to signed in users, additionaly you can choose which UserGroups can access this course -
-
- -
- } - functionToExecute={() => { - setIsPublic(false) - }} - status="info" - > -
- {!isPublic ? () : null} - + )} - ) + ); } - function UserGroupsSection({ usergroups }: { usergroups: any[] }) { - const course = useCourse() as any - const [userGroupModal, setUserGroupModal] = React.useState(false) + const course = useCourse() as any; + const [userGroupModal, setUserGroupModal] = useState(false); const session = useLHSession() as any; const access_token = session?.data?.tokens?.access_token; const removeUserGroupLink = async (usergroup_id: number) => { - const res = await unLinkResourcesToUserGroup(usergroup_id, course.courseStructure.course_uuid, access_token) - if (res.status === 200) { - toast.success('Successfully unliked from usergroup') - mutate(`${getAPIUrl()}usergroups/resource/${course.courseStructure.course_uuid}`) + try { + const res = await unLinkResourcesToUserGroup(usergroup_id, course.courseStructure.course_uuid, access_token); + if (res.status === 200) { + 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 ( <> -
+

UserGroups

- {' '} - 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

@@ -152,67 +152,48 @@ function UserGroupsSection({ usergroups }: { usergroups: any[] }) { - <> - - {usergroups?.map((usergroup: any) => ( - - - - - ))} - - + + {usergroups?.map((usergroup: any) => ( + + + + + ))} +
Actions
{usergroup.name} - - - Delete link - - } - functionToExecute={() => { - removeUserGroupLink(usergroup.id) - }} - status="warning" - > -
{usergroup.name} + + + Delete link + + } + functionToExecute={() => removeUserGroupLink(usergroup.id)} + status="warning" + /> +
-
+
- setUserGroupModal(!userGroupModal) - } + isDialogOpen={userGroupModal} + onOpenChange={() => setUserGroupModal(!userGroupModal)} minHeight="no-min" - minWidth='md' - dialogContent={ - - - } + minWidth="md" + dialogContent={} dialogTitle="Link Course to a UserGroup" - dialogDescription={ - 'Choose a UserGroup to link this course to, Users from this UserGroup will have access to this course.' - } + dialogDescription="Choose a UserGroup to link this course to. Users from this UserGroup will have access to this course." dialogTrigger={ - } /> -
- ) + ); } -export default EditCourseAccess \ No newline at end of file +export default EditCourseAccess; diff --git a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx index d001fa06..a079aa01 100644 --- a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx @@ -5,6 +5,7 @@ import { revalidateTags } from '@services/utils/ts/requests' import { Eye, File, + FilePenLine, MoreVertical, Pencil, Save, @@ -44,7 +45,7 @@ function ActivityElement(props: ActivitiyElementProps) { const activityUUID = props.activity.activity_uuid 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`) await revalidateTags(['courses'], props.orgslug) router.refresh() @@ -63,7 +64,7 @@ function ActivityElement(props: ActivitiyElementProps) { content: props.activity.content, } - await updateActivity(modifiedActivityCopy, activityUUID,access_token) + await updateActivity(modifiedActivityCopy, activityUUID, access_token) mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`) await revalidateTags(['courses'], props.orgslug) router.refresh() @@ -144,7 +145,9 @@ function ActivityElement(props: ActivitiyElementProps) { className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center" rel="noopener noreferrer" > -
Edit
+
+ Edit Page +
)} @@ -159,10 +162,11 @@ function ActivityElement(props: ActivitiyElementProps) { '' )}` } - className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md" + 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" > - + + Preview
{/* Delete Button */} diff --git a/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx b/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx index cc4b8470..867b03eb 100644 --- a/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx +++ b/apps/web/components/Objects/Modals/Dash/EditCourseAccess/LinkToUserGroup.tsx @@ -2,10 +2,11 @@ import { useCourse } from '@components/Contexts/CourseContext'; import { useLHSession } from '@components/Contexts/LHSessionContext'; 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 { swrFetcher } from '@services/utils/ts/requests'; import { Info } from 'lucide-react'; +import Link from 'next/link'; import React, { useEffect } from 'react' import toast from 'react-hot-toast'; import useSWR, { mutate } from 'swr' @@ -24,7 +25,7 @@ function LinkToUserGroup(props: LinkToUserGroupProps) { const { data: usergroups } = useSWR( courseStructure && org ? `${getAPIUrl()}usergroups/org/${org.id}` : null, - swrFetcher + (url) => swrFetcher(url, access_token) ) const [selectedUserGroup, setSelectedUserGroup] = React.useState(null) as any @@ -55,19 +56,26 @@ function LinkToUserGroup(props: LinkToUserGroupProps) {

Users that are not part of the UserGroup will no longer have access to this course

+ {usergroups?.length >= 1 && +
+ UserGroup Name -
- UserGroup Name - setSelectedUserGroup(e.target.value)} + defaultValue={selectedUserGroup} + > + {usergroups && usergroups.map((group: any) => ( + + ))} - -
+ + +
} + {usergroups?.length == 0 && +
+ No UserGroups available + Create a UserGroup +
}
From 0fb213ee4eb45969bae7bce5cdec26a37f1142a3 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 14 Jun 2024 00:35:57 +0100 Subject: [PATCH 04/10] fix: Error when you try to edit a course from a button --- .../activity/[activityuuid]/edit/page.tsx | 1 + .../web/components/Contexts/CourseContext.tsx | 2 +- .../DraggableElements/ActivityElement.tsx | 4 +++- .../Objects/Editor/EditorWrapper.tsx | 6 ++--- apps/web/package.json | 2 +- apps/web/pnpm-lock.yaml | 22 +++++++++---------- apps/web/services/config/config.ts | 2 ++ 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx index 72183f3a..4dca9687 100644 --- a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx +++ b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx @@ -49,6 +49,7 @@ const EditActivity = async (params: any) => { { revalidate: 0, tags: ['activities'] }, access_token ? access_token : null ) + const org = await getOrganizationContextInfoWithId(courseInfo.org_id, { revalidate: 180, tags: ['organizations'], diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx index bc615cc3..f407cd00 100644 --- a/apps/web/components/Contexts/CourseContext.tsx +++ b/apps/web/components/Contexts/CourseContext.tsx @@ -34,7 +34,7 @@ export function CourseProvider({ children, courseuuid }: any) { }, [courseStructureData]); if (error) return
Failed to load course structure
; - if (!courseStructureData) return ; + if (!courseStructureData) return ''; if (courseStructureData) { return ( diff --git a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx index a079aa01..a76c72e7 100644 --- a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx @@ -142,8 +142,9 @@ function ActivityElement(props: ActivitiyElementProps) { '' )}/edit` } + prefetch 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 >
Edit Page @@ -162,6 +163,7 @@ function ActivityElement(props: ActivitiyElementProps) { '' )}` } + 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" > diff --git a/apps/web/components/Objects/Editor/EditorWrapper.tsx b/apps/web/components/Objects/Editor/EditorWrapper.tsx index aa0c2364..6353a297 100644 --- a/apps/web/components/Objects/Editor/EditorWrapper.tsx +++ b/apps/web/components/Objects/Editor/EditorWrapper.tsx @@ -25,7 +25,7 @@ interface EditorWrapperProps { function EditorWrapper(props: EditorWrapperProps): JSX.Element { 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 const [provider, setProvider] = React.useState(null); const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string) @@ -51,7 +51,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element { }, 10); }; - + // Store the Y document in the browser new IndexeddbPersistence(props.activity.activity_uuid, doc) @@ -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...', success: Activity saved!, error: Could not save., diff --git a/apps/web/package.json b/apps/web/package.json index a265be62..f32535f1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -60,7 +60,7 @@ "tailwind-scrollbar": "^3.1.0", "uuid": "^9.0.1", "y-indexeddb": "^9.0.12", - "y-prosemirror": "^1.2.6", + "y-prosemirror": "^1.2.8", "y-webrtc": "^10.3.0", "yjs": "^13.6.16" }, diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 81df49ee..b01b013f 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -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) '@tiptap/extension-collaboration': 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': 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': specifier: ^2.4.0 version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) @@ -156,8 +156,8 @@ importers: specifier: ^9.0.12 version: 9.0.12(yjs@13.6.16) y-prosemirror: - specifier: ^1.2.6 - 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) + specifier: ^1.2.8 + 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: specifier: ^10.3.0 version: 10.3.0(yjs@13.6.16) @@ -2904,8 +2904,8 @@ packages: peerDependencies: yjs: ^13.0.0 - y-prosemirror@1.2.6: - resolution: {integrity: sha512-rGz8kX4v/uFJrLaqZvsezY1JGN/zTDSPMO76zRbNcpE63OEiw2PBCEQi9ZlfbEwgCMoeJLUT+otNyO/Oj73TGQ==} + y-prosemirror@1.2.8: + resolution: {integrity: sha512-xNDOEe9ViBXck0qwcTvzGgj832ecoz8GQSppoh6PwUokbXoEBDbAH76Qs15HOiatjZkSODHRGdpYlLBBkJPiGA==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: prosemirror-model: ^1.7.1 @@ -3549,16 +3549,16 @@ snapshots: dependencies: '@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: '@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: '@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) + 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))': dependencies: @@ -5903,7 +5903,7 @@ snapshots: lib0: 0.2.94 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: lib0: 0.2.94 prosemirror-model: 1.21.1 diff --git a/apps/web/services/config/config.ts b/apps/web/services/config/config.ts index c9ec36d0..b7ac9d56 100644 --- a/apps/web/services/config/config.ts +++ b/apps/web/services/config/config.ts @@ -51,3 +51,5 @@ export const getDefaultOrg = () => { export const getCollaborationServerUrl = () => { return `${LEARNHOUSE_COLLABORATION_WS_URL}` } + + From 9c1fb565af8864794f45c03c72101cd75afb738e Mon Sep 17 00:00:00 2001 From: swve Date: Sat, 15 Jun 2024 14:13:48 +0100 Subject: [PATCH 05/10] fix: Empty thumbnail from Editor Bug --- apps/web/components/Objects/Editor/Editor.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/components/Objects/Editor/Editor.tsx b/apps/web/components/Objects/Editor/Editor.tsx index d66d92d5..185c8907 100644 --- a/apps/web/components/Objects/Editor/Editor.tsx +++ b/apps/web/components/Objects/Editor/Editor.tsx @@ -47,6 +47,7 @@ import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures' import Collaboration from '@tiptap/extension-collaboration' import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import ActiveAvatars from './ActiveAvatars' +import { getUriWithOrg } from '@services/config/config' interface Editor { content: string @@ -182,11 +183,11 @@ function Editor(props: Editor) { From 46e09f27c25d96353f02f34962cef0079d9a0f87 Mon Sep 17 00:00:00 2001 From: swve Date: Sat, 15 Jun 2024 14:59:11 +0100 Subject: [PATCH 06/10] fix: collections bugs --- apps/api/src/security/rbac/rbac.py | 23 ++++++++++---------- apps/api/src/services/courses/collections.py | 14 +++++++----- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/api/src/security/rbac/rbac.py b/apps/api/src/security/rbac/rbac.py index 2e92ff09..d7055a79 100644 --- a/apps/api/src/security/rbac/rbac.py +++ b/apps/api/src/security/rbac/rbac.py @@ -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", ) - if element_nature == "collections" and action == "read": - - statement = select(Collection).where( - Collection.public == True, Collection.collection_uuid == element_uuid + if element_nature == "collections" and action == "read": + statement = select(Collection).where( + 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: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, diff --git a/apps/api/src/services/courses/collections.py b/apps/api/src/services/courses/collections.py index 83f976a7..ac9627b6 100644 --- a/apps/api/src/services/courses/collections.py +++ b/apps/api/src/services/courses/collections.py @@ -28,7 +28,7 @@ from fastapi import HTTPException, status, Request async def get_collection( request: Request, collection_uuid: str, - current_user: PublicUser, + current_user: PublicUser | AnonymousUser, db_session: Session, ) -> CollectionRead: statement = select(Collection).where(Collection.collection_uuid == collection_uuid) @@ -48,6 +48,7 @@ async def get_collection( statement_all = ( select(Course) .join(CollectionCourse, Course.id == CollectionCourse.course_id) + .where(CollectionCourse.org_id == collection.org_id) .distinct(Course.id) ) @@ -57,7 +58,7 @@ async def get_collection( .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 else: statement = statement_all @@ -88,7 +89,6 @@ async def create_collection( # Add collection to database db_session.add(collection) db_session.commit() - db_session.refresh(collection) # Link courses to collection @@ -184,6 +184,7 @@ async def update_collection( statement = ( select(Course) .join(CollectionCourse, Course.id == CollectionCourse.course_id) + .where(Course.org_id == collection.org_id) .distinct(Course.id) ) @@ -255,6 +256,7 @@ async def get_collections( statement_all = ( select(Course) .join(CollectionCourse, Course.id == CollectionCourse.course_id) + .where(CollectionCourse.org_id == collection.org_id) .distinct(Course.id) ) statement_public = ( @@ -297,8 +299,10 @@ async def rbac_check( detail="User rights : You are not allowed to read this collection", ) else: - res = await authorization_verify_based_on_roles_and_authorship_and_usergroups( - request, current_user.id, action, collection_uuid, db_session + res = ( + await authorization_verify_based_on_roles_and_authorship_and_usergroups( + request, current_user.id, action, collection_uuid, db_session + ) ) return res else: From c8cff9cbb4489eb6e8a6f7ec0b13b7f75324a9dd Mon Sep 17 00:00:00 2001 From: swve Date: Sat, 15 Jun 2024 15:19:11 +0100 Subject: [PATCH 07/10] chore: misc improvements --- apps/web/app/auth/forgot/forgot.tsx | 1 + apps/web/app/auth/login/login.tsx | 17 +++++-- .../Create/NewActivityModal/DynamicCanva.tsx | 2 +- .../Dash/OrgAccess/OrgInviteCodeGenerate.tsx | 49 ++++++++++++------- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/apps/web/app/auth/forgot/forgot.tsx b/apps/web/app/auth/forgot/forgot.tsx index 8b3cb7c2..ecfce06e 100644 --- a/apps/web/app/auth/forgot/forgot.tsx +++ b/apps/web/app/auth/forgot/forgot.tsx @@ -42,6 +42,7 @@ function ForgotPasswordClient() { email: '' }, validate, + validateOnBlur: true, onSubmit: async (values) => { setIsSubmitting(true) let res = await sendResetLink(values.email, org?.id) diff --git a/apps/web/app/auth/login/login.tsx b/apps/web/app/auth/login/login.tsx index e766cf51..49dd7161 100644 --- a/apps/web/app/auth/login/login.tsx +++ b/apps/web/app/auth/login/login.tsx @@ -51,8 +51,17 @@ const LoginClient = (props: LoginClientProps) => { password: '', }, validate, - onSubmit: async (values) => { + validateOnBlur: true, + validateOnChange: true, + onSubmit: async (values, {validateForm, setErrors, setSubmitting}) => { setIsSubmitting(true) + const errors = await validateForm(values); + if (Object.keys(errors).length > 0) { + setErrors(errors); + setSubmitting(false); + return; + } + const res = await signIn('credentials', { redirect: false, email: values.email, @@ -139,7 +148,7 @@ const LoginClient = (props: LoginClientProps) => { onChange={formik.handleChange} value={formik.values.email} type="email" - required + /> @@ -155,7 +164,7 @@ const LoginClient = (props: LoginClientProps) => { onChange={formik.handleChange} value={formik.values.password} type="password" - required + /> @@ -170,7 +179,7 @@ const LoginClient = (props: LoginClientProps) => {
- diff --git a/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/DynamicCanva.tsx b/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/DynamicCanva.tsx index faff1760..7d6082ab 100644 --- a/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/DynamicCanva.tsx +++ b/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/DynamicCanva.tsx @@ -59,7 +59,7 @@ function DynamicCanvaModal({ submitActivity, chapterId, course }: any) { -