feat: courses dashboard

This commit is contained in:
swve 2023-12-13 15:56:12 +01:00
parent 8d35085908
commit c39d9d5340
22 changed files with 611 additions and 67 deletions

View file

@ -0,0 +1,109 @@
'use client';
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
import CreateCourseModal from '@components/Objects/Modals/Course/Create/CreateCourse';
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail';
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton';
import Modal from '@components/StyledElements/Modal/Modal';
import Link from 'next/link'
import { useSearchParams } from 'next/navigation';
import React from 'react'
type CourseProps = {
orgslug: string;
courses: any;
org_id: string;
}
function CoursesHome(params: CourseProps) {
const searchParams = useSearchParams();
const isCreatingCourse = searchParams.get('new') ? true : false;
const [newCourseModal, setNewCourseModal] = React.useState(isCreatingCourse);
const orgslug = params.orgslug;
const courses = params.courses;
async function closeNewCourseModal() {
setNewCourseModal(false);
}
return (
<div className='h-full w-full bg-[#f8f8f8]'>
<div >
<div className='pl-10 mr-10 tracking-tighter'>
<BreadCrumbs type='courses' />
<div className='w-100 flex justify-between'>
<div className='flex font-bold text-4xl'>Courses</div>
<AuthenticatedClientElement checkMethod='roles'
action='create'
ressourceType='course'
orgId={params.org_id}>
<Modal
isDialogOpen={newCourseModal}
onOpenChange={setNewCourseModal}
minHeight="md"
dialogContent={<CreateCourseModal
closeModal={closeNewCourseModal}
orgslug={orgslug}
></CreateCourseModal>}
dialogTitle="Create Course"
dialogDescription="Create a new course"
dialogTrigger={
<button>
<NewCourseButton />
</button>}
/>
</AuthenticatedClientElement>
</div>
</div>
</div>
<div className="flex flex-wrap mx-8 mt-7">
{courses.map((course: any) => (
<div className="px-3" key={course.course_uuid}>
<CourseThumbnail course={course} orgslug={orgslug} />
</div>
))}
{courses.length == 0 &&
<div className="flex mx-auto h-[400px]">
<div className="flex flex-col justify-center text-center items-center space-y-5">
<div className='mx-auto'>
<svg width="120" height="120" viewBox="0 0 295 295" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.51" x="10" y="10" width="275" height="275" rx="75" stroke="#4B5564" strokeOpacity="0.15" strokeWidth="20" />
<path d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z" fill="#4B5564" fillOpacity="0.08" />
</svg>
</div>
<div className="space-y-0">
<h1 className="text-3xl font-bold text-gray-600">No courses yet</h1>
<p className="text-lg text-gray-400">Create a course to add content</p>
</div>
<AuthenticatedClientElement
action='create'
ressourceType='course'
checkMethod='roles' orgId={params.org_id}>
<Modal
isDialogOpen={newCourseModal}
onOpenChange={setNewCourseModal}
minHeight="md"
dialogContent={<CreateCourseModal
closeModal={closeNewCourseModal}
orgslug={orgslug}
></CreateCourseModal>}
dialogTitle="Create Course"
dialogDescription="Create a new course"
dialogTrigger={
<button>
<NewCourseButton />
</button>}
/>
</AuthenticatedClientElement>
</div>
</div>
}
</div>
</div>
)
}
export default CoursesHome

View file

@ -1,16 +1,20 @@
'use client';
import EditCourseStructure from '../../../../../../../../components/Dashboard/EditCourseStructure/EditCourseStructure'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import EditCourseStructure from '../../../../../../../../components/DashboardPages/EditCourseStructure/EditCourseStructure'
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
import PageLoading from '@components/Objects/Loaders/PageLoading';
import ClientComponentSkeleton from '@components/Utils/ClientComp';
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
import { swrFetcher } from '@services/utils/ts/requests';
import React, { createContext, use, useEffect, useState } from 'react'
import useSWR from 'swr';
import { CourseProvider, useCourse } from '../../../../../../../../components/Dashboard/CourseContext';
import SaveState from '@components/Dashboard/UI/SaveState';
import { CourseProvider, useCourse } from '../../../../../../../../components/DashboardPages/CourseContext';
import SaveState from '@components/DashboardPages/UI/SaveState';
import Link from 'next/link';
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop';
import { CourseOverviewTop } from '@components/DashboardPages/UI/CourseOverviewTop';
import { CSSTransition } from 'react-transition-group';
import { motion } from 'framer-motion';
import EditCourseGeneral from '@components/DashboardPages/EditCourseGeneral/EditCourseGeneral';
import { GalleryVertical, GalleryVerticalEnd, Info } from 'lucide-react';
export type CourseOverviewParams = {
orgslug: string,
@ -20,7 +24,6 @@ export type CourseOverviewParams = {
export const CourseStructureContext = createContext({}) as any;
function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
function getEntireCourseUUID(courseuuid: string) {
@ -30,23 +33,41 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
return (
<div className='h-full w-full bg-[#f8f8f8]'>
<CourseProvider courseuuid={getEntireCourseUUID(params.courseuuid)}>
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
<CourseOverviewTop params={params} />
<div className='flex space-x-5 font-black text-sm'>
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/general`}>
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>General</div>
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
<div className='flex items-center space-x-2.5 mx-2'>
<Info size={16} />
<div>General</div>
</div>
</div>
</Link>
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/structure`}>
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${params.subpage.toString() === 'structure' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>Structure</div>
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/content`}>
<div className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'content' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
<div className='flex items-center space-x-2.5 mx-2'>
<GalleryVerticalEnd size={16} />
<div>Content</div>
</div>
</div>
</Link>
</div>
</div>
<div className='h-6'></div>
{params.subpage == 'structure' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
<motion.div
initial={{ opacity: 0, }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.10, type: "spring", stiffness: 80 }}
>
{params.subpage == 'content' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
{params.subpage == 'general' ? <EditCourseGeneral orgslug={params.orgslug} /> : ''}
</motion.div>
</CourseProvider>
</div>
)

View file

@ -1,22 +1,57 @@
'use client';
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import Link from 'next/link'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth';
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses';
import { getOrganizationContextInfo } from '@services/organizations/orgs';
import { Metadata } from 'next';
import { cookies } from 'next/headers';
import React from 'react'
import CoursesHome from './client';
type MetadataProps = {
params: { orgslug: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params }: MetadataProps,
): Promise<Metadata> {
// Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
// SEO
return {
title: "Courses — " + org.name,
description: org.description,
keywords: `${org.name}, ${org.description}, courses, learning, education, online learning, edu, online courses, ${org.name} courses`,
robots: {
index: true,
follow: true,
nocache: true,
googleBot: {
index: true,
follow: true,
"max-image-preview": "large",
}
},
openGraph: {
title: "Courses — " + org.name,
description: org.description,
type: 'website',
},
};
}
async function CoursesPage(params: any) {
const orgslug = params.params.orgslug;
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
const cookieStore = cookies();
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null);
function CoursesHome() {
return (
<div>
<div className='h-full w-fullbg-white'>
<div className='pl-10 tracking-tighter'>
<BreadCrumbs type='courses' />
<div className='flex pt-2'>
<div className='font-bold text-4xl'>Courses</div>
</div>
</div>
</div>
</div>
<CoursesHome org_id={org.org_id} orgslug={orgslug} courses={courses} />
)
}
export default CoursesHome
export default CoursesPage

View file

@ -1,4 +1,4 @@
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import LeftMenu from '@components/DashboardPages/UI/LeftMenu'
import AuthProvider from '@components/Security/AuthProvider'
import React from 'react'