From 3014817d957773a04a4aa97b735f0d6017a154b2 Mon Sep 17 00:00:00 2001 From: swve Date: Wed, 23 Oct 2024 12:03:45 +0200 Subject: [PATCH] feat: add mobile dashboard menu, make dashboard pages responsive --- .../orgs/[orgslug]/dash/ClientAdminLayout.tsx | 18 +- .../assignments/[assignmentuuid]/page.tsx | 19 +- .../orgs/[orgslug]/dash/assignments/page.tsx | 19 +- apps/web/app/orgs/[orgslug]/dash/page.tsx | 108 ++++++------ .../dash/users/settings/[subpage]/page.tsx | 18 +- .../EditCourseAccess/EditCourseAccess.tsx | 96 ++++++----- .../DraggableElements/ActivityElement.tsx | 139 +++++++-------- .../DraggableElements/ChapterElement.tsx | 57 +++--- .../Org/OrgEditGeneral/OrgEditGeneral.tsx | 28 +-- .../UI/{LeftMenu.tsx => DashLeftMenu.tsx} | 4 +- .../Dashboard/UI/DashMobileMenu.tsx | 69 ++++++++ .../UserEditGeneral/UserEditGeneral.tsx | 163 +++++++----------- apps/web/components/Objects/Editor/Editor.tsx | 18 +- 13 files changed, 416 insertions(+), 340 deletions(-) rename apps/web/components/Dashboard/UI/{LeftMenu.tsx => DashLeftMenu.tsx} (99%) create mode 100644 apps/web/components/Dashboard/UI/DashMobileMenu.tsx diff --git a/apps/web/app/orgs/[orgslug]/dash/ClientAdminLayout.tsx b/apps/web/app/orgs/[orgslug]/dash/ClientAdminLayout.tsx index 32778e18..48370ca3 100644 --- a/apps/web/app/orgs/[orgslug]/dash/ClientAdminLayout.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/ClientAdminLayout.tsx @@ -1,8 +1,10 @@ 'use client'; -import LeftMenu from '@components/Dashboard/UI/LeftMenu' +import DashLeftMenu from '@components/Dashboard/UI/DashLeftMenu' +import DashMobileMenu from '@components/Dashboard/UI/DashMobileMenu' import AdminAuthorization from '@components/Security/AdminAuthorization' import { SessionProvider } from 'next-auth/react' -import React from 'react' +import React, { useState, useEffect } from 'react' +import { useMediaQuery } from 'usehooks-ts'; function ClientAdminLayout({ children, @@ -11,11 +13,17 @@ function ClientAdminLayout({ children: React.ReactNode params: any }) { + const isMobile = useMediaQuery('(max-width: 768px)') + return ( -
- +
+ {isMobile ? ( + + ) : ( + + )}
{children}
@@ -23,4 +31,4 @@ function ClientAdminLayout({ ) } -export default ClientAdminLayout \ No newline at end of file +export default ClientAdminLayout diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx index 1027ed60..7c3144a5 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx @@ -1,6 +1,6 @@ 'use client'; import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' -import { BookOpen, BookX, EllipsisVertical, Eye, Layers2, UserRoundPen } from 'lucide-react' +import { BookOpen, BookX, EllipsisVertical, Eye, Layers2, Monitor, UserRoundPen } from 'lucide-react' import React, { useEffect } from 'react' import { AssignmentProvider, useAssignments } from '@components/Contexts/Assignments/AssignmentContext'; import ToolTip from '@components/StyledElements/Tooltip/Tooltip'; @@ -15,12 +15,29 @@ import { updateActivity } from '@services/courses/activities'; // Lazy Loading import dynamic from 'next/dynamic'; import AssignmentEditorSubPage from './subpages/AssignmentEditorSubPage'; +import { useMediaQuery } from 'usehooks-ts'; const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/AssignmentSubmissionsSubPage')) function AssignmentEdit() { const params = useParams<{ assignmentuuid: string; }>() const searchParams = useSearchParams() const [selectedSubPage, setSelectedSubPage] = React.useState(searchParams.get('subpage') || 'editor') + const isMobile = useMediaQuery('(max-width: 767px)') + + if (isMobile) { + // TODO: Work on a better mobile experience + return ( +
+
+

Desktop Only

+ +

This page is only accessible from a desktop device.

+

Please switch to a desktop to view and manage the assignment.

+
+
+ ) + } + return (
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx index ad70f984..8e11e60b 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx @@ -46,19 +46,19 @@ function AssignmentsHome() { return (
-
+

Assignments

{courseAssignments.map((assignments: any, index: number) => ( -
+
-
+
-
+

Course

{courses[index].name}

@@ -75,10 +75,9 @@ function AssignmentsHome() {
- {assignments && assignments.map((assignment: any) => ( -
-
+
+

Assignment

@@ -86,7 +85,6 @@ function AssignmentsHome() {
{assignment.description}
- @@ -124,10 +121,8 @@ function AssignmentsHome() {
))} -
-
) } @@ -172,4 +167,4 @@ const MiniThumbnail = (props: { course: any }) => { } -export default AssignmentsHome \ No newline at end of file +export default AssignmentsHome diff --git a/apps/web/app/orgs/[orgslug]/dash/page.tsx b/apps/web/app/orgs/[orgslug]/dash/page.tsx index acc01033..a356a5fb 100644 --- a/apps/web/app/orgs/[orgslug]/dash/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/page.tsx @@ -7,84 +7,68 @@ import AdminAuthorization from '@components/Security/AdminAuthorization' function DashboardHome() { return ( -
-
+
+
learnhouse logo + className="w-48 sm:w-auto" + />
-
- -
- -
Courses
-

- Create and manage courses, chapters and ativities{' '} -

-
- - -
- -
- Organization -
-

- Configure your Organization general settings{' '} -

-
- - -
- -
Users
-

- Manage your Organization's users, roles{' '} -

-
- +
+ {/* Card components */} + } + title="Courses" + description="Create and manage courses, chapters and activities" + /> + } + title="Organization" + description="Configure your Organization general settings" + /> + } + title="Users" + description="Manage your Organization's users, roles" + />
-
+
- -
+ +
LearnHouse University
-
+
-
- -
Account Settings
-

- Configure your personal settings, passwords, email -

+
+ +
+
Account Settings
+

+ Configure your personal settings, passwords, email +

+
@@ -92,4 +76,20 @@ function DashboardHome() { ) } +// New component for dashboard cards +function DashboardCard({ href, icon, title, description }: { href: string, icon: React.ReactNode, title: string, description: string }) { + return ( + +
+ {icon} +
{title}
+

{description}

+
+ + ) +} + export default DashboardHome diff --git a/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx index 995c3a68..d6342e38 100644 --- a/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/users/settings/[subpage]/page.tsx @@ -2,8 +2,9 @@ import React, { useEffect } from 'react' import { motion } from 'framer-motion' import Link from 'next/link' +import { useMediaQuery } from 'usehooks-ts' import { getUriWithOrg } from '@services/config/config' -import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react' +import { Monitor, ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react' import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' import { useLHSession } from '@components/Contexts/LHSessionContext' import { useOrg } from '@components/Contexts/OrgContext' @@ -22,6 +23,7 @@ function UsersSettingsPage({ params }: { params: SettingsParams }) { const org = useOrg() as any const [H1Label, setH1Label] = React.useState('') const [H2Label, setH2Label] = React.useState('') + const isMobile = useMediaQuery('(max-width: 767px)') function handleLabels() { if (params.subpage == 'users') { @@ -46,6 +48,20 @@ function UsersSettingsPage({ params }: { params: SettingsParams }) { handleLabels() }, [session, org, params.subpage, params]) + if (isMobile) { + // TODO: Work on a better mobile experience + return ( +
+
+

Desktop Only

+ +

This page is only accessible from a desktop device.

+

Please switch to a desktop to view and manage user settings.

+
+
+ ) + } + return (
diff --git a/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx b/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx index eac3c8db..a682bef5 100644 --- a/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseAccess/EditCourseAccess.tsx @@ -50,14 +50,14 @@ function EditCourseAccess(props: EditCourseAccessProps) { {courseStructure && (
-
-
-

Access to the course

-

+
+
+

Access to the course

+

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

-
+
)} -
- -
+
+ +
Public
-
+
The Course is publicly available on the internet, it is indexed by search engines and can be accessed by anyone
@@ -94,12 +94,12 @@ function EditCourseAccess(props: EditCourseAccessProps) { Active
)} -
- -
+
+ +
Users Only
-
+
The Course is only accessible to signed in users, additionally you can choose which UserGroups can access this course
@@ -139,42 +139,44 @@ function UserGroupsSection({ usergroups }: { usergroups: any[] }) { return ( <> -
-

UserGroups

-

+
+

UserGroups

+

You can choose to give access to this course to specific groups of users only by linking it to a UserGroup

- - - - - - - - - {usergroups?.map((usergroup: any) => ( - - - +
+
NameActions
{usergroup.name} - - - Delete link - - } - functionToExecute={() => removeUserGroupLink(usergroup.id)} - status="warning" - /> -
+ + + + - ))} - -
NameActions
+ + + {usergroups?.map((usergroup: any) => ( + + {usergroup.name} + + + + Delete link + + } + functionToExecute={() => removeUserGroupLink(usergroup.id)} + status="warning" + /> + + + ))} + + +

- + } diff --git a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx index 54624707..642f9162 100644 --- a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx @@ -26,6 +26,7 @@ import { deleteAssignmentUsingActivityUUID, getAssignmentFromActivityUUID } from import { useOrg } from '@components/Contexts/OrgContext' import { useCourse } from '@components/Contexts/CourseContext' import toast from 'react-hot-toast' +import { useMediaQuery } from 'usehooks-ts' type ActivitiyElementProps = { orgslug: string @@ -50,6 +51,7 @@ function ActivityElement(props: ActivitiyElementProps) { string | undefined >(undefined) const activityUUID = props.activity.activity_uuid + const isMobile = useMediaQuery('(max-width: 767px)') async function deleteActivityUI() { const toast_loading = toast.loading('Deleting activity...') @@ -110,14 +112,14 @@ function ActivityElement(props: ActivitiyElementProps) { > {(provided, snapshot) => (
{/* Activity Type Icon */} - + {/* Centered Activity Name */}
@@ -143,13 +145,11 @@ function ActivityElement(props: ActivitiyElementProps) { onClick={() => updateActivityName(props.activity.id)} className="bg-transparent text-neutral-700 hover:cursor-pointer hover:text-neutral-900" > - +
) : ( -

{props.activity.name}

+

{props.activity.name}

)} setSelectedActivity(props.activity.id)} @@ -157,65 +157,60 @@ function ActivityElement(props: ActivitiyElementProps) { />
- - {/* Edit and View Button */} -
-
- - {/* Publishing */} -
changePublicStatus()} - > - {!props.activity.published ? ( - - ) : ( - - )} - {!props.activity.published ? 'Publish' : 'Unpublish'} -
- - - Preview - -
+ {/* Edit, View, Publish, and Delete Buttons */} +
+ + {/* Publishing */} + + + + Preview + {/* Delete Button */} -
- - - -
- } - functionToExecute={() => deleteActivityUI()} - status="warning" - > -
+ + + {!isMobile && Delete} + + } + functionToExecute={() => deleteActivityUI()} + status="warning" + />
)} @@ -242,11 +237,11 @@ const ACTIVITIES = { } } -const ActivityTypeIndicator = ({activityType} : { activityType: keyof typeof ACTIVITIES}) => { +const ActivityTypeIndicator = ({activityType, isMobile} : { activityType: keyof typeof ACTIVITIES, isMobile: boolean}) => { const {displayName, Icon} = ACTIVITIES[activityType] return ( -
+
{' '}
@@ -257,7 +252,7 @@ const ActivityTypeIndicator = ({activityType} : { activityType: keyof typeof ACT ) } -const ActivityElementOptions = ({ activity }: any) => { +const ActivityElementOptions = ({ activity, isMobile }: { activity: any; isMobile: boolean }) => { const [assignmentUUID, setAssignmentUUID] = useState(''); const org = useOrg() as any; const course = useCourse() as any; @@ -299,11 +294,11 @@ const ActivityElementOptions = ({ activity }: any) => { )}/edit` } prefetch - className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center" - target='_blank' // hotfix for an editor prosemirror bug + className={`hover:cursor-pointer p-1 ${isMobile ? 'px-2' : 'px-3'} bg-sky-700 rounded-md items-center`} + target='_blank' >
- Edit Page + Edit Page
@@ -316,10 +311,10 @@ const ActivityElementOptions = ({ activity }: any) => { `/dash/assignments/${assignmentUUID}` } prefetch - className=" hover:cursor-pointer p-1 px-3 bg-teal-700 rounded-md items-center" + className={`hover:cursor-pointer p-1 ${isMobile ? 'px-2' : 'px-3'} bg-teal-700 rounded-md items-center`} >
- Edit Assignment + {!isMobile && Edit Assignment}
diff --git a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ChapterElement.tsx b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ChapterElement.tsx index 57aab3ec..b3d27980 100644 --- a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ChapterElement.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ChapterElement.tsx @@ -6,6 +6,7 @@ import { Pencil, Save, X, + Trash2, } from 'lucide-react' import React from 'react' import { Draggable, Droppable } from 'react-beautiful-dnd' @@ -71,27 +72,27 @@ function ChapterElement(props: ChapterElementProps) { > {(provided, snapshot) => (
-
-
+
+
-
+
{selectedChapter === props.chapter.id ? ( -
+
updateChapterName(props.chapter.id)} className="bg-transparent text-neutral-700 hover:cursor-pointer hover:text-neutral-900" > - updateChapterName(props.chapter.id)} - /> +
) : ( -

+

{props.chapter.name}

)} @@ -127,23 +125,24 @@ function ChapterElement(props: ChapterElementProps) { />
- - - -

Delete Chapter

-
- } - functionToExecute={() => deleteChapterUI()} - status="warning" - > +
+ + + + + } + functionToExecute={() => deleteChapterUI()} + status="warning" + /> +
{}, [org]) return ( -
+
{({ isSubmitting }) => (
-
-
+
+
@@ -149,24 +149,24 @@ function OrgEditGeneral() {
-
- - +
+ + Logo Thumbnail
-
-
+
+
@@ -189,16 +189,16 @@ function OrgEditGeneral() {
-

Accepts PNG , JPG

+

Accepts PNG, JPG

-
-
+
+
diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/DashLeftMenu.tsx similarity index 99% rename from apps/web/components/Dashboard/UI/LeftMenu.tsx rename to apps/web/components/Dashboard/UI/DashLeftMenu.tsx index bc2eff28..d3ab2e3c 100644 --- a/apps/web/components/Dashboard/UI/LeftMenu.tsx +++ b/apps/web/components/Dashboard/UI/DashLeftMenu.tsx @@ -12,7 +12,7 @@ import AdminAuthorization from '@components/Security/AdminAuthorization' import { useLHSession } from '@components/Contexts/LHSessionContext' import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config' -function LeftMenu() { +function DashLeftMenu() { const org = useOrg() as any const session = useLHSession() as any const [loading, setLoading] = React.useState(true) @@ -176,4 +176,4 @@ function LeftMenu() { ) } -export default LeftMenu +export default DashLeftMenu diff --git a/apps/web/components/Dashboard/UI/DashMobileMenu.tsx b/apps/web/components/Dashboard/UI/DashMobileMenu.tsx new file mode 100644 index 00000000..231f758b --- /dev/null +++ b/apps/web/components/Dashboard/UI/DashMobileMenu.tsx @@ -0,0 +1,69 @@ +'use client' +import { useOrg } from '@components/Contexts/OrgContext' +import { signOut } from 'next-auth/react' +import { Backpack, BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react' +import Link from 'next/link' +import React from 'react' +import AdminAuthorization from '@components/Security/AdminAuthorization' +import { useLHSession } from '@components/Contexts/LHSessionContext' +import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config' +import ToolTip from '@components/StyledElements/Tooltip/Tooltip' + +function DashMobileMenu() { + const org = useOrg() as any + const session = useLHSession() as any + + async function logOutUI() { + const res = await signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/login?orgslug=' + org.slug) }) + if (res) { + getUriWithOrg(org.slug, '/') + } + } + + return ( +
+
+ + + + + Home + + + + + + Courses + + + + + + Assignments + + + + + + Users + + + + + + Org + + + + + + + Settings + + +
+
+ ) +} + +export default DashMobileMenu diff --git a/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx b/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx index 691783d4..d9ea3a74 100644 --- a/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx +++ b/apps/web/components/Dashboard/UserAccount/UserEditGeneral/UserEditGeneral.tsx @@ -40,7 +40,7 @@ function UserEditGeneral() { useEffect(() => { }, [session, session.data]) return ( -
+
{session.data.user && ( {({ isSubmitting }) => ( -
- - - - - - - - - - - - - - - - - - +
+ +
+ {[ + { label: 'Email', name: 'email', type: 'email' }, + { label: 'Username', name: 'username', type: 'text' }, + { label: 'First Name', name: 'first_name', type: 'text' }, + { label: 'Last Name', name: 'last_name', type: 'text' }, + { label: 'Bio', name: 'bio', type: 'text' }, + ].map((field) => ( +
+ + +
+ ))} +
-
- - {error && ( -
- -
- {error} +
+
+ + {error && ( +
+ + {error}
-
- )} - {success && ( -
- -
- {success} + )} + {success && ( +
+ + {success}
-
- )} -
-
-
+ )} +
+
{localAvatar ? ( )} -
- {isLoading ? ( -
- -
+ {isLoading ? ( +
Uploading
-
- ) : ( -
- - -
- )} + ) : ( + <> + + + + )} +
-
- +
+

Recommended size 100x100

diff --git a/apps/web/components/Objects/Editor/Editor.tsx b/apps/web/components/Objects/Editor/Editor.tsx index c255d637..594de491 100644 --- a/apps/web/components/Objects/Editor/Editor.tsx +++ b/apps/web/components/Objects/Editor/Editor.tsx @@ -21,7 +21,7 @@ import WarningCallout from './Extensions/Callout/Warning/WarningCallout' import ImageBlock from './Extensions/Image/ImageBlock' import Youtube from '@tiptap/extension-youtube' import VideoBlock from './Extensions/Video/VideoBlock' -import { Eye } from 'lucide-react' +import { ComputerIcon, Eye, Monitor } from 'lucide-react' import MathEquationBlock from './Extensions/MathEquation/MathEquationBlock' import PDFBlock from './Extensions/PDF/PDFBlock' import QuizBlock from './Extensions/Quiz/QuizBlock' @@ -51,6 +51,7 @@ import { getUriWithOrg } from '@services/config/config' import EmbedObjects from './Extensions/EmbedObjects/EmbedObjects' import Badges from './Extensions/Badges/Badges' import Buttons from './Extensions/Buttons/Buttons' +import { useMediaQuery } from 'usehooks-ts' interface Editor { content: string @@ -170,6 +171,21 @@ function Editor(props: Editor) { content: props.isCollabEnabledOnThisOrg ? null : props.content, }) + const isMobile = useMediaQuery('(max-width: 767px)') + if (isMobile) { + // TODO: Work on a better editor mobile experience + return ( +
+
+

Desktop Only

+ +

The editor is only accessible from a desktop device.

+

Please switch to a desktop to view.

+
+
+ ) + } + return (