From 939ec1a943d19926855f6903a2aa0c0fc2deac9d Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Sat, 29 Jul 2023 12:20:07 +0200 Subject: [PATCH 001/194] Fix WooCommerce deactivation handler The WooCommerce deactivation handler shall only act when monetize_by === 'wc': - Fix: When the WooCommerce plugin gets deactivated, do not disable other possible monetization methods like Paid Memberships Pro (pmpro) or EDD. - Fix: When the WooCommerce plugin gets deactivated, and WooCommerce was not enabled in Tutor LMS: - Do not show a notice saying that paid courses would have been made free and to enable WooCommerce monetization to fix this. --- classes/WooCommerce.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/classes/WooCommerce.php b/classes/WooCommerce.php index 4d497f90b4..8728425ece 100644 --- a/classes/WooCommerce.php +++ b/classes/WooCommerce.php @@ -89,7 +89,7 @@ public function __construct() { * @since 1.7.8 */ $woocommerce_path = dirname( dirname( __DIR__ ) ) . DIRECTORY_SEPARATOR . 'woocommerce' . DIRECTORY_SEPARATOR . 'woocommerce.php'; - register_deactivation_hook( $woocommerce_path, array( $this, 'disable_tutor_monetization' ) ); + register_deactivation_hook( $woocommerce_path, array( $this, 'woocommerce_deactivation_handler' ) ); /** * Redirect student on enrolled courses after course * Enrollment complete @@ -664,15 +664,25 @@ public function course_placing_order_from_customer( $item_id, $item, $order_id ) } /** - * Disable course monetization on woocommerce deactivation + * Handle disabling WooCommerce monetization on WooCommerce plugin deactivation * * @since 1.7.8 * * @return void */ - public function disable_tutor_monetization() { - tutor_utils()->update_option( 'monetize_by', 'free' ); - update_option( 'tutor_show_woocommerce_notice', true ); + public function woocommerce_deactivation_handler() { + if ( tutor_utils()->get_option( 'monetize_by' ) === 'wc' ) { + tutor_utils()->update_option( 'monetize_by', 'free' ); + /** + * Show a reminder to re-enable Tutor monetization to + * monetize courses after re-activating WooCommerce: + * + * Possible follow-up fix: Only show a notice when + * WooCommerce was re-activated after this forced + * disabling of WooCommerce monetisation took place: + */ + update_option( 'tutor_show_woocommerce_notice', true ); + } } /** From 709c15b7ff5b1940b32cfac07cda93d79ad94d55 Mon Sep 17 00:00:00 2001 From: Collins Agbonghama Date: Fri, 4 Aug 2023 11:06:36 +0100 Subject: [PATCH 002/194] Added $sub_url to tutor_dashboard_url filter --- classes/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Utils.php b/classes/Utils.php index 2545fa8a13..f2f47069a3 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -5299,7 +5299,7 @@ public function instructor_register_url() { public function tutor_dashboard_url( $sub_url = '' ) { $page_id = (int) $this->get_option( 'tutor_dashboard_page_id' ); $page_id = apply_filters( 'tutor_dashboard_page_id', $page_id ); - return apply_filters( 'tutor_dashboard_url', trailingslashit( get_the_permalink( $page_id ) ) . $sub_url ); + return apply_filters( 'tutor_dashboard_url', trailingslashit( get_the_permalink( $page_id ) ) . $sub_url, $sub_url ); } /** From 6f58a713a3d2d1bd967583b633489273988f0bae Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 3 Dec 2024 11:37:55 +0600 Subject: [PATCH 003/194] Removed old course builder js and scss files --- assets/react/course-builder/assignment.js | 120 --- assets/react/course-builder/attachment.js | 68 -- .../react/course-builder/certificate-more.js | 61 -- assets/react/course-builder/content-drip.js | 32 - assets/react/course-builder/course-price.js | 180 ----- assets/react/course-builder/index.js | 67 -- .../react/course-builder/instructor-multi.js | 164 ---- assets/react/course-builder/lesson.js | 431 ----------- assets/react/course-builder/quiz.js | 720 ------------------ assets/react/course-builder/topic.js | 115 --- assets/react/course-builder/video-picker.js | 60 -- assets/scss/course-builder/_frontend.scss | 262 ------- assets/scss/course-builder/index.scss | 99 --- .../scss/course-builder/modal/assignment.scss | 9 - assets/scss/course-builder/modal/quiz.scss | 266 ------- .../course-builder/segments/attachment.scss | 7 - .../course-builder/segments/content-drip.scss | 28 - .../course-builder/segments/instructor.scss | 114 --- .../scss/course-builder/segments/setting.scss | 327 -------- .../segments/topic-content.scss | 175 ----- .../scss/course-builder/segments/video.scss | 47 -- gulpfile.js | 2 - 22 files changed, 3354 deletions(-) delete mode 100644 assets/react/course-builder/assignment.js delete mode 100644 assets/react/course-builder/attachment.js delete mode 100644 assets/react/course-builder/certificate-more.js delete mode 100644 assets/react/course-builder/content-drip.js delete mode 100644 assets/react/course-builder/course-price.js delete mode 100644 assets/react/course-builder/index.js delete mode 100644 assets/react/course-builder/instructor-multi.js delete mode 100644 assets/react/course-builder/lesson.js delete mode 100644 assets/react/course-builder/quiz.js delete mode 100644 assets/react/course-builder/topic.js delete mode 100644 assets/react/course-builder/video-picker.js delete mode 100644 assets/scss/course-builder/_frontend.scss delete mode 100644 assets/scss/course-builder/index.scss delete mode 100644 assets/scss/course-builder/modal/assignment.scss delete mode 100644 assets/scss/course-builder/modal/quiz.scss delete mode 100644 assets/scss/course-builder/segments/attachment.scss delete mode 100644 assets/scss/course-builder/segments/content-drip.scss delete mode 100644 assets/scss/course-builder/segments/instructor.scss delete mode 100644 assets/scss/course-builder/segments/setting.scss delete mode 100644 assets/scss/course-builder/segments/topic-content.scss delete mode 100644 assets/scss/course-builder/segments/video.scss diff --git a/assets/react/course-builder/assignment.js b/assets/react/course-builder/assignment.js deleted file mode 100644 index 4a03ebfeff..0000000000 --- a/assets/react/course-builder/assignment.js +++ /dev/null @@ -1,120 +0,0 @@ -import codeSampleLang from "../lib/codesample-lang"; - -window.jQuery(document).ready(function($){ - - const { __, _x, _n, _nx } = wp.i18n; - - // Create/edit assignment opener - $(document).on('click', '.open-tutor-assignment-modal, .tutor-create-assignments-btn', function (e) { - e.preventDefault(); - - var $that = $(this); - var assignment_id = $that.hasClass('tutor-create-assignments-btn') ? 0 : $that.attr('data-assignment-id'); - var topic_id = $that.closest('.tutor-topics-wrap').data('topic-id'); - var course_id = $('#post_ID').val(); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - assignment_id: assignment_id, - topic_id: topic_id, - course_id: course_id, - action: 'tutor_load_assignments_builder_modal' - }, - beforeSend: function () { - $that.addClass('is-loading'); - }, - success: function (data) { - $('.tutor-assignment-modal-wrap .tutor-modal-container').html(data.data.output); - $('.tutor-assignment-modal-wrap').addClass('tutor-is-active'); - - let editor_id = 'tutor_assignments_modal_editor', - editor_wrap_selector = '#wp-tutor_assignments_modal_editor-wrap', - tinymceConfig = tinyMCEPreInit.mceInit.tutor_assignment_editor_config; - - if ($(editor_wrap_selector).hasClass('html-active')) { - $(editor_wrap_selector).removeClass('html-active'); - } - - $(editor_wrap_selector).addClass('tmce-active'); - - /** - * Add codesample plugin to support code snippet - * - * @since 2.0.9 - */ - if (tinymceConfig && _tutorobject.tutor_pro_url) { - if (!tinymceConfig.plugins.includes('codesample')) { - tinymceConfig.plugins = `${tinymceConfig.plugins}, codesample`; - tinymceConfig.codesample_languages = codeSampleLang; - tinymceConfig.toolbar1 = `${tinymceConfig.toolbar1}, codesample`; - } - } - - tinymceConfig.wpautop = false; - tinymce.init(tinymceConfig); - tinymce.execCommand('mceRemoveEditor', false, editor_id ); - tinyMCE.execCommand('mceAddEditor', false, editor_id ); - quicktags({ id: editor_id }); - - window.dispatchEvent(new Event(_tutorobject.content_change_event)); - window.dispatchEvent(new CustomEvent('tutor_modal_shown', {detail: e.target})); - }, - complete: function () { - $that.removeClass('is-loading'); - } - }); - }); - - /** - * Update Assignment Data - */ - $(document).on( 'click', '.update_assignment_modal_btn', function( event ){ - event.preventDefault(); - - var $that = $(this); - var content; - var inputid = 'tutor_assignments_modal_editor'; - var editor = tinyMCE.get(inputid); - if (editor) { - content = editor.getContent({format: 'raw'}); - } else { - content = $('#'+inputid).val(); - } - - // removing
- if(content === '


') { - content = ''; - } - - var form_data = $(this).closest('.tutor-modal').find('form.tutor_assignment_modal_form').serializeObject(); - form_data.assignment_content = content; - - $.ajax({ - url : window._tutorobject.ajaxurl, - type : 'POST', - data : form_data, - beforeSend: function () { - $that.addClass('is-loading'); - }, - success: function (data) { - if (data.success){ - $('#tutor-course-content-wrap').html(data.data.course_contents); - enable_sorting_topic_lesson(); - - //Close the modal - $('.tutor-assignment-modal-wrap').removeClass('tutor-is-active'); - - // Trigger content change event - window.dispatchEvent(new Event(window._tutorobject.content_change_event)); - - tutor_toast(__('Success', 'tutor'), __('Assignment Updated', 'tutor'), 'success'); - } - }, - complete: function () { - $that.removeClass('is-loading'); - } - }); - }); -}); \ No newline at end of file diff --git a/assets/react/course-builder/attachment.js b/assets/react/course-builder/attachment.js deleted file mode 100644 index 8ed2d3da72..0000000000 --- a/assets/react/course-builder/attachment.js +++ /dev/null @@ -1,68 +0,0 @@ - -window.jQuery(document).ready(function($){ - - const { __, _x, _n, _nx } = wp.i18n; - - $(document).on('click', '.tutor-attachment-cards:not(.tutor-no-control) .tutor-delete-attachment', function(e){ - e.preventDefault(); - $(this).closest('[data-attachment_id]').remove(); - }); - - $(document).on('click', '.tutorUploadAttachmentBtn', function(e){ - e.preventDefault(); - - var $that = $(this); - var name = $that.data('name'); - var card_wrapper = $that.parent().find('.tutor-attachment-cards'); - var frame; - - // If the media frame already exists, reopen it. - if ( frame ) { - frame.open(); - return; - } - - // Create a new media frame - frame = wp.media({ - title: __( 'Select or Upload Media Of Your Choice', 'tutor' ), - button: { - text: __( 'Upload media', 'tutor' ) - }, - multiple: true // Set to true to allow multiple files to be selected - }); - // When an image is selected in the media frame... - frame.on( 'select', function() { - // Get media attachment details from the frame state - var attachments = frame.state().get('selection').toJSON(); - if (attachments.length){ - for (var i=0; i < attachments.length; i++){ - var attachment = attachments[i]; - - var inputHtml = `
-
-
-
-
-
${attachment.filename}
-
${__('Size', 'tutor')}: ${attachment.filesizeHumanReadable}
- -
- -
- - - -
-
-
-
-
`; - - $that.parent().find('.tutor-attachment-cards').append(inputHtml); - } - } - }); - // Finally, open the modal on click - frame.open(); - }); -}); \ No newline at end of file diff --git a/assets/react/course-builder/certificate-more.js b/assets/react/course-builder/certificate-more.js deleted file mode 100644 index bb50532a2a..0000000000 --- a/assets/react/course-builder/certificate-more.js +++ /dev/null @@ -1,61 +0,0 @@ -readyState_complete(() => { - toggleCertificate(); - - const tabHeaderItem = document.querySelectorAll('.tab-header-item'); - tabHeaderItem.forEach((tabItem) => { - tabItem.onclick = (e) => { - setTimeout(() => { - toggleCertificate(); - }, 100); - }; - }); -}); - -const toggleCertificate = () => { - const { __ } = wp.i18n; - const moreButton = document.querySelector('.more_button'); - moreButton.style.display = 'block'; - const templateAlignment = document.querySelectorAll('[data-alignment]'); - - templateAlignment.forEach((templateType) => { - let templateItem = templateType && templateType.querySelector('.template-item'); - let templateItemWrap = templateItem && templateItem.closest('.tab-body-item'); - let activeView = templateItemWrap.classList.contains('is-active'); - - if (true == activeView && templateItemWrap) { - let templateItemAll = templateType && templateType.querySelectorAll('.template-item'); - console.log(Math.ceil(templateItemAll.length / 3)); - - let rowItem = 0; - templateItemAll.forEach((templateItem) => { - if (0 == templateItem.offsetTop) { - rowItem += 1; - } - }); - let primaryHeight = 2 * templateItem.offsetHeight; - let targetHeight = Math.ceil(templateItemAll.length / rowItem) * templateItem.offsetHeight; - console.log(primaryHeight); - moreButton.style.height = templateItem.offsetHeight - 36 + 'px'; - if (!templateItemWrap.classList.contains('more-loaded')) { - templateItemWrap.style.height = primaryHeight + 'px'; - } - moreButton.onclick = (e) => { - moreButton.querySelector('span').innerText = __("Show More", "tutor"); - templateItemWrap.classList.toggle('more-loaded'); - let moreLoaded = templateItemWrap.classList.contains('more-loaded'); - console.log(moreLoaded); - if (moreLoaded) { - templateItemWrap.style.height = primaryHeight + targetHeight + 'px'; - moreButton.querySelector('i').classList.remove('tutor-icon-plus'); - moreButton.querySelector('i').classList.add('tutor-icon-minus'); - moreButton.querySelector('span').innerText = __("Show Less", "tutor"); - } else { - templateItemWrap.style.height = primaryHeight + 'px'; - moreButton.querySelector('i').classList.remove('tutor-icon-minus'); - moreButton.querySelector('i').classList.add('tutor-icon-plus'); - moreButton.querySelector('span').innerText = __("Show More", "tutor"); - } - }; - } - }); -}; diff --git a/assets/react/course-builder/content-drip.js b/assets/react/course-builder/content-drip.js deleted file mode 100644 index 494c09a829..0000000000 --- a/assets/react/course-builder/content-drip.js +++ /dev/null @@ -1,32 +0,0 @@ -window.jQuery(document).ready(function($){ - - let dripCheckbox = $('#course_setting_content_drip') - let dripOptions = $('div.content-drip-options-wrapper') - if ( dripCheckbox.is(':checked') === false ) { - dripOptions.hide() - } - - // Update content drip data instantly on change. - // So lesson, quiz, assignment modal can get data without pressing course update/publish - $('#course_setting_content_drip, [name="_tutor_course_settings[content_drip_type]"]').change(function(){ - - if($(this).attr('type')=='radio' && !$(this).prop('checked')) { - return; - } - - var val = $(this).attr('type')=='checkbox' ? ($(this).prop('checked') ? 1 : 0) : $(this).val(); - - let isChecked = $(this).is(':checked') - isChecked ? dripOptions.show() : dripOptions.hide() - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - [$(this).attr('name')]: val, - course_id: $('#post_ID').val(), - action: 'tutor_content_drip_state_update' - } - }) - }); -}); \ No newline at end of file diff --git a/assets/react/course-builder/course-price.js b/assets/react/course-builder/course-price.js deleted file mode 100644 index 5986f8d030..0000000000 --- a/assets/react/course-builder/course-price.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Course price & product management - * - * @since v2.0.5 - */ -document.addEventListener('DOMContentLoaded', function () { - const { __ } = wp.i18n; - - /** - * Backend: Fetch WC product info on product dropdown change. - * @since 2.0.7 - */ - // jQuery('select[name="_tutor_course_product_id"]').on('change', function (e) { - // console.log(e.target.value); - // console.log(e.target.tagName); - // console.log(e.target.id); - // console.log(e.target.dataset); - - // const selectElem = document.getElementById('tutor-wc-product-select'); - // let id = jQuery(this).val(); - // const linkedProductId = jQuery(this).data('product-id'); - // if (!id) return; - // /** - // * If user select already linked product then return - // * it will prevent unnecessary ajax request. - // * - // * @since v2.1.0 - // */ - // if (id == linkedProductId) { - // return; - // } - // let data = { - // action: 'tutor_get_wc_product', - // product_id: id, - // course_id: jQuery(this).data('course-id'), - // } - - // jQuery.ajax({ - // url: _tutorobject.ajaxurl, - // type: 'POST', - // dataType: 'json', - // data: data, - // success: function (res) { - // const {success, data} = res; - // // console.log(res); - // if (success) { - // jQuery('input[name="course_price"]').val(res.data.regular_price) - // jQuery('input[name="course_sale_price"]').val(res.data.sale_price) - // selectElem.dataset.productId = id; - // } - // if (!success) { - // tutor_toast( - // __( 'Failed', 'tutor' ), - // __( data, 'tutor' ), - // 'error' - // ); - // selectElem.value = "-1"; - // } - // } - // }); - - // }) - - const attachMetaBox = document.getElementById('tutor-attach-product'); - if (attachMetaBox) { - attachMetaBox.onchange = (e) => { - const target = e.target; - if (target.tagName === 'SELECT' && target.id === 'tutor-wc-product-select') { - let id = target.value; - const linkedProductId = target.dataset.productId; - const jsSelect = target.nextElementSibling; - if (!id) return; - /** - * If user select already linked product then return - * it will prevent unnecessary ajax request. - * - * @since v2.1.0 - */ - if (id == linkedProductId) { - return; - } - let data = { - action: 'tutor_get_wc_product', - product_id: id, - course_id: target.dataset.courseId, - } - - jQuery.ajax({ - url: _tutorobject.ajaxurl, - type: 'POST', - dataType: 'json', - data: data, - success: function (res) { - const {success, data} = res; - // console.log(res); - if (success) { - jQuery('input[name="course_price"]').val(res.data.regular_price) - jQuery('input[name="course_sale_price"]').val(res.data.sale_price) - target.dataset.productId = id; - } - if (!success) { - tutor_toast( - __( 'Failed', 'tutor' ), - __( data, 'tutor' ), - 'error' - ); - target.value = linkedProductId == 0 ? "-1" : linkedProductId; - target.dataset.productId = linkedProductId == 0 ? 0 : linkedProductId;; - if (jsSelect) { - let label = jsSelect.querySelector('span.tutor-form-select-label'); - if (label) { - label.innerHTML = linkedProductId == 0 ? __( 'Select a product', 'tutor') : target.options[target.selectedIndex].text; - label.dataset.value = linkedProductId == 0 ? "-1" : linkedProductId; - - // attachMetaBox.querySelectorAll('[type=number]').forEach((elem) => { - // elem.setAttribute('value', ''); - // }) - } - } - } - } - }); - } - } - } - - /** - * Backend: Course price free/paid toggle - * - * @since v2.0.7 - */ - let priceTypeEl = jQuery('input[name="tutor_course_price_type"]'); - let togglePriceWrapper = function (priceType) { - let priceWrapper = jQuery('.tutor-course-product-fields'); - 'free' === priceType - ? priceWrapper.hide() - : priceWrapper.show(); - } - - let priceType = priceTypeEl.filter(":checked").val(); - togglePriceWrapper(priceType) - - setTimeout(() => { - priceTypeEl.change(function (e) { togglePriceWrapper(jQuery(this).val()) }) - }) - - - /** - * Price validation - * @since 2.0.7 - */ - let old_sale_price = jQuery('input[name="course_sale_price"]').val(); - jQuery('input[name="course_sale_price"]').on('blur', function () { - let regular_price = jQuery('input[name="course_price"]').val() - let sale_price = jQuery(this).val(); - if (Number(sale_price) >= Number(regular_price)) { - tutor_toast(__('Invalid Sale Price', 'tutor'), __('Sale price must be smaller than regular price', 'tutor'), 'error'); - jQuery('input[name="course_sale_price"]').val(old_sale_price) - } - }) - - - /** - * Frontend: Course price free/paid toggle - * - * @since v2.0.7 - */ - const priceToggleRadios = document.querySelectorAll(".tutor-course-price-toggle input[type='radio']"); - const priceRow = document.querySelector(".tutor-course-price-row"); - priceToggleRadios.forEach((radio) => { - radio.addEventListener('change', (e) => { - if (e.target.value === 'paid' && !priceRow.classList.contains('is-paid')) { - priceRow.classList.add('is-paid'); - } else { - priceRow.classList.remove('is-paid'); - } - }) - }) - -}); \ No newline at end of file diff --git a/assets/react/course-builder/index.js b/assets/react/course-builder/index.js deleted file mode 100644 index 3632d436fb..0000000000 --- a/assets/react/course-builder/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import './assignment'; -import './attachment'; -import './content-drip'; -import './instructor-multi'; -import './lesson'; -import './quiz'; -import './topic'; -import './video-picker'; -import './course-price'; - -/** - * Re init required - * Modal Loaded... - */ -const load_select2 = function() { - if (jQuery().select2) { - jQuery('.select2_multiselect').select2({ - dropdownCssClass: 'increasezindex', - }); - } -}; -window.addEventListener('DOMContentLoaded', load_select2); -window.addEventListener(_tutorobject.content_change_event, load_select2); -window.addEventListener(_tutorobject.content_change_event, () => console.log(_tutorobject.content_change_event)); - -/** - * Get the remaining length of input limit - * - * @return {Number} - */ - -function getRemainingLength(maxLength = 255, inputElement) { - return maxLength - (((inputElement || {}).value || {}).length || 0); -} - -/** - * Update the course title input tooltip value in 'keyup' - * and set the data initially - */ -const maxLength = 255; -const courseCreateTitle = document.getElementById('tutor-course-create-title'); -const courseTitleTooltip = courseCreateTitle?.previousElementSibling; -const courseCreateTitleTooptip = document.querySelector('#tutor-course-create-title-tooltip-wrapper .tooltip-txt'); - -if (courseTitleTooltip) { - courseTitleTooltip.innerHTML = getRemainingLength(maxLength, courseCreateTitle); -} - -if(courseCreateTitle && courseCreateTitleTooptip) { - - document.addEventListener('click', (e) => { - if (e.target === courseCreateTitle) { - if(courseCreateTitle === document.activeElement) { - courseCreateTitleTooptip.style.opacity = '1'; - courseCreateTitleTooptip.style.visibility = 'visible'; - } - } else { - courseCreateTitleTooptip.style.opacity = '0'; - courseCreateTitleTooptip.style.visibility = 'hidden'; - } - }) - - courseCreateTitle.addEventListener('keyup', (e) => { - const remainingLength = getRemainingLength(maxLength, courseCreateTitle); - courseTitleTooltip.innerHTML = remainingLength; - }); -} \ No newline at end of file diff --git a/assets/react/course-builder/instructor-multi.js b/assets/react/course-builder/instructor-multi.js deleted file mode 100644 index 9d51c38230..0000000000 --- a/assets/react/course-builder/instructor-multi.js +++ /dev/null @@ -1,164 +0,0 @@ -window.jQuery(document).ready($=>{ - const {__} = wp.i18n; - - var search_container = $('#tutor_course_instructor_modal .tutor-search-result'); - var shortlist_container = $('#tutor_course_instructor_modal .tutor-selected-result'); - var course_id = $('#tutor_course_instructor_modal').data('course_id'); - let search_timeout; - - var search_method=(user_id, callback)=>{ - var ajax_call=()=>{ - search_timeout = undefined; - - var input = $('#tutor_course_instructor_modal input[type="text"]'); - var search_terms = (input.val() || '').trim(); - - // Clear result if no keyword - if(!search_terms) { - search_container.empty(); - return; - } - - var shortlisted = []; - shortlist_container.find('[data-instructor-id]').each(function(){ - shortlisted.push($(this).data('instructor-id')); - }); - (user_id && !isNaN(user_id)) ? shortlisted.push(user_id) : 0; - - // Ajax request - $.ajax({ - url: _tutorobject.ajaxurl, - type: 'POST', - data: { - course_id, - search_terms, - shortlisted: shortlisted, - action: 'tutor_course_instructor_search' - }, - beforeSend:()=>{ - if(!callback){ - // Don't show if click on add. Then add loading icon should appear - search_container.html(''); - search_container.addClass('is-loading'); - // search_container.html('
'); - } - }, - success: function(resp) { - const {search_result, shortlisted, shortlisted_count} = resp.data || {}; - - search_container.removeClass('is-loading'); - - search_container.html(search_result); - shortlist_container.html(shortlisted); - - let btn_disabled = shortlisted_count ? false : true; - $('.add_instructor_to_course_btn').prop('disabled', btn_disabled); - - callback ? callback() : 0; - } - }); - } - - if(search_timeout) { - clearTimeout(search_timeout); - } - search_timeout = setTimeout(ajax_call, 350); - } - - // Search/Click input - $(document).on('input', '#tutor_course_instructor_modal input[type="text"]', search_method); - $(document).on('focus', '#tutor_course_instructor_modal input[type="text"]', function(){ - search_container.show(); - }); - - // Shortlist on plus click - $(document).on('click', '#tutor_course_instructor_modal .tutor-shortlist-instructor', function() { - $(this).addClass('is-loading'); - search_method($(this).closest('[data-user_id]').data('user_id'), ()=>{ - search_container.hide(); - }); - }); - - // Remove from shortlist - $(document).on('click', '#tutor_course_instructor_modal .tutor-selected-result .instructor-control a', function(){ - $(this).closest('.added-instructor-item').fadeOut(function(){ - $(this).remove(); - }); - }); - - // Add instructor to course from shortlist - $(document).on('click', '.add_instructor_to_course_btn', function(e){ - e.preventDefault(); - - var $that = $(this); - var course_id = $('#tutor_course_instructor_modal').data('course_id'); - - var shortlisted = []; - shortlist_container.find('[data-instructor-id]').each(function(){ - shortlisted.push($(this).data('instructor-id')); - }); - - $.ajax({ - url : window._tutorobject.ajaxurl, - type : 'POST', - data : { - course_id, - tutor_instructor_ids: shortlisted, - action: 'tutor_add_instructors_to_course' - }, - beforeSend: function () { - $that.addClass('is-loading'); - }, - success: function (data) { - if (data.success){ - - // remove search content - search_container.empty(); - shortlist_container.empty(); - - // Hide the modal - $('#tutor_course_instructor_modal').removeClass('tutor-is-active'); - $('body').removeClass('tutor-modal-open'); - - // Show the result in course editor - $('.tutor-course-instructors-metabox-wrap').parent().html(data.data.output); - $('.tutor-modal-wrap').removeClass('show'); - return; - } - - tutor_toast('Error!', get_response_message(data), 'error'); - }, - complete: function () { - $that.removeClass('is-loading'); - } - }); - }); - - $(document).on('click', '.tutor-instructor-delete-btn', function(e){ - e.preventDefault(); - - var $that = $(this); - var course_id = $('#post_ID').val(); - var instructor_id = $that.closest('.added-instructor-item').attr('data-instructor-id'); - - $.ajax({ - url : window._tutorobject.ajaxurl, - type : 'POST', - data : { - course_id, - instructor_id, - action : 'detach_instructor_from_course' - }, - beforeSend: ()=>$that.addClass('is-loading'), - complete: ()=>$that.removeClass('is-loading'), - success: function (data) { - if (data.success){ - $that.closest('.added-instructor-item').remove(); - return; - } - - tutor_toast('Error!', get_response_message(data), 'error'); - } - }); - }); -}) \ No newline at end of file diff --git a/assets/react/course-builder/lesson.js b/assets/react/course-builder/lesson.js deleted file mode 100644 index 8d7fc2478e..0000000000 --- a/assets/react/course-builder/lesson.js +++ /dev/null @@ -1,431 +0,0 @@ -import { get_response_message } from '../helper/response'; -import codeSampleLang from '../lib/codesample-lang'; - -(function($) { - window.enable_sorting_topic_lesson = function() { - const { __ } = wp.i18n; - - if (jQuery().sortable) { - $('.course-contents').sortable({ - handle: '.course-move-handle', - start: function(e, ui) { - ui.placeholder.css('visibility', 'visible'); - }, - stop: function(e, ui) { - console.log('e1', e, ui); - tutor_sorting_topics_and_lesson(); - }, - }); - $('.tutor-lessons:not(.drop-lessons)').sortable({ - connectWith: '.tutor-lessons', - items: 'div.course-content-item', - start: function(e, ui) { - ui.placeholder.css('visibility', 'visible'); - }, - stop: function(e, ui) { - // Store new updated order as input value - tutor_sorting_topics_and_lesson(ui); - }, - }); - } - }; - - window.tutor_sorting_topics_and_lesson = function(ui) { - var topics = {}; - $('.tutor-topics-wrap').each(function(index, item) { - var $topic = $(this); - var topics_id = parseInt($topic.attr('id').match(/\d+/)[0], 10); - var lessons = {}; - - $topic - .find('.course-content-item') - .each(function(lessonIndex, lessonItem) { - var $lesson = $(this); - var lesson_id = parseInt($lesson.attr('id').match(/\d+/)[0], 10); - - lessons[lessonIndex] = lesson_id; - }); - topics[index] = { - topic_id: topics_id, - lesson_ids: lessons, - }; - }); - $('#tutor_topics_lessons_sorting').val(JSON.stringify(topics)); - - let request_data = { - tutor_topics_lessons_sorting: JSON.stringify(topics), - action: 'tutor_update_course_content_order', - }; - - if (ui) { - // Update parent topic id fro the dropped content - let parent_topic_id = ui.item - .closest('[data-topic-id]') - .attr('data-topic-id'); - let content_id = ui.item.attr('data-course_content_id'); - - request_data.content_parent = { - parent_topic_id, - content_id, - }; - } - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: request_data, - success: function(r) { - if (!r.success) { - tutor_toast(__('Error', 'tutor'), get_response_message(r), 'error'); - } - }, - error: function() {}, - }); - }; -})(window.jQuery); - -window.jQuery(document).ready(function($) { - const { __ } = wp.i18n; - - enable_sorting_topic_lesson(); - - /** - * Open Lesson Modal - */ - $(document).on('click', '.open-tutor-lesson-modal', function(e) { - e.preventDefault(); - - var $that = $(this); - var lesson_id = $that.attr('data-lesson-id'); - var topic_id = $that.attr('data-topic-id'); - var course_id = $('#post_ID').val(); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - lesson_id: lesson_id, - topic_id: topic_id, - course_id: course_id, - action: 'tutor_load_edit_lesson_modal', - }, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - if (!data.success) { - tutor_toast( - __('Error', 'tutor'), - get_response_message(data), - 'error', - ); - return; - } - - $('.tutor-lesson-modal-wrap .tutor-modal-container').html(data.data.output); - $('.tutor-lesson-modal-wrap').attr({ - 'data-lesson-id': lesson_id, - 'data-topic-id': topic_id, - }); - - $('.tutor-lesson-modal-wrap').addClass('tutor-is-active'); - - let editor_id = 'tutor_lesson_modal_editor', - editor_wrap_selector = '#wp-tutor_lesson_modal_editor-wrap', - tinymceConfig = tinyMCEPreInit.mceInit.tutor_lesson_editor_config; - - if (!tinymceConfig) { - tinymceConfig = tinyMCEPreInit.mceInit.course_description; - } - - if ($(editor_wrap_selector).hasClass('html-active')) { - $(editor_wrap_selector).removeClass('html-active'); - } - $(editor_wrap_selector).addClass('tmce-active'); - - /** - * Code snippet support for PRO user. - * - * @since 2.0.9 - */ - if (_tutorobject.tutor_pro_url && tinymceConfig && !tinymceConfig.plugins.includes('codesample')) { - tinymceConfig.plugins = `${tinymceConfig.plugins}, codesample`; - tinymceConfig.codesample_languages = codeSampleLang; - tinymceConfig.toolbar1 = `${tinymceConfig.toolbar1}, codesample`; - } - - tinymceConfig.wpautop = false; - tinymce.init(tinymceConfig); - tinymce.execCommand('mceRemoveEditor', false, editor_id); - tinyMCE.execCommand('mceAddEditor', false, editor_id); - quicktags({ id: editor_id }); - - window.dispatchEvent(new Event(_tutorobject.content_change_event)); - window.dispatchEvent(new CustomEvent('tutor_modal_shown', { detail: e.target })); - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - // Video source - $(document).on('change', '.tutor_lesson_video_source', function(e) { - let val = $(this).val(); - $(this) - .nextAll() - .hide() - .filter('.video_source_wrap_' + val) - .show(); - $(this) - .prevAll() - .filter('[data-video_source]') - .attr('data-video_source', val); - }); - - /** - * Lesson Update From Lesson Modal - */ - $(document).on('click', '.update_lesson_modal_btn', function(event) { - event.preventDefault(); - - let $that = $(this), - editor = tinyMCE.get('tutor_lesson_modal_editor'), - editorWrap = document.getElementById('wp-tutor_lesson_modal_editor-wrap'), - isHtmlActive = editorWrap.classList.contains('html-active'), - content = editor.getContent({ format: 'html' }); - - // removing
- if (content === '


') { - content = ''; - } - - let form_data = $(this) - .closest('.tutor-modal') - .find('form') - .serializeObject(); - form_data.lesson_content = content; - form_data.is_html_active = isHtmlActive; - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: form_data, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - if (!data.success) { - tutor_toast( - __('Error', 'tutor'), - get_response_message(data), - 'error', - ); - return; - } - if (data.success) { - $('#tutor-course-content-wrap').html(data.data.course_contents); - enable_sorting_topic_lesson(); - - //Close the modal - $that.closest('.tutor-modal').removeClass('tutor-is-active'); - - tutor_toast( - __('Success', 'tutor'), - __('Lesson Updated', 'tutor'), - 'success', - ); - - window.dispatchEvent(new Event(_tutorobject.content_change_event)); - } - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - /** - * @since v.1.9.0 - * Parse and show video duration on link paste in lesson video - */ - var video_url_input = [ - '.video_source_wrap_external_url input', - '.video_source_wrap_vimeo input', - '.video_source_wrap_youtube input', - '.video_source_wrap_html5 input.input_source_video_id', - ].join(','); - - var autofill_url_timeout; - $(document) - .on('blur', video_url_input, function() { - var url = $(this).val(); - var regex = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; - if (url && regex.test(url) == false) { - $(this).val(''); - tutor_toast('Error!', __('Invalid Video URL', 'tutor'), 'error'); - } - }) - .on('paste', video_url_input, function(e) { - e.stopImmediatePropagation(); - - var root = $(this) - .closest('.tutor-lesson-modal-wrap') - .find('.tutor-option-field-video-duration'); - var duration_label = root.find('label'); - var is_wp_media = $(this).hasClass('input_source_video_id'); - var autofill_url = $(this).data('autofill_url'); - $(this).data('autofill_url', null); - - var video_url = is_wp_media - ? $(this).data('video_url') - : autofill_url || e.originalEvent.clipboardData.getData('text'); - - var toggle_loading = function(show) { - if (!show) { - duration_label.find('img').remove(); - return; - } - - // Show loading icon - if (duration_label.find('img').length == 0) { - duration_label.append( - ' ', - ); - } - }; - - var set_duration = function(sec_num) { - var hours = Math.floor(sec_num / 3600); - var minutes = Math.floor((sec_num - hours * 3600) / 60); - var seconds = Math.round(sec_num - hours * 3600 - minutes * 60); - - if (hours < 10) { - hours = '0' + hours; - } - if (minutes < 10) { - minutes = '0' + minutes; - } - if (seconds < 10) { - seconds = '0' + seconds; - } - - var fragments = [hours, minutes, seconds]; - var time_fields = root.find('input'); - for (var i = 0; i < 3; i++) { - time_fields.eq(i).val(fragments[i]); - } - }; - - var yt_to_seconds = function(duration) { - var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/); - - match = match.slice(1).map(function(x) { - if (x != null) { - return x.replace(/\D/, ''); - } - }); - - var hours = parseInt(match[0]) || 0; - var minutes = parseInt(match[1]) || 0; - var seconds = parseInt(match[2]) || 0; - - return hours * 3600 + minutes * 60 + seconds; - }; - - if ( - is_wp_media || - $(this) - .parent() - .hasClass('video_source_wrap_external_url') - ) { - var player = document.createElement('video'); - player.addEventListener('loadedmetadata', function() { - set_duration(player.duration); - toggle_loading(false); - }); - - toggle_loading(true); - player.src = video_url; - } else if ( - $(this) - .parent() - .hasClass('video_source_wrap_vimeo') - ) { - var regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/; - var match = video_url.match(regExp); - var video_id = match ? match[5] : null; - var is_ssl = _tutorobject.is_ssl ? 's' : ''; -; var vimeo_api_url = `http${is_ssl}://vimeo.com/api/v2/video/${video_id}/json`; - - if (video_id) { - toggle_loading(true); - - $.getJSON( - vimeo_api_url, - function(data) { - if ( - Array.isArray(data) && - data[0] && - data[0].duration !== undefined - ) { - set_duration(data[0].duration); - } - - toggle_loading(false); - }, - ); - } - } else if ( - $(this) - .parent() - .hasClass('video_source_wrap_youtube') - ) { - var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; - var match = video_url.match(regExp); - var video_id = match && match[7].length == 11 ? match[7] : false; - var api_key = $(this).data('youtube_api_key'); - - if (video_id && api_key) { - var result_url = - 'https://www.googleapis.com/youtube/v3/videos?id=' + - video_id + - '&key=' + - api_key + - '&part=contentDetails'; - toggle_loading(true); - - $.getJSON(result_url, function(data) { - if ( - typeof data == 'object' && - data.items && - data.items[0] && - data.items[0].contentDetails && - data.items[0].contentDetails.duration - ) { - set_duration( - yt_to_seconds(data.items[0].contentDetails.duration), - ); - } - - toggle_loading(false); - }); - } - } - }) - .on('input', video_url_input, function() { - if (autofill_url_timeout) { - clearTimeout(autofill_url_timeout); - } - - var $this = $(this); - autofill_url_timeout = setTimeout(function() { - var val = $this.val(); - val = val ? val.trim() : ''; - console.log('Trigger', val); - val ? $this.data('autofill_url', val).trigger('paste') : 0; - }, 700); - }); -}); diff --git a/assets/react/course-builder/quiz.js b/assets/react/course-builder/quiz.js deleted file mode 100644 index 7d387346a6..0000000000 --- a/assets/react/course-builder/quiz.js +++ /dev/null @@ -1,720 +0,0 @@ -import { get_response_message } from '../helper/response'; -import initTinyMCE from '../lib/tinymce'; -import codeSampleLang from "../lib/codesample-lang"; - -window.jQuery(document).ready(function($) { - const { __ } = wp.i18n; - - // TAB switching - var step_switch = function(modal, go_next, clear_next) { - var element = modal.find('.tutor-modal-steps'); - var current = element.find('li[data-tab="' + modal.attr('data-target') + '"]'); - var next = current.next(); - var prev = current.prev(); - - if (!go_next) { - var new_tab = prev.data('tab'); - prev.length ? modal.attr('data-target', new_tab) : 0; - clear_next - ? element - .find('li[data-tab="' + new_tab + '"]') - .nextAll() - .removeClass('tutor-is-completed') - : 0; - return; - } - - if (next.length) { - next.addClass('tutor-is-completed'); - modal.attr('data-target', next.data('tab')); - return true; - } - - // If there is no more next screen, it means quiz saved and show the toast - tutor_toast(__('Success', 'tutor'), __('Quiz Updated'), 'success'); - modal.removeClass('tutor-is-active'); - return null; - }; - - // Slider initiator - var tutor_slider_init = function() { - $('.tutor-field-slider').each(function() { - var $slider = $(this); - var $input = $slider.closest('.tutor-field-type-slider').find('input[type="hidden"]'); - var $showVal = $slider.closest('.tutor-field-type-slider').find('.tutor-field-type-slider-value'); - var min = parseFloat($slider.closest('.tutor-field-type-slider').attr('data-min')); - var max = parseFloat($slider.closest('.tutor-field-type-slider').attr('data-max')); - - $slider.slider({ - range: 'max', - min: min, - max: max, - value: $input.val(), - slide: function(event, ui) { - $showVal.text(ui.value); - $input.val(ui.value); - }, - }); - }); - }; - - function tutor_save_sorting_quiz_questions_order() { - var questions = {}; - $('.quiz-builder-question-wrap').each(function(index, item) { - var $question = $(this); - var question_id = parseInt($question.attr('data-question-id'), 10); - questions[index] = question_id; - }); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - sorted_question_ids: questions, - action: 'tutor_quiz_question_sorting', - }, - }); - } - - // Sort quiz question - function enable_quiz_questions_sorting() { - if (jQuery().sortable) { - $('.quiz-builder-questions-wrap').sortable({ - handle: '.question-sorting', - start: function(e, ui) { - ui.placeholder.css('visibility', 'visible'); - }, - stop: function(e, ui) { - tutor_save_sorting_quiz_questions_order(); - }, - }); - } - } - - /** - * Save answer sorting placement - * - * @since v.1.0.0 - */ - function enable_quiz_answer_sorting() { - if (jQuery().sortable) { - $('#tutor_quiz_question_answers').sortable({ - handle: '.tutor-quiz-answer-sort-icon', - start: function(e, ui) { - ui.placeholder.css('visibility', 'visible'); - }, - stop: function(e, ui) { - tutor_save_sorting_quiz_answer_order(); - }, - }); - } - } - - function tutor_save_sorting_quiz_answer_order() { - var answers = {}; - $('.tutor-quiz-answer-wrap').each(function(index, item) { - var $answer = $(this); - var answer_id = parseInt($answer.attr('data-answer-id'), 10); - answers[index] = answer_id; - }); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { sorted_answer_ids: answers, action: 'tutor_quiz_answer_sorting' }, - }); - } - - function tutor_select() { - var obj = { - init: function() { - $(document).on('click', '.question-type-select .tutor-select-option', function(e) { - e.preventDefault(); - - var $that = $(this); - if ($that.attr('data-is-pro') !== 'true') { - var $html = $that.html().trim(); - $that - .closest('.question-type-select') - .find('.select-header .lead-option') - .html($html); - $that - .closest('.question-type-select') - .find('.select-header input.tutor_select_value_holder') - .val($that.attr('data-value')) - .trigger('change'); - $that.closest('.tutor-select-options').hide(); - } else { - alert('Tutor Pro version required'); - } - }); - $(document).on('click', '.question-type-select .select-header', function(e) { - e.preventDefault(); - - var $that = $(this); - $that - .closest('.question-type-select') - .find('.tutor-select-options') - .slideToggle(); - }); - - this.setValue(); - this.hideOnOutSideClick(); - }, - setValue: function() { - $('.question-type-select').each(function() { - var $that = $(this); - var $option = $that.find('.tutor-select-option'); - - if ($option.length) { - $option.each(function() { - var $thisOption = $(this); - - if ($thisOption.attr('data-selected') === 'selected') { - var $html = $thisOption.html().trim(); - $thisOption - .closest('.question-type-select') - .find('.select-header .lead-option') - .html($html); - $thisOption - .closest('.question-type-select') - .find('.select-header input.tutor_select_value_holder') - .val($thisOption.attr('data-value')); - } - }); - } - }); - }, - hideOnOutSideClick: function() { - $(document).mouseup(function(e) { - var $option_wrap = $('.tutor-select-options'); - if ( - !$(e.target).closest('.select-header').length && - !$option_wrap.is(e.target) && - $option_wrap.has(e.target).length === 0 - ) { - $option_wrap.hide(); - } - }); - }, - reInit: function() { - this.setValue(); - }, - }; - - return obj; - } - - tutor_select().init(); - tutor_slider_init(); - - // Create/Edit quiz opener - $(document).on('click', '.tutor-add-quiz-btn, .open-tutor-quiz-modal, .back-to-quiz-questions-btn', function(e) { - e.preventDefault(); - - if(e.originalEvent){ - // Save question before back - // So it can check if there is no correct answer selected - - let question_type = $(this).closest('#tutor-quiz-question-wrapper').find('.tutor_select_value_holder').val(); - - if(['single_choice', 'multiple_choice'].indexOf(question_type)>-1){ - $('.quiz-modal-question-save-btn').trigger('click'); - return; - } - } - - var $that = $(this); - var step_1 = $(this).hasClass('open-tutor-quiz-modal') || $(this).hasClass('tutor-add-quiz-btn'); - var modal = $('.tutor-modal.tutor-quiz-builder-modal-wrap'); - var quiz_id = $that.hasClass('tutor-add-quiz-btn') ? 0 : $that.attr('data-quiz-id'); - var topic_id = $that.closest('.tutor-topics-wrap').data('topic-id'); - var course_id = $('#post_ID').val(); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - quiz_id: quiz_id, - topic_id: topic_id, - course_id: course_id, - action: 'tutor_load_quiz_builder_modal', - }, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - if (!data.success) { - tutor_toast('Error', get_response_message(data), 'error'); - return; - } - - $('.tutor-quiz-builder-modal-wrap').addClass('tutor-is-active'); - $('.tutor-quiz-builder-modal-wrap .tutor-modal-container').html(data.data.output); - $('.tutor-quiz-builder-modal-wrap') - .attr('data-quiz-id', quiz_id) - .attr('data-topic-id-of-quiz', topic_id); - - modal.removeClass('tutor-has-question-from'); - - if (step_1) { - step_switch(modal, false, true); // Back to second from third - step_switch(modal, false, true); // Back to first from second - } - - window.dispatchEvent(new Event(_tutorobject.content_change_event)); - - tutor_slider_init(); - enable_quiz_questions_sorting(); - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - // Quiz modal next click - $(document).on('click', '.tutor-quiz-builder-modal-wrap button', function(e) { - // DOM findar - var btn = $(this); - var modal = btn.closest('.tutor-modal'); - var current_tab = modal.attr('data-target'); - var action = $(this).data('action'); - - if (action == 'back') { - step_switch(modal, false); - return; - } else if (action != 'next') { - return; - } - - // Quiz meta data - var course_id = $('#post_ID').val(); - var topic_id = $(this) - .closest('.tutor-quiz-builder-modal-wrap') - .attr('data-topic-id-of-quiz'); - - var quiz_id = modal.find('[name="quiz_id"]').val(); - - if (current_tab == 'quiz-builder-tab-quiz-info' || current_tab == 'quiz-builder-tab-settings') { - // Save quiz info. Title and description - var quiz_title = modal.find('[name="quiz_title"]').val(); - var quiz_description = modal.find('[name="quiz_description"]').val(); - - var settings = modal - .find('#quiz-builder-tab-settings :input, #quiz-builder-tab-advanced-options :input') - .serializeObject(); - var quiz_info_required = { - quiz_title, - course_id, - quiz_id, - topic_id, - }; - - for (let k in quiz_info_required) { - if (!quiz_info_required[k]) { - if (k == 'quiz_title') { - tutor_toast('Error!', __('Quiz title required', 'tutor'), 'error'); - } - return; - } - } - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - ...settings, - ...quiz_info_required, - quiz_description, - action: 'tutor_quiz_save', - }, - beforeSend: function() { - btn.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - if (!data.success) { - tutor_toast('Error', get_response_message(data), 'error'); - return; - } - if (quiz_id && quiz_id != 0) { - // Update if exists already - $('#tutor-quiz-' + quiz_id).replaceWith(data.data.output_quiz_row); - } else { - // Otherwise create new row - $('#tutor-topics-' + topic_id + ' .tutor-lessons').append(data.data.output_quiz_row); - } - - // Update modal content - $('.tutor-quiz-builder-modal-wrap .tutor-modal-container').html(data.data.output); - - window.dispatchEvent(new Event(_tutorobject.content_change_event)); - - tutor_slider_init(); - step_switch(modal, true); - - enable_quiz_questions_sorting(); - - // Trigger change to set background based on checked status - $('[name="quiz_option[feedback_mode]"]').trigger('change'); - }, - complete: function() { - btn.removeClass('is-loading').attr('disabled', false); - }, - }); - } else if (current_tab == 'quiz-builder-tab-questions') { - step_switch(modal, true); - } - }); - - /** - * Add or edit Quiz question with modal - */ - $(document).on('click', '.tutor-quiz-open-question-form', function(e) { - e.preventDefault(); - - // Prepare related data for the question - let $that = $(this), - modal = $that.closest('.tutor-modal'), - quiz_id = modal.find('[name="quiz_id"]').val(), - topic_id = modal.find('[name="topic_id"]').val(), - course_id = $('#post_ID').val(), - question_id = $that.attr('data-question-id'); - - let data = { - quiz_id, - topic_id, - course_id, - question_id, - action: 'tutor_quiz_builder_get_question_form', - }; - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: data, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - // Add the question form in modal. - modal.find('.tutor-modal-container').html(data.data.output); - modal.addClass('tutor-has-question-from'); - - // Enable quiz answer sorting for multi/radio select. - enable_quiz_answer_sorting(); - - /** - * WP editor support to quiz question description for PRO user. - * - * @since 2.0.9 Rich text support. - * @since 2.2.3 WP editor support added to quiz question description. - */ - if (_tutorobject.tutor_pro_url && data.data.output ) { - let id = 'tutor_quiz_desc_text_editor', - editor_wrap_selector = '#wp-tutor_quiz_desc_text_editor-wrap', - tinymceConfig = tinyMCEPreInit.mceInit.tutor_lesson_editor_config; - - if (tinymceConfig) { - if ($(editor_wrap_selector).hasClass('html-active')) { - $(editor_wrap_selector).removeClass('html-active'); - } - $(editor_wrap_selector).addClass('tmce-active'); - - if (!tinymceConfig.plugins.includes('codesample')) { - tinymceConfig.plugins = `${tinymceConfig.plugins}, codesample`; - tinymceConfig.codesample_languages = codeSampleLang; - tinymceConfig.toolbar1 = `${tinymceConfig.toolbar1}, codesample`; - } - - tinymceConfig.wpautop = false; - tinymce.init(tinymceConfig); - tinymce.execCommand('mceRemoveEditor', false, id); - tinymce.execCommand('mceAddEditor', false, id); - quicktags({ id: id }); - } - - /** - * Quiz answer explanation rich text editor - * - * @since 2.2.0 - */ - if (document.getElementById('tutor_answer_explanation')) { - tinyMCE.remove('textarea#tutor_answer_explanation'); - initTinyMCE('textarea#tutor_answer_explanation', 'codesample image', 'codesample image'); - } - } - - window.dispatchEvent(new CustomEvent('tutor_modal_shown', {detail: e.target})); - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - // Trash question - $(document).on('click', '.tutor-quiz-question-trash', function(e) { - e.preventDefault(); - - var $that = $(this); - var question_id = $that.attr('data-question-id'); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - question_id: question_id, - action: 'tutor_quiz_builder_question_delete', - }, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function() { - $that.closest('.quiz-builder-question-wrap').fadeOut(function() { - $(this).remove(); - }); - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - /** - * Get question answers option form to save/edit multiple/single/true-false options - * - * @since v.1.0.0 - */ - $(document).on('click', '.add_question_answers_option, .tutor-quiz-answer-edit a', function(e) { - e.preventDefault(); - - var $that = $(this); - var question_id = $that.closest('[data-question-id]').attr('data-question-id'); - var answer_id = $(this).hasClass('add_question_answers_option') - ? null - : $that.closest('.tutor-quiz-answer-wrap').attr('data-answer-id'); - - var $formInput = $('#tutor-quiz-question-wrapper :input').serializeObject(); - $formInput.question_id = question_id; - $formInput.answer_id = answer_id; - $formInput.action = 'tutor_quiz_question_answer_editor'; - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: $formInput, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - $('#tutor_quiz_builder_answer_wrapper').html(data.data.output); - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - /** - * Quiz Question edit save and continue - */ - $(document).on('click', '.quiz-modal-question-save-btn', function(e) { - e.preventDefault(); - - let $that = $(this), - modal = $that.closest('.tutor-modal'), - $formInput = $('#tutor-quiz-question-wrapper :input').serializeObject(); - - $formInput.action = 'tutor_quiz_modal_update_question'; - // If pro active then get desc text from tinyMCE editor - if (_tutorobject.tutor_pro_url) { - const questionId = $formInput.tutor_quiz_question_id, - eidtorId = 'tutor_quiz_desc_text_editor', - ansExplainEidtorId = 'tutor_answer_explanation', - editorWrap = document.getElementById('wp-tutor_quiz_desc_text_editor-wrap'), - isHtmlActive = editorWrap.classList.contains('html-active'); - $formInput.is_html_active = isHtmlActive; - - if (tinyMCE.get(eidtorId)) { - $formInput["tutor_quiz_question["+questionId+"][question_description]"] = tinyMCE.get(eidtorId)?.getContent({format: 'html'}) - } - - if (tinyMCE.get(ansExplainEidtorId)) { - $formInput["answer_explanation"] = tinyMCE.get(ansExplainEidtorId)?.getContent({format: 'raw'}) - } - - } - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: $formInput, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - if (data.success) { - modal.find('.back-to-quiz-questions-btn').trigger('click'); - } else { - tutor_toast('Error', get_response_message(data), 'error'); - } - }, - complete: function() { - setTimeout(() => $that.removeClass('is-loading').attr('disabled', false), 2000); - }, - }); - }); - - /** - * If change question type from quiz builder question - * - * @since v.1.0.0 - */ - $(document).on('change', 'input.tutor_select_value_holder', function(e) { - // Firstly remove older content and show loading spinner - var answer_wrapper = $('#tutor_quiz_builder_answer_wrapper'); - answer_wrapper.html( - `
- -
`, - ); - - answer_wrapper.get(0).scrollIntoView({ block: 'center', behavior: 'smooth' }); - - var question_id = $(this) - .closest('[data-question-id]') - .attr('data-question-id'); - var question_type = $(this).val(); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - question_id: question_id, - question_type: question_type, - action: 'tutor_quiz_builder_change_type', - }, - success: function(data) { - if (data.success) { - $('#tutor_quiz_builder_answer_wrapper').html(data.data.output); - answer_wrapper.get(0).scrollIntoView({ block: 'center', behavior: 'smooth' }); - enable_quiz_answer_sorting(); - } else { - tutor_toast('Error', get_response_message(data), 'error'); - } - }, - }); - }); - - /** - * Saving question answers options - * Student should select the right answer at quiz attempts - * - * @since v.1.0.0 - */ - $(document).on('click', '#quiz-answer-save-btn', function(e) { - e.preventDefault(); - var $that = $(this); - var $formInput = $('#tutor-quiz-question-wrapper :input').serializeObject(); - $formInput.action = $formInput.tutor_quiz_answer_id - ? 'tutor_update_quiz_answer_options' - : 'tutor_save_quiz_answer_options'; - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: $formInput, - beforeSend: function() { - $that.addClass('is-loading').attr('disabled', true); - }, - success: function(data) { - if (!data.success) { - tutor_toast('Error', get_response_message(data), 'error'); - return; - } - - $('.tutor_select_value_holder').trigger('change'); - }, - complete: function() { - $that.removeClass('is-loading').attr('disabled', false); - }, - }); - }); - - /** - * Updating Answer - * - * @since v.1.0.0 - */ - $(document).on('change', '.tutor-quiz-answers-mark-correct-wrap input', function(e) { - e.preventDefault(); - - var $that = $(this); - - var answer_id = $that.val(); - var inputValue = 1; - if (!$that.prop('checked')) { - inputValue = 0; - } - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - answer_id: answer_id, - inputValue: inputValue, - action: 'tutor_mark_answer_as_correct', - }, - }); - }); - - /** - * Delete answer for a question in quiz builder - * - * @since v.1.0.0 - */ - $(document).on('click', '.tutor-quiz-answer-trash-wrap a.answer-trash-btn', function(e) { - e.preventDefault(); - - var $that = $(this); - var answer_id = $that.attr('data-answer-id'); - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { answer_id: answer_id, action: 'tutor_quiz_builder_delete_answer' }, - beforeSend: function() { - $that.closest('.tutor-quiz-answer-wrap').remove(); - }, - }); - }); - - // Collapse/expand advanced settings - $(document).on('click', '.tutor-quiz-advance-settings .tutor-quiz-advance-header', function() { - $(this) - .parent() - .toggleClass('tutor-is-active') - .find('.tutor-icon-angle-down') - .toggleClass('tutor-icon-angle-up'); - }); - - // Change background of quiz feedback mode - $(document).on('change', '[name="quiz_option[feedback_mode]"]', function() { - if ($(this).prop('checked')) { - $(this) - .parent() - .addClass('tutor-bg-white') - .removeClass('tutor-bg-transparent') - .siblings() - .filter('.tutor-radio-select') - .addClass('tutor-bg-transparent') - .removeClass('tutor-bg-white'); - - // Show attempt slider if reveal - let is_retry = $(this).val()=='retry'; - $('.tutor-attempt-allowed-slider')[is_retry ? 'show' : 'hide'](); - $('.tutor-pass-required-field')[is_retry ? 'show' : 'hide'](); - } - }); -}); diff --git a/assets/react/course-builder/topic.js b/assets/react/course-builder/topic.js deleted file mode 100644 index 279f89a1d6..0000000000 --- a/assets/react/course-builder/topic.js +++ /dev/null @@ -1,115 +0,0 @@ -window.jQuery(document).ready(function($) { - const { __ } = wp.i18n; - - $(document).on('click', '.tutor-save-topic-btn', function(e) { - e.preventDefault(); - - let $button = $(this); - let modal = $button.closest('.tutor-modal'); - - let topic_id = modal.find('[name="topic_id"]').val(); - let topic_title = modal.find('[name="topic_title"]').val(); - let topic_summery = modal.find('[name="topic_summery"]').val(); - let topic_course_id = modal.find('[name="topic_course_id"]').val(); - - let data = { - topic_title, - topic_summery, - topic_id, - topic_course_id, - action: 'tutor_save_topic', - }; - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: data, - beforeSend: function() { - $button.addClass('is-loading'); - }, - success: function(resp) { - const { data = {}, success } = resp; - const { message = __('Something Went Wrong!', 'tutor'), course_contents, topic_title } = data; - - if (!success) { - tutor_toast('Error!', message, 'error'); - return; - } - - // Close Modal - // modal.removeClass('tutor-is-active', $('#tutor-course-content-wrap')); - modal.removeClass('tutor-is-active'); - - // Show updated contents - if (topic_id) { - // It's topic update - $button - .closest('.tutor-topics-wrap') - .find('span.topic-inner-title') - .text(topic_title); - } else { - // It's new topic creation - $('#tutor-course-content-wrap').html(course_contents); - modal.find('[name="topic_title"]').val(''); - modal.find('[name="topic_summery"]').val(''); - enable_sorting_topic_lesson(); - } - - window.dispatchEvent(new Event(_tutorobject.content_change_event)); - }, - complete: function() { - $button.removeClass('is-loading'); - $('body').removeClass('tutor-modal-open'); - }, - }); - }); - - /** - * Confirmation for deleting Topic - */ - $(document).on('click', '.tutor-topics-wrap [action-delete-course-topic]', function(e) { - var $that = $(this); - var container = $(this).closest('.tutor-topics-wrap'); - var topic_id = container.attr('data-topic-id'); - - if (!confirm(__('Are you sure to delete the topic?', 'tutor'))) { - return; - } - - $.ajax({ - url: window._tutorobject.ajaxurl, - type: 'POST', - data: { - action: 'tutor_delete_topic', - topic_id, - }, - beforeSend: function() { - $that.addClass('is-loading'); - }, - success: function(data) { - // To Do: Load updated topic list here - if (data.success) { - container.remove(); - return; - } - - tutor_toast('Error!', (data.data || {}).message || __('Something Went Wrong', 'tutor'), 'error'); - }, - complete: function() { - $that.removeClass('is-loading'); - }, - }); - }); - - $(document).on('click', '.topic-inner-title, .expand-collapse-wrap', function(e) { - e.preventDefault(); - - var wrapper = $(this).closest('.tutor-topics-wrap'); - wrapper.find('.tutor-topics-body').slideToggle(); - wrapper - .find('.expand-collapse-wrap') - .toggleClass('is-expanded') - .find('i') - .toggleClass('tutor-icon-angle-down tutor-icon-angle-up'); - }); -}); diff --git a/assets/react/course-builder/video-picker.js b/assets/react/course-builder/video-picker.js deleted file mode 100644 index cc665c854d..0000000000 --- a/assets/react/course-builder/video-picker.js +++ /dev/null @@ -1,60 +0,0 @@ -window.jQuery(document).ready($=>{ - - const {__} = wp.i18n; - - // On Remove video - $(document).on('click', '.video_source_wrap_html5 .tutor-attachment-cards .tutor-delete-attachment', function() { - $(this) - .closest('.video_source_wrap_html5') - .removeClass('tutor-has-video') - .find('input.input_source_video_id').val(''); - }); - - // Upload video - $(document).on("click", ".video_source_wrap_html5 .video_upload_btn", function(event) { - event.preventDefault(); - - var container = $(this).closest('.video_source_wrap_html5'); - var attachment_card = container.find('.tutor-attachment-cards'); - var frame; - - // If the media frame already exists, reopen it. - if (frame) { - frame.open(); - return; - } - - // Create a new media frame - frame = wp.media({ - title: __("Select or Upload Media Of Your Choice", "tutor"), - button: { - text: __("Upload media", "tutor"), - }, - library: { type: "video" }, - multiple: false, // Set to true to allow multiple files to be selected - }); - - // When an image is selected in the media frame... - frame.on("select", function() { - // Get media attachment details from the frame state - var attachment = frame.state().get("selection").first().toJSON(); - - // Show video info - attachment_card.find(".filename").text(attachment.name).attr('href', attachment.url); - attachment_card.find('.filesize').text(attachment.filesizeHumanReadable); - - // Add video id to hidden input - container - .find("input.input_source_video_id") - .val(attachment.id) - .data('video_url', attachment.url) - .trigger('paste'); - - // Add identifer that video added - container.addClass('tutor-has-video'); - }); - - // Finally, open the modal on click - frame.open(); - }); -}); \ No newline at end of file diff --git a/assets/scss/course-builder/_frontend.scss b/assets/scss/course-builder/_frontend.scss deleted file mode 100644 index 5195e20550..0000000000 --- a/assets/scss/course-builder/_frontend.scss +++ /dev/null @@ -1,262 +0,0 @@ -.tutor-option-field, -.tutor-form-group { - textarea { - height: 100px; - } -} - -.tutor-form-row { - margin-top: 20px; - margin-bottom: 20px; -} - -.tutor-course-builder-section { - margin-bottom: 60px; - - .wp-editor-wrap { - width: 100%; - } - - .tutor-frontend-builder-item-scope { - margin-bottom: 30px; - - &:last-child { - margin-bottom: 0; - } - - .tutor-builder-item-heading { - font-weight: 500; - line-height: 21px; - margin-bottom: 10px; - display: block; - } - } - - .tutor-course-builder-section-title { - border-bottom: 1px solid #c0c3cb; - padding-bottom: 20px; - position: relative; - overflow: hidden; - z-index: 1; - margin: 0 0 24px; - cursor: pointer; - - h3 { - font-size: 20px; - font-style: normal; - font-weight: bold; - color: var(--tutor-color-secondary); - - span, - i { - float: left; - font-size: 20px; - line-height: 20px; - } - - i { - color: var(--tutor-color-primary); - font-size: 30px; - } - - span { - padding: 0 15px 0 9px; - } - } - } - - .tutor-course-field-label { - font-style: normal; - font-weight: 500; - font-size: 16px; - color: #212327; - margin-bottom: 10px; - display: block; - } -} - -.tutor-dashboard-builder-header { - padding: 10px 0px; - border-bottom: 1px solid #c0c3cb; - top: 0px; - background: #fff; - width: 100%; - z-index: 1999; - - // Keep sticky until 601 px. - @media (min-width: 601px) { - position: sticky; - } - - @at-root { - // Top position in case of admin bar - body.admin-bar { - .tutor-dashboard-builder-header { - top: 32px !important; - @media (max-width: 783px) { - top: 46px !important; - } - } - } - } - - .tutor-row { - justify-content: space-between; - .tutor-col-auto { - @media (max-width: 991px) { - flex: 0 0 100%; - max-width: 100%; - &:first-child { - margin-bottom: 15px; - } - } - } - } - - .tutor-dashboard-builder-header-left { - display: flex; - align-items: center; - - .tutor-dashboard-builder-logo { - padding: 0 24px; - position: relative; - img { - max-height: 28px; - } - } - } -} - -#wp-course_description-editor-container { - border-radius: 6px; - overflow: hidden; - border: 1px solid #c0c3cb; - - .mce-top-part { - &::before { - box-shadow: none; - } - - div.mce-toolbar-grp { - background: $color-gray-10; - border-bottom: none; - } - } -} - -/* - * Course Level Meta - */ - -.tutor-course-level-meta { - display: flex; - justify-content: space-between; - - label { - margin: 0 40px 0 0; - font-weight: 600; - } -} - -.tutor-frontend-builder-course-price { - color: var(--tutor-body-color); - - .tutor-form-group input { - margin-bottom: 0; - } - - .field-container { - display: flex; - align-items: center; - - input[name='course_price'] { - width: 110px; - } - - & > label { - display: inline-flex; - &:first-child { - margin-right: 25px; - } - } - } -} - -.tutor-course-builder-btn-group { - display: flex; - justify-content: space-between; - flex-wrap: wrap; -} - -/** -Content Drip (Pro) - */ -.select2-dropdown.increasezindex { - z-index: 9999999999999; -} - -.select2-selection__rendered [class^='tutor-icon-'] { - vertical-align: middle; -} - -// Course builder tips -.tutor-course-builder-upload-tips { - position: sticky; - top: 103px; - - @at-root { - body.admin-bar { - .tutor-course-builder-upload-tips { - top: 135px !important; - } - } - } - - @media (max-width: 991px) { - position: static; - } - - ul { - margin: 0; - padding: 0 0 0 35px; - list-style: none; - - li { - position: relative; - margin-bottom: 20px; - line-height: 24px; - - &::after { - content: ''; - position: absolute; - height: 8px; - width: 8px; - border-radius: 50%; - background: var(--tutor-color-primary); - left: -26px; - top: 9px; - } - } - - // rtl - @at-root .rtl { - .tutor-course-builder-upload-tips ul { - padding: 0 35px 0 0; - - li:after { - left: initial; - right: -26px; - } - } - } - } -} - - -.tutor-course-price-row{ - display: none; - - &.is-paid{ - display: flex; - } -} \ No newline at end of file diff --git a/assets/scss/course-builder/index.scss b/assets/scss/course-builder/index.scss deleted file mode 100644 index e86850c0c3..0000000000 --- a/assets/scss/course-builder/index.scss +++ /dev/null @@ -1,99 +0,0 @@ -@import '../../../v2-library/src/scss/reusables.scss'; - -body.tutor-screen-course-builder { - - $body: &; - - // Reusable thumbnail wrapper - .builder-course-thumbnail-upload-wrap { - & > div { - font-size: 15px; - line-height: 25px; - margin-bottom: 20px; - font-weight: 400; - } - - .button-transparent { - float: right; - background: transparent !important; - &:hover { - background: var(--tutor-color-primary) !important; - } - } - } - - .builder-course-thumbnail-img-src { - position: relative; - - .tutor-course-thumbnail-delete-btn { - font-size: 10px; - position: absolute; - top: -4px; - left: -4px; - color: #e53935; - -webkit-transition: 300ms; - transition: 300ms; - border-radius: 50%; - width: 20px; - height: 20px; - line-height: 20px; - background: #fff; - text-align: center; - i { - line-height: 20px; - } - } - } - - // Course segments - @import './segments/video.scss'; - @import './segments/setting.scss'; - @import './segments/instructor.scss'; - @import './segments/attachment.scss'; - @import './segments/topic-content.scss'; - @import './segments/content-drip.scss'; - - // LQA Modals - .tutor-modal { - .wp-editor-wrap { - width: 100%; - } - - &.tutor-quiz-builder-modal-wrap{ - @import './modal/quiz.scss'; - } - - &.tutor-assignment-modal-wrap{ - @import './modal/assignment.scss'; - } - } - - /* Screen specific declarations (Frontend editor) */ - &.tutor-screen-course-builder-frontend{ - @import './frontend.scss'; - } - - .tutor-dropdown-icon-pack { - position: absolute; - top: 9px; - left: 10px; - - &+select { - padding-left: 35px !important; - } - - [data-for] { - display: none; - } - - &[data-video_source=html5] [data-for=html5], - &[data-video_source=youtube] [data-for=youtube], - &[data-video_source=vimeo] [data-for=vimeo], - &[data-video_source=external_url] [data-for=external_url], - &[data-video_source=embedded] [data-for=embedded], - &[data-video_source=shortcode] [data-for=shortcode] - { - display: block; - } - } -} \ No newline at end of file diff --git a/assets/scss/course-builder/modal/assignment.scss b/assets/scss/course-builder/modal/assignment.scss deleted file mode 100644 index 9f2a3ec229..0000000000 --- a/assets/scss/course-builder/modal/assignment.scss +++ /dev/null @@ -1,9 +0,0 @@ -.tutor-input-group{ - flex-flow: column; - - input{ - &:not([name=assignment_title]) { - max-width: 135px; - } - } -} diff --git a/assets/scss/course-builder/modal/quiz.scss b/assets/scss/course-builder/modal/quiz.scss deleted file mode 100644 index 22de79dbe9..0000000000 --- a/assets/scss/course-builder/modal/quiz.scss +++ /dev/null @@ -1,266 +0,0 @@ -&:not(.tutor-has-question-from) { - .tutor-pass-required-toggle{ - font-size: 14px; - color: var(--tutor-body-color); - line-height: 24px; - margin: 0 0 10px; - font-weight: 500; - margin-bottom: 0; - } - - &[data-target='quiz-builder-tab-quiz-info'] { - [action-tutor-prev-quiz], - [action-tutor-save-quiz] { - display: none; - } - - #quiz-builder-tab-questions, - #quiz-builder-tab-settings { - display: none; - } - } - - &[data-target='quiz-builder-tab-questions'] { - #quiz-builder-tab-quiz-info, - #quiz-builder-tab-settings { - display: none; - } - - [action-tutor-save-quiz] { - display: none; - } - } - - &[data-target='quiz-builder-tab-settings'] { - #quiz-builder-tab-quiz-info, - #quiz-builder-tab-questions { - display: none; - } - - [action-tutor-save-quiz] { - display: none; - } - } -} - -&.tutor-has-question-from { - [action-tutor-prev-quiz], - [action-tutor-next-quiz] { - display: none; - } -} - -.question-type-select { - position: relative; - - .select-header { - border: 1px solid #dedede; - margin: 0; - padding: 10px; - width: 100%; - box-shadow: none; - background-color: #fff; - display: flex; - cursor: pointer; - box-sizing: border-box; - border-radius: 3px; - - .lead-option { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - .question-type-pro { - display: none; - } - } - - .select-dropdown { - line-height: 22px; - } - } - - .tutor-select-options { - border: 1px solid #C0C3CB; - background-color: #fff; - padding: 22px 10px 8px; - width: 581px; - position: absolute; - font-size: 0; - z-index: 9; - - display: flex; - - flex-wrap: wrap; - box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.08); - top: 55px; - border-radius: 3px; - - .tutor-select-option { - width: calc(33.3333% - 22px); - display: inline-block; - padding: 8px; - cursor: pointer; - position: relative; - box-sizing: border-box; - margin: 0 11px 15px; - border: 1px solid #C0C3CB; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - - &:hover { - border-color: var(--tutor-color-primary); - } - } - } -} - -#quiz-builder-tab-settings { - .tutor-input-group { - flex-flow: column; - - input { - &:not([name='assignment_title']) { - max-width: 135px; - } - } - } -} - -#tutor_quiz_builder_answer_wrapper { - background: #e3e5eb; - margin-left: -30px; - margin-right: -30px; - - & > div { - padding: 30px; - - &:empty { - display: none; - } - } - - & > a { - display: block; - padding: 15px 30px; - background: #eff1f6; - border-top: 1px solid #cdcfd5; - } - - .tutor-add-option-wrapper { - padding: 10px 15px; - background: #eff1f7; - border-top: 1px solid #cdcfd5; - } - - .tutor_quiz_question_answers { - padding: 15px; - } - - .tutor-quiz-question-answers-form { - padding: 20px 30px; - } - - .tutor-quiz-answer-wrap { - &:not(:last-child) { - margin-bottom: 15px; - } - - .tutor-quiz-answer { - display: flex; - background-color: #fff; - flex: 1; - padding: 10px 15px; - border: 1px solid #dedede; - line-height: 22px; - border-radius: 3px; - - .tutor-quiz-answers-mark-correct-wrap { - input { - cursor: pointer; - } - } - } - } -} - -.quiz-builder-question-wrap { - display: flex; - margin-bottom: 15px; -} - -.tutor-quiz-advance-settings { - border: 1px solid #e0e2ea; - box-sizing: border-box; - border-radius: 6px; - - h4 { - font-style: normal; - font-weight: 500; - font-size: 15px; - color: #41454f; - margin: 0; - margin-bottom: 10px; - } - - p.help { - font-style: normal; - font-weight: normal; - font-size: 13px; - color: #757c8e; - } - - // Header bottom border only when expanded - &.tutor-is-active { - .tutor-quiz-advance-header { - & > div:first-child { - border-bottom: 1px solid #e0e2ea; - border-bottom-right-radius: 6px; - } - } - } - - .tutor-quiz-advance-header { - & > div { - padding: 12px; - - &:first-child { - border-right: 1px solid #e0e2ea; - - span { - display: inline-flex; - align-items: center; - justify-content: center; - - width: 37px; - height: 37px; - background: rgba(var(--tutor-color-primary-rgb), 0.15); - border-radius: 50%; - - font-style: normal; - font-weight: 500; - font-size: 20px; - color: var(--tutor-body-color); - - i { - color: var(--tutor-color-primary); - } - } - } - - &:last-child { - padding: 0 16px; - font-size: 24px; - color: var(--tutor-color-primary); - } - } - } - - &:not(.tutor-is-active) { - .tutor-quiz-advance-content { - display: none; - } - } -} diff --git a/assets/scss/course-builder/segments/attachment.scss b/assets/scss/course-builder/segments/attachment.scss deleted file mode 100644 index 7c82afb5bc..0000000000 --- a/assets/scss/course-builder/segments/attachment.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Course attachment styles -.tutor-course-builder-attachments{ - &:empty { - display: none; - } -} - diff --git a/assets/scss/course-builder/segments/content-drip.scss b/assets/scss/course-builder/segments/content-drip.scss deleted file mode 100644 index be318f9436..0000000000 --- a/assets/scss/course-builder/segments/content-drip.scss +++ /dev/null @@ -1,28 +0,0 @@ -.lqa-content-drip-wrap { - background: #E3E5EB; - margin: 0 -30px; - margin-bottom: 30px; - padding: 15px 30px; - border-top: 1px solid #CDCFD5; - border-bottom: 1px solid #CDCFD5; - - &>span{ - font-style: normal; - font-weight: 500; - font-size: 15px; - color: #41454F; - margin: 0; - padding: 0; - display: inline-block; - margin-bottom: 15px; - } - - label{ - font-style: normal; - display: inline-block; - margin-bottom: 10px; - font-weight: normal; - font-size: 13px; - color: #757C8E; - } -} \ No newline at end of file diff --git a/assets/scss/course-builder/segments/instructor.scss b/assets/scss/course-builder/segments/instructor.scss deleted file mode 100644 index 666da0a7b9..0000000000 --- a/assets/scss/course-builder/segments/instructor.scss +++ /dev/null @@ -1,114 +0,0 @@ -$inst_gap: 30px; - -// Multi instructor list styles -#wpcontent { - .tutor-course-available-instructors { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 400px)); - gap: 20px; - justify-content: flex-start; - .added-instructor-item { - max-width: unset !important; - } - } -} - -.tutor-course-available-instructors { - .added-instructor-item { - background-color: #FFFFFF; - border: 1px solid #dcdfe5; - padding: 13px; - display: inline-flex; - align-items: center; - border-radius: 4px; - position: relative; - box-sizing: border-box; - - &:not(:hover){ - .instructor-control { - display: none; - } - } - - .instructor-name { - position: relative; - font-style: normal; - font-weight: 500; - font-size: 18px; - color: #212327; - width: calc(100% - 45px); - flex-basis: calc(100% - 45px); - - img { - display: inline-block; - margin-left: 5px; - width: 18px; - } - - span{ - // Email address - display: block; - font-style: normal; - font-weight: normal; - font-size: 13px; - color: #5B616F; - } - } - } -} - -// Multi instructor picker modal -#tutor_course_instructor_modal { - .tutor-search-result { - position: absolute; - z-index: 99; - width: 400px; - box-sizing: border-box; - background: white; - max-height: 216px; - overflow: auto; - border: 1px solid #CDCFD5; - border-radius: 6px; - - &:empty{ - display: none; - border: none; - margin: 0; - padding: 0; - height: 0; - } - - & > div{ - display: flex; - align-items: center; - padding: 12px 20px; - - &:hover{ - background: #EFF1F7; - } - - img { - width: 36px; - height: 36px; - border-radius: 50px; - overflow: hidden; - margin: 0 16px 0 0; - } - - .tutor-instructor-single-action { - margin-left: auto; - } - - &:not(:hover){ - .tutor-instructor-single-action { - display: none; - } - } - } - } - - .tutor-selected-result { - min-height: 150px; - margin-bottom: 20px; - } -} \ No newline at end of file diff --git a/assets/scss/course-builder/segments/setting.scss b/assets/scss/course-builder/segments/setting.scss deleted file mode 100644 index 0b64012d92..0000000000 --- a/assets/scss/course-builder/segments/setting.scss +++ /dev/null @@ -1,327 +0,0 @@ -/* Course settings module */ -.select2-container .select2-search__field { - padding: 12px 3px 8px 6px !important; -} - -#wpcontent, -#tutor-frontend-course-builder { - .tutor-certificate-template { - .template-item { - .template-radio-field { - height: 100% !important; - } - } - } - .tutor-modal { - .tutor-delete-attachment.tutor-icon-line-cross { - margin-left: 5px; - } - .tutor-modal-icon { - margin: 0 auto !important; - } - .date-range-input i.tutor-icon-clock, - .date-range-input i.tutor-icon-lock { - font-size: 18px; - position: absolute; - right: 10px; - top: 14px; - } - .tutor-quiz-answer { - align-items: center; - } - .tutor-select-options { - padding: 23px 10px 9px; - p { - margin-bottom: 15px; - } - } - .add_question_answers_option { - i { - margin-right: 8px; - } - } - } - .btn-csv-download { - padding: 0px; - svg { - fill: #757c8e; - top: 0 px; - width: 16px; - height: 16px; - } - } - - .tutor-add-instructor-btn, - .create_new_topic_btn { - padding: 7px 20px; - } - .tutor-form-toggle .tutor-form-toggle-control { - margin: 0 10px 0px 0px !important; - } - - .select2-container { - li.select2-selection__choice { - background: #eff1f7; - } - } - - .tutor-course-available-instructors { - display: flex; - flex-wrap: wrap; - gap: 20px; - justify-content: space-between; - .added-instructor-item { - margin: 0 !important; - max-width: calc(50% - 20px) !important; - flex-basis: calc(50% - 20px) !important; - @include breakpoint-max(mobile) { - max-width: 100% !important; - flex-basis: 100% !important; - } - .instructor-name { - width: calc(100% - 45px); - flex-basis: calc(100% - 45px); - .instructor-intro { - display: flex; - align-items: center; - .instructor-email { - margin-top: -4px; - } - } - } - } - } - - .tutor-quiz-advance-settings p.help { - line-height: 1.34; - margin-top: 10px; - } - .tutor-form-group input:not([type='submit']):focus { - border: 0px solid transparent !important; - } - - .tutor-quiz-builder-modal-wrap { - .select-header { - align-items: center; - } - .tutor-quiz-item .tutor-quiz-item-label .tutor-quiz-item-draggable { - padding: 0 5px 0 14px; - } - .tutor-quiz-item-type { - .tooltip-btn { - display: flex; - } - } - .question-type-select { - .lead-option { - display: flex; - align-items: center; - span { - display: flex; - margin-top: -1px; - } - } - .select-dropdown { - i { - display: flex; - } - } - .tutor-select-options { - span { - i { - margin-top: -2px; - } - } - } - } - } - .tutor_assignment_modal_form { - .tutor-form-select { - width: 135px; - } - } - label.tutor-form-label { - margin-bottom: 12px !important; - } - - .tutor-zoom-meeting-modal-wrap { - #tutor-zoom-meeting-modal-form { - .date-range-input { - input { - border-radius: 6px !important; - border: 1px solid #d7dadf !important; - } - } - - input[data-name='meeting_duration'] { - font-size: 15px; - } - } - } - - #tutor-course-content-wrap { - .tutor-topics-top { - .tutor-topic-title { - background: #f4f6f9; - } - } - } -} -#tutor-metabox-course-settings-tabs { - background-color: #fff; - border: 1px solid var(--tutor-border-color); - box-sizing: border-box; - border-radius: 6px; - width: 100%; - - .settings-tabs-heading { - padding: 1em; - border-bottom: 1px solid #e5e5e5; - - h3 { - font-size: 14px; - margin: 0; - line-height: 1.4; - } - } - - .tutor-field-radio { - p { - margin-top: 0; - } - } - - .course-settings-tabs-container { - display: flex; - - .settings-tabs-navs-wrap { - flex: 0 0 200px; - background: $color-gray-10; - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; - overflow: hidden; - - ul.settings-tabs-navs { - margin: 0; - list-style: none; - padding: 0; - - li { - margin: 0; - &:not(:first-child) { - a { - border-top: 1px solid transparent; - } - } - - a { - display: block; - padding: 13px 9px; - color: #23282d; - text-decoration: none; - text-transform: capitalize; - border-left: 3px solid transparent; - display: flex; - align-items: center; - border-bottom: 1px solid transparent; - transition: background 300ms, border 300ms; - &:focus { - box-shadow: none; - } - - &, - i { - font-style: normal; - font-weight: 500; - font-size: 15px; - color: #41454f; - line-height: initial; - vertical-align: middle; - } - - i { - margin-right: 8px; - width: 24px; - height: 24px; - font-size: 20px; - display: flex; - align-items: center; - } - } - - &.active { - a { - background-color: white; - border-left: 3px solid var(--tutor-color-primary); - border-bottom-color: #e5e5e5; - border-top-color: #e5e5e5; - } - } - - &:not(.active):not(:last-child) { - a { - // border-bottom-color: #e5e5e5; - } - } - } - } - } - - .settings-tabs-container { - padding: 30px 40px 0px 40px; - background: white; - flex: 1; - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; - overflow: hidden; - - label.tutor-course-setting-label { - font-style: normal; - font-weight: 500; - font-size: 16px; - } - input.tutor-form-check-input { - margin-top: 0; - } - } - } - - @at-root { - #{$body}:not(.tutor-screen-course-builder-frontend) { - #course-settings { - .inside { - margin: 0; - padding: 0; - } - } - - #tutor-metabox-course-settings-tabs { - border: none; - - &, - .settings-tabs-navs-wrap, - .settings-tabs-container { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - } - } - } -} -#tutor-frontend-course-builder { - p { - margin-bottom: 0px; - } -} -#wpcontent { - .tutor-course-content-top { - > a { - margin-top: 4px; - } - } - .tutor-question-answer-image { - display: flex; - } -} diff --git a/assets/scss/course-builder/segments/topic-content.scss b/assets/scss/course-builder/segments/topic-content.scss deleted file mode 100644 index 67af865c46..0000000000 --- a/assets/scss/course-builder/segments/topic-content.scss +++ /dev/null @@ -1,175 +0,0 @@ -#tutor-course-content-builder-root { - position: relative; - // Course topics - #tutor-course-content-wrap, - #tutor-course-topics { - position: relative; - - // Common style - a { - text-decoration: none; - &:focus { - box-shadow: none; - } - } - - .tutor-topics-wrap { - border-bottom: 1px solid #f6f8fa; - padding-bottom: 0; - margin: 0; - background: $color-gray-10; - border-radius: 6px; - margin-bottom: 15px; - border: 1px solid var(--tutor-border-color); - overflow: hidden; - .tutor-topic-title { - display: flex; - align-items: center; - font-size: 16px; - font-weight: 300; - - .topic-inner-title { - font-style: normal; - font-weight: 500; - font-size: 16px; - color: var(--tutor-body-color); - flex: 1; - - a { - color: #393c40; - } - } - - .expand-collapse-wrap { - display: flex; - align-items: center; - justify-content: center; - height: 48px; - padding: 4px 16px; - color: var(--tutor-color-primary); - margin-left: 4px; - cursor: pointer; - - &.is-expanded { - background: #fbfbfd; - } - } - } - - .tutor-topics-body { - background: #fbfbfd; - padding: 15px 20px; - - .course-content-item { - padding: 10px 10px 10px 10px; - border: 1px solid var(--tutor-border-color); - box-sizing: border-box; - border-radius: 6px; - margin-bottom: 16px; - background: white; - } - } - - .tutor_add_content_wrap { - padding: 0; - display: flex; - flex-wrap: wrap; - gap: 12px; - - & > span > button, - & > button { - font-style: normal; - font-weight: 500; - padding: 3px 8px; - margin-top: 3px; - margin-bottom: 3px; - } - } - - .course-move-handle { - cursor: grab; - &:active { - cursor: grabbing; - } - } - } - - .drop-lessons { - p { - margin: 0; - } - } - - .tutor-lessons { - padding-left: 0; - margin-bottom: 20px; - - &:empty { - display: none; - } - - &.ui-sortable { - min-height: 20px; - } - } - - .tutor-course-content-top { - display: flex; - font-size: 14px; - - &, - i { - font-size: 15px; - } - - i { - &.tutor-icon-pencil { - margin: 0 10px; - } - - &.tutor-icon-move { - margin-right: 10px; - cursor: grab; - line-height: unset; - - &:active { - cursor: grabbing; - } - } - } - - a { - color: #393c40; - &:nth-child(2) { - flex: 1; - } - } - - .open-tutor-quiz-modal { - i { - display: inline-block; - vertical-align: middle; - margin-right: 5px; - } - } - } - - @at-root { - #{$body}.rtl { - #tutor-course-content-wrap .tutor-course-content-top { - i.tutor-icon-move { - margin-right: 0; - margin-left: 10px; - } - - .open-tutor-quiz-modal { - i { - margin-right: 0; - margin-left: 5px; - } - } - } - } - } - } -} \ No newline at end of file diff --git a/assets/scss/course-builder/segments/video.scss b/assets/scss/course-builder/segments/video.scss deleted file mode 100644 index b4ad05d469..0000000000 --- a/assets/scss/course-builder/segments/video.scss +++ /dev/null @@ -1,47 +0,0 @@ - -.tutor-video-upload-wrap { - - .tutor-dashed-uploader { - border: 2px dashed var(--tutor-color-primary); - box-sizing: border-box; - border-radius: 6px; - padding: 30px; - } - - .video_source_wrap_html5 { - width: 100%; - background: white; - - .video-metabox-source-html5-upload { - background-color: #fff; - text-align: center; - padding: 40px 20px; - display: none; - - p { - margin-bottom: 5px; - } - - .video-upload-icon { - i { - font-size: 67px; - color: var(--tutor-color-primary); - } - } - - .video_source_upload_wrap_html5 { - margin-top: 10px; - } - } - - &:not(.tutor-has-video) { - .video-metabox-source-html5-upload { - display: block; - } - - .html5-video-data { - display: none; - } - } - } -} diff --git a/gulpfile.js b/gulpfile.js index 5de1261e78..39ce0457a6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -154,8 +154,6 @@ gulp.task('watch', function () { gulp.parallel('tutor_admin', 'tutor_setup')(); } else if (e.history[0].includes('/frontend-dashboard/')) { gulp.parallel('tutor_front_dashboard')(); - } else if (e.history[0].includes('/course-builder/')) { - gulp.parallel('tutor_course_builder')(); } else if (e.history[0].includes('modules/')) { gulp.parallel('tutor_front', 'tutor_admin', 'tutor_front_dashboard')(); } else { From efe2976d0b277f30aba45fa549fd96b959f27a0b Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 3 Dec 2024 13:03:38 +0600 Subject: [PATCH 004/194] New addon list app initiated --- .../addons-list/addons-list-main.js | 18 -- .../addons-list/components/AddonCard.js | 109 --------- .../addons-list/components/AddonList.js | 42 ---- .../addons-list/components/App.js | 25 -- .../addons-list/components/Header.js | 56 ----- .../addons-list/components/Search.js | 41 ---- .../addons-list/context/AddonsContext.js | 221 ------------------ .../admin-dashboard/segments/addonlist.js | 72 ------ assets/react/admin-dashboard/tutor-admin.js | 2 - .../v3/entries/addon-list/components/App.tsx | 39 ++++ .../addon-list/components/layout/Main.tsx | 28 +++ .../addon-list/components/layout/Topbar.tsx | 38 +++ assets/react/v3/entries/addon-list/index.tsx | 18 ++ .../v3/entries/addon-list/services/addons.ts | 0 assets/scss/admin-dashboard/_tutor-admin.scss | 23 -- .../v2/custom-pages/_addons-list.scss | 45 ---- .../v2/custom-pages/index.scss | 1 - assets/scss/front/__archive.scss | 5 - classes/Admin.php | 7 +- classes/Assets.php | 5 + tsconfig.json | 3 +- views/pages/enable_disable_addons.php | 2 +- views/pages/tutor-pro-addons.php | 67 ------ webpack.config.js | 2 + 24 files changed, 134 insertions(+), 735 deletions(-) delete mode 100644 assets/react/admin-dashboard/addons-list/addons-list-main.js delete mode 100644 assets/react/admin-dashboard/addons-list/components/AddonCard.js delete mode 100644 assets/react/admin-dashboard/addons-list/components/AddonList.js delete mode 100644 assets/react/admin-dashboard/addons-list/components/App.js delete mode 100644 assets/react/admin-dashboard/addons-list/components/Header.js delete mode 100644 assets/react/admin-dashboard/addons-list/components/Search.js delete mode 100644 assets/react/admin-dashboard/addons-list/context/AddonsContext.js delete mode 100644 assets/react/admin-dashboard/segments/addonlist.js create mode 100644 assets/react/v3/entries/addon-list/components/App.tsx create mode 100644 assets/react/v3/entries/addon-list/components/layout/Main.tsx create mode 100644 assets/react/v3/entries/addon-list/components/layout/Topbar.tsx create mode 100644 assets/react/v3/entries/addon-list/index.tsx create mode 100644 assets/react/v3/entries/addon-list/services/addons.ts delete mode 100644 assets/scss/admin-dashboard/v2/custom-pages/_addons-list.scss delete mode 100644 views/pages/tutor-pro-addons.php diff --git a/assets/react/admin-dashboard/addons-list/addons-list-main.js b/assets/react/admin-dashboard/addons-list/addons-list-main.js deleted file mode 100644 index 73e286ee86..0000000000 --- a/assets/react/admin-dashboard/addons-list/addons-list-main.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; - -import App from './components/App'; - -window.addEventListener('DOMContentLoaded', () => { - function tutorAddonsList() { - const element = ( - - ); - const addonWrapper = document.getElementById('tutor-addons-list-wrapper'); - if (null !== addonWrapper) { - const root = createRoot(addonWrapper); - root.render(element); - } - } - tutorAddonsList(); -}); \ No newline at end of file diff --git a/assets/react/admin-dashboard/addons-list/components/AddonCard.js b/assets/react/admin-dashboard/addons-list/components/AddonCard.js deleted file mode 100644 index dfced6af38..0000000000 --- a/assets/react/admin-dashboard/addons-list/components/AddonCard.js +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import { useAddonsUpdate } from '../context/AddonsContext'; - -const { __ } = wp.i18n; - -const AddonCard = ({ addon, addonId }) => { - const { handleOnChange, addonLoading } = useAddonsUpdate(); - - return ( -
-
0 ? 'not-subscribed' : '' - } tutor-addon-card-${addonId + 1}`} - style={{ transitionDelay: `${100 * addonId}ms` }} - > -
-
-
- {addon.name} -
-
-
- {addon.name} -
-
- {addon.description} -
-
- -
-
-
- - {addon.plugins_required?.length > 0 ? - __('Required Plugin(s)', 'tutor') - : addon.ext_required?.length > 0 ? - __('Required for Push Notification', 'tutor') - : addon.required_settings === true ? - addon.required_title ? addon.required_title : '' - : - __('No extra plugin required', 'tutor') - } - - {addon.ext_required?.length > 0 ? -
- - {addon.ext_required.map((extension, index) => { - return ( -
- -
- ); - })} -
-
- - : addon.depend_plugins && addon.plugins_required.length ? -
- - { - addon.plugins_required.map((plugin, index) => { - return ( -
- - {plugin} - {index !== addon.plugins_required.length - 1 && ','} - -
- ); - }) - } -
-
- : addon.required_settings === true && addon.required_message ? -
- -
- {addon.required_message ? addon.required_message : ''} -
-
-
- : '' - } -
-
- - { - !addon.disable_on_off && addon.plugins_required?.length === 0 && !addon.required_settings ? -
- -
- : '' - } -
-
-
- ); -}; - -export default AddonCard; diff --git a/assets/react/admin-dashboard/addons-list/components/AddonList.js b/assets/react/admin-dashboard/addons-list/components/AddonList.js deleted file mode 100644 index 0c6468ebff..0000000000 --- a/assets/react/admin-dashboard/addons-list/components/AddonList.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { useAddons } from '../context/AddonsContext'; -import AddonCard from './AddonCard'; - -const { __ } = wp.i18n; - -const emptyStateImg = `${_tutorobject.tutor_url}assets/images/addon-empty-state.svg`; - -const AddonList = () => { - const { allAddons, loading } = useAddons(); - - return ( -
- {allAddons.length ? ( - allAddons.map((addon, index) => { - return ; - }) - ) : loading ? ( -
-
-
- ) : ( -
-
-
-
- {__('Empty -
-
{__('No Addons Found!', 'tutor')}
-
-
-
- )} -
- ); -}; - -export default AddonList; \ No newline at end of file diff --git a/assets/react/admin-dashboard/addons-list/components/App.js b/assets/react/admin-dashboard/addons-list/components/App.js deleted file mode 100644 index 6b9b46c4fa..0000000000 --- a/assets/react/admin-dashboard/addons-list/components/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import Header from './Header'; -import Search from './Search'; -import AddonList from './AddonList'; -import { AddonsContextProvider } from '../context/AddonsContext'; - -const App = () => { - return ( - -
-
-
-
-
- - -
-
-
-
-
- ); -}; - -export default App; diff --git a/assets/react/admin-dashboard/addons-list/components/Header.js b/assets/react/admin-dashboard/addons-list/components/Header.js deleted file mode 100644 index efa5e78139..0000000000 --- a/assets/react/admin-dashboard/addons-list/components/Header.js +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import { useAddons, useAddonsUpdate } from '../context/AddonsContext'; - -const { __ } = wp.i18n; -const capitalize = ( string ) => { - return string.charAt(0).toUpperCase() + string.slice(1); -} -const Header = () => { - const filterBtns = ['all', 'active', 'deactive', 'required']; - const { addonList } = useAddons(); - const { activeTab, getTabStatus } = useAddonsUpdate(); - const activeCount = addonList?.reduce((sum, addon) => sum + Number(addon.is_enabled), 0); - const deactiveCount = addonList?.reduce((sum, addon) => sum + Number(!addon.is_enabled), 0); - const requiredCount = addonList?.reduce((sum, addon) => sum + Number(addon.hasOwnProperty('depend_plugins') || 0), 0); - - return ( -
- -
- ); -}; - -export default Header; diff --git a/assets/react/admin-dashboard/addons-list/components/Search.js b/assets/react/admin-dashboard/addons-list/components/Search.js deleted file mode 100644 index d1d2305ace..0000000000 --- a/assets/react/admin-dashboard/addons-list/components/Search.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { useAddonsUpdate } from '../context/AddonsContext'; -const { __ } = wp.i18n; - -const debounce = (fn, delay = 500) => { - let timer = null; - return function() { - const context = this, - args = arguments; - clearTimeout(timer); - timer = setTimeout(() => { - fn.apply(context, args); - }, delay); - }; -}; - -const Search = () => { - const { search, setSearch } = useAddonsUpdate(); - - const handleChange = (e) => { - const { value } = e.target; - setSearch(value); - }; - - return ( -
-
- - -
-
- ); -}; - -export default Search; diff --git a/assets/react/admin-dashboard/addons-list/context/AddonsContext.js b/assets/react/admin-dashboard/addons-list/context/AddonsContext.js deleted file mode 100644 index 6293536894..0000000000 --- a/assets/react/admin-dashboard/addons-list/context/AddonsContext.js +++ /dev/null @@ -1,221 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; - -// Custom contexts. -const AddonsContext = createContext(); -const AddonsUpdateContext = createContext(); - -/** - * Custom hook useAddons - * @returns AddonsContext - */ -export const useAddons = () => { - return useContext(AddonsContext); -}; - -/** - * Custom hook useAddonsUpdate - * @returns AddonsUpdateContext - */ -export const useAddonsUpdate = () => { - return useContext(AddonsUpdateContext); -}; - -/** - * Addons Context Provider - * @param {*} props - * @returns All ContextProviders with children - */ -export const AddonsContextProvider = (props) => { - const [loading, setLoding] = useState(true); - const [allAddons, setAllAddons] = useState([]); - const [activeTab, setActiveTab] = useState('all'); - const initialRenderRef = useRef(false); - const allAddonsRef = useRef(null); - const [addons, setAddons] = useState([]); - const [addonLoading, setAddonLoading] = useState({}); - const [search, setSearch] = useState(''); - - /** - * - * @param {Array} data - * @returns {Object} - */ - function getAddonsState(data) { - return data?.reduce((store, obj) => { - store = { ...store, [obj.basename]: Boolean(obj.is_enabled) }; - return store; - }, {}); - } - - const fetchAddons = async () => { - const formData = new FormData(); - formData.set('action', 'tutor_get_all_addons'); - formData.set(window.tutor_get_nonce_data(true).key, window.tutor_get_nonce_data(true).value); - - try { - const addons = await fetch(_tutorobject.ajaxurl, { - method: 'POST', - body: formData, - }); - - if (addons.ok) { - const response = await addons.json(); - const data = response.data.addons; - if (data && data.length) { - const addonsLoadingState = getAddonsState(data); - // setAddonLoading(addonsLoadingState); - setAllAddons(data); - allAddonsRef.current = data; - setLoding(false); - } - } - } catch (error) { - console.log(error); - setLoding(false); - } - }; - - // Render the component with initial data at on mount. - useEffect(() => { - fetchAddons(); - }, []); - - useEffect(() => { - if (initialRenderRef.current) { - if (activeTab === 'all') { - setAllAddons(allAddonsRef.current); - } else { - const activeAddons = allAddonsRef.current.filter((addon) => { - if (activeTab === 'active') return addon.is_enabled; - else if (activeTab === 'deactive') return !addon.is_enabled; - else if (activeTab === 'required') return addon?.depend_plugins; - }); - setAllAddons(activeAddons); - } - } else if (!initialRenderRef.current) initialRenderRef.current = true; - }, [activeTab]); - - useEffect(() => { - setAddons(allAddonsRef.current); - }); - - function filterList(list, fn) { - return list.filter(fn); - } - - const handleOnChange = (event, addonBaseName) => { - const { checked } = event.target; - setAddonLoading((prev) => ({ ...prev, [addonBaseName]: true })); - - const updatedAddonList = allAddonsRef.current.map((addon) => { - if (addon.basename === addonBaseName) return { ...addon, is_enabled: checked }; - return addon; - }); - - const filterUpdatedAddonList = filterList(updatedAddonList, (item) => - item.name.toLowerCase().includes(search.toLowerCase()), - ); - - if (activeTab === 'active') { - const activeAddons = filterList( - search.trim() === '' ? updatedAddonList : filterUpdatedAddonList, - (addon) => addon.is_enabled, - ); - - setAllAddons(activeAddons); - } else if (activeTab === 'deactive') { - const deActiveAddons = filterList( - search.trim() === '' ? updatedAddonList : filterUpdatedAddonList, - (addon) => !addon.is_enabled, - ); - - setAllAddons(deActiveAddons); - } else if (activeTab === 'required') { - const requiredAddons = filterList( - search.trim() === '' ? updatedAddonList : filterUpdatedAddonList, - (addon) => addon?.depend_plugins, - ); - - setAllAddons(requiredAddons); - } else if (activeTab === 'all') { - setAllAddons(filterUpdatedAddonList); - } - - allAddonsRef.current = updatedAddonList; - - const toggleAddonStatus = async () => { - const prevData = getAddonsState(updatedAddonList); - // console.log(prevData); - - const formData = new FormData(); - formData.set('action', 'addon_enable_disable'); - formData.set('isEnable', Number(checked)); - formData.set('addonFieldName', prevData); - formData.set('addonFieldNames', JSON.stringify(prevData)); - formData.set(window.tutor_get_nonce_data(true).key, window.tutor_get_nonce_data(true).value); - - try { - await fetch(_tutorobject.ajaxurl, { - method: 'POST', - body: formData, - }); - setAddonLoading(prevData); - } catch (error) { - console.log(error); - } - }; - toggleAddonStatus(); - }; - - const getTabStatus = (btn) => { - switch (btn) { - case 'active': - setActiveTab('active'); - break; - case 'deactive': - setActiveTab('deactive'); - break; - case 'required': - setActiveTab('required'); - break; - case 'all': - setActiveTab('all'); - break; - - default: - setActiveTab('all'); - break; - } - }; - - useEffect(() => { - const searchValue = search.trim(); - if (searchValue) { - const filteredAddons = allAddonsRef.current.filter((addon) => - addon.name.toLowerCase().includes(searchValue.toLowerCase()), - ); - setAllAddons(filteredAddons); - } else { - setAllAddons(allAddonsRef.current ?? []); - } - }, [search]); - - return ( - - - {props.children} - - - ); -}; diff --git a/assets/react/admin-dashboard/segments/addonlist.js b/assets/react/admin-dashboard/segments/addonlist.js deleted file mode 100644 index ad0a71d6de..0000000000 --- a/assets/react/admin-dashboard/segments/addonlist.js +++ /dev/null @@ -1,72 +0,0 @@ -let addonsData = _tutorobject.addons_data; - -const addonsList = document.getElementById('tutor-free-addons'); -const searchBar = document.getElementById('free-addons-search'); -let freeAddonsList = _tutorobject.addons_data || []; -let searchString = ''; -const emptyStateImg = `${_tutorobject.tutor_url}assets/images/addon-empty-state.svg`; - -if (null !== searchBar) { - searchBar.addEventListener('input', (e) => { - searchString = e.target.value.toLowerCase(); - const filteredAddons = freeAddonsList.filter((addon) => { - return addon.name.toLowerCase().includes(searchString); - }); - - if (filteredAddons.length > 0) { - displayAddons(filteredAddons); - } else { - emptySearch(); - } - }); -} - -const emptySearch = () => { - const nothingFound = ` -
-
-
- Empty State Illustration -
-
No Addons Found!
-
-
`; - if (null !== addonsList) { - addonsList.innerHTML = nothingFound; - } -}; - -const displayAddons = (addons) => { - const htmlString = addons - .map((addon) => { - const { name, url, description } = addon; - return ` -
-
-
- Available in Pro -
-
- -
- ${name} -
-
- ${description} -
-
-
-
`; - }) - .join(''); - if (null !== addonsList) { - addons.length < 3 ? addonsList.classList.add('is-less-items') : addonsList.classList.remove('is-less-items'); - addonsList.innerHTML = htmlString; - } -}; - -displayAddons(freeAddonsList); diff --git a/assets/react/admin-dashboard/tutor-admin.js b/assets/react/admin-dashboard/tutor-admin.js index 5de3c57e6c..23826333e3 100644 --- a/assets/react/admin-dashboard/tutor-admin.js +++ b/assets/react/admin-dashboard/tutor-admin.js @@ -1,6 +1,4 @@ import '../front/_select_dd_search'; -import './addons-list/addons-list-main'; -import './segments/addonlist'; import './segments/color-preset'; import './segments/editor_full'; import './segments/filter'; diff --git a/assets/react/v3/entries/addon-list/components/App.tsx b/assets/react/v3/entries/addon-list/components/App.tsx new file mode 100644 index 0000000000..3f3f576a34 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/App.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react'; +import { Global } from '@emotion/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import ToastProvider from '@Atoms/Toast'; +import RTLProvider from '@Components/RTLProvider'; +import { createGlobalCss } from '@Utils/style-utils'; +import Main from './layout/Main'; + +function App() { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + refetchOnWindowFocus: false, + networkMode: 'always', + }, + mutations: { + retry: false, + networkMode: 'always', + }, + }, + }), + ); + + return ( + + + + +
+ + + + ); +} + +export default App; diff --git a/assets/react/v3/entries/addon-list/components/layout/Main.tsx b/assets/react/v3/entries/addon-list/components/layout/Main.tsx new file mode 100644 index 0000000000..d65f9504f4 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/layout/Main.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react'; +import Container from '@Components/Container'; +import { colorTokens, spacing } from '@Config/styles'; +import Topbar, { TOPBAR_HEIGHT } from './Topbar'; + +function Main() { + return ( +
+ + +
Addon list
+
+
+ ); +} + +export default Main; + +const styles = { + wrapper: css` + background-color: ${colorTokens.background.default}; + `, + content: css` + min-height: calc(100vh - ${TOPBAR_HEIGHT}px); + width: 100%; + margin-top: ${spacing[32]}; + `, +}; diff --git a/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx b/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx new file mode 100644 index 0000000000..fc01c8416e --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx @@ -0,0 +1,38 @@ +import { css } from '@emotion/react'; +import Container from '@Components/Container'; +import { colorTokens, zIndex } from '@Config/styles'; + +export const TOPBAR_HEIGHT = 96; + +function Topbar() { + return ( +
+ +
+
Left
+
right
+
+
+
+ ); +} + +export default Topbar; + +const styles = { + wrapper: css` + height: ${TOPBAR_HEIGHT}px; + background: ${colorTokens.background.white}; + position: sticky; + top: 32px; + z-index: ${zIndex.positive}; + `, + innerWrapper: css` + display: flex; + align-items: center; + justify-content: space-between; + height: 100%; + `, + left: css``, + right: css``, +}; diff --git a/assets/react/v3/entries/addon-list/index.tsx b/assets/react/v3/entries/addon-list/index.tsx new file mode 100644 index 0000000000..0d2f2fa135 --- /dev/null +++ b/assets/react/v3/entries/addon-list/index.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { HashRouter } from 'react-router-dom'; + +import ErrorBoundary from '@Components/ErrorBoundary'; +import App from '@AddonList/components/App'; + +const root = ReactDOM.createRoot(document.getElementById('tutor-addon-list-wrapper') as HTMLElement); + +root.render( + + + + + + + , +); diff --git a/assets/react/v3/entries/addon-list/services/addons.ts b/assets/react/v3/entries/addon-list/services/addons.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/assets/scss/admin-dashboard/_tutor-admin.scss b/assets/scss/admin-dashboard/_tutor-admin.scss index 5b86a2b37b..d09c15b36b 100644 --- a/assets/scss/admin-dashboard/_tutor-admin.scss +++ b/assets/scss/admin-dashboard/_tutor-admin.scss @@ -518,29 +518,6 @@ a.addon-buynow-link { display: inline-block; } -/** - Add-ons list - */ - -.tutor-addons-list { - background-color: #fff; - min-height: 500px; - padding: 20px; -} - -.tutor-addons-list .plugin-icon { - height: 120px !important; - width: auto !important; -} - -.tutor-addons-list .plugin-card .desc { - margin-right: 0; -} - -.tutor-addons-list .plugin-card .name { - margin-right: 50px; -} - /* RTL Style for name and desc */ .tutor-lms-pro_page_tutor-addons.rtl .plugin-card .desc, diff --git a/assets/scss/admin-dashboard/v2/custom-pages/_addons-list.scss b/assets/scss/admin-dashboard/v2/custom-pages/_addons-list.scss deleted file mode 100644 index a14af37844..0000000000 --- a/assets/scss/admin-dashboard/v2/custom-pages/_addons-list.scss +++ /dev/null @@ -1,45 +0,0 @@ -.tutor-addon-card { - height: 100%; - .tutor-addon { - &-logo { - width: 80px; - height: 80px; - border-radius: 80px; - overflow: hidden; - } - } - - .tutor-card-footer { - background-color: var(--tutor-color-gray-10); - } - - &.not-subscribed { - .tutor-card-footer { - background: rgba(252, 231, 199, 0.3); - border-top-color: 1px solid #f9d093; - } - } - - // @todo: use more universal method - .tooltip-wrap.tutor-lock-tooltip { - position: absolute; - top: 30px; - right: 30px; - width: 22px; - height: 24px; - background-image: url("data:image/svg+xml,%3Csvg width='22' height='24' viewBox='0 0 22 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4.33268 10.6113C3.41221 10.6113 2.66602 11.3575 2.66602 12.278V20.278C2.66602 21.1985 3.41221 21.9447 4.33268 21.9447H17.666C18.5865 21.9447 19.3327 21.1985 19.3327 20.278V12.278C19.3327 11.3575 18.5865 10.6113 17.666 10.6113H4.33268ZM0.666016 12.278C0.666016 10.253 2.30764 8.61133 4.33268 8.61133H17.666C19.6911 8.61133 21.3327 10.253 21.3327 12.278V20.278C21.3327 22.303 19.6911 23.9447 17.666 23.9447H4.33268C2.30764 23.9447 0.666016 22.303 0.666016 20.278V12.278Z' fill='%23C0C3CB'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 17.7143C10.8422 17.7143 10.7143 17.8422 10.7143 18C10.7143 18.1578 10.8422 18.2857 11 18.2857C11.1578 18.2857 11.2857 18.1578 11.2857 18C11.2857 17.8422 11.1578 17.7143 11 17.7143ZM9 18C9 16.8954 9.89543 16 11 16C12.1046 16 13 16.8954 13 18C13 19.1046 12.1046 20 11 20C9.89543 20 9 19.1046 9 18Z' fill='%23C0C3CB'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.9989 2.05566C9.83782 2.05566 8.72431 2.51689 7.90332 3.33789C7.08232 4.15888 6.62109 5.27239 6.62109 6.43345V8.8779C6.62109 9.43019 6.17338 9.8779 5.62109 9.8779C5.06881 9.8779 4.62109 9.43019 4.62109 8.8779V6.43345C4.62109 4.74196 5.29304 3.11974 6.4891 1.92368C7.68517 0.727608 9.30739 0.0556641 10.9989 0.0556641C12.6904 0.0556641 14.3126 0.727608 15.5087 1.92368C16.7047 3.11974 17.3767 4.74196 17.3767 6.43345V8.8779C17.3767 9.43019 16.929 9.8779 16.3767 9.8779C15.8244 9.8779 15.3767 9.43019 15.3767 8.8779V6.43345C15.3767 5.27239 14.9154 4.15888 14.0944 3.33789C13.2735 2.51689 12.1599 2.05566 10.9989 2.05566Z' fill='%23C0C3CB'/%3E%3C/svg%3E"); - } -} - -.tutor-addons-empty { - background: #fff; - border-radius: 6px; - text-align: center; - grid-area: 1/-1; - max-width: 500px; - margin: 0 auto; - - > * { - max-width: 100%; - } -} diff --git a/assets/scss/admin-dashboard/v2/custom-pages/index.scss b/assets/scss/admin-dashboard/v2/custom-pages/index.scss index 48bdf4b7b8..4c413f4c84 100644 --- a/assets/scss/admin-dashboard/v2/custom-pages/index.scss +++ b/assets/scss/admin-dashboard/v2/custom-pages/index.scss @@ -1,3 +1,2 @@ // Custom Pages -@import './addons-list'; @import './course-tagcategory'; diff --git a/assets/scss/front/__archive.scss b/assets/scss/front/__archive.scss index b696814bc7..911c754e26 100644 --- a/assets/scss/front/__archive.scss +++ b/assets/scss/front/__archive.scss @@ -1158,11 +1158,6 @@ span.tutor-quiz-answer-title { #End Quiz Modal */ -.tutor-addons-list .plugin-icon { - height: 120px !important; - width: auto !important; -} - .tutor-video-embeded-wrap { position: relative; padding-bottom: 56.25%; diff --git a/classes/Admin.php b/classes/Admin.php index dd6093d8c5..a0a1b43e99 100644 --- a/classes/Admin.php +++ b/classes/Admin.php @@ -278,12 +278,7 @@ public function withdraw_requests() { * @return void */ public function enable_disable_addons() { - - if ( defined( 'TUTOR_PRO_VERSION' ) ) { - include tutor()->path . 'views/pages/enable_disable_addons.php'; - } else { - include tutor()->path . 'views/pages/tutor-pro-addons.php'; - } + include tutor()->path . 'views/pages/enable_disable_addons.php'; } /** diff --git a/classes/Assets.php b/classes/Assets.php index ed50acc445..4a3adce445 100644 --- a/classes/Assets.php +++ b/classes/Assets.php @@ -234,6 +234,11 @@ public function admin_scripts() { wp_enqueue_script( 'tutor-payment-settings.min', tutor()->url . 'assets/js/tutor-payment-settings.min.js', array( 'wp-i18n', 'wp-element', 'tutor-shared' ), TUTOR_VERSION, true ); } } + + if ( 'tutor-addons' === $page ) { + wp_enqueue_script( 'tutor-shared', tutor()->url . 'assets/js/tutor-shared.min.js', array( 'wp-i18n', 'wp-element' ), TUTOR_VERSION, true ); + wp_enqueue_script( 'tutor-coupon', tutor()->url . 'assets/js/tutor-addon-list.min.js', array( 'wp-i18n', 'wp-element', 'tutor-shared' ), TUTOR_VERSION, true ); + } } /** diff --git a/tsconfig.json b/tsconfig.json index 1e5d357aca..b920e87f30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -46,7 +46,8 @@ "@CouponComponents/*": ["assets/react/v3/entries/coupon-details/components/*"], "@CouponServices/*": ["assets/react/v3/entries/coupon-details/services/*"], "@EnrollmentComponents/*": ["assets/react/v3/entries/pro/manual-enrollment/components/*"], - "@EnrollmentServices/*": ["assets/react/v3/entries/pro/manual-enrollment/services/*"] + "@EnrollmentServices/*": ["assets/react/v3/entries/pro/manual-enrollment/services/*"], + "@AddonList/*": ["assets/react/v3/entries/addon-list/*"] } }, "include": ["./assets/react/v3"], diff --git a/views/pages/enable_disable_addons.php b/views/pages/enable_disable_addons.php index 78e5760012..d4080349b3 100644 --- a/views/pages/enable_disable_addons.php +++ b/views/pages/enable_disable_addons.php @@ -9,4 +9,4 @@ */ ?> -
+
diff --git a/views/pages/tutor-pro-addons.php b/views/pages/tutor-pro-addons.php deleted file mode 100644 index fec037362c..0000000000 --- a/views/pages/tutor-pro-addons.php +++ /dev/null @@ -1,67 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> -
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
- - -
- -
- - -
- -
- - -
- - - - -
-
-
-
-
- -
-
-
-
- - -
-
-
- -
-
-
-
-
diff --git a/webpack.config.js b/webpack.config.js index bab9d5baca..697f5c9cd2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -87,6 +87,7 @@ module.exports = (env, options) => { 'tutor-tax-settings.min': './assets/react/v3/entries/tax-settings/index.tsx', 'tutor-payment-settings.min': './assets/react/v3/entries/payment-settings/index.tsx', 'tutor-coupon.min': './assets/react/v3/entries/coupon-details/index.tsx', + 'tutor-addon-list.min': './assets/react/v3/entries/addon-list/index.tsx', }, clean: true, }, @@ -146,6 +147,7 @@ module.exports = (env, options) => { '@CouponServices': path.resolve(__dirname, './assets/react/v3/entries/coupon-details/services/'), '@EnrollmentComponents': path.resolve(__dirname, './assets/react/v3/entries/pro/manual-enrollment/components/'), '@EnrollmentServices': path.resolve(__dirname, './assets/react/v3/entries/pro/manual-enrollment/services/'), + '@AddonList': path.resolve(__dirname, './assets/react/v3/entries/addon-list/'), }, }, }), From ed9231ae4a784504a55b8e9cf4b585b466b7d4c1 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Wed, 4 Dec 2024 13:54:24 +0600 Subject: [PATCH 005/194] Update eslint.config.mjs --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 9b846e3892..213eafe915 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,7 @@ export default [ 'react/react-in-jsx-scope': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', + 'react/display-name': 'off', '@typescript-eslint/consistent-type-imports': [ 'error', { From 81dff07f4ed569c3c998039c0d79780148646d40 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Wed, 4 Dec 2024 13:55:06 +0600 Subject: [PATCH 006/194] Addon list design implemented --- assets/react/v3/@types/index.d.ts | 4 +- .../addon-list/components/AddonCard.tsx | 130 ++++++++++++++++++ .../addon-list/components/AddonList.tsx | 53 +++++++ .../components/layout/Container.tsx | 21 +++ .../addon-list/components/layout/Main.tsx | 26 ++-- .../addon-list/components/layout/Topbar.tsx | 60 ++++++-- .../addon-list/contexts/addon-context.tsx | 52 +++++++ .../v3/entries/addon-list/services/addons.ts | 36 +++++ assets/react/v3/shared/atoms/Switch.tsx | 38 ++++- assets/react/v3/shared/atoms/TextInput.tsx | 10 +- assets/react/v3/shared/config/icon-list.ts | 8 ++ assets/react/v3/shared/config/styles.ts | 1 + assets/react/v3/shared/utils/endpoints.ts | 3 + assets/react/v3/shared/utils/style-utils.ts | 49 +++---- 14 files changed, 422 insertions(+), 69 deletions(-) create mode 100644 assets/react/v3/entries/addon-list/components/AddonCard.tsx create mode 100644 assets/react/v3/entries/addon-list/components/AddonList.tsx create mode 100644 assets/react/v3/entries/addon-list/components/layout/Container.tsx create mode 100644 assets/react/v3/entries/addon-list/contexts/addon-context.tsx diff --git a/assets/react/v3/@types/index.d.ts b/assets/react/v3/@types/index.d.ts index bde95485f4..25ca284731 100644 --- a/assets/react/v3/@types/index.d.ts +++ b/assets/react/v3/@types/index.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ export type {}; declare module '*.png'; @@ -6,13 +7,10 @@ declare module '*.jpeg'; declare module '*.jpg'; declare global { - // biome-ignore lint/suspicious/noExplicitAny: const wp: any; interface Window { - // biome-ignore lint/suspicious/noExplicitAny: wp: any; ajaxurl: string; - // biome-ignore lint/suspicious/noExplicitAny: tinymce: any; _tutorobject: { ajaxurl: string; diff --git a/assets/react/v3/entries/addon-list/components/AddonCard.tsx b/assets/react/v3/entries/addon-list/components/AddonCard.tsx new file mode 100644 index 0000000000..fa7fce365f --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/AddonCard.tsx @@ -0,0 +1,130 @@ +import { css } from '@emotion/react'; +import { type Addon } from '../services/addons'; +import { borderRadius, colorTokens, fontSize, fontWeight, lineHeight, spacing } from '@/v3/shared/config/styles'; +import Switch from '@/v3/shared/atoms/Switch'; +import { tutorConfig } from '@/v3/shared/config/config'; +import Show from '@/v3/shared/controls/Show'; +import SVGIcon from '@/v3/shared/atoms/SVGIcon'; +import Tooltip from '@/v3/shared/atoms/Tooltip'; +import { __ } from '@wordpress/i18n'; + +function AddonCard({ addon }: { addon: Addon }) { + const isTutorPro = !!tutorConfig.tutor_pro_url; + + return ( +
+
+
+ {addon.name} +
+
+ + + + } + > + + + + +
+
+
+ {addon.name} + + +
{__('Plugin Required', 'tutor')}
+
+
+ + +
{__('Settings Required', 'tutor')}
+
+
+
+
{addon.description}
+
+ ); +} + +export default AddonCard; + +const styles = { + wrapper: (isEnabled: boolean) => css` + background-color: ${colorTokens.background.white}; + padding: ${spacing[16]} ${spacing[12]}; + border-radius: ${borderRadius[6]}; + + ${!isEnabled && + css` + [data-addon-action] { + display: none; + } + `} + + &:hover { + [data-addon-action] { + display: block; + } + } + `, + addonTop: css` + display: flex; + align-items: start; + justify-content: space-between; + `, + thumb: css` + width: 32px; + height: 32px; + border-radius: ${borderRadius.circle}; + overflow: hidden; + + img { + max-width: 100%; + } + `, + addonAction: css` + svg { + color: ${colorTokens.icon.default}; + } + `, + addonTitle: css` + font-size: ${fontSize[16]}; + line-height: ${lineHeight[26]}; + font-weight: ${fontWeight.semiBold}; + color: ${colorTokens.text.primary}; + margin-top: ${spacing[16]}; + margin-bottom: ${spacing[4]}; + + display: flex; + align-items: center; + gap: ${spacing[8]}; + + > div { + flex-shrink: 0; + } + `, + addonTitleText: css` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + `, + requiredBadge: css` + min-width: fit-content; + background-color: ${colorTokens.icon.warning}; + color: ${colorTokens.text.primary}; + border-radius: ${borderRadius[4]}; + font-size: ${fontSize[11]}; + line-height: ${lineHeight[16]}; + font-weight: ${fontWeight.semiBold}; + padding: 1px ${spacing[8]}; + `, + addonDescription: css` + font-size: ${fontSize[14]}; + line-height: ${lineHeight[22]}; + color: ${colorTokens.text.subdued}; + `, +}; diff --git a/assets/react/v3/entries/addon-list/components/AddonList.tsx b/assets/react/v3/entries/addon-list/components/AddonList.tsx new file mode 100644 index 0000000000..342ab60ad8 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/AddonList.tsx @@ -0,0 +1,53 @@ +import { css } from '@emotion/react'; +import { useAddonContext } from '../contexts/addon-context'; +import AddonCard from './AddonCard'; +import { spacing } from '@/v3/shared/config/styles'; +import { typography } from '@/v3/shared/config/typography'; +import { __ } from '@wordpress/i18n'; +import Show from '@/v3/shared/controls/Show'; + +function AddonList() { + const { addons } = useAddonContext(); + const activeAddons = addons.filter((addon) => !!addon.is_enabled); + const availableAddons = addons.filter((addon) => !addon.is_enabled); + + return ( +
+ +
{__('Active Add-Ons', 'tutor')}
+
+ {activeAddons.map((addon) => { + return ; + })} +
+
+ + +
{__('Available Add-Ons', 'tutor')}
+
+ {availableAddons.map((addon) => { + return ; + })} +
+
+
+ ); +} + +export default AddonList; + +const styles = { + wrapper: css` + margin-top: ${spacing[40]}; + `, + addonListWrapper: css` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(275px, 1fr)); + gap: ${spacing[24]}; + margin-bottom: ${spacing[40]}; + `, + addonListTitle: css` + ${typography.heading5('medium')}; + margin-bottom: ${spacing[16]}; + `, +}; diff --git a/assets/react/v3/entries/addon-list/components/layout/Container.tsx b/assets/react/v3/entries/addon-list/components/layout/Container.tsx new file mode 100644 index 0000000000..e17f5e6093 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/layout/Container.tsx @@ -0,0 +1,21 @@ +import { css } from '@emotion/react'; +import type { ReactNode } from 'react'; + +const CONTAINER_WIDTH = 1196; + +function Container({ children }: { children: ReactNode }) { + return
{children}
; +} + +export default Container; + +const styles = { + wrapper: css` + max-width: ${CONTAINER_WIDTH}px; + padding-left: 12px; + padding-right: 12px; + margin: 0 auto; + height: 100%; + width: 100%; + `, +}; diff --git a/assets/react/v3/entries/addon-list/components/layout/Main.tsx b/assets/react/v3/entries/addon-list/components/layout/Main.tsx index d65f9504f4..2c3e579b26 100644 --- a/assets/react/v3/entries/addon-list/components/layout/Main.tsx +++ b/assets/react/v3/entries/addon-list/components/layout/Main.tsx @@ -1,15 +1,18 @@ import { css } from '@emotion/react'; -import Container from '@Components/Container'; -import { colorTokens, spacing } from '@Config/styles'; -import Topbar, { TOPBAR_HEIGHT } from './Topbar'; +import Topbar from './Topbar'; +import { AddonProvider } from '@AddonList/contexts/addon-context'; +import AddonList from '@AddonList/components/AddonList'; +import Container from './Container'; function Main() { return (
- - -
Addon list
-
+ + + + + +
); } @@ -17,12 +20,5 @@ function Main() { export default Main; const styles = { - wrapper: css` - background-color: ${colorTokens.background.default}; - `, - content: css` - min-height: calc(100vh - ${TOPBAR_HEIGHT}px); - width: 100%; - margin-top: ${spacing[32]}; - `, + wrapper: css``, }; diff --git a/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx b/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx index fc01c8416e..9a4f9e2570 100644 --- a/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx +++ b/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx @@ -1,16 +1,34 @@ import { css } from '@emotion/react'; -import Container from '@Components/Container'; -import { colorTokens, zIndex } from '@Config/styles'; +import { Breakpoint, colorTokens, fontSize, fontWeight, lineHeight, spacing } from '@Config/styles'; +import { useAddonContext } from '../../contexts/addon-context'; +import Container from './Container'; +import SVGIcon from '@/v3/shared/atoms/SVGIcon'; +import { __ } from '@wordpress/i18n'; +import TextInput from '@/v3/shared/atoms/TextInput'; -export const TOPBAR_HEIGHT = 96; +export const TOPBAR_HEIGHT = 80; function Topbar() { + const { searchTerm, setSearchTerm } = useAddonContext(); + return (
-
Left
-
right
+
+ + {__('Add-one', 'tutor')} +
+
+ +
@@ -21,18 +39,36 @@ export default Topbar; const styles = { wrapper: css` - height: ${TOPBAR_HEIGHT}px; - background: ${colorTokens.background.white}; - position: sticky; - top: 32px; - z-index: ${zIndex.positive}; + min-height: ${TOPBAR_HEIGHT}px; `, innerWrapper: css` display: flex; align-items: center; justify-content: space-between; height: 100%; + border-bottom: 1px solid ${colorTokens.stroke.divider}; + padding: ${spacing[20]} 0px; + + ${Breakpoint.mobile} { + flex-direction: column; + gap: ${spacing[12]}; + } + `, + left: css` + display: flex; + align-items: center; + gap: ${spacing[12]}; + + font-size: ${fontSize[20]}; + line-height: ${lineHeight[28]}; + font-weight: ${fontWeight.medium}; + color: ${colorTokens.text.primary}; + + svg { + color: ${colorTokens.icon.hover}; + } + `, + right: css` + min-width: 300px; `, - left: css``, - right: css``, }; diff --git a/assets/react/v3/entries/addon-list/contexts/addon-context.tsx b/assets/react/v3/entries/addon-list/contexts/addon-context.tsx new file mode 100644 index 0000000000..6410df4e36 --- /dev/null +++ b/assets/react/v3/entries/addon-list/contexts/addon-context.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useState } from 'react'; +import { LoadingSection } from '@Atoms/LoadingSpinner'; +import { type Addon, useAddonListQuery } from '../services/addons'; +import { tutorConfig } from '@Config/config'; + +interface AddonContextType { + addons: Addon[]; + searchTerm: string; + setSearchTerm: (term: string) => void; +} + +const AddonContext = React.createContext({ + addons: [] as Addon[], + searchTerm: '' as string, + setSearchTerm: () => {}, +}); + +export const useAddonContext = () => React.useContext(AddonContext); + +export const AddonProvider = ({ children }: { children: React.ReactNode }) => { + const isTutorPro = !!tutorConfig.tutor_pro_url; + const [addonList, setAddonList] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + + const addonListQuery = useAddonListQuery(); + + useEffect(() => { + if (addonListQuery.isLoading) { + return; + } + + let baseAddons = []; + + if (isTutorPro && addonListQuery.data) { + baseAddons = addonListQuery.data.addons || []; + } else { + baseAddons = tutorConfig.addons_data; + } + + const filteredAddons = baseAddons.filter((addon) => addon.name.toLowerCase().includes(searchTerm.toLowerCase())); + + setAddonList(filteredAddons); + }, [addonListQuery.data, addonListQuery.isLoading, isTutorPro, searchTerm]); + + if (addonListQuery.isLoading) { + return ; + } + + return ( + {children} + ); +}; diff --git a/assets/react/v3/entries/addon-list/services/addons.ts b/assets/react/v3/entries/addon-list/services/addons.ts index e69de29bb2..0c552adc1a 100644 --- a/assets/react/v3/entries/addon-list/services/addons.ts +++ b/assets/react/v3/entries/addon-list/services/addons.ts @@ -0,0 +1,36 @@ +import endpoints from '@Utils/endpoints'; +import { wpAjaxInstance } from '@Utils/api'; +import { tutorConfig } from '@Config/config'; +import { useQuery } from '@tanstack/react-query'; + +export interface Addon { + name: string; + basename?: string; + description: string; + url: string; + is_enabled: boolean | number; + base_name?: string; + path?: string; + required_settings?: boolean; + required_title?: string; + required_message?: string; + thumb_url?: string; + plugins_required?: string[]; +} + +interface AddonListResponse { + addons: Addon[]; + success: boolean; +} + +const getAddonList = () => { + return wpAjaxInstance.get(endpoints.GET_ADDON_LIST).then((response) => response.data); +}; + +export const useAddonListQuery = () => { + return useQuery({ + enabled: !!tutorConfig.tutor_pro_url, + queryKey: ['AddonList'], + queryFn: () => getAddonList(), + }); +}; diff --git a/assets/react/v3/shared/atoms/Switch.tsx b/assets/react/v3/shared/atoms/Switch.tsx index d1aefd2310..a939ce485c 100644 --- a/assets/react/v3/shared/atoms/Switch.tsx +++ b/assets/react/v3/shared/atoms/Switch.tsx @@ -5,9 +5,10 @@ import { type SerializedStyles, css } from '@emotion/react'; import React, { type ChangeEvent } from 'react'; type labelPositionType = 'left' | 'right'; +type SwitchSize = 'large' | 'regular' | 'small'; const styles = { - switchStyles: css` + switchStyles: (size: SwitchSize) => css` /** Increasing the css specificity */ &[data-input] { all: unset; @@ -23,6 +24,12 @@ const styles = { cursor: pointer; transition: background-color 0.25s cubic-bezier(0.785, 0.135, 0.15, 0.86); + ${size === 'small' && + css` + width: 26px; + height: 16px; + `} + &::before { display: none !important; } @@ -49,12 +56,25 @@ const styles = { border-radius: ${borderRadius.circle}; box-shadow: ${shadow.switch}; transition: left 0.25s cubic-bezier(0.785, 0.135, 0.15, 0.86); + + ${size === 'small' && + css` + top: 2px; + left: 3px; + width: 12px; + height: 12px; + `} } &:checked { background: ${colorTokens.primary.main}; &:after { left: 18px; + + ${size === 'small' && + css` + left: 11px; + `} } } @@ -91,10 +111,22 @@ interface SwitchProps { disabled?: boolean; labelPosition?: labelPositionType; labelCss?: SerializedStyles; + size?: SwitchSize; } const Switch = React.forwardRef((props: SwitchProps, ref) => { - const { id = nanoid(), name, label, value, checked, disabled, onChange, labelPosition = 'left', labelCss } = props; + const { + id = nanoid(), + name, + label, + value, + checked, + disabled, + onChange, + labelPosition = 'left', + labelCss, + size = 'regular', + } = props; const handleChange = (event: ChangeEvent) => { onChange?.(event.target.checked, event); @@ -115,7 +147,7 @@ const Switch = React.forwardRef((props: SwitchPro id={id} checked={!!checked} disabled={disabled} - css={styles.switchStyles} + css={styles.switchStyles(size)} onChange={handleChange} data-input /> diff --git a/assets/react/v3/shared/atoms/TextInput.tsx b/assets/react/v3/shared/atoms/TextInput.tsx index e3b333205e..d7150f4eae 100644 --- a/assets/react/v3/shared/atoms/TextInput.tsx +++ b/assets/react/v3/shared/atoms/TextInput.tsx @@ -100,7 +100,7 @@ const TextInput = ({ {variant === 'search' && ( - + )} @@ -188,10 +188,12 @@ const styles = { `, rightIconButton: css` position: absolute; - right: 0; - top: 0; + right: ${spacing[4]}; + top: ${spacing[4]}; + button { - padding: ${spacing[8]}; + padding: ${spacing[4]}; + border-radius: ${borderRadius[2]}; } `, searchIcon: css` diff --git a/assets/react/v3/shared/config/icon-list.ts b/assets/react/v3/shared/config/icon-list.ts index d992d3ff7b..19ae621849 100644 --- a/assets/react/v3/shared/config/icon-list.ts +++ b/assets/react/v3/shared/config/icon-list.ts @@ -147,6 +147,10 @@ const collection = { icon: '', viewBox: '0 0 16 16', }, + lockStroke: { + icon: '', + viewBox: '0 0 24 24', + }, drop: { icon: '', viewBox: '0 0 16 16', @@ -871,6 +875,10 @@ const collection = { icon: '', viewBox: '0 0 28 28', }, + addons: { + icon: '', + viewBox: '0 0 32 32', + }, } as const; export default collection; diff --git a/assets/react/v3/shared/config/styles.ts b/assets/react/v3/shared/config/styles.ts index 5702af6bc3..56faa43a4a 100644 --- a/assets/react/v3/shared/config/styles.ts +++ b/assets/react/v3/shared/config/styles.ts @@ -338,6 +338,7 @@ export const lineHeight = { 18: '1.125rem', 20: '1.25rem', 21: '1.313rem', + 22: '1.375rem', 24: '1.5rem', 26: '1.625rem', 28: '1.75rem', diff --git a/assets/react/v3/shared/utils/endpoints.ts b/assets/react/v3/shared/utils/endpoints.ts index e5d182910a..d8f8c66b19 100644 --- a/assets/react/v3/shared/utils/endpoints.ts +++ b/assets/react/v3/shared/utils/endpoints.ts @@ -48,6 +48,9 @@ const endpoints = { GET_PAYMENT_GATEWAYS: 'tutor_payment_gateways', INSTALL_PAYMENT_GATEWAY: 'tutor_install_payment_gateway', REMOVE_PAYMENT_GATEWAY: 'tutor_remove_payment_gateway', + + // ADDON LIST + GET_ADDON_LIST: 'tutor_get_all_addons', }; export default endpoints; diff --git a/assets/react/v3/shared/utils/style-utils.ts b/assets/react/v3/shared/utils/style-utils.ts index 5273a4442a..b34476dc11 100644 --- a/assets/react/v3/shared/utils/style-utils.ts +++ b/assets/react/v3/shared/utils/style-utils.ts @@ -3,7 +3,7 @@ import { css } from '@emotion/react'; import { typography } from '../config/typography'; export const createGlobalCss = () => css` - body:not(.tutor-screen-backend-settings) { + body:not(.tutor-screen-backend-settings):not(.tutor-backend-tutor-addons) { #wpcontent { padding-left: 0; } @@ -226,12 +226,10 @@ export const styleUtils = { align-items: center; flex-direction: row; - ${ - direction === 'column' && - css` + ${direction === 'column' && + css` flex-direction: column; - ` - } + `} `, boxReset: css` padding: 0; @@ -506,13 +504,7 @@ export const styleUtils = { outline: 2px solid ${colorTokens.stroke.brand}; } `, - optionCounter: ({ - isEditing, - isSelected = false, - }: { - isEditing: boolean; - isSelected?: boolean; - }) => css` + optionCounter: ({ isEditing, isSelected = false }: { isEditing: boolean; isSelected?: boolean }) => css` height: ${spacing[24]}; width: ${spacing[24]}; border-radius: ${borderRadius.min}; @@ -520,19 +512,13 @@ export const styleUtils = { color: ${colorTokens.text.subdued}; background-color: ${colorTokens.background.default}; text-align: center; - ${ - isSelected && - !isEditing && - css` - background-color: ${colorTokens.bg.white}; - ` - } + ${isSelected && + !isEditing && + css` + background-color: ${colorTokens.bg.white}; + `} `, - optionDragButton: ({ - isOverlay, - }: { - isOverlay: boolean; - }) => css` + optionDragButton: ({ isOverlay }: { isOverlay: boolean }) => css` background: none; border: none; outline: none; @@ -555,12 +541,10 @@ export const styleUtils = { outline-offset: 1px; } - ${ - isOverlay && - css` - cursor: grabbing; - ` - } + ${isOverlay && + css` + cursor: grabbing; + `} `, optionInputWrapper: css` display: flex; @@ -568,7 +552,8 @@ export const styleUtils = { width: 100%; gap: ${spacing[12]}; - input, textarea { + input, + textarea { background: none; border: none; outline: none; From c9c27e68000e4cb4217d5bc54770f37ec666d76f Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Wed, 4 Dec 2024 16:52:35 +0600 Subject: [PATCH 007/194] Content updated --- .../v3/entries/addon-list/components/AddonList.tsx | 4 ++-- .../addon-list/components/layout/Topbar.tsx | 2 +- classes/Addons.php | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/assets/react/v3/entries/addon-list/components/AddonList.tsx b/assets/react/v3/entries/addon-list/components/AddonList.tsx index 342ab60ad8..8c1160306c 100644 --- a/assets/react/v3/entries/addon-list/components/AddonList.tsx +++ b/assets/react/v3/entries/addon-list/components/AddonList.tsx @@ -14,7 +14,7 @@ function AddonList() { return (
-
{__('Active Add-Ons', 'tutor')}
+
{__('Active Addons', 'tutor')}
{activeAddons.map((addon) => { return ; @@ -23,7 +23,7 @@ function AddonList() { -
{__('Available Add-Ons', 'tutor')}
+
{__('Available Addons', 'tutor')}
{availableAddons.map((addon) => { return ; diff --git a/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx b/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx index 9a4f9e2570..322fd01824 100644 --- a/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx +++ b/assets/react/v3/entries/addon-list/components/layout/Topbar.tsx @@ -17,7 +17,7 @@ function Topbar() {
- {__('Add-one', 'tutor')} + {__('Addons', 'tutor')}
array( 'name' => __( 'Social Login', 'tutor' ), - 'description' => __( 'Let users register & login through social network like Facebook, Google, etc.', 'tutor' ), + 'description' => __( 'Let users register & login through social networks.', 'tutor' ), ), 'content-drip' => array( 'name' => __( 'Content Drip', 'tutor' ), @@ -289,7 +289,7 @@ public function addons_lists_to_show() { ), 'tutor-zoom' => array( 'name' => __( 'Tutor Zoom Integration', 'tutor' ), - 'description' => __( 'Connect Tutor LMS with Zoom to host live online classes. Students can attend live classes right from the lesson page.', 'tutor' ), + 'description' => __( 'Connect Tutor LMS with Zoom to host live online classes.', 'tutor' ), ), 'quiz-import-export' => array( 'name' => __( 'Quiz Export/Import', 'tutor' ), @@ -321,7 +321,7 @@ public function addons_lists_to_show() { ), 'pmpro' => array( 'name' => __( 'Paid Memberships Pro', 'tutor' ), - 'description' => __( 'Maximize revenue by selling membership access to all of your courses.', 'tutor' ), + 'description' => __( 'Boost revenue by selling course memberships.', 'tutor' ), ), 'restrict-content-pro' => array( 'name' => __( 'Restrict Content Pro', 'tutor' ), @@ -329,14 +329,14 @@ public function addons_lists_to_show() { ), 'tutor-weglot' => array( 'name' => 'Weglot', - 'description' => __( 'Translate & manage multilingual courses for global reach with full edit control.', 'tutor' ), + 'description' => __( 'Translate & manage multilingual courses for global reach.', 'tutor' ), ), 'tutor-wpml' => array( - 'name' => __( 'WPML Multilingual CMS', 'tutor' ), - 'description' => __( 'Create multilingual courses, lessons, dashboard and more for a global audience.', 'tutor' ), + 'name' => __( 'WPML', 'tutor' ), + 'description' => __( 'Create multilingual courses, lessons, dashboard and more.', 'tutor' ), ), 'h5p' => array( - 'name' => __( 'H5P Integration', 'tutor' ), + 'name' => __( 'H5P', 'tutor' ), 'description' => __( 'Integrate H5P to add interactivity and engagement to your courses.', 'tutor' ), ), ); From f6654d0a632b4cf8622884bed4b38a1c365071b7 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Dec 2024 12:47:12 +0600 Subject: [PATCH 008/194] visible prop added to tooltip --- assets/react/v3/shared/atoms/Tooltip.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/assets/react/v3/shared/atoms/Tooltip.tsx b/assets/react/v3/shared/atoms/Tooltip.tsx index 0ea5967573..284059b15a 100644 --- a/assets/react/v3/shared/atoms/Tooltip.tsx +++ b/assets/react/v3/shared/atoms/Tooltip.tsx @@ -16,6 +16,7 @@ interface TooltipProps { hideOnClick?: boolean; delay?: number; disabled?: boolean; + visible?: boolean; } const initialStyles = { opacity: 0, transform: 'scale(0.8)' }; @@ -29,11 +30,12 @@ const Tooltip = ({ hideOnClick, delay = 0, disabled = false, + visible = false, }: TooltipProps) => { - if (disabled) return children; - const [props, setSpring] = useSpring(() => initialStyles); + if (disabled) return children; + const onMount = () => { setSpring.start({ opacity: 1, @@ -66,6 +68,7 @@ const Tooltip = ({ delay={[delay, 100]} hideOnClick={hideOnClick} placement={placement} + visible={visible} >
{children}
From 7e0b53824ba2af440b718573ba53f7b91e19610e Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Dec 2024 12:49:07 +0600 Subject: [PATCH 009/194] Addon enable disable option added --- .../addon-list/components/AddonCard.tsx | 124 ++++++++++++------ .../addon-list/contexts/addon-context.tsx | 17 ++- .../v3/entries/addon-list/services/addons.ts | 31 ++++- assets/react/v3/shared/utils/endpoints.ts | 1 + classes/Addons.php | 1 + 5 files changed, 133 insertions(+), 41 deletions(-) diff --git a/assets/react/v3/entries/addon-list/components/AddonCard.tsx b/assets/react/v3/entries/addon-list/components/AddonCard.tsx index fa7fce365f..911fed4715 100644 --- a/assets/react/v3/entries/addon-list/components/AddonCard.tsx +++ b/assets/react/v3/entries/addon-list/components/AddonCard.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/react'; -import { type Addon } from '../services/addons'; +import { useEnableDisableAddon, type Addon } from '../services/addons'; import { borderRadius, colorTokens, fontSize, fontWeight, lineHeight, spacing } from '@/v3/shared/config/styles'; import Switch from '@/v3/shared/atoms/Switch'; import { tutorConfig } from '@/v3/shared/config/config'; @@ -7,42 +7,99 @@ import Show from '@/v3/shared/controls/Show'; import SVGIcon from '@/v3/shared/atoms/SVGIcon'; import Tooltip from '@/v3/shared/atoms/Tooltip'; import { __ } from '@wordpress/i18n'; +import { useState } from 'react'; +import { useAddonContext } from '../contexts/addon-context'; +import { useToast } from '@/v3/shared/atoms/Toast'; function AddonCard({ addon }: { addon: Addon }) { const isTutorPro = !!tutorConfig.tutor_pro_url; + const { showToast } = useToast(); + const { addons, updatedAddons, setUpdatedAddons } = useAddonContext(); + + const [isChecked, setIsChecked] = useState(!!addon.is_enabled); + const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + const enableDisableAddon = useEnableDisableAddon(); + + async function handleAddonChange(checked: boolean) { + setIsChecked(checked); + + const addonObject = {} as Record; + + addons.forEach((item) => { + const alreadyUpdatedItem = updatedAddons.find((updatedItem) => updatedItem.basename === item.basename); + if (item.basename === addon.basename) { + addonObject[item.basename as string] = checked ? 1 : 0; + } else if (alreadyUpdatedItem) { + addonObject[item.basename as string] = alreadyUpdatedItem.is_enabled ? 1 : 0; + } else { + addonObject[item.basename as string] = item.is_enabled ? 1 : 0; + } + }); + + const response = await enableDisableAddon.mutateAsync({ + addonFieldNames: JSON.stringify(addonObject), + }); + + if (response.success) { + setUpdatedAddons([ + ...updatedAddons.filter((item) => item.base_name === addon.base_name), + { ...addon, is_enabled: checked ? 1 : 0 }, + ]); + } else { + setIsChecked(!checked); + showToast({ type: 'danger', message: __('Something went wrong!', 'tutor') }); + } + } return ( -
+
setIsTooltipVisible(true)} + onMouseLeave={() => setIsTooltipVisible(false)} + >
{addon.name}
-
+
+ } > - - + + } + > + + +
{__('Plugin Required', 'tutor')}
+
+
+ + +
{__('Settings Required', 'tutor')}
+
+
- {addon.name} - - -
{__('Plugin Required', 'tutor')}
-
-
- - -
{__('Settings Required', 'tutor')}
-
+ {addon.name} + +
{__('New', 'tutor')}
{addon.description}
@@ -53,23 +110,10 @@ function AddonCard({ addon }: { addon: Addon }) { export default AddonCard; const styles = { - wrapper: (isEnabled: boolean) => css` + wrapper: css` background-color: ${colorTokens.background.white}; - padding: ${spacing[16]} ${spacing[12]}; + padding: ${spacing[16]}; border-radius: ${borderRadius[6]}; - - ${!isEnabled && - css` - [data-addon-action] { - display: none; - } - `} - - &:hover { - [data-addon-action] { - display: block; - } - } `, addonTop: css` display: flex; @@ -102,15 +146,17 @@ const styles = { display: flex; align-items: center; gap: ${spacing[8]}; - - > div { - flex-shrink: 0; - } `, - addonTitleText: css` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + newBadge: css` + min-width: fit-content; + background-color: ${colorTokens.brand.blue}; + color: ${colorTokens.text.white}; + border-radius: ${borderRadius[4]}; + font-size: ${fontSize[11]}; + line-height: ${lineHeight[15]}; + font-weight: ${fontWeight.semiBold}; + padding: ${spacing[2]} ${spacing[8]} 1px; + text-transform: uppercase; `, requiredBadge: css` min-width: fit-content; diff --git a/assets/react/v3/entries/addon-list/contexts/addon-context.tsx b/assets/react/v3/entries/addon-list/contexts/addon-context.tsx index 6410df4e36..afc8d60e78 100644 --- a/assets/react/v3/entries/addon-list/contexts/addon-context.tsx +++ b/assets/react/v3/entries/addon-list/contexts/addon-context.tsx @@ -5,12 +5,16 @@ import { tutorConfig } from '@Config/config'; interface AddonContextType { addons: Addon[]; + updatedAddons: Addon[]; + setUpdatedAddons: (addon: Addon[]) => void; searchTerm: string; setSearchTerm: (term: string) => void; } const AddonContext = React.createContext({ addons: [] as Addon[], + updatedAddons: [] as Addon[], + setUpdatedAddons: () => {}, searchTerm: '' as string, setSearchTerm: () => {}, }); @@ -20,6 +24,7 @@ export const useAddonContext = () => React.useContext(AddonContext); export const AddonProvider = ({ children }: { children: React.ReactNode }) => { const isTutorPro = !!tutorConfig.tutor_pro_url; const [addonList, setAddonList] = useState([]); + const [updatedAddons, setUpdatedAddons] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const addonListQuery = useAddonListQuery(); @@ -47,6 +52,16 @@ export const AddonProvider = ({ children }: { children: React.ReactNode }) => { } return ( - {children} + + {children} + ); }; diff --git a/assets/react/v3/entries/addon-list/services/addons.ts b/assets/react/v3/entries/addon-list/services/addons.ts index 0c552adc1a..075cf2a34e 100644 --- a/assets/react/v3/entries/addon-list/services/addons.ts +++ b/assets/react/v3/entries/addon-list/services/addons.ts @@ -1,7 +1,10 @@ import endpoints from '@Utils/endpoints'; import { wpAjaxInstance } from '@Utils/api'; import { tutorConfig } from '@Config/config'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { convertToErrorMessage } from '@/v3/shared/utils/util'; +import { type ErrorResponse } from '@/v3/shared/utils/form'; +import { useToast } from '@/v3/shared/atoms/Toast'; export interface Addon { name: string; @@ -16,6 +19,7 @@ export interface Addon { required_message?: string; thumb_url?: string; plugins_required?: string[]; + is_new?: boolean; } interface AddonListResponse { @@ -23,6 +27,14 @@ interface AddonListResponse { success: boolean; } +interface Response { + success: boolean; +} + +interface AddonPayload { + addonFieldNames: string; +} + const getAddonList = () => { return wpAjaxInstance.get(endpoints.GET_ADDON_LIST).then((response) => response.data); }; @@ -34,3 +46,20 @@ export const useAddonListQuery = () => { queryFn: () => getAddonList(), }); }; + +const addonEnableDisable = (payload: AddonPayload) => { + return wpAjaxInstance.post(endpoints.ADDON_ENABLE_DISABLE, { + ...payload, + }); +}; + +export const useEnableDisableAddon = () => { + const { showToast } = useToast(); + + return useMutation({ + mutationFn: addonEnableDisable, + onError: (error: ErrorResponse) => { + showToast({ type: 'danger', message: convertToErrorMessage(error) }); + }, + }); +}; diff --git a/assets/react/v3/shared/utils/endpoints.ts b/assets/react/v3/shared/utils/endpoints.ts index d8f8c66b19..340860944e 100644 --- a/assets/react/v3/shared/utils/endpoints.ts +++ b/assets/react/v3/shared/utils/endpoints.ts @@ -51,6 +51,7 @@ const endpoints = { // ADDON LIST GET_ADDON_LIST: 'tutor_get_all_addons', + ADDON_ENABLE_DISABLE: 'addon_enable_disable', }; export default endpoints; diff --git a/classes/Addons.php b/classes/Addons.php index a41fec562e..0dfc0d16a9 100644 --- a/classes/Addons.php +++ b/classes/Addons.php @@ -338,6 +338,7 @@ public function addons_lists_to_show() { 'h5p' => array( 'name' => __( 'H5P', 'tutor' ), 'description' => __( 'Integrate H5P to add interactivity and engagement to your courses.', 'tutor' ), + 'is_new' => true, ), ); From d418b2531af1d26ef71d9d106dc4f9a7317d70ae Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Dec 2024 12:53:02 +0600 Subject: [PATCH 010/194] Addon icons design improved --- assets/react/v3/entries/addon-list/components/AddonCard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/react/v3/entries/addon-list/components/AddonCard.tsx b/assets/react/v3/entries/addon-list/components/AddonCard.tsx index 911fed4715..0bf42cf05d 100644 --- a/assets/react/v3/entries/addon-list/components/AddonCard.tsx +++ b/assets/react/v3/entries/addon-list/components/AddonCard.tsx @@ -123,11 +123,13 @@ const styles = { thumb: css` width: 32px; height: 32px; - border-radius: ${borderRadius.circle}; + background-color: ${colorTokens.background.hover}; + border-radius: ${borderRadius[4]}; overflow: hidden; img { max-width: 100%; + border-radius: ${borderRadius.circle}; } `, addonAction: css` From e21a6efb8fc3eefffa59785aec74ee8752e9d5e4 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Dec 2024 20:55:38 +0600 Subject: [PATCH 011/194] InstallationPopover added --- .../components/InstallationPopover.tsx | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 assets/react/v3/entries/addon-list/components/InstallationPopover.tsx diff --git a/assets/react/v3/entries/addon-list/components/InstallationPopover.tsx b/assets/react/v3/entries/addon-list/components/InstallationPopover.tsx new file mode 100644 index 0000000000..f166b06d79 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/InstallationPopover.tsx @@ -0,0 +1,99 @@ +import Button from '@/v3/shared/atoms/Button'; +import { borderRadius, colorTokens, shadow, spacing } from '@/v3/shared/config/styles'; +import { typography } from '@/v3/shared/config/typography'; +import For from '@/v3/shared/controls/For'; +import { css } from '@emotion/react'; +import { __, sprintf } from '@wordpress/i18n'; + +interface Plugin { + name: string; + thumb?: string; +} + +interface InstallationPopoverProps { + addonName: string; + handleClose: () => void; + plugins: Plugin[]; +} + +function InstallationPopover({ addonName, plugins, handleClose }: InstallationPopoverProps) { + return ( +
+

+ {sprintf(__("The following plugins will be installed upon activating the '%s'.", 'tutor'), addonName)} +

+ +
+ + {(item) => ( +
+
+ {item.name} +
+
{item.name}
+
+ )} +
+
+ +
+ + +
+
+ ); +} +export default InstallationPopover; + +const styles = { + wrapper: css` + min-width: 300px; + background-color: ${colorTokens.background.white}; + border-radius: ${borderRadius.card}; + box-shadow: ${shadow.popover}; + padding: ${spacing[16]}; + + display: flex; + flex-direction: column; + gap: ${spacing[16]}; + `, + content: css` + ${typography.body('medium')}; + margin: 0px; + `, + pluginsWrapper: css` + display: flex; + flex-direction: column; + gap: ${spacing[12]}; + `, + pluginItem: css` + display: flex; + align-items: center; + gap: ${spacing[8]}; + padding: ${spacing[12]}; + background-color: ${colorTokens.surface.wordpress}; + border-radius: ${borderRadius[6]}; + `, + pluginThumb: css` + height: 32px; + width: 32px; + overflow: hidden; + border-radius: ${borderRadius.circle}; + + img { + max-width: 100%; + } + `, + pluginName: css` + ${typography.caption('medium')}; + `, + buttonWrapper: css` + display: flex; + justify-content: end; + gap: ${spacing[8]}; + `, +}; From a8084caa4b76d137854f9393e7b1f333eab97ec1 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Dec 2024 21:51:31 +0600 Subject: [PATCH 012/194] _select2.scss removed --- v2-library/src/scss/legacy/_select2.scss | 95 ------------------------ v2-library/src/scss/legacy/index.scss | 1 - 2 files changed, 96 deletions(-) delete mode 100644 v2-library/src/scss/legacy/_select2.scss diff --git a/v2-library/src/scss/legacy/_select2.scss b/v2-library/src/scss/legacy/_select2.scss deleted file mode 100644 index d9d9902bbc..0000000000 --- a/v2-library/src/scss/legacy/_select2.scss +++ /dev/null @@ -1,95 +0,0 @@ -body.tutor-screen-course-builder{ - - .select2-container--default .select2-selection--single .select2-selection__rendered { - line-height: 46px; - } - - .select2-container--default .select2-selection--single .select2-selection__arrow { - height: 46px; - } - - .select2-container .select2-selection--single .select2-selection__rendered { - padding-left: 13px; - font-size: 16px; - } - - .select2-container .select2-selection--single { - height: 48px; - } - - .select2-container .select2-selection--multiple { - min-height: 45px; - border-radius: 6px; - } - - // Only for frontend course builder - // Backend course builder ok somehow - &.tutor-screen-course-builder-frontend{ - .select2-dropdown{ - &.select2-dropdown--below, &.select2-dropdown--above{ - margin-top: 32px; - } - } - } - - .select2-container { - width: 100% !important; - - ul.select2-selection__rendered { - padding: 4px 9px; - display: block; - } - - li.select2-selection__choice { - background: #E3E5EB; - line-height: 29px; - border-radius: 100px; - padding: 1px 9px 1px 16.5px; - margin: 5px; - border: none; - font-style: normal; - font-weight: 500; - font-size: 16px; - color: #41454F; - - .select2-selection__choice__remove { - margin-left: 11px; - float: right; - position: static; - border: none; - } - } - - .select2-search__field { - padding: 8px 3px 8px 6px; - box-sizing: border-box; - margin: 0; - &::-webkit-input-placeholder { - color: #abafb6; - } - &::-moz-placeholder { - color: #abafb6; - } - &:-ms-input-placeholder { - color: #abafb6; - } - &:-moz-placeholder { - color: #abafb6; - } - } - - .select2-selection--single, .select2-selection--multiple { - border-color: #dcdfe5; - } - - &.select2-container--focus { - .select2-selection--single, .select2-selection--multiple { - border-color: var(--tutor-color-primary); - } - } - - .select2-search__field { - min-width: 1em !important; - } - } -} \ No newline at end of file diff --git a/v2-library/src/scss/legacy/index.scss b/v2-library/src/scss/legacy/index.scss index 28817f332b..ba2c54bc12 100644 --- a/v2-library/src/scss/legacy/index.scss +++ b/v2-library/src/scss/legacy/index.scss @@ -5,7 +5,6 @@ @import './toast.scss'; @import './ui-date-picker.scss'; @import './slider-input'; -@import './select2'; // @todo: to be removed .tutorPlayer { From b2f4386685045cb103bd01e7dfe248468ebc7fde Mon Sep 17 00:00:00 2001 From: Muhammad Nabeel Amin <61421054+Nabeel70@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:15:44 +0500 Subject: [PATCH 013/194] Update login-form.php Enhance User Interaction by update "Forget?" to "Forget Password?" for better understanding to users --- templates/login-form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/login-form.php b/templates/login-form.php index e011308393..44ca2538ac 100644 --- a/templates/login-form.php +++ b/templates/login-form.php @@ -77,7 +77,7 @@
- +
From 6a471bb9538fcdacc5f6e08fbb4af10bc3429b2c Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 10 Dec 2024 12:17:08 +0600 Subject: [PATCH 014/194] Empty state and free banner design added --- .../addon-list/components/AddonList.tsx | 14 ++- .../addon-list/components/EmptyState.tsx | 36 ++++++++ .../addon-list/components/FreeBanner.tsx | 80 ++++++++++++++++++ .../v3/public/images/addons-empty-state.webp | Bin 0 -> 9390 bytes .../v3/public/images/free-addons-banner.webp | Bin 0 -> 8266 bytes assets/react/v3/shared/utils/style-utils.ts | 4 +- 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 assets/react/v3/entries/addon-list/components/EmptyState.tsx create mode 100644 assets/react/v3/entries/addon-list/components/FreeBanner.tsx create mode 100644 assets/react/v3/public/images/addons-empty-state.webp create mode 100644 assets/react/v3/public/images/free-addons-banner.webp diff --git a/assets/react/v3/entries/addon-list/components/AddonList.tsx b/assets/react/v3/entries/addon-list/components/AddonList.tsx index 8c1160306c..08442e335c 100644 --- a/assets/react/v3/entries/addon-list/components/AddonList.tsx +++ b/assets/react/v3/entries/addon-list/components/AddonList.tsx @@ -5,14 +5,26 @@ import { spacing } from '@/v3/shared/config/styles'; import { typography } from '@/v3/shared/config/typography'; import { __ } from '@wordpress/i18n'; import Show from '@/v3/shared/controls/Show'; +import FreeBanner from './FreeBanner'; +import { tutorConfig } from '@/v3/shared/config/config'; +import EmptyState from './EmptyState'; function AddonList() { - const { addons } = useAddonContext(); + const isTutorPro = !!tutorConfig.tutor_pro_url; + const { addons, searchTerm } = useAddonContext(); const activeAddons = addons.filter((addon) => !!addon.is_enabled); const availableAddons = addons.filter((addon) => !addon.is_enabled); + if (searchTerm.length && addons.length === 0) { + return ; + } + return (
+ + + +
{__('Active Addons', 'tutor')}
diff --git a/assets/react/v3/entries/addon-list/components/EmptyState.tsx b/assets/react/v3/entries/addon-list/components/EmptyState.tsx new file mode 100644 index 0000000000..438cc6a2f4 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/EmptyState.tsx @@ -0,0 +1,36 @@ +import { spacing } from '@/v3/shared/config/styles'; +import { typography } from '@/v3/shared/config/typography'; +import { css } from '@emotion/react'; +import emptyStateBanner from '@Images/addons-empty-state.webp'; +import { __ } from '@wordpress/i18n'; + +function EmptyState() { + return ( +
+ {__('Empty +

{__('No matching results found.', 'tutor')}

+
+ ); +} + +export default EmptyState; + +const styles = { + wrapper: css` + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: ${spacing[20]}; + margin-top: ${spacing[96]}; + + img { + max-width: 160px; + } + + p { + ${typography.body('medium')}; + margin-bottom: 0; + } + `, +}; diff --git a/assets/react/v3/entries/addon-list/components/FreeBanner.tsx b/assets/react/v3/entries/addon-list/components/FreeBanner.tsx new file mode 100644 index 0000000000..d93bad8771 --- /dev/null +++ b/assets/react/v3/entries/addon-list/components/FreeBanner.tsx @@ -0,0 +1,80 @@ +import Button from '@/v3/shared/atoms/Button'; +import SVGIcon from '@/v3/shared/atoms/SVGIcon'; +import { borderRadius, Breakpoint, colorTokens, lineHeight, spacing } from '@/v3/shared/config/styles'; +import { typography } from '@/v3/shared/config/typography'; +import { css } from '@emotion/react'; +import { __ } from '@wordpress/i18n'; +import bannerImage from '@Images/free-addons-banner.webp'; +import config from '@/v3/shared/config/config'; + +function FreeBanner() { + return ( +
+
+ {__('Get +
+
+
{__('Get All of Add-Ons for a Single Price', 'tutor')}
+

+ { + // prettier-ignore + __( 'Unlock all add-ons with one payment! Easily enable them and customize for enhanced functionality and usability. Tailor your experience effortlessly.', 'tutor' ) + } +

+ +
+
+ ); +} + +export default FreeBanner; + +const styles = { + wrapper: css` + background-color: ${colorTokens.background.white}; + border-radius: ${spacing[6]}; + padding: ${spacing[32]}; + margin-bottom: ${spacing[32]}; + + display: flex; + align-items: center; + gap: ${spacing[32]}; + + ${Breakpoint.mobile} { + flex-direction: column; + padding: ${spacing[24]}; + } + `, + image: css` + img { + width: 100%; + max-width: 235px; + border-radius: ${borderRadius[6]}; + + ${Breakpoint.mobile} { + max-width: 100%; + } + } + `, + content: css` + max-width: 510px; + `, + title: css` + ${typography.heading6('semiBold')}; + line-height: ${lineHeight[28]}; + margin-bottom: ${spacing[8]}; + `, + paragraph: css` + ${typography.caption('regular')}; + line-height: ${lineHeight[22]}; + color: ${colorTokens.text.subdued}; + margin-bottom: ${spacing[20]}; + `, +}; diff --git a/assets/react/v3/public/images/addons-empty-state.webp b/assets/react/v3/public/images/addons-empty-state.webp new file mode 100644 index 0000000000000000000000000000000000000000..50d72f9feed004dbcbc977101ddfd6c2ee33feea GIT binary patch literal 9390 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 literal 0 HcmV?d00001 diff --git a/assets/react/v3/public/images/free-addons-banner.webp b/assets/react/v3/public/images/free-addons-banner.webp new file mode 100644 index 0000000000000000000000000000000000000000..79c528511a1d4f655352b840e3d59c7024a9f045 GIT binary patch literal 8266 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 css` - body:not(.tutor-screen-backend-settings):not(.tutor-backend-tutor-addons) { + body:not(.tutor-screen-backend-settings) { #wpcontent { padding-left: 0; } - #wpbody { + &:not(.tutor-backend-tutor-addons) #wpbody { background-color: ${colorTokens.background.default}; } } From fa1bedf48990573889034f56be9b3172909d3227 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 10 Dec 2024 12:21:21 +0600 Subject: [PATCH 015/194] Code refactored --- .../react/v3/entries/addon-list/components/AddonCard.tsx | 4 ++-- .../v3/entries/addon-list/components/layout/Container.tsx | 4 ++-- .../react/v3/entries/addon-list/components/layout/Main.tsx | 7 +------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/assets/react/v3/entries/addon-list/components/AddonCard.tsx b/assets/react/v3/entries/addon-list/components/AddonCard.tsx index 0bf42cf05d..9726532eac 100644 --- a/assets/react/v3/entries/addon-list/components/AddonCard.tsx +++ b/assets/react/v3/entries/addon-list/components/AddonCard.tsx @@ -21,7 +21,7 @@ function AddonCard({ addon }: { addon: Addon }) { const enableDisableAddon = useEnableDisableAddon(); - async function handleAddonChange(checked: boolean) { + const handleAddonChange = async (checked: boolean) => { setIsChecked(checked); const addonObject = {} as Record; @@ -50,7 +50,7 @@ function AddonCard({ addon }: { addon: Addon }) { setIsChecked(!checked); showToast({ type: 'danger', message: __('Something went wrong!', 'tutor') }); } - } + }; return (
+
@@ -18,7 +17,3 @@ function Main() { } export default Main; - -const styles = { - wrapper: css``, -}; From 62c74f703a36e4b95a7d029f4d78c702f7049773 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 10 Dec 2024 13:27:30 +0600 Subject: [PATCH 016/194] Translations fixed --- classes/Addons.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/classes/Addons.php b/classes/Addons.php index 0dfc0d16a9..bc25bad888 100644 --- a/classes/Addons.php +++ b/classes/Addons.php @@ -245,23 +245,23 @@ public function addons_lists_to_show() { ), 'content-drip' => array( 'name' => __( 'Content Drip', 'tutor' ), - 'description' => 'Unlock lessons by schedule or when students meet a specific condition.', + 'description' => __( 'Unlock lessons by schedule or when students meet a specific condition.', 'tutor' ), ), 'tutor-multi-instructors' => array( 'name' => __( 'Tutor Multi Instructors', 'tutor' ), - 'description' => 'Collaborate and add multiple instructors to a course.', + 'description' => __( 'Collaborate and add multiple instructors to a course.', 'tutor' ), ), 'tutor-assignments' => array( 'name' => __( 'Tutor Assignments', 'tutor' ), - 'description' => 'Assess student learning with assignments.', + 'description' => __( 'Assess student learning with assignments.', 'tutor' ), ), 'tutor-course-preview' => array( 'name' => __( 'Tutor Course Preview', 'tutor' ), - 'description' => 'Offer free previews of specific lessons before enrollment.', + 'description' => __( 'Offer free previews of specific lessons before enrollment.', 'tutor' ), ), 'tutor-course-attachments' => array( 'name' => __( 'Tutor Course Attachments', 'tutor' ), - 'description' => 'Add unlimited attachments/ private files to any Tutor course', + 'description' => __( 'Add unlimited attachments/ private files to any Tutor course', 'tutor' ), ), 'google-meet' => array( 'name' => __( 'Tutor Google Meet Integration', 'tutor' ), @@ -276,11 +276,11 @@ public function addons_lists_to_show() { 'description' => __( 'Send automated and customized emails for various Tutor events.', 'tutor' ), ), 'calendar' => array( - 'name' => 'Calendar', + 'name' => __( 'Calendar', 'tutor' ), 'description' => __( 'Enable to let students view all your course events in one place.', 'tutor' ), ), 'tutor-notifications' => array( - 'name' => 'Notifications', + 'name' => __( 'Notifications', 'tutor' ), 'description' => __( 'Keep students and instructors notified of course events on their dashboard.', 'tutor' ), ), 'google-classroom' => array( @@ -328,7 +328,7 @@ public function addons_lists_to_show() { 'description' => __( 'Enable to manage content access through Restrict Content Pro. ', 'tutor' ), ), 'tutor-weglot' => array( - 'name' => 'Weglot', + 'name' => __( 'Weglot', 'tutor' ), 'description' => __( 'Translate & manage multilingual courses for global reach.', 'tutor' ), ), 'tutor-wpml' => array( From 16b42013ab6f7ac485358816cc8334891ac8f56e Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Fri, 13 Dec 2024 16:03:19 +0600 Subject: [PATCH 017/194] Manual enrollment page responsive design fixed --- .../components/layout/Main.tsx | 19 ++++++++++++++++-- .../components/layout/Topbar.tsx | 20 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/assets/react/v3/entries/pro/manual-enrollment/components/layout/Main.tsx b/assets/react/v3/entries/pro/manual-enrollment/components/layout/Main.tsx index a2f9c8687d..1d4a19a281 100644 --- a/assets/react/v3/entries/pro/manual-enrollment/components/layout/Main.tsx +++ b/assets/react/v3/entries/pro/manual-enrollment/components/layout/Main.tsx @@ -1,5 +1,5 @@ import FormSelectInput from '@Components/fields/FormSelectInput'; -import { borderRadius, colorTokens, spacing } from '@Config/styles'; +import { borderRadius, Breakpoint, colorTokens, spacing } from '@Config/styles'; import FormSelectCourse from '@EnrollmentComponents/FormSelectCourse'; import FormSelectStudents from '@EnrollmentComponents/FormSelectStudents'; import type { Enrollment } from '@EnrollmentServices/enrollment'; @@ -114,9 +114,14 @@ export default Main; const styles = { wrapper: css` background-color: ${colorTokens.background.default}; + margin-left: ${spacing[20]}; + + ${Breakpoint.mobile} { + margin-left: ${spacing[12]}; + } `, container: css` - max-width: 1030px; + max-width: 1054px; margin: 0 auto; height: 100%; `, @@ -126,6 +131,11 @@ const styles = { display: grid; grid-template-columns: 255px 1fr; gap: ${spacing[24]}; + padding-inline: ${spacing[12]}; + + ${Breakpoint.mobile} { + display: block; + } `, left: css` display: flex; @@ -137,6 +147,11 @@ const styles = { border-left: 1px solid ${colorTokens.stroke.divider}; padding-left: ${spacing[24]}; padding-top: ${spacing[32]}; + + ${Breakpoint.mobile} { + border-left: none; + padding-left: 0; + } `, studentsWrapper: css` background-color: ${colorTokens.background.white}; diff --git a/assets/react/v3/entries/pro/manual-enrollment/components/layout/Topbar.tsx b/assets/react/v3/entries/pro/manual-enrollment/components/layout/Topbar.tsx index f0795edd80..a5d4fda2e1 100644 --- a/assets/react/v3/entries/pro/manual-enrollment/components/layout/Topbar.tsx +++ b/assets/react/v3/entries/pro/manual-enrollment/components/layout/Topbar.tsx @@ -1,7 +1,7 @@ import Button from '@Atoms/Button'; import SVGIcon from '@Atoms/SVGIcon'; import { tutorConfig } from '@Config/config'; -import { borderRadius, colorTokens, spacing } from '@Config/styles'; +import { Breakpoint, colorTokens, spacing } from '@Config/styles'; import { typography } from '@Config/typography'; import { type Enrollment, useCreateEnrollmentMutation } from '@EnrollmentServices/enrollment'; import { styleUtils } from '@Utils/style-utils'; @@ -70,9 +70,14 @@ const styles = { wrapper: css` height: ${TOPBAR_HEIGHT}px; background: ${colorTokens.background.white}; + + ${Breakpoint.smallMobile} { + padding-inline: ${spacing[8]}; + height: auto; + } `, container: css` - max-width: 1030px; + max-width: 1054px; margin: 0 auto; height: 100%; `, @@ -81,6 +86,13 @@ const styles = { align-items: center; justify-content: space-between; height: 100%; + padding-inline: ${spacing[12]}; + + ${Breakpoint.smallMobile} { + padding-block: ${spacing[12]}; + flex-direction: column; + gap: ${spacing[8]}; + } `, headerContent: css` display: flex; @@ -90,6 +102,10 @@ const styles = { left: css` display: flex; gap: ${spacing[16]}; + + ${Breakpoint.smallMobile} { + width: 100%; + } `, right: css` display: flex; From 92c906ee31e524430b205b13a99031b6e4c1d72b Mon Sep 17 00:00:00 2001 From: Fahim Faisal Date: Fri, 13 Dec 2024 16:57:09 +0600 Subject: [PATCH 018/194] Refactor FormDateInput to use parseISO for date handling and improve date validation --- .../components/fields/FormDateInput.tsx | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/assets/react/v3/shared/components/fields/FormDateInput.tsx b/assets/react/v3/shared/components/fields/FormDateInput.tsx index 990cd7e932..78e47213f2 100644 --- a/assets/react/v3/shared/components/fields/FormDateInput.tsx +++ b/assets/react/v3/shared/components/fields/FormDateInput.tsx @@ -1,14 +1,17 @@ +import { css } from '@emotion/react'; +import { format, isValid, parseISO } from 'date-fns'; +import { useRef, useState } from 'react'; +import { DayPicker } from 'react-day-picker'; + import Button from '@Atoms/Button'; import SVGIcon from '@Atoms/SVGIcon'; + import { DateFormats, isRTL } from '@Config/constants'; import { borderRadius, colorTokens, fontSize, shadow, spacing } from '@Config/styles'; import { Portal, usePortalPopover } from '@Hooks/usePortalPopover'; import type { FormControllerProps } from '@Utils/form'; import { styleUtils } from '@Utils/style-utils'; -import { css } from '@emotion/react'; -import { format, isValid } from 'date-fns'; -import { useRef, useState } from 'react'; -import { DayPicker } from 'react-day-picker'; + import 'react-day-picker/dist/style.css'; import FormFieldWrapper from './FormFieldWrapper'; @@ -42,7 +45,9 @@ const FormDateInput = ({ }: FormDateInputProps) => { const inputRef = useRef(null); const [isOpen, setIsOpen] = useState(false); - const fieldValue = isValid(new Date(field.value)) ? format(new Date(field.value), dateFormat) : ''; + const parsedISODate = parseISO(field.value); + const isValidDate = isValid(new Date(field.value)); + const fieldValue = isValidDate ? format(parsedISODate, dateFormat) : ''; const { triggerRef, position, popoverRef } = usePortalPopover({ isOpen, @@ -115,10 +120,10 @@ const FormDateInput = ({ { if (value) { const formattedDate = format(value, DateFormats.yearMonthDay); @@ -134,9 +139,9 @@ const FormDateInput = ({ showOutsideDays captionLayout="dropdown-buttons" initialFocus={true} - defaultMonth={isValid(new Date(field.value)) ? new Date(field.value) : new Date()} - fromMonth={disabledBefore ? new Date(disabledBefore) : new Date(new Date().getFullYear() - 10, 0)} - toMonth={disabledAfter ? new Date(disabledAfter) : new Date(new Date().getFullYear() + 10, 11)} + defaultMonth={isValidDate ? parsedISODate : new Date()} + fromMonth={disabledBefore ? parseISO(disabledBefore) : new Date(new Date().getFullYear() - 10, 0)} + toMonth={disabledAfter ? parseISO(disabledAfter) : new Date(new Date().getFullYear() + 10, 11)} />
From 7374d47c6cc734230cc3b81b7d811f4510839c69 Mon Sep 17 00:00:00 2001 From: "Md.Harun-Ur-Rashid" Date: Fri, 20 Dec 2024 12:20:38 +0600 Subject: [PATCH 019/194] refactor comment and callback --- tutor.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tutor.php b/tutor.php index 83d112219c..463997c532 100644 --- a/tutor.php +++ b/tutor.php @@ -22,7 +22,7 @@ require_once __DIR__ . '/vendor/autoload.php'; /** - * Defined the tutor main file + * Constants for tutor plugin. */ define( 'TUTOR_VERSION', '3.1.0' ); define( 'TUTOR_FILE', __FILE__ ); @@ -30,12 +30,7 @@ /** * Load tutor text domain for translation */ -add_action( - 'init', - function () { - load_plugin_textdomain( 'tutor', false, basename( dirname( __FILE__ ) ) . '/languages' ); - } -); +add_action( 'init', fn () => load_plugin_textdomain( 'tutor', false, basename( dirname( __FILE__ ) ) . '/languages' ) ); if ( ! function_exists( 'tutor' ) ) { /** @@ -129,7 +124,7 @@ function tutor_utils() { if ( ! function_exists( 'tutils' ) ) { /** - * Alis of tutor_utils() + * Alias of tutor_utils() * * @since 1.3.4 * @@ -182,4 +177,4 @@ function str_contains( string $haystack, string $needle ) { } } -$GLOBALS['tutor'] = tutor_lms(); \ No newline at end of file +$GLOBALS['tutor'] = tutor_lms(); From b7584ca496b4455b025b021191e408f72830e303 Mon Sep 17 00:00:00 2001 From: "Md.Harun-Ur-Rashid" Date: Fri, 20 Dec 2024 12:22:11 +0600 Subject: [PATCH 020/194] removed unused key name from tutor object to fix earlier call before translation ready --- tutor.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tutor.php b/tutor.php index 463997c532..3e334b64eb 100644 --- a/tutor.php +++ b/tutor.php @@ -71,7 +71,6 @@ function tutor() { 'bundle_post_type' => apply_filters( 'tutor_bundle_post_type', 'course-bundle' ), 'lesson_post_type' => apply_filters( 'tutor_lesson_post_type', 'lesson' ), 'instructor_role' => apply_filters( 'tutor_instructor_role', 'tutor_instructor' ), - 'instructor_role_name' => apply_filters( 'tutor_instructor_role_name', __( 'Tutor Instructor', 'tutor' ) ), 'template_path' => apply_filters( 'tutor_template_path', 'tutor/' ), 'has_pro' => apply_filters( 'tutor_has_pro', $has_pro ), // @since v2.0.6. From 6a1e1a7ee0c598f6fc0d440b1beaab2f88b32a8c Mon Sep 17 00:00:00 2001 From: "Md.Harun-Ur-Rashid" Date: Fri, 20 Dec 2024 12:27:31 +0600 Subject: [PATCH 021/194] removed str_contains helper which is build in wp core --- tutor.php | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/tutor.php b/tutor.php index 3e334b64eb..03a509c5b3 100644 --- a/tutor.php +++ b/tutor.php @@ -138,10 +138,7 @@ function tutils() { * Do some task during activation * * @since 1.5.2 - * - * @since 2.6.2 - * - * Uninstall hook registered + * @since 2.6.2 Uninstall hook registered */ register_activation_hook( TUTOR_FILE, array( '\TUTOR\Tutor', 'tutor_activate' ) ); register_deactivation_hook( TUTOR_FILE, array( '\TUTOR\Tutor', 'tutor_deactivation' ) ); @@ -160,20 +157,4 @@ function tutor_lms() { } } -if ( ! function_exists( 'str_contains' ) ) { - /** - * String helper for str contains - * - * @since 1.0.0 - * - * @param string $haystack haystack. - * @param string $needle needle. - * - * @return bool - */ - function str_contains( string $haystack, string $needle ) { - return empty( $needle ) || strpos( $haystack, $needle ) !== false; - } -} - $GLOBALS['tutor'] = tutor_lms(); From 87ca6efafd041a187a439874915e652bccec7975 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Fri, 20 Dec 2024 15:18:28 +0600 Subject: [PATCH 022/194] Update custom-button.js --- assets/react/gutenberg/custom-button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/react/gutenberg/custom-button.js b/assets/react/gutenberg/custom-button.js index 5d12a2749a..a14af47213 100644 --- a/assets/react/gutenberg/custom-button.js +++ b/assets/react/gutenberg/custom-button.js @@ -11,7 +11,7 @@ const buttonId = 'tutor-frontend-builder-trigger'; // prepare our custom link's html. const buttonHtml = ` - + ${__('Edit with Frontend Course Builder', 'tutor')} `; From b7d82328eca649b08255d32d3d29972fc9b00814 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Mon, 23 Dec 2024 10:14:58 +0600 Subject: [PATCH 023/194] Payments settings responsive improved --- .../payment-settings/components/PaymentItem.tsx | 14 +++++++++----- .../components/PaymentSettings.tsx | 10 +++++++--- .../payment-settings/fields/OptionWebhookUrl.tsx | 5 +++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/assets/react/v3/entries/payment-settings/components/PaymentItem.tsx b/assets/react/v3/entries/payment-settings/components/PaymentItem.tsx index 3f9cfe7a94..d0b44434f0 100644 --- a/assets/react/v3/entries/payment-settings/components/PaymentItem.tsx +++ b/assets/react/v3/entries/payment-settings/components/PaymentItem.tsx @@ -17,7 +17,7 @@ import FormTextareaInput from '@Components/fields/FormTextareaInput'; import { useModal } from '@Components/modals/Modal'; import StaticConfirmationModal from '@Components/modals/StaticConfirmationModal'; -import { borderRadius, colorTokens, fontWeight, lineHeight, shadow, spacing, zIndex } from '@Config/styles'; +import { borderRadius, Breakpoint, colorTokens, fontWeight, lineHeight, shadow, spacing, zIndex } from '@Config/styles'; import For from '@Controls/For'; import Show from '@Controls/Show'; import { animateLayoutChanges } from '@Utils/dndkit'; @@ -37,6 +37,7 @@ import { useInstallPaymentMutation, useRemovePaymentMutation, } from '../services/payment'; +import { CURRENT_VIEWPORT } from '@/v3/shared/config/constants'; interface PaymentItemProps { data: PaymentMethod; @@ -82,11 +83,11 @@ const PaymentItem = ({ data, paymentIndex, isOverlay = false }: PaymentItemProps .getValues(`payment_methods.${paymentIndex}.fields`) .some((field) => !['icon', 'webhook_url'].includes(field.name) && !field.value); - // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if (hasEmptyFields) { form.setValue(`payment_methods.${paymentIndex}.is_active`, false, { shouldDirty: true }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasEmptyFields]); return ( @@ -197,7 +198,7 @@ const PaymentItem = ({ data, paymentIndex, isOverlay = false }: PaymentItemProps ? convertToOptions(field.options as Record) : (field.options ?? []) } - isInlineLabel + isInlineLabel={CURRENT_VIEWPORT.isAboveSmallMobile} /> ); @@ -208,7 +209,7 @@ const PaymentItem = ({ data, paymentIndex, isOverlay = false }: PaymentItemProps type="password" isPassword label={field.label} - isInlineLabel + isInlineLabel={CURRENT_VIEWPORT.isAboveSmallMobile} /> ); @@ -250,7 +251,7 @@ const PaymentItem = ({ data, paymentIndex, isOverlay = false }: PaymentItemProps { if (data.is_manual) { form.setValue(`payment_methods.${paymentIndex}.label`, String(value)); @@ -381,6 +382,9 @@ const styles = { input[type='text'], input[type='password'] { min-width: 350px; + ${Breakpoint.mobile} { + min-width: 250px; + } } `, dragButton: ({ isOverlay }: { isOverlay: boolean }) => css` diff --git a/assets/react/v3/entries/payment-settings/components/PaymentSettings.tsx b/assets/react/v3/entries/payment-settings/components/PaymentSettings.tsx index 05e00b7493..e0f6a39941 100644 --- a/assets/react/v3/entries/payment-settings/components/PaymentSettings.tsx +++ b/assets/react/v3/entries/payment-settings/components/PaymentSettings.tsx @@ -10,7 +10,7 @@ import { useModal } from '@Components/modals/Modal'; import StaticConfirmationModal from '@Components/modals/StaticConfirmationModal'; import { tutorConfig } from '@Config/config'; -import { colorTokens, fontSize, spacing, zIndex } from '@Config/styles'; +import { Breakpoint, colorTokens, fontSize, spacing, zIndex } from '@Config/styles'; import { typography } from '@Config/typography'; import Show from '@Controls/Show'; import { useFormWithGlobalError } from '@Hooks/useFormWithGlobalError'; @@ -35,15 +35,14 @@ const TaxSettingsPage = () => { const { reset } = form; const formData = form.watch(); - // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if (form.formState.isDirty) { document.getElementById('save_tutor_option')?.removeAttribute('disabled'); form.reset(form.getValues(), { keepValues: true }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [form.formState.isDirty]); - // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if (payment_settings) { const methods = convertPaymentMethods(payment_settings.payment_methods, payment_gateways); @@ -53,6 +52,7 @@ const TaxSettingsPage = () => { payment_methods: methods, }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [reset, payment_settings]); return ( @@ -199,6 +199,10 @@ const styles = { buttonWrapper: css` display: flex; gap: ${spacing[16]}; + + ${Breakpoint.smallMobile} { + flex-direction: column; + } `, noPaymentMethod: css` ${typography.caption()}; diff --git a/assets/react/v3/entries/payment-settings/fields/OptionWebhookUrl.tsx b/assets/react/v3/entries/payment-settings/fields/OptionWebhookUrl.tsx index 448eb92d78..1f90c57d88 100644 --- a/assets/react/v3/entries/payment-settings/fields/OptionWebhookUrl.tsx +++ b/assets/react/v3/entries/payment-settings/fields/OptionWebhookUrl.tsx @@ -1,4 +1,5 @@ -import { copyToClipboard } from '@/v3/shared/utils/util'; +import { CURRENT_VIEWPORT } from '@Config/constants'; +import { copyToClipboard } from '@Utils/util'; import Button from '@Atoms/Button'; import SVGIcon from '@Atoms/SVGIcon'; import { useToast } from '@Atoms/Toast'; @@ -53,7 +54,7 @@ const OptionWebhookUrl = ({ loading={loading} placeholder={placeholder} helpText={helpText} - isInlineLabel + isInlineLabel={CURRENT_VIEWPORT.isAboveSmallMobile} > {() => { return ( From 268c42e617a276a62dc475793736e9184974e249 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Mon, 23 Dec 2024 11:50:47 +0600 Subject: [PATCH 024/194] Old course builders php files removed --- classes/Course.php | 23 -- classes/TutorEDD.php | 23 -- classes/WooCommerce.php | 23 -- views/metabox/course/field-types/checkbox.php | 33 -- views/metabox/course/field-types/color.php | 16 - .../course/field-types/group_fields.php | 30 -- .../course/field-types/groups/checkbox.php | 15 - .../course/field-types/groups/number.php | 17 -- .../course/field-types/groups/select.php | 25 -- .../course/field-types/groups/text.php | 19 -- views/metabox/course/field-types/media.php | 32 -- views/metabox/course/field-types/number.php | 16 - views/metabox/course/field-types/radio.php | 23 -- views/metabox/course/field-types/select.php | 25 -- views/metabox/course/field-types/slider.php | 16 - views/metabox/course/field-types/text.php | 16 - views/metabox/course/field-types/textarea.php | 13 - views/metabox/course/settings-tabs.php | 109 ------- views/metabox/lesson-metabox.php | 41 --- views/modal/edit-lesson.php | 113 ------- views/modal/edit_quiz.php | 280 ----------------- views/modal/question_answer_form.php | 289 ------------------ views/modal/question_answer_list.php | 105 ------- views/modal/question_form.php | 178 ----------- views/modal/topic-form.php | 60 ---- 25 files changed, 1540 deletions(-) delete mode 100644 views/metabox/course/field-types/checkbox.php delete mode 100644 views/metabox/course/field-types/color.php delete mode 100644 views/metabox/course/field-types/group_fields.php delete mode 100644 views/metabox/course/field-types/groups/checkbox.php delete mode 100644 views/metabox/course/field-types/groups/number.php delete mode 100644 views/metabox/course/field-types/groups/select.php delete mode 100644 views/metabox/course/field-types/groups/text.php delete mode 100644 views/metabox/course/field-types/media.php delete mode 100644 views/metabox/course/field-types/number.php delete mode 100644 views/metabox/course/field-types/radio.php delete mode 100644 views/metabox/course/field-types/select.php delete mode 100644 views/metabox/course/field-types/slider.php delete mode 100644 views/metabox/course/field-types/text.php delete mode 100644 views/metabox/course/field-types/textarea.php delete mode 100644 views/metabox/course/settings-tabs.php delete mode 100644 views/metabox/lesson-metabox.php delete mode 100644 views/modal/edit-lesson.php delete mode 100644 views/modal/edit_quiz.php delete mode 100644 views/modal/question_answer_form.php delete mode 100644 views/modal/question_answer_list.php delete mode 100644 views/modal/question_form.php delete mode 100644 views/modal/topic-form.php diff --git a/classes/Course.php b/classes/Course.php index 86d927b476..868c81ed58 100644 --- a/classes/Course.php +++ b/classes/Course.php @@ -1564,29 +1564,6 @@ public function restrict_media( $where ) { return $where; } - /** - * Course meta box (Topics) - * - * @since 1.0.0 - * @param boolean $echo display or not. - * @return string - */ - public function course_meta_box( $echo = true ) { - $file_path = tutor()->path . 'views/metabox/course-topics.php'; - - if ( $echo ) { - /** - * Use echo raise WPCS security issue - * Helper wp_kses_post break content. - */ - include $file_path; - } else { - ob_start(); - include $file_path; - return ob_get_clean(); - } - } - /** * Save course content order * diff --git a/classes/TutorEDD.php b/classes/TutorEDD.php index 438e8a9cef..2a0bc8ef11 100644 --- a/classes/TutorEDD.php +++ b/classes/TutorEDD.php @@ -36,7 +36,6 @@ public function __construct() { return; } - add_action( 'add_meta_boxes', array( $this, 'register_meta_box' ) ); add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ) ); /** @@ -96,28 +95,6 @@ public function tutor_monetization_options( $arr ) { return $arr; } - /** - * Register meta box - * - * @since 1.0.0 - * - * @return void - */ - public function register_meta_box() { - tutor_meta_box_wrapper( 'tutor-attached-edd-product', __( 'Add Product', 'tutor' ), array( $this, 'course_add_product_metabox' ), $this->course_post_type, 'advanced', 'high', 'tutor-admin-post-meta' ); - } - - /** - * MetaBox for Lesson Modal Edit Mode - * - * @since 1.0.0 - * - * @return void - */ - public function course_add_product_metabox() { - include tutor()->path . 'views/metabox/course-add-edd-product-metabox.php'; - } - /** * Save course meta * diff --git a/classes/WooCommerce.php b/classes/WooCommerce.php index 18224e9f09..1b3f0afbd0 100644 --- a/classes/WooCommerce.php +++ b/classes/WooCommerce.php @@ -50,7 +50,6 @@ public function __construct() { add_filter( 'product_type_options', array( $this, 'add_tutor_type_in_wc_product' ) ); - add_action( 'add_meta_boxes', array( $this, 'register_meta_box' ) ); add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ), 10, 2 ); add_action( 'save_post_product', array( $this, 'save_wc_product_meta' ) ); @@ -350,28 +349,6 @@ public function save_course_meta( $post_ID, $post ) { do_action( 'save_tutor_course', $post_ID, $post ); } - /** - * Register meta box - * - * @since 1.0.0 - * - * @return void - */ - public function register_meta_box() { - tutor_meta_box_wrapper( 'tutor-attach-product', __( 'Add Product', 'tutor' ), array( $this, 'course_add_product_metabox' ), $this->course_post_type, 'advanced', 'high', 'tutor-admin-post-meta' ); - } - - /** - * Meta box view - * - * @since 1.0.0 - * - * @return void - */ - public function course_add_product_metabox() { - include tutor()->path . 'views/metabox/course-add-product-metabox.php'; - } - /** * Save WC product meta * diff --git a/views/metabox/course/field-types/checkbox.php b/views/metabox/course/field-types/checkbox.php deleted file mode 100644 index 230935b868..0000000000 --- a/views/metabox/course/field-types/checkbox.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -if ( empty( $field['options'] ) ) { - $default = isset( $field['default'] ) ? $field['default'] : ''; - $option_value = $this->get( $field['field_key'], $default ); - $label_title = isset( $field['label_title'] ) ? $field['label_title'] : $field['label']; - ?> - - $field_option ) { - ?> - -
- diff --git a/views/metabox/course/field-types/color.php b/views/metabox/course/field-types/color.php deleted file mode 100644 index f9fa81dc43..0000000000 --- a/views/metabox/course/field-types/color.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -$value = $this->get( $field['field_key'] ); -if ( ! $value && isset( $field['default'] ) ) { - $value = $field['default']; -} -?> - diff --git a/views/metabox/course/field-types/group_fields.php b/views/metabox/course/field-types/group_fields.php deleted file mode 100644 index b8119a285e..0000000000 --- a/views/metabox/course/field-types/group_fields.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -if ( ! isset( $field['group_fields'] ) || ! is_array( $field['group_fields'] ) || ! count( $field['group_fields'] ) ) { - return; -} -?> -
- $group_field ) { - $input_name = "_tutor_course_settings[{$field['field_key']}][{$groupFieldKey}]"; - $default_value = isset( $group_field['default'] ) ? $group_field['default'] : false; - $input_value = $this->get( $field['field_key'] . '.' . $groupFieldKey, $default_value ); - $label = tutor_utils()->avalue_dot( 'label', $group_field ); - ?> -
- path . 'views/options/field-types/groups/' . esc_attr( $group_field['type'] ) . '.php'; ?> -
- -
- diff --git a/views/metabox/course/field-types/groups/checkbox.php b/views/metabox/course/field-types/groups/checkbox.php deleted file mode 100644 index 04a7222590..0000000000 --- a/views/metabox/course/field-types/groups/checkbox.php +++ /dev/null @@ -1,15 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> - diff --git a/views/metabox/course/field-types/groups/number.php b/views/metabox/course/field-types/groups/number.php deleted file mode 100644 index ac3727f9ca..0000000000 --- a/views/metabox/course/field-types/groups/number.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> - -' . esc_html( $label ) . '

'; -} -?> diff --git a/views/metabox/course/field-types/groups/select.php b/views/metabox/course/field-types/groups/select.php deleted file mode 100644 index ca4def91a3..0000000000 --- a/views/metabox/course/field-types/groups/select.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> - diff --git a/views/metabox/course/field-types/groups/text.php b/views/metabox/course/field-types/groups/text.php deleted file mode 100644 index 7621e5926a..0000000000 --- a/views/metabox/course/field-types/groups/text.php +++ /dev/null @@ -1,19 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> - - -

- diff --git a/views/metabox/course/field-types/media.php b/views/metabox/course/field-types/media.php deleted file mode 100644 index 8e981106de..0000000000 --- a/views/metabox/course/field-types/media.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -$value = (int) $this->get( $field['field_key'] ); -?> - - -
-
- - - -
- - - -
- diff --git a/views/metabox/course/field-types/number.php b/views/metabox/course/field-types/number.php deleted file mode 100644 index 513a56d2a3..0000000000 --- a/views/metabox/course/field-types/number.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -$value = $this->get( $field['field_key'] ); -if ( ! $value && isset( $field['default'] ) ) { - $value = $field['default']; -} -?> - diff --git a/views/metabox/course/field-types/radio.php b/views/metabox/course/field-types/radio.php deleted file mode 100644 index d0849d88a6..0000000000 --- a/views/metabox/course/field-types/radio.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -if ( ! empty( $field['options'] ) ) { - foreach ( $field['options'] as $optionKey => $option ) { - $option_value = $this->get( $field['field_key'], tutils()->array_get( 'default', $field ) ); - ?> -

- -

- diff --git a/views/metabox/course/field-types/select.php b/views/metabox/course/field-types/select.php deleted file mode 100644 index e9327479fc..0000000000 --- a/views/metabox/course/field-types/select.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> - diff --git a/views/metabox/course/field-types/slider.php b/views/metabox/course/field-types/slider.php deleted file mode 100644 index 6aa3db4301..0000000000 --- a/views/metabox/course/field-types/slider.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> -
-

get( $field['field_key'], $field['default'] ) ); ?>

-
- -
diff --git a/views/metabox/course/field-types/text.php b/views/metabox/course/field-types/text.php deleted file mode 100644 index 75ddbf5324..0000000000 --- a/views/metabox/course/field-types/text.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -$value = $this->get( $field['field_key'] ); -if ( ! $value && isset( $field['default'] ) ) { - $value = $field['default']; -} -?> - diff --git a/views/metabox/course/field-types/textarea.php b/views/metabox/course/field-types/textarea.php deleted file mode 100644 index 27eba40265..0000000000 --- a/views/metabox/course/field-types/textarea.php +++ /dev/null @@ -1,13 +0,0 @@ - - * @link https://themeum.com - * @since 2.0.0 - */ - -?> - - diff --git a/views/metabox/course/settings-tabs.php b/views/metabox/course/settings-tabs.php deleted file mode 100644 index 092892c832..0000000000 --- a/views/metabox/course/settings-tabs.php +++ /dev/null @@ -1,109 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -$args = $this->args; -$current_tab = tutils()->array_get( 'settings_tab', tutor_sanitize_data( $_GET ) ); - -?> - -
- - is_gutenberg_enable ) { - ?> -
-

-
- - -
-
-
    - $arg ) { - $i++; - - if ( $current_tab ) { - $active = $current_tab === $key ? 'active' : ''; - } else { - $active = 1 === $i ? 'active' : ''; - } - - $label = tutils()->array_get( 'label', $arg ); - $icon_class = tutils()->array_get( 'icon_class', $arg ); - $url = add_query_arg( array( 'settings_tab' => $key ) ); - - $icon = ''; - if ( $icon_class ) { - $icon = ''; - } - - echo '
  • - ' . - wp_kses( $icon, tutor_utils()->allowed_icon_tags() ) . ' ' . esc_html( $label ) . - ' -
  • '; - } - ?> -
-
- -
- $tab ) { - $i++; - - $label = tutils()->array_get( 'label', $tab ); - $callback = tutils()->array_get( 'callback', $tab ); - $fields = tutils()->array_get( 'fields', $tab ); - - if ( $current_tab ) { - $active = $current_tab === $key ? 'active' : ''; - $display = $current_tab === $key ? 'block' : 'none'; - } else { - $active = 1 === $i ? 'active' : ''; - $display = 1 === $i ? 'block' : 'none'; - } - - echo '
'; - - do_action( 'tutor_course/settings_tab_content/before', $key, $tab ); - do_action( 'tutor_course/settings_tab_content/before/' . esc_attr( $key ) . '', $tab ); - - if ( tutils()->count( $fields ) ) { - $this->generate_field( $fields ); - } - - /** - * Handling Callback - */ - if ( $callback && is_callable( $callback ) ) { - call_user_func( $callback, $key, $tab ); - } - - do_action( 'tutor_course/settings_tab_content/after', $key, $tab ); - do_action( 'tutor_course/settings_tab_content/after/' . esc_attr( $key ) . '', $tab ); - - echo '
'; - } - ?> -
- -
- - -
diff --git a/views/metabox/lesson-metabox.php b/views/metabox/lesson-metabox.php deleted file mode 100644 index b18b643109..0000000000 --- a/views/metabox/lesson-metabox.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -?> -
-
- -
-
- get_courses_for_instructors(); - ?> - - - -

- -

-
-
diff --git a/views/modal/edit-lesson.php b/views/modal/edit-lesson.php deleted file mode 100644 index 26750b784e..0000000000 --- a/views/modal/edit-lesson.php +++ /dev/null @@ -1,113 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -use TUTOR\Input; - -?> -
- nonce_action, tutor()->nonce ); ?> - - - - - - -
- - - -
- -
- - - post_content ) ) ); - wp_editor( $sanitized_content, 'tutor_lesson_modal_editor', array( 'editor_height' => 150 ) ); - ?> - - -
- -
- - ID ) ) { - $lesson_thumbnail_id = get_post_meta( $post->ID, '_thumbnail_id', true ); - } - - tutor_load_template_from_custom_path( - tutor()->path . '/views/fragments/thumbnail-uploader.php', - array( - 'media_id' => $lesson_thumbnail_id, - 'input_name' => '_lesson_thumbnail_id', - ), - false - ); - ?> -
- - path . 'views/metabox/video-metabox.php'; - do_action( 'tutor_lesson_edit_modal_after_video' ); - - require tutor()->path . 'views/metabox/lesson-attachments-metabox.php'; - do_action( 'tutor_lesson_edit_modal_after_attachment' ); - - do_action( 'tutor_lesson_edit_modal_form_after', $post ); - ?> -
- diff --git a/views/modal/edit_quiz.php b/views/modal/edit_quiz.php deleted file mode 100644 index 008464b958..0000000000 --- a/views/modal/edit_quiz.php +++ /dev/null @@ -1,280 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -use TUTOR\Quiz; - -?> - -
-
- -
- -
-
-
- -
- -
-
- -
- -
-
- 0 ) ? tutor_utils()->get_questions_by_quiz( $quiz_id ) : array(); - - if ( $questions ) { - foreach ( $questions as $question ) { - $id_target = 'quiz-popup-menu-' . $question->question_id; - ?> -
-
- -
- question_title ); ?> -
-
-
-
- get_question_types( $question->question_type ); - echo wp_kses( stripslashes( $type['icon'] ), tutor_utils()->allowed_icon_tags() ) . ' ' . esc_html( $type['name'] ); - ?> -
-
- - -
-
-
- -
- - - - -
- -
-
- -
-
- -
-
- get_quiz_option( $quiz_id, 'time_limit.time_type', 'minutes' ); ?> - -
-
- -
-
- -
- -
- -
-
- () -
- - - -
-
- -
- - get_option( 'quiz_attempts_allowed' ); - $attempts_allowed = (int) tutor_utils()->get_quiz_option( $quiz_id, 'attempts_allowed', $default_attempts_allowed ); - ?> -
-

-
- -
- -
- - - -
- - - -
- -
- - - -
- - - -
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- - -
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
- -
-
- -
- -
- - - -
-
-
-
- - -
- diff --git a/views/modal/question_answer_form.php b/views/modal/question_answer_form.php deleted file mode 100644 index 90cde3f5e5..0000000000 --- a/views/modal/question_answer_form.php +++ /dev/null @@ -1,289 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -if ( 'open_ended' === $question_type || 'short_answer' === $question_type ) { - echo '

' . - esc_html__( 'No option is necessary for this answer type', 'tutor' ) . - '

'; - return ''; -} - -empty( $old_answer ) ? $old_answer = (object) array() : 0; -$answer_title = ! empty( $old_answer->answer_title ) ? stripslashes( $old_answer->answer_title ) : ''; -$image_id = ! empty( $old_answer->image_id ) ? $old_answer->image_id : ''; -$answer_view_format = ! empty( $old_answer->answer_view_format ) ? $old_answer->answer_view_format : 'text'; -$answer_two_gap_match = ! empty( $old_answer->answer_two_gap_match ) ? stripslashes( $old_answer->answer_two_gap_match ) : ''; -?> - -
- - - -
- -
-
- - -
-
-
- - -
- -
- -
-
- -
- - - path . '/views/fragments/thumbnail-uploader.php', - array( - 'media_id' => $image_id, - 'input_name' => 'quiz_answer[' . $question_id . '][image_id]', - ), - false - ); - ?> -
- -
-
- -
-
-
- /> - -
-
-
-
- /> - -
-
-
-
- /> - -
-
-
- - -
- -
- - -
-
- -
- -
- - -
-
- - -
-

-
-
- -
-
-
- -
-

-
-
- -
-
-

-
- - -
- -
- -
-
- -
- -
- -
-
- -
- - - path . '/views/fragments/thumbnail-uploader.php', - array( - 'media_id' => $image_id, - 'input_name' => 'quiz_answer[' . $question_id . '][image_id]', - ), - false - ); - ?> -
- -
-
- -
-
-
- /> - -
-
-
-
- /> - -
-
-
-
- /> - -
-
-
- -
- - - path . '/views/fragments/thumbnail-uploader.php', - array( - 'media_id' => $image_id, - 'input_name' => 'quiz_answer[' . $question_id . '][image_id]', - ), - false - ); - ?> -
-
- -
- -
-
- - -
- - - path . '/views/fragments/thumbnail-uploader.php', - array( - 'media_id' => $image_id, - 'input_name' => 'quiz_answer[' . $question_id . '][image_id]', - ), - false - ); - ?> -
- -
- -
- - -
-
- - - -
diff --git a/views/modal/question_answer_list.php b/views/modal/question_answer_list.php deleted file mode 100644 index 4e2aea205c..0000000000 --- a/views/modal/question_answer_list.php +++ /dev/null @@ -1,105 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -use TUTOR\Question_Answers_List; - -if ( 'open_ended' === $question_type || 'short_answer' === $question_type ) { - echo '

' . - esc_html__( 'No option is necessary for this answer type', 'tutor' ) . - '

'; - return ''; -} -?> - -
- -
-
- - answer_title ) ); - if ( 'fill_in_the_blank' === $answer->belongs_question_type ) { - ?> - - - answer_two_gap_match ) ); ?> - - belongs_question_type ) { - echo ' - ' . esc_html( stripslashes( $answer->answer_two_gap_match ) ); - } - ?> - - - image_id ) { - echo ' - '; - } - - if ( 'true_false' === $question_type || 'single_choice' === $question_type ) { - ?> - - is_correct ); ?> > - - - - is_correct ); ?> > - - - - - - - - - - - - - - - - -
- - -
- - - -
- -
- -
- - - - - - - diff --git a/views/modal/question_form.php b/views/modal/question_form.php deleted file mode 100644 index f34c568ab0..0000000000 --- a/views/modal/question_form.php +++ /dev/null @@ -1,178 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -global $wpdb; -$settings = maybe_unserialize( $question->question_settings ); -?> - -
- - - - - -
- -
- -
-
- -
- -
-
-
-
-
- get_question_types(); - $current_type = $question->question_type ? $question->question_type : 'true_false'; - ?> - -
- - array( - 'class' => array(), - ), - 'i' => array( - 'class' => array(), - ), - ); - - //phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped - echo tutor_utils()->clean_html_content( - $question_types[ $current_type ]['icon'], - $allowed_icon_html_tags - ); - echo esc_html( $question_types[ $current_type ]['name'] ); - ?> - - - - - -
- - -
-
-
-
-
- -
-
- -
-
-
-
-
-
-
- -
- -
-
-
- -
-
- -
-
-
-
- -
- -
- has_pro ) { - do_action( 'tutor_quiz_question_desc_field', $question ); - } else { - $field_name = "tutor_quiz_question[{$question_id}][question_description]"; - ?> - - -
-
- -
- -
- -
- question_type; - $question_id = $question_id; - - require __DIR__ . '/question_answer_list.php'; - ?> -
- - -
diff --git a/views/modal/topic-form.php b/views/modal/topic-form.php deleted file mode 100644 index 7116545190..0000000000 --- a/views/modal/topic-form.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @link https://themeum.com - * @since 1.0.0 - */ - -?> -
-
-
-
-
-
- -
- - -
- -
-
- - - -
- -
- - - - - -
-
- - -
-
-
From 48bdf9f8bbe91062ba424ed6b15d683ef6675c88 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 24 Dec 2024 10:23:27 +0600 Subject: [PATCH 025/194] New settings added for instructors --- assets/react/v3/@types/index.d.ts | 2 ++ .../course-basic/CourseBasicSidebar.tsx | 12 +++---- classes/Admin.php | 5 +-- classes/Course.php | 4 ++- classes/Options_V2.php | 31 +++++++++++-------- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/assets/react/v3/@types/index.d.ts b/assets/react/v3/@types/index.d.ts index bff9989fed..d137da0794 100644 --- a/assets/react/v3/@types/index.d.ts +++ b/assets/react/v3/@types/index.d.ts @@ -102,6 +102,8 @@ declare global { enrollment_expiry_enabled: 'on' | 'off'; enable_q_and_a_on_course: 'on' | 'off'; instructor_can_delete_course: 'on' | 'off'; + instructor_can_change_author: 'on' | 'off'; + instructor_can_modify_instructors: 'on' | 'off'; chatgpt_enable: 'on' | 'off'; course_builder_logo_url: string | false; chatgpt_key_exist: boolean; diff --git a/assets/react/v3/entries/course-builder/components/course-basic/CourseBasicSidebar.tsx b/assets/react/v3/entries/course-builder/components/course-basic/CourseBasicSidebar.tsx index a2855814fa..10f3a6d070 100644 --- a/assets/react/v3/entries/course-builder/components/course-basic/CourseBasicSidebar.tsx +++ b/assets/react/v3/entries/course-builder/components/course-basic/CourseBasicSidebar.tsx @@ -44,20 +44,20 @@ const CourseBasicSidebar = () => { const isMultiInstructorEnabled = isAddonEnabled(Addons.TUTOR_MULTI_INSTRUCTORS); const isTutorPro = !!tutorConfig.tutor_pro_url; const isOpenAiEnabled = tutorConfig.settings?.chatgpt_enable === 'on'; + const canInstructorChangeAuthor = tutorConfig.settings?.instructor_can_change_author !== 'off'; + const canInstructorModifyInstructors = tutorConfig.settings?.instructor_can_modify_instructors !== 'off'; + const currentUserIsAuthor = String(currentUser.data.id) === String(courseDetails?.post_author.ID || ''); const isAdministrator = currentUser.roles.includes(TutorRoles.ADMINISTRATOR); const isInstructor = (courseDetails?.course_instructors || []).find( (instructor) => String(instructor.id) === String(currentUser.data.id), ); const currentAuthor = form.watch('post_author'); + const isInstructorVisible = - isTutorPro && - isMultiInstructorEnabled && - tutorConfig.settings?.enable_course_marketplace === 'on' && - (isAdministrator || String(currentUser.data.id) === String(courseDetails?.post_author.ID || '') || isInstructor); + isTutorPro && isMultiInstructorEnabled && (isAdministrator || (isInstructor && canInstructorModifyInstructors)); - const isAuthorEditable = - isAdministrator || String(currentUser.data.id) === String(courseDetails?.post_author.ID || ''); + const isAuthorEditable = isAdministrator || (currentUserIsAuthor && canInstructorChangeAuthor); const visibilityStatus = useWatch({ control: form.control, diff --git a/classes/Admin.php b/classes/Admin.php index dd6093d8c5..915b1366bf 100644 --- a/classes/Admin.php +++ b/classes/Admin.php @@ -152,6 +152,7 @@ public function register_menu() { if ( $enable_course_marketplace ) { add_submenu_page( 'tutor', __( 'Instructors', 'tutor' ), __( 'Instructors', 'tutor' ), 'manage_tutor', Instructors_List::INSTRUCTOR_LIST_PAGE, array( $this, 'tutor_instructors' ) ); + add_submenu_page( 'tutor', __( 'Withdraw Requests', 'tutor' ), __( 'Withdraw Requests', 'tutor' ), 'manage_tutor', Withdraw_Requests_List::WITHDRAW_REQUEST_LIST_PAGE, array( $this, 'withdraw_requests' ) ); } add_submenu_page( 'tutor', __( 'Announcements', 'tutor' ), __( 'Announcements', 'tutor' ), 'manage_tutor_instructor', 'tutor_announcements', array( $this, 'tutor_announcements' ) ); @@ -160,10 +161,6 @@ public function register_menu() { add_submenu_page( 'tutor', __( 'Quiz Attempts', 'tutor' ), __( 'Quiz Attempts', 'tutor' ), 'manage_tutor_instructor', Quiz_Attempts_List::QUIZ_ATTEMPT_PAGE, array( $this, 'quiz_attempts' ) ); - if ( $enable_course_marketplace ) { - add_submenu_page( 'tutor', __( 'Withdraw Requests', 'tutor' ), __( 'Withdraw Requests', 'tutor' ), 'manage_tutor', Withdraw_Requests_List::WITHDRAW_REQUEST_LIST_PAGE, array( $this, 'withdraw_requests' ) ); - } - add_submenu_page( 'tutor', __( 'Addons', 'tutor' ), __( 'Addons', 'tutor' ), 'manage_tutor', 'tutor-addons', array( $this, 'enable_disable_addons' ) ); do_action( 'tutor_admin_register' ); diff --git a/classes/Course.php b/classes/Course.php index 86d927b476..ea21286058 100644 --- a/classes/Course.php +++ b/classes/Course.php @@ -1017,7 +1017,7 @@ public function ajax_update_course() { } $this->json_response( - __( 'Course update successfully', 'tutor' ), + __( 'Course updated successfully.', 'tutor' ), $update_id, HttpHelper::STATUS_OK ); @@ -1308,6 +1308,8 @@ public function enqueue_course_builder_assets() { 'hide_admin_bar_for_users', 'enable_redirect_on_course_publish_from_frontend', 'instructor_can_publish_course', + 'instructor_can_change_author', + 'instructor_can_modify_instructors', ); $full_settings = get_option( 'tutor_option', array() ); diff --git a/classes/Options_V2.php b/classes/Options_V2.php index 54ac88cbe5..c7b2f0ab84 100644 --- a/classes/Options_V2.php +++ b/classes/Options_V2.php @@ -10,9 +10,6 @@ namespace Tutor; -use Tutor\Ecommerce\CartController; -use Tutor\Ecommerce\CheckoutController; -use Tutor\Ecommerce\Ecommerce; use Tutor\Ecommerce\OptionKeys; use TUTOR\Input; @@ -717,7 +714,7 @@ public function get_setting_fields() { 'label' => __( 'Enable Marketplace', 'tutor' ), 'label_title' => '', 'default' => 'off', - 'desc' => __( 'Allow multiple instructors to upload their courses.', 'tutor' ), + 'desc' => __( 'Allow multiple instructors to sell their courses.', 'tutor' ), ), array( 'key' => 'pagination_per_page', @@ -734,6 +731,14 @@ public function get_setting_fields() { 'slug' => 'instructor', 'block_type' => 'uniform', 'fields' => array( + array( + 'key' => 'enable_become_instructor_btn', + 'type' => 'toggle_switch', + 'label' => __( 'Become an Instructor Button', 'tutor' ), + 'label_title' => '', + 'default' => 'off', + 'desc' => __( 'Enable the option to display this button on the student dashboard.', 'tutor' ), + ), array( 'key' => 'instructor_can_publish_course', 'type' => 'toggle_switch', @@ -751,12 +756,12 @@ public function get_setting_fields() { 'desc' => __( 'Enable this setting to allow instructors to delete courses.', 'tutor' ), ), array( - 'key' => 'enable_become_instructor_btn', + 'key' => 'instructor_can_change_author', 'type' => 'toggle_switch', - 'label' => __( 'Become an Instructor Button', 'tutor' ), + 'label' => __( 'Allow Instructors to Change Author', 'tutor' ), 'label_title' => '', - 'default' => 'off', - 'desc' => __( 'Enable the option to display this button on the student dashboard.', 'tutor' ), + 'default' => 'on', + 'desc' => __( 'Enable this setting to let instructors change the course author.', 'tutor' ), ), ), ), @@ -1712,11 +1717,11 @@ public function get_setting_fields() { 'searchable' => true, ), array( - 'key' => 'lesson_video_duration_youtube_api_key', - 'type' => 'text', - 'label' => __( 'YouTube API Key', 'tutor' ), - 'default' => '', - 'desc' => __( + 'key' => 'lesson_video_duration_youtube_api_key', + 'type' => 'text', + 'label' => __( 'YouTube API Key', 'tutor' ), + 'default' => '', + 'desc' => __( 'To host live videos on your platform using YouTube, enter your YouTube API key.', 'tutor' ), From fc361dfdea75decd2178b7de9555e49bc2683428 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 24 Dec 2024 11:27:02 +0600 Subject: [PATCH 026/194] Make wp editor link modal translatable --- views/modal/wp-editor-link.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/views/modal/wp-editor-link.php b/views/modal/wp-editor-link.php index b97303ec6f..5c2ded84f7 100644 --- a/views/modal/wp-editor-link.php +++ b/views/modal/wp-editor-link.php @@ -13,31 +13,31 @@