From 24476af5b25ca836d6548f52de9707f1b929864c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 19 Feb 2026 16:10:54 +0000 Subject: [PATCH] 1 --- frontend/src/pages/courses/courses-view.tsx | 972 +++++--------------- frontend/src/pages/dashboard.tsx | 529 +++-------- frontend/src/pages/index.tsx | 243 ++--- 3 files changed, 460 insertions(+), 1284 deletions(-) diff --git a/frontend/src/pages/courses/courses-view.tsx b/frontend/src/pages/courses/courses-view.tsx index bc3c195..007a0c1 100644 --- a/frontend/src/pages/courses/courses-view.tsx +++ b/frontend/src/pages/courses/courses-view.tsx @@ -1,14 +1,12 @@ + import React, { ReactElement, useEffect } from 'react'; import Head from 'next/head' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; import dayjs from "dayjs"; import {useAppDispatch, useAppSelector} from "../../stores/hooks"; import {useRouter} from "next/router"; import { fetch } from '../../stores/courses/coursesSlice' -import {saveFile} from "../../helpers/fileSaver"; +import { fetch as fetchEnrollments, create as enrollInCourse } from '../../stores/enrollments/enrollmentsSlice' import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; import LayoutAuthenticated from "../../layouts/Authenticated"; import {getPageTitle} from "../../config"; import SectionTitleLineWithButton from "../../components/SectionTitleLineWithButton"; @@ -16,781 +14,235 @@ import SectionMain from "../../components/SectionMain"; import CardBox from "../../components/CardBox"; import BaseButton from "../../components/BaseButton"; import BaseDivider from "../../components/BaseDivider"; -import {mdiChartTimelineVariant} from "@mdi/js"; -import {SwitchField} from "../../components/SwitchField"; -import FormField from "../../components/FormField"; +import * as icon from "@mdi/js"; +import BaseIcon from "../../components/BaseIcon"; const CoursesView = () => { const router = useRouter() const dispatch = useAppDispatch() const { courses } = useAppSelector((state) => state.courses) + const { currentUser } = useAppSelector((state) => state.auth) + const { enrollments } = useAppSelector((state) => state.enrollments) - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str,`str`) - return str.slice(0, -1); - } useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); + if (id) { + dispatch(fetch({ id })); + } + if (currentUser) { + dispatch(fetchEnrollments({ query: `?student=${currentUser.id}` })); + } + }, [dispatch, id, currentUser]); + const isEnrolled = Array.isArray(enrollments) && enrollments.some((e: any) => e.courseId === id); + const isLearner = currentUser?.app_role?.name === 'Learner'; + + const handleEnroll = async () => { + if (!id || !currentUser) return; + const data = { + course: id, + student: currentUser.id, + status: 'active', + enrolled_at: new Date().toISOString() + }; + await dispatch(enrollInCourse(data)); + dispatch(fetchEnrollments({ query: `?student=${currentUser.id}` })); + }; return ( <> - {getPageTitle('View courses')} + {getPageTitle(courses?.title || 'View Course')} - - - - - - - -
-

Title

-

{courses?.title}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Subtitle

-

{courses?.subtitle}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Description

- {courses.description - ?

- :

No data

- } -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Thumbnail

- {courses?.thumbnail?.length - ? ( - +
+ {!isLearner && ( + - ) :

No Thumbnail

- } + )} + router.push(isLearner ? '/dashboard' : '/courses/courses-list')} + />
- - - - - - - - - - - - - - - - - - - - -
-

Status

-

{courses?.status ?? 'No data'}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Level

-

{courses?.level ?? 'No data'}

-
- - - - - - - - - - - - - - -
-

Category

-

{courses?.category}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Language

-

{courses?.language}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

EstimatedDuration(Minutes)

-

{courses?.estimated_duration_minutes || 'No data'}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Instructor

- - -

{courses?.instructor?.firstName ?? 'No data'}

- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - {courses.published_at ? :

No PublishedAt

} -
- - - - - - - - - - - - - - - - - - - - - - - <> -

