diff --git a/.gitignore b/.gitignore index e427ff3..35390a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +/backend/node_modules +/frontend/node_modules node_modules/ */node_modules/ -*/build/ +**/node_modules/ +*/build/ \ No newline at end of file diff --git a/502.html b/502.html index 09b66a6..f7da3b1 100644 --- a/502.html +++ b/502.html @@ -129,8 +129,8 @@
The application is currently launching. The page will automatically refresh once site is available.
Manage products, customers, orders, payments, and fulfillment workflows for retail operations.
+CourseFlow LMS: instructor and student workflows for courses, lessons, enrollments, and progress tracking.
CREATE DATABASE db_store_operations_manager;`
+ - `postgres=> CREATE DATABASE db_courseflow_lms;`
- Then give that new user privileges to the new database then quit the `psql`.
- - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_store_operations_manager TO admin;`
+ - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_courseflow_lms TO admin;`
- `postgres=> \q`
------------
diff --git a/backend/package.json b/backend/package.json
index ffbfb37..539bf4f 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
- "name": "storeoperationsmanager",
- "description": "Store Operations Manager - template backend",
+ "name": "courseflowlms",
+ "description": "CourseFlow LMS - template backend",
"scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate",
diff --git a/backend/src/config.js b/backend/src/config.js
index cbeace4..a0cfedb 100644
--- a/backend/src/config.js
+++ b/backend/src/config.js
@@ -11,15 +11,15 @@ const config = {
bcrypt: {
saltRounds: 12
},
- admin_pass: "b5fc3cee",
- user_pass: "51bd3a434c2c",
+ admin_pass: "578d9f61",
+ user_pass: "ac39c47254f1",
admin_email: "admin@flatlogic.com",
providers: {
LOCAL: 'local',
GOOGLE: 'google',
MICROSOFT: 'microsoft'
},
- secret_key: process.env.SECRET_KEY || 'b5fc3cee-cc82-4cf6-bbcd-51bd3a434c2c',
+ secret_key: process.env.SECRET_KEY || '578d9f61-c443-4a62-87c0-ac39c47254f1',
remote: '',
port: process.env.NODE_ENV === "production" ? "" : "8080",
hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
@@ -39,7 +39,7 @@ const config = {
},
uploadDir: os.tmpdir(),
email: {
- from: 'Store Operations Manager No data to display
Name
-{ item.name }
+Course
+{ dataFormatter.coursesOneListFormatter(item.course) }
Order
-{ dataFormatter.ordersOneListFormatter(item.order) }
+Title
+{ item.title }
Product
-{ dataFormatter.productsOneListFormatter(item.product) }
+Content
+{ item.content }
Quantity
-{ item.quantity }
+PublishedAt
+{ dataFormatter.dateTimeFormatter(item.published_at) }
UnitPrice
-{ item.unit_price }
-TotalPrice
-{ item.total_price }
+Active
+{ dataFormatter.booleanFormatter(item.is_active) }
No data to display
Are you sure you want to delete this item?
+No data to display
+Serial
+{ item.serial }
+Student
+{ dataFormatter.usersOneListFormatter(item.student) }
+Course
+{ dataFormatter.coursesOneListFormatter(item.course) }
+IssuedAt
+{ dataFormatter.dateTimeFormatter(item.issued_at) }
+File
+ {dataFormatter.filesFormatter(item.file).map(link => ( + + ))} +No data to display
+No data to display
Description
-{ item.description }
+Slug
+{ item.slug }
ParentCategory
-{ dataFormatter.categoriesOneListFormatter(item.parent) }
+Description
+{ item.description }
No data to display
Are you sure you want to delete this item?
+{item.name}
+{item.title}
@@ -75,8 +75,8 @@ const CardProducts = ({No data to display
SKU
-{ item.sku }
+Title
+{ item.title }
Name
-{ item.name }
+ShortDescription
+{ item.short_description }
Instructor
+{ dataFormatter.usersOneListFormatter(item.instructor) }
+Category
+{ dataFormatter.course_categoriesOneListFormatter(item.category) }
+Level
+{ item.level }
+Language
+{ item.language }
+Price
{ item.price }
@@ -87,18 +119,26 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPaStock
-{ item.stock }
+Published
+{ dataFormatter.booleanFormatter(item.published) }
Images
+PublishedAt
+{ dataFormatter.dateTimeFormatter(item.published_at) }
+Thumbnail
Category
-{ dataFormatter.categoriesOneListFormatter(item.category) }
-Status
-{ item.status }
+Duration(minutes)
+{ item.duration }
No data to display
No data to display
+EnrollmentCode
+{ item.enrollment_code }
+Student
+{ dataFormatter.usersOneListFormatter(item.student) }
+Course
+{ dataFormatter.coursesOneListFormatter(item.course) }
+EnrolledAt
+{ dataFormatter.dateTimeFormatter(item.enrolled_at) }
+Status
+{ item.status }
+Progress(%)
+{ item.progress_percent }
+CertificateFile
+ {dataFormatter.filesFormatter(item.certificate).map(link => ( + + ))} +No data to display
+No data to display
OrderNumber
-{ item.order_number }
+Title
+{ item.title }
Customer
-{ dataFormatter.customersOneListFormatter(item.customer) }
+Course
+{ dataFormatter.coursesOneListFormatter(item.course) }
Status
-{ item.status }
+Content
+{ item.content }
TotalAmount
-{ item.total }
+VideoFiles
+ {dataFormatter.filesFormatter(item.video_files).map(link => ( + + ))}PlacedAt
-{ dataFormatter.dateTimeFormatter(item.placed_at) }
+Order
+{ item.order }
ShippedAt
-{ dataFormatter.dateTimeFormatter(item.shipped_at) }
+Duration(minutes)
+{ item.duration }
DeliveryDate
-{ dataFormatter.dateTimeFormatter(item.delivery_date) }
+StartAt
+{ dataFormatter.dateTimeFormatter(item.start_at) }
PaymentStatus
-{ item.payment_status }
+EndAt
+{ dataFormatter.dateTimeFormatter(item.end_at) }
ShippingAddress
-{ item.shipping_address }
+Published
+{ dataFormatter.booleanFormatter(item.is_published) }
No data to display
No data to display
Name
-{ item.name }
+Summary
+{ item.summary }
{ item.email }
+Student
+{ dataFormatter.usersOneListFormatter(item.student) }
Phone
-{ item.phone }
+Lesson
+{ dataFormatter.lessonsOneListFormatter(item.lesson) }
Address
-{ item.address }
+Completed
+{ dataFormatter.booleanFormatter(item.completed) }
+CompletedAt
+{ dataFormatter.dateTimeFormatter(item.completed_at) }
+Percent
+{ item.percent }
VIP
-{ dataFormatter.booleanFormatter(item.vip) }
-TaxNumber
-{ item.tax_number }
-No data to display
No data to display
Order
-{ dataFormatter.ordersOneListFormatter(item.order) }
+Quiz
+{ dataFormatter.quizzesOneListFormatter(item.quiz) }
Carrier
-{ item.carrier }
+Question
+{ item.question }
TrackingNumber
-{ item.tracking_number }
+QuestionType
+{ item.question_type }
ShippedAt
-{ dataFormatter.dateTimeFormatter(item.shipped_at) }
+Choices
+{ item.choices }
DeliveredAt
-{ dataFormatter.dateTimeFormatter(item.delivered_at) }
-Status
-{ item.status }
+Answer
+{ item.answer }
No data to display
No data to display
Reference
-{ item.reference }
+Title
+{ item.title }
Order
-{ dataFormatter.ordersOneListFormatter(item.order) }
+Lesson
+{ dataFormatter.lessonsOneListFormatter(item.lesson) }
Amount
-{ item.amount }
+Description
+{ item.description }
Method
-{ item.method }
+PassingScore
+{ item.passing_score }
Status
-{ item.status }
+TimeLimit(minutes)
+{ item.time_limit }
PaidAt
-{ dataFormatter.dateTimeFormatter(item.paid_at) }
+Active
+{ dataFormatter.booleanFormatter(item.is_active) }
No data to display
Reference
-{payments?.reference}
-Order
+Course
@@ -119,11 +87,15 @@ const PaymentsView = () => { +{announcements?.course?.title ?? 'No data'}
+ + + + -{payments?.order?.order_number ?? 'No data'}
@@ -144,15 +116,9 @@ const PaymentsView = () => { - - - - - -Amount
-{payments?.amount || 'No data'}
+Title
+{announcements?.title}
Method
-{payments?.method ?? 'No data'}
+Content
+ {announcements.content + ? + :No data
+ }Status
-{payments?.status ?? 'No data'}
-No PaidAt
} + /> :No PublishedAt
}Name
-{order_items?.name}
+Serial
+{certificates?.serial}
Order
+Student
+ + +{certificates?.student?.firstName ?? 'No data'}
+ + @@ -123,7 +128,6 @@ const Order_itemsView = () => { -{order_items?.order?.order_number ?? 'No data'}
@@ -164,7 +168,7 @@ const Order_itemsView = () => {Product
+Course
@@ -173,7 +177,11 @@ const Order_itemsView = () => { -{order_items?.product?.name ?? 'No data'}
+ + +{certificates?.course?.title ?? 'No data'}
+ + @@ -197,6 +205,64 @@ const Order_itemsView = () => { + + + + + + + + + + + + + +No IssuedAt
} +Quantity
-{order_items?.quantity || 'No data'}
+File
+ {certificates?.file?.length + ? dataFormatter.filesFormatter(certificates.file).map(link => ( + + )) :No File
+ }UnitPrice
-{order_items?.unit_price || 'No data'}
-TotalPrice
-{order_items?.total_price || 'No data'}
-Name
-{categories?.name}
+{course_categories?.name}
+Slug
+{course_categories?.slug}
ParentCategory
- - - - - - - - - - -{categories?.parent?.name ?? 'No data'}
- - - - - - - - - - - - -Products Category
+Courses Category
Title
+{courses?.title}
+Description
+ {courses.description + ? + :No data
+ } +Instructor
+ + +{courses?.instructor?.firstName ?? 'No data'}
+ + + + + + + + + + + + + + + + + + + + + + + + +Category
+ + + + + + + + +{courses?.category?.name ?? 'No data'}
+ + + + + + + + + + + + + + + + + + +Level
+{courses?.level ?? 'No data'}
+Language
+{courses?.language}
+Price
+{courses?.price || 'No data'}
+No PublishedAt
} +Thumbnail
+ {courses?.thumbnail?.length + ? ( +No Thumbnail
+ } +Duration(minutes)
+{courses?.duration || 'No data'}
+Lessons Course
+| Title | + + + + + + + + + +Order | + + + +Duration(minutes) | + + + +StartAt | + + + +EndAt | + + + +Published | + + +
|---|---|---|---|---|---|
| + { item.title } + | + + + + + + + + + ++ { item.order } + | + + + ++ { item.duration } + | + + + ++ { dataFormatter.dateTimeFormatter(item.start_at) } + | + + + ++ { dataFormatter.dateTimeFormatter(item.end_at) } + | + + + ++ { dataFormatter.booleanFormatter(item.is_published) } + | + + +
Enrollments Course
+| EnrollmentCode | + + + + + + + +EnrolledAt | + + + +Status | + + + +Progress(%) | + + + + +
|---|---|---|---|
| + { item.enrollment_code } + | + + + + + + + ++ { dataFormatter.dateTimeFormatter(item.enrolled_at) } + | + + + ++ { item.status } + | + + + ++ { item.progress_percent } + | + + + + +
Announcements Course
+| Title | + + + + + +PublishedAt | + + + +Active | + + +
|---|---|---|
| + { item.title } + | + + + + + ++ { dataFormatter.dateTimeFormatter(item.published_at) } + | + + + ++ { dataFormatter.booleanFormatter(item.is_active) } + | + + +
Certificates Course
+| Serial | + + + + + + + +IssuedAt | + + + + +
|---|---|
| + { item.serial } + | + + + + + + + ++ { dataFormatter.dateTimeFormatter(item.issued_at) } + | + + + + +
Name
-{customers?.name}
-{customers?.email}
-Phone
-{customers?.phone}
-TaxNumber
-{customers?.tax_number}
-Orders Customer
-| OrderNumber | - - - - - -Status | - - - -TotalAmount | - - - -PlacedAt | - - - -ShippedAt | - - - -DeliveryDate | - - - -PaymentStatus | - - - -ShippingAddress | - - -
|---|---|---|---|---|---|---|---|
| - { item.order_number } - | - - - - - -- { item.status } - | - - - -- { item.total } - | - - - -- { dataFormatter.dateTimeFormatter(item.placed_at) } - | - - - -- { dataFormatter.dateTimeFormatter(item.shipped_at) } - | - - - -- { dataFormatter.dateTimeFormatter(item.delivery_date) } - | - - - -- { item.payment_status } - | - - - -- { item.shipping_address } - | - - -
EnrollmentCode
+{enrollments?.enrollment_code}
+Student
+ + +{enrollments?.student?.firstName ?? 'No data'}
+ + + + + + + + + + + + + + + + + + + + + + + + +Course
+ + + + + + + + + + +{enrollments?.course?.title ?? 'No data'}
+ + + + + + + + + + + + + + + + +No EnrolledAt
} +Status
+{enrollments?.status ?? 'No data'}
+Progress(%)
+{enrollments?.progress_percent || 'No data'}
+CertificateFile
+ {enrollments?.certificate?.length + ? dataFormatter.filesFormatter(enrollments.certificate).map(link => ( + + )) :No CertificateFile
+ } +This is a React.js/Node.js app generated by the Flatlogic Web App Generator
diff --git a/frontend/src/pages/orders/[ordersId].tsx b/frontend/src/pages/lessons/[lessonsId].tsx similarity index 60% rename from frontend/src/pages/orders/[ordersId].tsx rename to frontend/src/pages/lessons/[lessonsId].tsx index a6e9b46..cffb614 100644 --- a/frontend/src/pages/orders/[ordersId].tsx +++ b/frontend/src/pages/lessons/[lessonsId].tsx @@ -25,7 +25,7 @@ import { SelectFieldMany } from "../../components/SelectFieldMany"; import { SwitchField } from '../../components/SwitchField' import {RichTextField} from "../../components/RichTextField"; -import { update, fetch } from '../../stores/orders/ordersSlice' +import { update, fetch } from '../../stores/lessons/lessonsSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import {saveFile} from "../../helpers/fileSaver"; @@ -34,13 +34,13 @@ import ImageField from "../../components/ImageField"; -const EditOrders = () => { +const EditLessons = () => { const router = useRouter() const dispatch = useAppDispatch() const initVals = { - 'order_number': '', + 'title': '', @@ -90,7 +90,7 @@ const EditOrders = () => { - customer: null, + course: null, @@ -100,7 +100,7 @@ const EditOrders = () => { - + content: '', @@ -111,8 +111,6 @@ const EditOrders = () => { - - status: '', @@ -122,9 +120,9 @@ const EditOrders = () => { + - 'total': '', @@ -143,6 +141,8 @@ const EditOrders = () => { + + video_files: [], @@ -158,11 +158,11 @@ const EditOrders = () => { - + order: '', - placed_at: new Date(), + @@ -186,11 +186,11 @@ const EditOrders = () => { - + duration: '', - shipped_at: new Date(), + @@ -218,7 +218,7 @@ const EditOrders = () => { - delivery_date: new Date(), + start_at: new Date(), @@ -246,13 +246,13 @@ const EditOrders = () => { - + end_at: new Date(), - payment_status: '', + @@ -266,8 +266,6 @@ const EditOrders = () => { - shipping_address: '', - @@ -277,6 +275,8 @@ const EditOrders = () => { + + is_published: false, @@ -295,44 +295,44 @@ const EditOrders = () => { } const [initialValues, setInitialValues] = useState(initVals) - const { orders } = useAppSelector((state) => state.orders) + const { lessons } = useAppSelector((state) => state.lessons) - const { ordersId } = router.query + const { lessonsId } = router.query useEffect(() => { - dispatch(fetch({ id: ordersId })) - }, [ordersId]) + dispatch(fetch({ id: lessonsId })) + }, [lessonsId]) useEffect(() => { - if (typeof orders === 'object') { - setInitialValues(orders) + if (typeof lessons === 'object') { + setInitialValues(lessons) } - }, [orders]) + }, [lessons]) useEffect(() => { - if (typeof orders === 'object') { + if (typeof lessons === 'object') { const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (orders)[el]) + Object.keys(initVals).forEach(el => newInitialVal[el] = (lessons)[el]) setInitialValues(newInitialVal); } - }, [orders]) + }, [lessons]) const handleSubmit = async (data) => { - await dispatch(update({ id: ordersId, data })) - await router.push('/orders/orders-list') + await dispatch(update({ id: lessonsId, data })) + await router.push('/lessons/lessons-list') } return ( <> -OrderNumber
-{orders?.order_number}
+Title
+{lessons?.title}
Customer
+Course
@@ -119,9 +119,13 @@ const OrdersView = () => { +{lessons?.course?.title ?? 'No data'}
+ + + + -{orders?.customer?.name ?? 'No data'}
@@ -143,24 +147,17 @@ const OrdersView = () => { - - - - - - - - - -Status
-{orders?.status ?? 'No data'}
+Content
+ {lessons.content + ? + :No data
+ }TotalAmount
-{orders?.total || 'No data'}
+VideoFiles
+ {lessons?.video_files?.length + ? dataFormatter.filesFormatter(lessons.video_files).map(link => ( + + )) :No VideoFiles
+ } +Order
+{lessons?.order || 'No data'}
+Duration(minutes)
+{lessons?.duration || 'No data'}
No PlacedAt
} + /> :No StartAt
}No ShippedAt
} + /> :No EndAt
}No DeliveryDate
} + />PaymentStatus
-{orders?.payment_status ?? 'No data'}
-Order_items Order
+Progress Lesson
Payments Order
+Quizzes Lesson
Shipments Order
-| Carrier | - - - -TrackingNumber | - - - -ShippedAt | - - - -DeliveredAt | - - - -Status | - - -
|---|---|---|---|---|
| - { item.carrier } - | - - - -- { item.tracking_number } - | - - - -- { dataFormatter.dateTimeFormatter(item.shipped_at) } - | - - - -- { dataFormatter.dateTimeFormatter(item.delivered_at) } - | - - - -- { item.status } - | - - -
Use{' '}
setLogin(e.target)}>admin@flatlogic.com{' / '}
- b5fc3cee{' / '}
+ 578d9f61{' / '}
to login as Admin
Use setLogin(e.target)}>client@hello.com{' / '}
- 51bd3a434c2c{' / '}
+ ac39c47254f1{' / '}
to login as User
Summary
+{progress?.summary}
+Order
+Student
+ + +{progress?.student?.firstName ?? 'No data'}
+ + @@ -91,7 +128,6 @@ const ShipmentsView = () => { -{shipments?.order?.order_number ?? 'No data'}
@@ -112,11 +148,6 @@ const ShipmentsView = () => { -Carrier
-{shipments?.carrier}
-TrackingNumber
-{shipments?.tracking_number}
+Lesson
+ + + + + + + + + + + + +{progress?.lesson?.title ?? 'No data'}
+ + + + + + + + + + + + + +No ShippedAt
} + /> :No CompletedAt
}Percent
+{progress?.percent || 'No data'}
+No DeliveredAt
} + + + + + + + + + + + + + + + + + +Status
-{shipments?.status ?? 'No data'}
-Quiz
+ + + + + + + + + + + + + + + + + + +{quiz_questions?.quiz?.title ?? 'No data'}
+ + + + + + + + +QuestionType
+{quiz_questions?.question_type ?? 'No data'}
+SKU
-{products?.sku}
+Title
+{quizzes?.title}
Name
-{products?.name}
+Lesson
+ + + + + + + + + + + + +{quizzes?.lesson?.title ?? 'No data'}
+ + + + + + + + + + + + + +Description
- {products.description - ? + {quizzes.description + ? :No data
}Price
-{products?.price || 'No data'}
+PassingScore
+{quizzes?.passing_score || 'No data'}
Stock
-{products?.stock || 'No data'}
+TimeLimit(minutes)
+{quizzes?.time_limit || 'No data'}
Images
- {products?.images?.length - ? ( -No Images
- } -Category
- - - - - - - - - - -{products?.category?.name ?? 'No data'}
- - - - - - - - - - - - -Status
-{products?.status ?? 'No data'}
-Order_items Product
+Quiz_questions Quiz
Courses Instructor
+| Title | + + + +ShortDescription | + + + + + + + + + +Level | + + + +Language | + + + +Price | + + + +Published | + + + +PublishedAt | + + + + + +Duration(minutes) | + + +
|---|---|---|---|---|---|---|---|
| + { item.title } + | + + + ++ { item.short_description } + | + + + + + + + + + ++ { item.level } + | + + + ++ { item.language } + | + + + ++ { item.price } + | + + + ++ { dataFormatter.booleanFormatter(item.published) } + | + + + ++ { dataFormatter.dateTimeFormatter(item.published_at) } + | + + + + + ++ { item.duration } + | + + +
Enrollments Student
+| EnrollmentCode | + + + + + + + +EnrolledAt | + + + +Status | + + + +Progress(%) | + + + + +
|---|---|---|---|
| + { item.enrollment_code } + | + + + + + + + ++ { dataFormatter.dateTimeFormatter(item.enrolled_at) } + | + + + ++ { item.status } + | + + + ++ { item.progress_percent } + | + + + + +
Progress Student
+| Summary | + + + + + + + +Completed | + + + +CompletedAt | + + + +Percent | + + + +Notes | + + +
|---|---|---|---|---|
| + { item.summary } + | + + + + + + + ++ { dataFormatter.booleanFormatter(item.completed) } + | + + + ++ { dataFormatter.dateTimeFormatter(item.completed_at) } + | + + + ++ { item.percent } + | + + + ++ { item.notes } + | + + +
Certificates Student
+| Serial | + + + + + + + +IssuedAt | + + + + +
|---|---|
| + { item.serial } + | + + + + + + + ++ { dataFormatter.dateTimeFormatter(item.issued_at) } + | + + + + +