diff --git a/backend/Actions/SenseiLMS/RecordApiHelper.php b/backend/Actions/SenseiLMS/RecordApiHelper.php new file mode 100644 index 000000000..96d5ab0dc --- /dev/null +++ b/backend/Actions/SenseiLMS/RecordApiHelper.php @@ -0,0 +1,127 @@ +_integrationDetails = $integrationDetails; + $this->_integrationID = $integId; + } + + public function execute($fieldValues, $fieldMap, $utilities) + { + if (!class_exists('Sensei_Main')) { + return [ + 'success' => false, + 'message' => __('Sensei LMS is not installed or activated', 'bit-integrations') + ]; + } + + $fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues); + + $mainAction = $this->_integrationDetails->mainAction ?? null; + + $defaultResponse = [ + 'success' => false, + // translators: %s: Plugin name + 'message' => wp_sprintf(__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integrations Pro') + ]; + + switch ($mainAction) { + case 'enroll_user_in_course': + $response = Hooks::apply(Config::withPrefix('sensei_lms_enroll_user_in_course'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'withdraw_user_from_course': + $response = Hooks::apply(Config::withPrefix('sensei_lms_withdraw_user_from_course'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'start_course_for_user': + $response = Hooks::apply(Config::withPrefix('sensei_lms_start_course_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'complete_course_for_user': + $response = Hooks::apply(Config::withPrefix('sensei_lms_complete_course_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'reset_course_for_user': + $response = Hooks::apply(Config::withPrefix('sensei_lms_reset_course_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'start_lesson_for_user': + $response = Hooks::apply(Config::withPrefix('sensei_lms_start_lesson_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'update_lesson_status': + $response = Hooks::apply(Config::withPrefix('sensei_lms_update_lesson_status'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'reset_lesson_for_user': + $response = Hooks::apply(Config::withPrefix('sensei_lms_reset_lesson_for_user'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'grade_quiz': + $response = Hooks::apply(Config::withPrefix('sensei_lms_grade_quiz'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'create_course': + $response = Hooks::apply(Config::withPrefix('sensei_lms_create_course'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'create_lesson': + $response = Hooks::apply(Config::withPrefix('sensei_lms_create_lesson'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + case 'create_certificate': + $response = Hooks::apply(Config::withPrefix('sensei_lms_create_certificate'), $defaultResponse, $fieldData, $this->_integrationDetails); + + break; + default: + $response = ['success' => false, 'message' => __('Invalid action', 'bit-integrations')]; + + break; + } + + $responseType = isset($response['success']) && $response['success'] ? 'success' : 'error'; + LogHandler::save($this->_integrationID, ['type' => 'SenseiLMS', 'type_name' => $mainAction], $responseType, $response); + + return $response; + } + + private static function generateReqDataFromFieldMap($fieldMap, $fieldValues) + { + $dataFinal = []; + foreach ($fieldMap as $item) { + if (empty($item->senseiLMSField)) { + continue; + } + + $triggerValue = $item->formField; + $actionValue = $item->senseiLMSField; + + $dataFinal[$actionValue] = $triggerValue === 'custom' && isset($item->customValue) + ? Common::replaceFieldWithValue($item->customValue, $fieldValues) + : $fieldValues[$triggerValue] ?? ''; + } + + return $dataFinal; + } +} diff --git a/backend/Actions/SenseiLMS/Routes.php b/backend/Actions/SenseiLMS/Routes.php new file mode 100644 index 000000000..d63c7f576 --- /dev/null +++ b/backend/Actions/SenseiLMS/Routes.php @@ -0,0 +1,12 @@ +flow_details; + $integId = $integrationData->id; + $fieldMap = $integrationDetails->field_map; + $utilities = isset($integrationDetails->utilities) ? $integrationDetails->utilities : []; + + if (empty($fieldMap)) { + return new WP_Error('field_map_empty', __('Field map is empty', 'bit-integrations')); + } + + $recordApiHelper = new RecordApiHelper($integrationDetails, $integId); + + return $recordApiHelper->execute($fieldValues, $fieldMap, $utilities); + } + + private static function postOptions($postType) + { + $options = []; + + $posts = get_posts( + [ + 'post_type' => $postType, + 'post_status' => 'publish', + 'numberposts' => -1, + ] + ); + + foreach ($posts as $post) { + $options[] = (object) [ + 'value' => $post->ID, + 'label' => $post->post_title, + ]; + } + + return $options; + } +} diff --git a/frontend/src/components/AllIntegrations/EditInteg.jsx b/frontend/src/components/AllIntegrations/EditInteg.jsx index d4daa3176..a9bb84ab4 100644 --- a/frontend/src/components/AllIntegrations/EditInteg.jsx +++ b/frontend/src/components/AllIntegrations/EditInteg.jsx @@ -180,6 +180,7 @@ const EditCreatorLms = lazy(() => import('./CreatorLms/EditCreatorLms')) const EditUltimateAffiliatePro = lazy(() => import('./UltimateAffiliatePro/EditUltimateAffiliatePro')) const EditBookly = lazy(() => import('./Bookly/EditBookly')) const EditFluentCart = lazy(() => import('./FluentCart/EditFluentCart')) +const EditSenseiLMS = lazy(() => import('./SenseiLMS/EditSenseiLMS')) const EditFluentPlayer = lazy(() => import('./FluentPlayer/EditFluentPlayer')) const EditBitCrm = lazy(() => import('./BitCrm/EditBitCrm')) const EditWsms = lazy(() => import('./Wsms/EditWsms')) @@ -631,6 +632,8 @@ const IntegType = memo(({ allIntegURL, flow }) => { return case 'FluentCart': return + case 'SenseiLMS': + return case 'FluentPlayer': return case 'BitCrm': diff --git a/frontend/src/components/AllIntegrations/IntegInfo.jsx b/frontend/src/components/AllIntegrations/IntegInfo.jsx index 057cbd6b0..66ef4bee3 100644 --- a/frontend/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend/src/components/AllIntegrations/IntegInfo.jsx @@ -186,6 +186,7 @@ const UltimateAffiliateProAuthorization = lazy( ) const BooklyAuthorization = lazy(() => import('./Bookly/BooklyAuthorization')) const FluentCartAuthorization = lazy(() => import('./FluentCart/FluentCartAuthorization')) +const SenseiLMSAuthorization = lazy(() => import('./SenseiLMS/SenseiLMSAuthorization')) const FluentPlayerAuthorization = lazy(() => import('./FluentPlayer/FluentPlayerAuthorization')) const BitCrmAuthorization = lazy(() => import('./BitCrm/BitCrmAuthorization')) const WsmsAuthorization = lazy(() => import('./Wsms/WsmsAuthorization')) @@ -671,6 +672,8 @@ const IntegrationInfo = memo(({ integrationConf, location, editUrl }) => { return case 'FluentCart': return + case 'SenseiLMS': + return case 'FluentPlayer': return case 'BitCrm': diff --git a/frontend/src/components/AllIntegrations/NewInteg.jsx b/frontend/src/components/AllIntegrations/NewInteg.jsx index ab927ddcc..b8a89742c 100644 --- a/frontend/src/components/AllIntegrations/NewInteg.jsx +++ b/frontend/src/components/AllIntegrations/NewInteg.jsx @@ -179,6 +179,7 @@ const CreatorLms = lazy(() => import('./CreatorLms/CreatorLms')) const UltimateAffiliatePro = lazy(() => import('./UltimateAffiliatePro/UltimateAffiliatePro')) const Bookly = lazy(() => import('./Bookly/Bookly')) const FluentCart = lazy(() => import('./FluentCart/FluentCart')) +const SenseiLMS = lazy(() => import('./SenseiLMS/SenseiLMS')) const FluentPlayer = lazy(() => import('./FluentPlayer/FluentPlayer')) const BitCrm = lazy(() => import('./BitCrm/BitCrm')) const Wsms = lazy(() => import('./Wsms/Wsms')) @@ -1750,6 +1751,15 @@ const NewIntegs = memo(({ integUrlName, allIntegURL, flow, setFlow }) => { setFlow={setFlow} /> ) + case 'SenseiLMS': + return ( + + ) case 'FluentPlayer': return ( { + const action = senseiLMSConf?.mainAction + if (!action) { + return + } + + setSenseiLMSConf(prevConf => + create(prevConf, draftConf => { + draftConf.senseiLMSFields = senseiLMSStaticData[action] || [] + }) + ) + + if (courseActions.includes(action)) { + refreshCourses(setSenseiLMSConf, setIsLoading) + } else if (lessonActions.includes(action)) { + refreshLessons(setSenseiLMSConf, setIsLoading) + } else if (quizActions.includes(action)) { + refreshQuizzes(setSenseiLMSConf, setIsLoading) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} + handleInput(e, senseiLMSConf, setSenseiLMSConf)} + name="name" + value={senseiLMSConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> +
+
+ + + + + + + saveActionConf({ + flow, + setFlow, + allIntegURL, + conf: senseiLMSConf, + navigate, + id, + edit: 1, + setIsLoading, + setSnackbar + }) + } + disabled={!checkMappedFields(senseiLMSConf)} + isLoading={isLoading} + dataConf={senseiLMSConf} + setDataConf={setSenseiLMSConf} + formFields={formFields} + /> +
+
+ ) +} diff --git a/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMS.jsx b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMS.jsx new file mode 100644 index 000000000..05c86b817 --- /dev/null +++ b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMS.jsx @@ -0,0 +1,103 @@ +import { useState } from 'react' +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useNavigate, useParams } from 'react-router' +import BackIcn from '../../../Icons/BackIcn' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import SenseiLMSAuthorization from './SenseiLMSAuthorization' +import { checkMappedFields } from './SenseiLMSCommonFunc' +import SenseiLMSIntegLayout from './SenseiLMSIntegLayout' + +export default function SenseiLMS({ formFields, setFlow, flow, allIntegURL, isInfo }) { + const navigate = useNavigate() + const { formID } = useParams() + const [isLoading, setIsLoading] = useState(false) + const [step, setStep] = useState(1) + const [snack, setSnackbar] = useState({ show: false }) + const [senseiLMSConf, setSenseiLMSConf] = useState({ + name: 'Sensei LMS', + type: 'SenseiLMS', + field_map: [{ formField: '', senseiLMSField: '' }], + actions: {}, + mainAction: '' + }) + + const nextPage = val => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + + if (val === 3) { + if (!checkMappedFields(senseiLMSConf)) { + setSnackbar({ + show: true, + msg: __('Please map all required fields to continue.', 'bit-integrations') + }) + return + } + + if (senseiLMSConf.name !== '' && senseiLMSConf.field_map.length > 0) { + setStep(val) + } + } else { + setStep(val) + } + } + + return ( +
+ +
+ + + +
+ +
+
+
+ +
+ + + saveIntegConfig(flow, setFlow, allIntegURL, senseiLMSConf, navigate, '', '', setIsLoading) + } + isLoading={isLoading} + dataConf={senseiLMSConf} + setDataConf={setSenseiLMSConf} + formFields={formFields} + /> +
+ ) +} diff --git a/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSAuthorization.jsx b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSAuthorization.jsx new file mode 100644 index 000000000..c9da42259 --- /dev/null +++ b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSAuthorization.jsx @@ -0,0 +1,38 @@ +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' + +export default function SenseiLMSAuthorization({ + senseiLMSConf, + setSenseiLMSConf, + step, + nextPage, + isInfo +}) { + const setStep = useCallback(value => nextPage?.(value), [nextPage]) + + return ( + + ) +} diff --git a/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSCommonFunc.js b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSCommonFunc.js new file mode 100644 index 000000000..90400caa4 --- /dev/null +++ b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSCommonFunc.js @@ -0,0 +1,68 @@ +import { create } from 'mutative' +import toast from 'react-hot-toast' +import bitsFetch from '../../../Utils/bitsFetch' +import { __ } from '../../../Utils/i18nwrap' + +export const handleInput = (e, senseiLMSConf, setSenseiLMSConf) => { + const { name, value } = e.target + + setSenseiLMSConf(prevConf => + create(prevConf, draftConf => { + draftConf[name] = value + }) + ) +} + +const refreshResource = (route, dataKey, confKey, setSenseiLMSConf, setIsLoading) => { + setIsLoading(true) + bitsFetch(null, route) + .then(result => { + if (result && result?.success && result?.data?.[dataKey]) { + setSenseiLMSConf(prevConf => + create(prevConf, draftConf => { + draftConf[confKey] = result.data[dataKey] + }) + ) + setIsLoading(false) + toast.success(__('Fetched successfully', 'bit-integrations')) + return + } + setIsLoading(false) + toast.error(__('Sensei LMS fetch failed. Please try again', 'bit-integrations')) + }) + .catch(() => setIsLoading(false)) +} + +export const refreshCourses = (setSenseiLMSConf, setIsLoading) => + refreshResource('refresh_sensei_lms_courses', 'courses', 'allCourses', setSenseiLMSConf, setIsLoading) + +export const refreshLessons = (setSenseiLMSConf, setIsLoading) => + refreshResource('refresh_sensei_lms_lessons', 'lessons', 'allLessons', setSenseiLMSConf, setIsLoading) + +export const refreshQuizzes = (setSenseiLMSConf, setIsLoading) => + refreshResource('refresh_sensei_lms_quizzes', 'quizzes', 'allQuizzes', setSenseiLMSConf, setIsLoading) + +export const checkMappedFields = senseiLMSConf => { + const mappedFields = senseiLMSConf?.field_map + ? senseiLMSConf.field_map.filter( + mappedField => + !mappedField.formField || + !mappedField.senseiLMSField || + (mappedField.formField === 'custom' && !mappedField.customValue) + ) + : [] + + if (!senseiLMSConf?.mainAction || mappedFields.length > 0) { + return false + } + + return true +} + +export const generateMappedField = fields => { + const requiredFlds = fields.filter(fld => fld.required === true) + + return requiredFlds.length > 0 + ? requiredFlds.map(field => ({ formField: '', senseiLMSField: field.key })) + : [{ formField: '', senseiLMSField: '' }] +} diff --git a/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSFieldMap.jsx b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSFieldMap.jsx new file mode 100644 index 000000000..de90c3ac6 --- /dev/null +++ b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSFieldMap.jsx @@ -0,0 +1,104 @@ +import { useRecoilValue } from 'recoil' +import { $appConfigState } from '../../../GlobalStates' +import { __, sprintf } from '../../../Utils/i18nwrap' +import { SmartTagField } from '../../../Utils/StaticData/SmartTagField' +import TagifyInput from '../../Utilities/TagifyInput' +import { + addFieldMap, + delFieldMap, + handleCustomValue, + handleFieldMapping +} from '../GlobalIntegrationHelper' + +export default function SenseiLMSFieldMap({ i, formFields, field, senseiLMSConf, setSenseiLMSConf }) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const requiredFlds = senseiLMSConf?.senseiLMSFields?.filter(fld => fld.required === true) || [] + const nonRequiredFlds = senseiLMSConf?.senseiLMSFields?.filter(fld => fld.required === false) || [] + + return ( +
+
+
+ + + {field.formField === 'custom' && ( + handleCustomValue(e, i, senseiLMSConf, setSenseiLMSConf)} + label={__('Custom Value', 'bit-integrations')} + className="mr-2" + type="text" + value={field.customValue} + placeholder={__('Custom Value', 'bit-integrations')} + formFields={formFields} + /> + )} + + +
+ {i >= requiredFlds.length && ( + <> + + + + )} +
+
+ ) +} diff --git a/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSIntegLayout.jsx b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSIntegLayout.jsx new file mode 100644 index 000000000..45ea7d32f --- /dev/null +++ b/frontend/src/components/AllIntegrations/SenseiLMS/SenseiLMSIntegLayout.jsx @@ -0,0 +1,223 @@ +import { create } from 'mutative' +import MultiSelect from 'react-multiple-select-dropdown-lite' +import { useRecoilValue } from 'recoil' +import { $appConfigState } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import Loader from '../../Loaders/Loader' +import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers' +import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers' +import { + courseActions, + gradeTypeActions, + gradeTypeOptions, + lessonActions, + lessonStatusActions, + lessonStatusOptions, + markCompleteActions, + markCompleteOptions, + modules, + postStatusOptions, + quizActions, + senseiLMSStaticData, + statusActions +} from './staticData' +import { + generateMappedField, + refreshCourses, + refreshLessons, + refreshQuizzes +} from './SenseiLMSCommonFunc' +import SenseiLMSFieldMap from './SenseiLMSFieldMap' + +export default function SenseiLMSIntegLayout({ + formFields, + senseiLMSConf, + setSenseiLMSConf, + isLoading, + setIsLoading +}) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const setVal = (key, val) => + setSenseiLMSConf(prevConf => + create(prevConf, draftConf => { + draftConf[key] = val + }) + ) + + const handleMainAction = value => { + setSenseiLMSConf(prevConf => + create(prevConf, draftConf => { + draftConf.mainAction = value + draftConf.senseiLMSFields = senseiLMSStaticData[value] || [] + draftConf.field_map = generateMappedField(draftConf.senseiLMSFields) + }) + ) + + if (courseActions.includes(value)) { + refreshCourses(setSenseiLMSConf, setIsLoading) + } else if (lessonActions.includes(value)) { + refreshLessons(setSenseiLMSConf, setIsLoading) + } else if (quizActions.includes(value)) { + refreshQuizzes(setSenseiLMSConf, setIsLoading) + } + } + + const resourceDropdown = (label, confKey, listKey, refreshFn) => ( + <> +
+
+ {label} + ({ + label: item.label, + value: item.value?.toString() + })) + : [] + } + onChange={val => setVal(confKey, val)} + singleSelect + closeOnSelect + /> + +
+ + ) + + const staticDropdown = (label, confKey, options) => ( + <> +
+
+ {label} + setVal(confKey, val)} + singleSelect + closeOnSelect + /> +
+ + ) + + return ( + <> +
+
+ {__('Action:', 'bit-integrations')} + handleMainAction(value)} + options={modules?.map(action => ({ + label: checkIsPro(isPro, action.is_pro) ? action.label : getProLabel(action.label), + value: action.name, + disabled: !checkIsPro(isPro, action.is_pro) + }))} + singleSelect + closeOnSelect + /> +
+ + {courseActions.includes(senseiLMSConf?.mainAction) && + resourceDropdown( + __('Course:', 'bit-integrations'), + 'selectedCourse', + 'allCourses', + refreshCourses + )} + + {lessonActions.includes(senseiLMSConf?.mainAction) && + resourceDropdown( + __('Lesson:', 'bit-integrations'), + 'selectedLesson', + 'allLessons', + refreshLessons + )} + + {quizActions.includes(senseiLMSConf?.mainAction) && + resourceDropdown(__('Quiz:', 'bit-integrations'), 'selectedQuiz', 'allQuizzes', refreshQuizzes)} + + {statusActions.includes(senseiLMSConf?.mainAction) && + staticDropdown(__('Status:', 'bit-integrations'), 'selectedStatus', postStatusOptions)} + + {lessonStatusActions.includes(senseiLMSConf?.mainAction) && + staticDropdown(__('Status:', 'bit-integrations'), 'selectedLessonStatus', lessonStatusOptions)} + + {markCompleteActions.includes(senseiLMSConf?.mainAction) && + staticDropdown( + __('Mark Complete:', 'bit-integrations'), + 'selectedMarkComplete', + markCompleteOptions + )} + + {gradeTypeActions.includes(senseiLMSConf?.mainAction) && + staticDropdown(__('Grade Type:', 'bit-integrations'), 'selectedGradeType', gradeTypeOptions)} + + {isLoading && ( + + )} + + {senseiLMSConf?.mainAction && senseiLMSConf.senseiLMSFields && ( +
+ {__('Map Fields', 'bit-integrations')} +
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('Sensei LMS Fields', 'bit-integrations')} +
+
+ + {senseiLMSConf?.field_map?.map((itm, i) => ( + + ))} +
+ +
+
+
+ )} + + ) +} diff --git a/frontend/src/components/AllIntegrations/SenseiLMS/staticData.js b/frontend/src/components/AllIntegrations/SenseiLMS/staticData.js new file mode 100644 index 000000000..b0c688c3d --- /dev/null +++ b/frontend/src/components/AllIntegrations/SenseiLMS/staticData.js @@ -0,0 +1,123 @@ +import { __ } from '../../../Utils/i18nwrap' + +export const modules = [ + { + name: 'enroll_user_in_course', + label: __('Enroll User in Course', 'bit-integrations'), + is_pro: true + }, + { + name: 'withdraw_user_from_course', + label: __('Withdraw User from Course', 'bit-integrations'), + is_pro: true + }, + { + name: 'start_course_for_user', + label: __('Start Course for User', 'bit-integrations'), + is_pro: true + }, + { + name: 'complete_course_for_user', + label: __('Complete Course for User', 'bit-integrations'), + is_pro: true + }, + { + name: 'reset_course_for_user', + label: __('Reset Course Progress', 'bit-integrations'), + is_pro: true + }, + { + name: 'start_lesson_for_user', + label: __('Start Lesson for User', 'bit-integrations'), + is_pro: true + }, + { name: 'update_lesson_status', label: __('Update Lesson Status', 'bit-integrations'), is_pro: true }, + { + name: 'reset_lesson_for_user', + label: __('Reset Lesson Progress', 'bit-integrations'), + is_pro: true + }, + { name: 'grade_quiz', label: __('Grade Quiz for User', 'bit-integrations'), is_pro: true }, + { name: 'create_course', label: __('Create Course', 'bit-integrations'), is_pro: true }, + { name: 'create_lesson', label: __('Create Lesson', 'bit-integrations'), is_pro: true }, + { + name: 'create_certificate', + label: __('Create Certificate for User', 'bit-integrations'), + is_pro: true + } +] + +const userEmailFields = [ + { key: 'user_email', label: __('User Email', 'bit-integrations'), required: true } +] + +const postContentFields = [ + { key: 'post_content', label: __('Content', 'bit-integrations'), required: false }, + { key: 'post_excerpt', label: __('Excerpt', 'bit-integrations'), required: false } +] + +export const senseiLMSStaticData = { + enroll_user_in_course: userEmailFields, + withdraw_user_from_course: userEmailFields, + start_course_for_user: userEmailFields, + complete_course_for_user: userEmailFields, + reset_course_for_user: userEmailFields, + reset_lesson_for_user: userEmailFields, + create_certificate: userEmailFields, + start_lesson_for_user: userEmailFields, + update_lesson_status: userEmailFields, + grade_quiz: [ + ...userEmailFields, + { key: 'grade', label: __('Grade', 'bit-integrations'), required: true } + ], + create_course: [ + { key: 'post_title', label: __('Title', 'bit-integrations'), required: true }, + ...postContentFields, + { key: 'author_email', label: __('Author Email', 'bit-integrations'), required: false } + ], + create_lesson: [ + { key: 'post_title', label: __('Title', 'bit-integrations'), required: true }, + ...postContentFields + ] +} + +// action -> which resource dropdown it needs +export const courseActions = [ + 'enroll_user_in_course', + 'withdraw_user_from_course', + 'start_course_for_user', + 'complete_course_for_user', + 'reset_course_for_user', + 'create_lesson', + 'create_certificate' +] +export const lessonActions = ['start_lesson_for_user', 'update_lesson_status', 'reset_lesson_for_user'] +export const quizActions = ['grade_quiz'] + +// actions whose static choices are shown as Utilities dropdowns (not field-map rows) +export const statusActions = ['create_course', 'create_lesson'] +export const markCompleteActions = ['start_lesson_for_user'] +export const gradeTypeActions = ['grade_quiz'] +export const lessonStatusActions = ['update_lesson_status'] + +export const postStatusOptions = [ + { label: __('Publish', 'bit-integrations'), value: 'publish' }, + { label: __('Draft', 'bit-integrations'), value: 'draft' }, + { label: __('Pending', 'bit-integrations'), value: 'pending' }, + { label: __('Private', 'bit-integrations'), value: 'private' } +] +export const markCompleteOptions = [ + { label: __('No', 'bit-integrations'), value: 'no' }, + { label: __('Yes', 'bit-integrations'), value: 'yes' } +] +export const gradeTypeOptions = [ + { label: __('Auto', 'bit-integrations'), value: 'auto' }, + { label: __('Manual', 'bit-integrations'), value: 'manual' } +] +export const lessonStatusOptions = [ + { label: __('In Progress', 'bit-integrations'), value: 'in-progress' }, + { label: __('Ungraded', 'bit-integrations'), value: 'ungraded' }, + { label: __('Graded', 'bit-integrations'), value: 'graded' }, + { label: __('Passed', 'bit-integrations'), value: 'passed' }, + { label: __('Failed', 'bit-integrations'), value: 'failed' } +] diff --git a/frontend/src/components/Flow/New/SelectAction.jsx b/frontend/src/components/Flow/New/SelectAction.jsx index f2efc8ff1..d8190b32b 100644 --- a/frontend/src/components/Flow/New/SelectAction.jsx +++ b/frontend/src/components/Flow/New/SelectAction.jsx @@ -185,6 +185,7 @@ export default function SelectAction() { { type: 'CreatorLms', is_pro: true }, { type: 'Bookly', is_pro: true }, { type: 'FluentCart', is_pro: true }, + { type: 'SenseiLMS', logo: 'senseiLMS', is_pro: true }, { type: 'FluentPlayer', is_pro: true }, { type: 'BitCrm', name: 'Bit CRM', logo: 'bitCrm', is_pro: false }, { type: 'MoreConvert Wishlist', logo: 'moreConvertWishlist', is_pro: true }, diff --git a/frontend/src/resource/img/integ/senseiLMS.webp b/frontend/src/resource/img/integ/senseiLMS.webp index d6ec8074c..4261c1b94 100644 Binary files a/frontend/src/resource/img/integ/senseiLMS.webp and b/frontend/src/resource/img/integ/senseiLMS.webp differ