Lessons Course

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {courses.lessons_course && Array.isArray(courses.lessons_course) && - courses.lessons_course.map((item: any) => ( - router.push(`/lessons/lessons-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
TitleVideoURLOrderIndexStatusIsPreviewEstimatedMinutesPublishedAt
- { item.title } - - { item.video_url } - - { item.order_index } - - { item.status } - - { dataFormatter.booleanFormatter(item.is_preview) } - - { item.estimated_minutes } - - { dataFormatter.dateTimeFormatter(item.published_at) } -
+ + +
+ {/* Main Content */} +
+ +
+

{courses?.title}

+

{courses?.subtitle}

- {!courses?.lessons_course?.length &&
No data
} -
- - - - <> -

Enrollments Course

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - {courses.enrollments_course && Array.isArray(courses.enrollments_course) && - courses.enrollments_course.map((item: any) => ( - router.push(`/enrollments/enrollments-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - ))} - -
StatusEnrolledAtCompletedAtProgressPercent
- { item.status } - - { dataFormatter.dateTimeFormatter(item.enrolled_at) } - - { dataFormatter.dateTimeFormatter(item.completed_at) } - - { item.progress_percent } -
+ + + +
+

About this course

+ {courses.description + ?
+ :

No description available.

+ }
- {!courses?.enrollments_course?.length &&
No data
} - - - - - - <> -

Course_announcements Course

- -
- - - - - - - - - - - - - - - - - - - - - - - - {courses.course_announcements_course && Array.isArray(courses.course_announcements_course) && - courses.course_announcements_course.map((item: any) => ( - router.push(`/course_announcements/course_announcements-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - ))} - -
TitlePublishedAtIsPublished
- { item.title } - - { dataFormatter.dateTimeFormatter(item.published_at) } - - { dataFormatter.booleanFormatter(item.is_published) } -
-
- {!courses?.course_announcements_course?.length &&
No data
} -
- - - - <> -

Course_reviews Course

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - {courses.course_reviews_course && Array.isArray(courses.course_reviews_course) && - courses.course_reviews_course.map((item: any) => ( - router.push(`/course_reviews/course_reviews-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - ))} - -
RatingCommentReviewedAtIsPublished
- { item.rating } - - { item.comment } - - { dataFormatter.dateTimeFormatter(item.reviewed_at) } - - { dataFormatter.booleanFormatter(item.is_published) } -
-
- {!courses?.course_reviews_course?.length &&
No data
} -
- - - - +
+
+ +

Level

+

{courses?.level || 'Beginner'}

+
+
+ +

Language

+

{courses?.language || 'English'}

+
+
+ +

Duration

+

{courses?.estimated_duration_minutes || '0'} min

+
+
+ +

Published

+

{courses?.published_at ? dayjs(courses.published_at).format('MMM D, YYYY') : 'TBA'}

+
+
+ - router.push('/courses/courses-list')} - /> - + {/* Lessons Section */} + +

+ + Course Curriculum +

+
+ {courses.lessons_course && courses.lessons_course.length > 0 ? ( + courses.lessons_course.sort((a: any, b: any) => (a.order_index || 0) - (b.order_index || 0)).map((lesson: any, idx: number) => ( +
(isEnrolled || lesson.is_preview) && router.push(`/lessons/lessons-view/?id=${lesson.id}`)} + > +
+ {idx + 1} +
+
+

{lesson.title}

+

{lesson.estimated_minutes || 0} minutes

+
+
+ {lesson.is_preview && !isEnrolled && ( + Preview + )} + +
+
+ )) + ) : ( +

No lessons available for this course yet.

+ )} +
+
+
+ + {/* Sidebar */} +
+ {/* Enrollment Card */} + +
+ {courses?.thumbnail && courses.thumbnail[0] ? ( + {courses.title} + ) : ( + + )} +
+
+ {courses?.category || 'Educational'} +
+
+ + {isEnrolled ? ( +
+
+ + You are enrolled! +
+ { + if (courses.lessons_course?.length > 0) { + router.push(`/lessons/lessons-view/?id=${courses.lessons_course[0].id}`); + } + }} + /> +
+ ) : ( +
+

Ready to start your journey in {courses?.title}?

+ +

Join thousands of students learning this today!

+
+ )} + + + +
+

Course Highlights

+
    +
  • + + Lifetime access +
  • +
  • + + Self-paced learning +
  • +
  • + + Mobile friendly +
  • +
+
+ + + {/* Instructor Info */} + +

Your Instructor

+
+
+ {courses?.instructor?.firstName?.[0] || 'I'} +
+
+

{courses?.instructor?.firstName} {courses?.instructor?.lastName || ''}

+

Expert Course Creator

