mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: courses dashboard
This commit is contained in:
parent
8d35085908
commit
c39d9d5340
22 changed files with 611 additions and 67 deletions
109
apps/web/app/orgs/[orgslug]/dash/courses/client.tsx
Normal file
109
apps/web/app/orgs/[orgslug]/dash/courses/client.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
'use client';
|
||||
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
|
||||
import CreateCourseModal from '@components/Objects/Modals/Course/Create/CreateCourse';
|
||||
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail';
|
||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
||||
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import Link from 'next/link'
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import React from 'react'
|
||||
|
||||
type CourseProps = {
|
||||
orgslug: string;
|
||||
courses: any;
|
||||
org_id: string;
|
||||
}
|
||||
|
||||
function CoursesHome(params: CourseProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const isCreatingCourse = searchParams.get('new') ? true : false;
|
||||
const [newCourseModal, setNewCourseModal] = React.useState(isCreatingCourse);
|
||||
const orgslug = params.orgslug;
|
||||
const courses = params.courses;
|
||||
|
||||
|
||||
async function closeNewCourseModal() {
|
||||
setNewCourseModal(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full w-full bg-[#f8f8f8]'>
|
||||
<div >
|
||||
<div className='pl-10 mr-10 tracking-tighter'>
|
||||
<BreadCrumbs type='courses' />
|
||||
|
||||
<div className='w-100 flex justify-between'>
|
||||
<div className='flex font-bold text-4xl'>Courses</div>
|
||||
<AuthenticatedClientElement checkMethod='roles'
|
||||
action='create'
|
||||
ressourceType='course'
|
||||
orgId={params.org_id}>
|
||||
<Modal
|
||||
isDialogOpen={newCourseModal}
|
||||
onOpenChange={setNewCourseModal}
|
||||
minHeight="md"
|
||||
dialogContent={<CreateCourseModal
|
||||
closeModal={closeNewCourseModal}
|
||||
orgslug={orgslug}
|
||||
></CreateCourseModal>}
|
||||
dialogTitle="Create Course"
|
||||
dialogDescription="Create a new course"
|
||||
dialogTrigger={
|
||||
|
||||
<button>
|
||||
<NewCourseButton />
|
||||
</button>}
|
||||
/>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap mx-8 mt-7">
|
||||
{courses.map((course: any) => (
|
||||
<div className="px-3" key={course.course_uuid}>
|
||||
<CourseThumbnail course={course} orgslug={orgslug} />
|
||||
</div>
|
||||
))}
|
||||
{courses.length == 0 &&
|
||||
<div className="flex mx-auto h-[400px]">
|
||||
<div className="flex flex-col justify-center text-center items-center space-y-5">
|
||||
<div className='mx-auto'>
|
||||
<svg width="120" height="120" viewBox="0 0 295 295" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.51" x="10" y="10" width="275" height="275" rx="75" stroke="#4B5564" strokeOpacity="0.15" strokeWidth="20" />
|
||||
<path d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z" fill="#4B5564" fillOpacity="0.08" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="space-y-0">
|
||||
<h1 className="text-3xl font-bold text-gray-600">No courses yet</h1>
|
||||
<p className="text-lg text-gray-400">Create a course to add content</p>
|
||||
</div>
|
||||
<AuthenticatedClientElement
|
||||
action='create'
|
||||
ressourceType='course'
|
||||
checkMethod='roles' orgId={params.org_id}>
|
||||
<Modal
|
||||
isDialogOpen={newCourseModal}
|
||||
onOpenChange={setNewCourseModal}
|
||||
minHeight="md"
|
||||
dialogContent={<CreateCourseModal
|
||||
closeModal={closeNewCourseModal}
|
||||
orgslug={orgslug}
|
||||
></CreateCourseModal>}
|
||||
dialogTitle="Create Course"
|
||||
dialogDescription="Create a new course"
|
||||
dialogTrigger={
|
||||
<button>
|
||||
<NewCourseButton />
|
||||
</button>}
|
||||
/>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoursesHome
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
'use client';
|
||||
import EditCourseStructure from '../../../../../../../../components/Dashboard/EditCourseStructure/EditCourseStructure'
|
||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||
import EditCourseStructure from '../../../../../../../../components/DashboardPages/EditCourseStructure/EditCourseStructure'
|
||||
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading';
|
||||
import ClientComponentSkeleton from '@components/Utils/ClientComp';
|
||||
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import React, { createContext, use, useEffect, useState } from 'react'
|
||||
import useSWR from 'swr';
|
||||
import { CourseProvider, useCourse } from '../../../../../../../../components/Dashboard/CourseContext';
|
||||
import SaveState from '@components/Dashboard/UI/SaveState';
|
||||
import { CourseProvider, useCourse } from '../../../../../../../../components/DashboardPages/CourseContext';
|
||||
import SaveState from '@components/DashboardPages/UI/SaveState';
|
||||
import Link from 'next/link';
|
||||
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop';
|
||||
import { CourseOverviewTop } from '@components/DashboardPages/UI/CourseOverviewTop';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import { motion } from 'framer-motion';
|
||||
import EditCourseGeneral from '@components/DashboardPages/EditCourseGeneral/EditCourseGeneral';
|
||||
import { GalleryVertical, GalleryVerticalEnd, Info } from 'lucide-react';
|
||||
|
||||
export type CourseOverviewParams = {
|
||||
orgslug: string,
|
||||
|
|
@ -20,7 +24,6 @@ export type CourseOverviewParams = {
|
|||
|
||||
export const CourseStructureContext = createContext({}) as any;
|
||||
|
||||
|
||||
function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
|
||||
|
||||
function getEntireCourseUUID(courseuuid: string) {
|
||||
|
|
@ -30,23 +33,41 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
|
|||
|
||||
return (
|
||||
<div className='h-full w-full bg-[#f8f8f8]'>
|
||||
|
||||
<CourseProvider courseuuid={getEntireCourseUUID(params.courseuuid)}>
|
||||
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
|
||||
<CourseOverviewTop params={params} />
|
||||
<div className='flex space-x-5 font-black text-sm'>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/general`}>
|
||||
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>General</div>
|
||||
<div className={`py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<Info size={16} />
|
||||
<div>General</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/structure`}>
|
||||
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${params.subpage.toString() === 'structure' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>Structure</div>
|
||||
<Link href={getUriWithOrg(params.orgslug, "") + `/dash/courses/course/${params.courseuuid}/content`}>
|
||||
<div className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${params.subpage.toString() === 'content' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>
|
||||
<div className='flex items-center space-x-2.5 mx-2'>
|
||||
<GalleryVerticalEnd size={16} />
|
||||
<div>Content</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='h-6'></div>
|
||||
{params.subpage == 'structure' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.10, type: "spring", stiffness: 80 }}
|
||||
>
|
||||
{params.subpage == 'content' ? <EditCourseStructure orgslug={params.orgslug} /> : ''}
|
||||
{params.subpage == 'general' ? <EditCourseGeneral orgslug={params.orgslug} /> : ''}
|
||||
</motion.div>
|
||||
</CourseProvider>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,57 @@
|
|||
'use client';
|
||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||
import Link from 'next/link'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth';
|
||||
import { getOrgCoursesWithAuthHeader } from '@services/courses/courses';
|
||||
import { getOrganizationContextInfo } from '@services/organizations/orgs';
|
||||
import { Metadata } from 'next';
|
||||
import { cookies } from 'next/headers';
|
||||
import React from 'react'
|
||||
import CoursesHome from './client';
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
};
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: MetadataProps,
|
||||
): Promise<Metadata> {
|
||||
|
||||
// Get Org context information
|
||||
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
|
||||
|
||||
// SEO
|
||||
return {
|
||||
title: "Courses — " + org.name,
|
||||
description: org.description,
|
||||
keywords: `${org.name}, ${org.description}, courses, learning, education, online learning, edu, online courses, ${org.name} courses`,
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
nocache: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-image-preview": "large",
|
||||
}
|
||||
},
|
||||
openGraph: {
|
||||
title: "Courses — " + org.name,
|
||||
description: org.description,
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function CoursesPage(params: any) {
|
||||
const orgslug = params.params.orgslug;
|
||||
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
|
||||
const cookieStore = cookies();
|
||||
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
|
||||
const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null);
|
||||
|
||||
|
||||
function CoursesHome() {
|
||||
return (
|
||||
<div>
|
||||
<div className='h-full w-fullbg-white'>
|
||||
<div className='pl-10 tracking-tighter'>
|
||||
<BreadCrumbs type='courses' />
|
||||
<div className='flex pt-2'>
|
||||
<div className='font-bold text-4xl'>Courses</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<CoursesHome org_id={org.org_id} orgslug={orgslug} courses={courses} />
|
||||
)
|
||||
}
|
||||
|
||||
export default CoursesHome
|
||||
export default CoursesPage
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
|
||||
import LeftMenu from '@components/DashboardPages/UI/LeftMenu'
|
||||
import AuthProvider from '@components/Security/AuthProvider'
|
||||
import React from 'react'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue