diff --git a/assets/pasted-20260115-162026-20208a7c.jpg b/assets/pasted-20260115-162026-20208a7c.jpg
new file mode 100644
index 0000000..1caa96c
Binary files /dev/null and b/assets/pasted-20260115-162026-20208a7c.jpg differ
diff --git a/assets/pasted-20260115-162603-0edb92a7.jpg b/assets/pasted-20260115-162603-0edb92a7.jpg
new file mode 100644
index 0000000..d86b8d4
Binary files /dev/null and b/assets/pasted-20260115-162603-0edb92a7.jpg differ
diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js
index 5edbaf1..0e5a8db 100644
--- a/backend/src/db/seeders/20231127130745-sample-data.js
+++ b/backend/src/db/seeders/20231127130745-sample-data.js
@@ -129,97 +129,188 @@ const CoursesData = [
+
+
+
+
"description": "Deep dive into hooks, context, performance optimization, and patterns.",
+
+
+
+
+
+
- "status": "archived",
+
+ "status": "published",
+
+
+
+
+
+
+
// type code here for "relation_one" field
+
+
+
+
+
+
+
"category": "Web Development",
+
+
+
+
+
+
+
"price": 79.99,
+
+
+
+
+
+
+
"duration": 360,
+
+
+
+
+
+
+
// type code here for "images" field
+
+
+
+
+
+
+
"published_at": new Date('2025-12-05T09:00:00Z'),
+
+
+
+
+
+
+
"enrollment_count": 64,
+
+
+
+
},
+
+
{
+
+
+
+
+
"title": "UX for Developers",
+
+
+
+
+
+
+
"description": "Design thinking and practical UX techniques for building better interfaces.",
+
+
+
+
+
+
- "status": "archived",
+
+ "status": "published",
+
+
+
+
+
+
- // type code here for "relation_one" field
-
+
+ // type code here for "relation_one" field
diff --git a/backend/src/index.js b/backend/src/index.js
index d0a4630..2d51171 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -15,6 +15,7 @@ const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const pexelsRoutes = require('./routes/pexels');
+const publicRoutes = require('./routes/public');
const openaiRoutes = require('./routes/openai');
@@ -87,6 +88,7 @@ require('./auth/auth');
app.use(bodyParser.json());
app.use('/api/auth', authRoutes);
+app.use('/api/public', publicRoutes);
app.use('/api/file', fileRoutes);
app.use('/api/pexels', pexelsRoutes);
app.enable('trust proxy');
diff --git a/backend/src/routes/public.js b/backend/src/routes/public.js
new file mode 100644
index 0000000..e256399
--- /dev/null
+++ b/backend/src/routes/public.js
@@ -0,0 +1,17 @@
+const express = require('express');
+const router = express.Router();
+const CoursesDBApi = require('../db/api/courses');
+
+router.get('/courses', async (req, res) => {
+ try {
+ const publishedCourses = await CoursesDBApi.findAll({
+ where: { status: 'published' },
+ });
+ res.json(publishedCourses);
+ } catch (error) {
+ console.error(error);
+ res.status(500).send('Internal Server Error');
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/frontend/public/assets/vm-shot-2026-01-15T16-20-21-040Z.jpg b/frontend/public/assets/vm-shot-2026-01-15T16-20-21-040Z.jpg
new file mode 100644
index 0000000..1caa96c
Binary files /dev/null and b/frontend/public/assets/vm-shot-2026-01-15T16-20-21-040Z.jpg differ
diff --git a/frontend/public/assets/vm-shot-2026-01-15T16-24-24-855Z.jpg b/frontend/public/assets/vm-shot-2026-01-15T16-24-24-855Z.jpg
new file mode 100644
index 0000000..b3c4dfd
Binary files /dev/null and b/frontend/public/assets/vm-shot-2026-01-15T16-24-24-855Z.jpg differ
diff --git a/frontend/public/assets/vm-shot-2026-01-15T16-25-54-285Z.jpg b/frontend/public/assets/vm-shot-2026-01-15T16-25-54-285Z.jpg
new file mode 100644
index 0000000..d86b8d4
Binary files /dev/null and b/frontend/public/assets/vm-shot-2026-01-15T16-25-54-285Z.jpg differ
diff --git a/frontend/src/components/Courses/PublicCourseCard.tsx b/frontend/src/components/Courses/PublicCourseCard.tsx
new file mode 100644
index 0000000..4a7c493
--- /dev/null
+++ b/frontend/src/components/Courses/PublicCourseCard.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import ImageField from '../ImageField';
+import Link from 'next/link';
+
+type Props = {
+ course: any;
+};
+
+const PublicCourseCard = ({ course }: Props) => {
+ return (
+
+
+
+ {course.title}
+
+
+ );
+};
+
+export default PublicCourseCard;
diff --git a/frontend/src/components/Courses/PublicCourseList.tsx b/frontend/src/components/Courses/PublicCourseList.tsx
new file mode 100644
index 0000000..d3afec0
--- /dev/null
+++ b/frontend/src/components/Courses/PublicCourseList.tsx
@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import axios from 'axios';
+import PublicCourseCard from './PublicCourseCard';
+import LoadingSpinner from '../LoadingSpinner';
+
+const PublicCourseList = () => {
+ const [courses, setCourses] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ axios
+ .get('/public/courses')
+ .then((res) => {
+ setCourses(res.data.rows);
+ setLoading(false);
+ })
+ .catch((err) => {
+ console.error(err);
+ setLoading(false);
+ });
+ }, []);
+
+ return (
+
+
Our Courses
+ {loading &&
}
+
+ {!loading &&
+ courses.map((course) => )}
+
+
+ );
+};
+
+export default PublicCourseList;
diff --git a/frontend/src/components/Courses/TableCourses.tsx b/frontend/src/components/Courses/TableCourses.tsx
index 27c67cc..66c51b3 100644
--- a/frontend/src/components/Courses/TableCourses.tsx
+++ b/frontend/src/components/Courses/TableCourses.tsx
@@ -1,505 +1,52 @@
-import React, { useEffect, useState, useMemo } from 'react'
-import { createPortal } from 'react-dom';
-import { ToastContainer, toast } from 'react-toastify';
-import BaseButton from '../BaseButton'
-import CardBoxModal from '../CardBoxModal'
-import CardBox from "../CardBox";
-import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/courses/coursesSlice'
+import React, { useEffect } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
+import { fetch } from '../../stores/courses/coursesSlice'
+import Link from 'next/link'
+import { mdiPencil } from '@mdi/js'
+import BaseButton from '../BaseButton'
import { useRouter } from 'next/router'
-import { Field, Form, Formik } from "formik";
-import {
- DataGrid,
- GridColDef,
-} from '@mui/x-data-grid';
-import {loadColumns} from "./configureCoursesCols";
-import _ from 'lodash';
-import dataFormatter from '../../helpers/dataFormatter'
-import {dataGridStyles} from "../../styles";
+const TableCourses = () => {
+ const dispatch = useAppDispatch()
+ const router = useRouter()
-import KanbanBoard from '../KanbanBoard/KanbanBoard';
-import axios from 'axios';
-
-
-const perPage = 10
-
-const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) => {
- const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
-
- const dispatch = useAppDispatch();
- const router = useRouter();
-
- const pagesList = [];
- const [id, setId] = useState(null);
- const [currentPage, setCurrentPage] = useState(0);
- const [filterRequest, setFilterRequest] = React.useState('');
- const [columns, setColumns] = useState([]);
- const [selectedRows, setSelectedRows] = useState([]);
- const [sortModel, setSortModel] = useState([
- {
- field: '',
- sort: 'desc',
- },
- ]);
-
- const [kanbanColumns, setKanbanColumns] = useState | null>(null);
- const [kanbanFilters, setKanbanFilters] = useState('');
-
- const { courses, loading, count, notify: coursesNotify, refetch } = useAppSelector((state) => state.courses)
- const { currentUser } = useAppSelector((state) => state.auth);
- const focusRing = useAppSelector((state) => state.style.focusRingColor);
- const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
- const corners = useAppSelector((state) => state.style.corners);
- const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
- for (let i = 0; i < numPages; i++) {
- pagesList.push(i);
- }
-
- const loadData = async (page = currentPage, request = filterRequest) => {
- if (page !== currentPage) setCurrentPage(page);
- if (request !== filterRequest) setFilterRequest(request);
- const { sort, field } = sortModel[0];
-
- const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
- dispatch(fetch({ limit: perPage, page, query }));
- };
+ const { courses, loading } = useAppSelector((state) => state.courses)
useEffect(() => {
- if (coursesNotify.showNotification) {
- notify(coursesNotify.typeNotification, coursesNotify.textNotification);
- }
- }, [coursesNotify.showNotification]);
+ dispatch(fetch({}))
+ }, [dispatch])
- useEffect(() => {
- if (!currentUser) return;
- loadData();
- }, [sortModel, currentUser]);
-
- useEffect(() => {
- if (refetch) {
- loadData(0);
- dispatch(setRefetch(false));
- }
- }, [refetch, dispatch]);
-
- const [isModalInfoActive, setIsModalInfoActive] = useState(false)
- const [isModalTrashActive, setIsModalTrashActive] = useState(false)
-
- const handleModalAction = () => {
- setIsModalInfoActive(false)
- setIsModalTrashActive(false)
+ if (loading) {
+ return Loading...
}
-
- useEffect(() => {
-
-
-
- setKanbanColumns([
-
- { id: "draft", label: "draft" },
-
- { id: "published", label: "published" },
-
- { id: "archived", label: "archived" },
-
- ]);
-
-
- }, []);
-
-
-
-
- const handleDeleteModalAction = (id: string) => {
- setId(id)
- setIsModalTrashActive(true)
- }
- const handleDeleteAction = async () => {
- if (id) {
- await dispatch(deleteItem(id));
- await loadData(0);
- setIsModalTrashActive(false);
- }
- };
-
- const generateFilterRequests = useMemo(() => {
- let request = '&';
- filterItems.forEach((item) => {
- const isRangeFilter = filters.find(
- (filter) =>
- filter.title === item.fields.selectedField &&
- (filter.number || filter.date),
- );
-
- if (isRangeFilter) {
- const from = item.fields.filterValueFrom;
- const to = item.fields.filterValueTo;
- if (from) {
- request += `${item.fields.selectedField}Range=${from}&`;
- }
- if (to) {
- request += `${item.fields.selectedField}Range=${to}&`;
- }
- } else {
- const value = item.fields.filterValue;
- if (value) {
- request += `${item.fields.selectedField}=${value}&`;
- }
- }
- });
- return request;
- }, [filterItems, filters]);
-
- const deleteFilter = (value) => {
- const newItems = filterItems.filter((item) => item.id !== value);
-
- if (newItems.length) {
- setFilterItems(newItems);
- } else {
- loadData(0, '');
-
- setKanbanFilters('');
-
- setFilterItems(newItems);
- }
- };
-
- const handleSubmit = () => {
- loadData(0, generateFilterRequests);
-
- setKanbanFilters(generateFilterRequests);
-
- };
-
- const handleChange = (id) => (e) => {
- const value = e.target.value;
- const name = e.target.name;
-
- setFilterItems(
- filterItems.map((item) => {
- if (item.id !== id) return item;
- if (name === 'selectedField') return { id, fields: { [name]: value } };
-
- return { id, fields: { ...item.fields, [name]: value } }
- }),
- );
- };
-
- const handleReset = () => {
- setFilterItems([]);
- loadData(0, '');
-
- setKanbanFilters('');
-
- };
-
- const onPageChange = (page: number) => {
- loadData(page);
- setCurrentPage(page);
- };
-
-
- useEffect(() => {
- if (!currentUser) return;
-
- loadColumns(
- handleDeleteModalAction,
- `courses`,
- currentUser,
- ).then((newCols) => setColumns(newCols));
- }, [currentUser]);
-
-
-
- const handleTableSubmit = async (id: string, data) => {
-
- if (!_.isEmpty(data)) {
- await dispatch(update({ id, data }))
- .unwrap()
- .then((res) => res)
- .catch((err) => {
- throw new Error(err);
- });
- }
- };
-
- const onDeleteRows = async (selectedRows) => {
- await dispatch(deleteItemsByIds(selectedRows));
- await loadData(0);
- };
-
- const controlClasses =
- 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
- ` ${bgColor} ${focusRing} ${corners} ` +
- 'dark:bg-slate-800 border';
-
-
- const dataGrid = (
-
- `datagrid--row`}
- rows={courses ?? []}
- columns={columns}
- initialState={{
- pagination: {
- paginationModel: {
- pageSize: 10,
- },
- },
- }}
- disableRowSelectionOnClick
- onProcessRowUpdateError={(params) => {
- console.log('Error', params);
- }}
- processRowUpdate={async (newRow, oldRow) => {
- const data = dataFormatter.dataGridEditFormatter(newRow);
-
- try {
- await handleTableSubmit(newRow.id, data);
- return newRow;
- } catch {
- return oldRow;
- }
- }}
- sortingMode={'server'}
- checkboxSelection
- onRowSelectionModelChange={(ids) => {
- setSelectedRows(ids)
- }}
- onSortModelChange={(params) => {
- params.length
- ? setSortModel(params)
- : setSortModel([{ field: '', sort: 'desc' }]);
- }}
- rowCount={count}
- pageSizeOptions={[10]}
- paginationMode={'server'}
- loading={loading}
- onPaginationModelChange={(params) => {
- onPageChange(params.page);
- }}
- />
-
- )
-
return (
- <>
- {filterItems && Array.isArray( filterItems ) && filterItems.length ?
-
- null}
- >
-
-
- : null
- }
-
- Are you sure you want to delete this item?
-
-
-
-
- {!showGrid && kanbanColumns && (
-
- )}
-
-
-
- {showGrid && dataGrid}
-
-
- {selectedRows.length > 0 &&
- createPortal(
- onDeleteRows(selectedRows)}
- />,
- document.getElementById('delete-rows-button'),
- )}
-
- >
+
+
+
+ | Title |
+ Description |
+ Actions |
+
+
+
+ {courses &&
+ courses.map((course) => (
+
+ | {course.title} |
+ {course.description} |
+
+
+
+
+
+
+ |
+
+ ))}
+
+
)
}
-export default TableSampleCourses
+export default TableCourses
\ No newline at end of file
diff --git a/frontend/src/css/_landing.css b/frontend/src/css/_landing.css
new file mode 100644
index 0000000..93aff2b
--- /dev/null
+++ b/frontend/src/css/_landing.css
@@ -0,0 +1,40 @@
+
+.landing-gradient {
+ background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
+ background-size: 400% 400%;
+ animation: gradient 15s ease infinite;
+}
+
+@keyframes gradient {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+.glass-card {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.fade-in-up {
+ animation: fadeInUp 1s ease-out forwards;
+}
+
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index 1b4bc09..1722e4e 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -66,8 +66,13 @@ const menuAside: MenuAsideItem[] = [
},
{
href: '/profile',
- label: 'Profile',
icon: icon.mdiAccountCircle,
+ label: 'Profile',
+ },
+ {
+ href: '/courses',
+ label: 'Courses',
+ icon: icon.mdiBookOpenPageVariant,
},
diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx
index 02b0a0a..ddd9058 100644
--- a/frontend/src/pages/_app.tsx
+++ b/frontend/src/pages/_app.tsx
@@ -6,6 +6,7 @@ import Head from 'next/head';
import { store } from '../stores/store';
import { Provider } from 'react-redux';
import '../css/main.css';
+import '../css/_landing.css';
import axios from 'axios';
import { baseURLApi } from '../config';
import { useRouter } from 'next/router';
diff --git a/frontend/src/pages/courses/[coursesId].tsx b/frontend/src/pages/courses/[id].tsx
similarity index 95%
rename from frontend/src/pages/courses/[coursesId].tsx
rename to frontend/src/pages/courses/[id].tsx
index ca282ee..84441ea 100644
--- a/frontend/src/pages/courses/[coursesId].tsx
+++ b/frontend/src/pages/courses/[id].tsx
@@ -34,9 +34,9 @@ import ImageField from "../../components/ImageField";
-const EditCourses = () => {
+const CoursesEdit = () => {
const router = useRouter()
- const dispatch = useAppDispatch()
+ const dispatch = useAppAppDispatch()
const initVals = {
@@ -326,11 +326,11 @@ const EditCourses = () => {
const { courses } = useAppSelector((state) => state.courses)
- const { coursesId } = router.query
+ const { id } = router.query
useEffect(() => {
- dispatch(fetch({ id: coursesId }))
- }, [coursesId])
+ dispatch(fetch({ id }))
+ }, [id])
useEffect(() => {
if (typeof courses === 'object') {
@@ -350,8 +350,8 @@ const EditCourses = () => {
}, [courses])
const handleSubmit = async (data) => {
- await dispatch(update({ id: coursesId, data }))
- await router.push('/courses/courses-list')
+ await dispatch(update({ id, data }))
+ await router.push('/courses')
}
return (
@@ -786,7 +786,7 @@ const EditCourses = () => {
- router.push('/courses/courses-list')}/>
+ router.push('/courses')}/>
@@ -808,4 +808,4 @@ EditCourses.getLayout = function getLayout(page: ReactElement) {
)
}
-export default EditCourses
+export default CoursesEdit
diff --git a/frontend/src/pages/courses/index.tsx b/frontend/src/pages/courses/index.tsx
new file mode 100644
index 0000000..df38ed4
--- /dev/null
+++ b/frontend/src/pages/courses/index.tsx
@@ -0,0 +1,47 @@
+
+import { mdiChartTimelineVariant } from '@mdi/js'
+import Head from 'next/head'
+import React, { ReactElement } from 'react'
+import CardBox from '../../components/CardBox'
+import LayoutAuthenticated from '../../layouts/Authenticated'
+import SectionMain from '../../components/SectionMain'
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
+import { getPageTitle } from '../../config'
+import TableCourses from '../../components/Courses/TableCourses'
+import BaseButton from '../../components/BaseButton'
+import { useAppSelector } from '../../stores/hooks'
+import { hasPermission } from '../../helpers/userPermissions'
+
+const CoursesPage = () => {
+ const { currentUser } = useAppSelector((state) => state.auth)
+ const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_COURSES')
+
+ return (
+ <>
+
+ {getPageTitle('Courses')}
+
+
+
+ {''}
+
+
+ {hasCreatePermission && (
+
+
+
+ )}
+
+
+
+
+
+ >
+ )
+}
+
+CoursesPage.getLayout = function getLayout(page: ReactElement) {
+ return {page}
+}
+
+export default CoursesPage
diff --git a/frontend/src/pages/courses/new.tsx b/frontend/src/pages/courses/new.tsx
new file mode 100644
index 0000000..6fcdf2a
--- /dev/null
+++ b/frontend/src/pages/courses/new.tsx
@@ -0,0 +1,77 @@
+
+import { mdiChartTimelineVariant } from '@mdi/js'
+import Head from 'next/head'
+import React, { ReactElement } from 'react'
+import CardBox from '../../components/CardBox'
+import LayoutAuthenticated from '../../layouts/Authenticated'
+import SectionMain from '../../components/SectionMain'
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
+import { getPageTitle } from '../../config'
+import { Field, Form, Formik } from 'formik'
+import FormField from '../../components/FormField'
+import BaseDivider from '../../components/BaseDivider'
+import BaseButtons from '../../components/BaseButtons'
+import BaseButton from '../../components/BaseButton'
+import { RichTextField } from '../../components/RichTextField'
+import { create } from '../../stores/courses/coursesSlice'
+import { useAppDispatch } from '../../stores/hooks'
+import { useRouter } from 'next/router'
+
+const initialValues = {
+ title: '',
+ description: '',
+}
+
+const CoursesNew = () => {
+ const router = useRouter()
+ const dispatch = useAppDispatch()
+
+ const handleSubmit = async (data) => {
+ await dispatch(create(data))
+ await router.push('/courses')
+ }
+ return (
+ <>
+
+ {getPageTitle('New Course')}
+
+
+
+ {''}
+
+
+ handleSubmit(values)}>
+
+
+
+
+ >
+ )
+}
+
+CoursesNew.getLayout = function getLayout(page: ReactElement) {
+ return {page}
+}
+
+export default CoursesNew
diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx
index bc1e56c..c92c74e 100644
--- a/frontend/src/pages/dashboard.tsx
+++ b/frontend/src/pages/dashboard.tsx
@@ -98,12 +98,6 @@ const Dashboard = () => {
{''}
- {hasPermission(currentUser, 'CREATE_ROLES') && }
{!!rolesWidgets.length &&
hasPermission(currentUser, 'CREATE_ROLES') && (
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 4d79de8..6ada819 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,173 +1,135 @@
+import React from 'react'
+import type { ReactElement } from 'react'
+import Head from 'next/head'
+import Link from 'next/link'
+import BaseButton from '../components/BaseButton'
+import SectionFullScreen from '../components/SectionFullScreen'
+import LayoutGuest from '../layouts/Guest'
+import { getPageTitle } from '../config'
+import { useAppSelector } from '../stores/hooks'
+import {
+ mdiBookOpenPageVariant,
+ mdiAccountGroup,
+ mdiChartLine,
+ mdiGithub,
+ mdiTwitter,
+ mdiLinkedin,
+} from '@mdi/js'
+import BaseIcon from '../components/BaseIcon'
+import PublicCourseList from '../components/Courses/PublicCourseList'
-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';
+export default function Landing() {
+ const textColor = useAppSelector((state) => state.style.linkColor)
+ const title = 'CourseFlow LMS'
+ const features = [
+ {
+ icon: mdiBookOpenPageVariant,
+ title: 'Interactive Courses',
+ description: 'Engage your learners with rich multimedia content, quizzes, and assignments.',
+ },
+ {
+ icon: mdiAccountGroup,
+ title: 'User Management',
+ description: 'Easily manage users, roles, and permissions with our flexible system.',
+ },
+ {
+ icon: mdiChartLine,
+ title: 'Progress Tracking',
+ description: 'Monitor learner progress with detailed analytics and reporting tools.',
+ },
+ ]
-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('right');
- const textColor = useAppSelector((state) => state.style.linkColor);
-
- const title = 'CourseFlow 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 (
-
-
-
-
)
- }
- };
+ const socialLinks = [
+ { href: 'https://github.com', icon: mdiGithub },
+ { href: 'https://twitter.com', icon: mdiTwitter },
+ { href: 'https://linkedin.com', icon: mdiLinkedin },
+ ]
return (
-
+
-
{getPageTitle('Starter Page')}
+
{getPageTitle('Welcome')}
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Welcome to {title}
+
+
+ A modern and flexible platform for your online courses.
+
+
+
+
-
-
-
© 2024 {title}. All rights reserved
-
- Privacy Policy
-
-
+
+
+
+
Why Choose Us?
+
+ Everything you need to create and manage your online learning experience.
+
+
+
+ {features.map((feature, index) => (
+
+
+
{feature.title}
+
{feature.description}
+
+ ))}
+
+
+
+
+
+
+
- );
+ )
}
-Starter.getLayout = function getLayout(page: ReactElement) {
- return {page};
-};
-
+Landing.getLayout = function getLayout(page: ReactElement) {
+ return {page}
+}
\ No newline at end of file
diff --git a/frontend/src/pages/profile.tsx b/frontend/src/pages/profile/edit.tsx
similarity index 86%
rename from frontend/src/pages/profile.tsx
rename to frontend/src/pages/profile/edit.tsx
index f5eb7cf..25302dc 100644
--- a/frontend/src/pages/profile.tsx
+++ b/frontend/src/pages/profile/edit.tsx
@@ -8,27 +8,27 @@ import { ToastContainer, toast } from 'react-toastify';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
-import CardBox from '../components/CardBox';
-import LayoutAuthenticated from '../layouts/Authenticated';
-import SectionMain from '../components/SectionMain';
-import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
-import { getPageTitle } from '../config';
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
import { Field, Form, Formik } from 'formik';
-import FormField from '../components/FormField';
-import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
-import BaseButton from '../components/BaseButton';
-import FormCheckRadio from '../components/FormCheckRadio';
-import FormCheckRadioGroup from '../components/FormCheckRadioGroup';
-import FormImagePicker from '../components/FormImagePicker';
-import { SwitchField } from '../components/SwitchField';
-import { SelectField } from '../components/SelectField';
+import FormField from '../../components/FormField';
+import BaseDivider from '../../components/BaseDivider';
+import BaseButtons from '../../components/BaseButtons';
+import BaseButton from '../../components/BaseButton';
+import FormCheckRadio from '../../components/FormCheckRadio';
+import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
+import FormImagePicker from '../../components/FormImagePicker';
+import { SwitchField } from '../../components/SwitchField';
+import { SelectField } from '../../components/SelectField';
-import { update, fetch } from '../stores/users/usersSlice';
-import { useAppDispatch, useAppSelector } from '../stores/hooks';
+import { update, fetch } from '../../stores/users/usersSlice';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
import { useRouter } from 'next/router';
-import {findMe} from "../stores/authSlice";
+import {findMe} from "../../stores/authSlice";
const EditUsers = () => {
const { currentUser, isFetching, token } = useAppSelector(
diff --git a/frontend/src/pages/profile/index.tsx b/frontend/src/pages/profile/index.tsx
new file mode 100644
index 0000000..b7e381f
--- /dev/null
+++ b/frontend/src/pages/profile/index.tsx
@@ -0,0 +1,103 @@
+
+import {
+ mdiChartTimelineVariant,
+ mdiPencil
+} from '@mdi/js';
+import Head from 'next/head';
+import React, { ReactElement } from 'react';
+
+
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
+
+import BaseButton from '../../components/BaseButton';
+
+import { useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import UserAvatar from '../../components/UserAvatar';
+import LoadingSpinner from '../../components/LoadingSpinner';
+
+const ProfilePage = () => {
+ const { currentUser } = useAppSelector(
+ (state) => state.auth,
+ );
+ const router = useRouter();
+
+
+ const handleEdit = () => {
+ router.push('/profile/edit');
+ }
+
+ if (!currentUser) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+ {getPageTitle('Profile')}
+
+
+
+
+
+
+
+
+
+ {currentUser.avatar && currentUser.avatar.length > 0 ? (
+

+ ) : (
+
+ )}
+
+
+
+
{`${currentUser?.firstName || ''} ${
+ currentUser?.lastName || ''
+ }`}
+
{currentUser?.email}
+ {currentUser.app_role && (
+
{
+ currentUser.app_role.name.charAt(0).toUpperCase() +
+ currentUser.app_role.name.slice(1)
+ }
+ )}
+
+
+
+ >
+ );
+};
+
+ProfilePage.getLayout = function getLayout(page: ReactElement) {
+ return {page};
+};
+
+export default ProfilePage;