+
+
+
+
+
); @@ -798,14 +250,10 @@ const CoursesView = () => { CoursesView.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ) } -export default CoursesView; \ No newline at end of file +export default CoursesView; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 58563f3..4e41453 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -1,7 +1,6 @@ import * as icon from '@mdi/js'; import Head from 'next/head' -import React from 'react' -import axios from 'axios'; +import React, { useEffect } from 'react' import type { ReactElement } from 'react' import LayoutAuthenticated from '../layouts/Authenticated' import SectionMain from '../components/SectionMain' @@ -9,402 +8,170 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton import BaseIcon from "../components/BaseIcon"; import { getPageTitle } from '../config' import Link from "next/link"; - -import { hasPermission } from "../helpers/userPermissions"; -import { fetchWidgets } from '../stores/roles/rolesSlice'; -import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; -import { SmartWidget } from '../components/SmartWidget/SmartWidget'; +import BaseButton from '../components/BaseButton'; +import CardBox from '../components/CardBox'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { fetch as fetchCourses } from '../stores/courses/coursesSlice'; +import { fetch as fetchEnrollments, create as enrollInCourse } from '../stores/enrollments/enrollmentsSlice'; + const Dashboard = () => { const dispatch = useAppDispatch(); - const iconsColor = useAppSelector((state) => state.style.iconsColor); - const corners = useAppSelector((state) => state.style.corners); - const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - - const loadingMessage = 'Loading...'; - - - const [users, setUsers] = React.useState(loadingMessage); - const [roles, setRoles] = React.useState(loadingMessage); - const [permissions, setPermissions] = React.useState(loadingMessage); - const [courses, setCourses] = React.useState(loadingMessage); - const [lessons, setLessons] = React.useState(loadingMessage); - const [enrollments, setEnrollments] = React.useState(loadingMessage); - const [lesson_progress, setLesson_progress] = React.useState(loadingMessage); - const [course_announcements, setCourse_announcements] = React.useState(loadingMessage); - const [course_reviews, setCourse_reviews] = React.useState(loadingMessage); - - - const [widgetsRole, setWidgetsRole] = React.useState({ - role: { value: '', label: '' }, - }); const { currentUser } = useAppSelector((state) => state.auth); - const { isFetchingQuery } = useAppSelector((state) => state.openAi); - - const { rolesWidgets, loading } = useAppSelector((state) => state.roles); - - - async function loadData() { - const entities = ['users','roles','permissions','courses','lessons','enrollments','lesson_progress','course_announcements','course_reviews',]; - const fns = [setUsers,setRoles,setPermissions,setCourses,setLessons,setEnrollments,setLesson_progress,setCourse_announcements,setCourse_reviews,]; + const { courses } = useAppSelector((state) => state.courses); + const { enrollments } = useAppSelector((state) => state.enrollments); - const requests = entities.map((entity, index) => { - - if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { - return axios.get(`/${entity.toLowerCase()}/count`); - } else { - fns[index](null); - return Promise.resolve({data: {count: null}}); - } - - }); + useEffect(() => { + if (currentUser) { + dispatch(fetchCourses({ query: '' })); + dispatch(fetchEnrollments({ query: `?student=${currentUser.id}` })); + } + }, [currentUser, dispatch]); - Promise.allSettled(requests).then((results) => { - results.forEach((result, i) => { - if (result.status === 'fulfilled') { - fns[i](result.value.data.count); - } else { - fns[i](result.reason.message); - } - }); - }); + const handleEnroll = async (courseId: string) => { + const data = { + course: courseId, + student: currentUser?.id, + status: 'active', + enrolled_at: new Date().toISOString() + }; + await dispatch(enrollInCourse(data)); + dispatch(fetchEnrollments({ query: `?student=${currentUser?.id}` })); + }; + + const isEnrolled = (courseId: string) => { + return Array.isArray(enrollments) && enrollments.some((e: any) => e.courseId === courseId); + }; + + const isLearner = currentUser?.app_role?.name === 'Learner'; + + if (!isLearner) { + return ( + <> + {getPageTitle('Overview')} + + +
+ + +
+
+

Total Courses

+

{courses.length || 0}

+
+ +
+
+ + + +
+
+

Total Users

+

...

+
+ +
+
+ + + +
+
+

Total Enrollments

+

...

+
+ +
+
+ +
+
+ + ); } - - async function getWidgets(roleId) { - await dispatch(fetchWidgets(roleId)); - } - React.useEffect(() => { - if (!currentUser) return; - loadData().then(); - setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }); - }, [currentUser]); - React.useEffect(() => { - if (!currentUser || !widgetsRole?.role?.value) return; - getWidgets(widgetsRole?.role?.value || '').then(); - }, [widgetsRole?.role?.value]); - - return ( - <> - - - {getPageTitle('Overview')} - - - - - {''} - - - {hasPermission(currentUser, 'CREATE_ROLES') && } - {!!rolesWidgets.length && - hasPermission(currentUser, 'CREATE_ROLES') && ( -

- {`${widgetsRole?.role?.label || 'Users'}'s widgets`} -

- )} + return ( + <> + {getPageTitle('Student Dashboard')} + + + +

Your Enrolled Courses

+
+ {Array.isArray(enrollments) && enrollments.length > 0 ? ( + enrollments.map((enrollment: any) => ( + +
+

{enrollment.course?.title}

+

{enrollment.course?.subtitle}

+
+ + Status: {enrollment.status} +
+
+ +
+ )) + ) : ( + + +

You haven't enrolled in any courses yet.

+

Browse available courses below to get started!

+
+ )} +
-
- {(isFetchingQuery || loading) && ( -
- {' '} - Loading widgets... -
- )} +
- { rolesWidgets && - rolesWidgets.map((widget) => ( - - ))} -
- - {!!rolesWidgets.length &&
} - -
- - - {hasPermission(currentUser, 'READ_USERS') && -
-
-
-
- Users +

Explore Courses

+
+ {Array.isArray(courses) && courses.map((course: any) => ( + +
+
+ {course.thumbnail && course.thumbnail[0] ? ( + {course.title} + ) : ( + + )} +
+

{course.title}

+

{course.category}

+

{course.description?.replace(/<[^>]*>?/gm, '')}

-
- {users} +
+ {isEnrolled(course.id) ? ( + + ) : ( + handleEnroll(course.id)} + className="w-full bg-indigo-600 hover:bg-indigo-700 border-none" + /> + )}
-
-
- -
-
+ + ))}
- } - - {hasPermission(currentUser, 'READ_ROLES') && -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PERMISSIONS') && -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_COURSES') && -
-
-
-
- Courses -
-
- {courses} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_LESSONS') && -
-
-
-
- Lessons -
-
- {lessons} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ENROLLMENTS') && -
-
-
-
- Enrollments -
-
- {enrollments} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_LESSON_PROGRESS') && -
-
-
-
- Lesson progress -
-
- {lesson_progress} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_COURSE_ANNOUNCEMENTS') && -
-
-
-
- Course announcements -
-
- {course_announcements} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_COURSE_REVIEWS') && -
-
-
-
- Course reviews -
-
- {course_reviews} -
-
-
- -
-
-
- } - - -
- - - ) + + + ); } Dashboard.getLayout = function getLayout(page: ReactElement) { return {page} } -export default Dashboard +export default Dashboard \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index cf2b3bd..ed561b2 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,127 @@ - import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import { mdiSchool, mdiBookOpenVariant, mdiAccountGroup, mdiCheckDecagram } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; - -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('left'); +export default function LandingPage() { const textColor = useAppSelector((state) => state.style.linkColor); - const title = 'Course LMS' - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( - - ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- - -
) - } - }; - return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('Welcome to Course LMS')} - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

+ {/* Hero Section */} +
+
+
+

+ Master New Skills with Our Expert-Led Courses +

+

+ Access high-quality education from anywhere in the world. Start your journey today with our comprehensive library of lessons. +

+
+ + + Get Started + + + + Browse Courses +
- - - - - - +
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+ {/* Background Decorative Elements */} +
+ +
+ + {/* Features Section */} +
+
+
+

Learn Faster

+

+ Everything you need to succeed +

+
+
+
+
+
+ +
+
Structured Lessons
+
+

Well-organized content that guides you step-by-step through complex topics.

+
+
+
+
+ +
+
Expert Instructors
+
+

Learn from industry professionals with years of real-world experience.

+
+
+
+
+ +
+
Track Progress
+
+

Keep track of your learning milestones and completed lessons easily.

+
+
+
+
+ +
+
Certificates
+
+

Earn recognition for your hard work upon course completion.

+
+
+
+
+
+
+ + {/* Footer */} +
+
+
+ + Privacy Policy + + + Terms of Use + +
+
+

+ © 2026 {title}. All rights reserved. Built with Flatlogic. +

+
+
+
); } -Starter.getLayout = function getLayout(page: ReactElement) { +LandingPage.getLayout = function getLayout(page: ReactElement) { return {page}; -}; - +}; \ No newline at end of file