diff --git a/assets/react/admin-dashboard/template-import.js b/assets/react/admin-dashboard/template-import.js new file mode 100644 index 0000000000..71784ec9cd --- /dev/null +++ b/assets/react/admin-dashboard/template-import.js @@ -0,0 +1,83 @@ +document.addEventListener('DOMContentLoaded', function () { + // template preview variables + const templatesDemoImportRoot = document.querySelector(".tutor-templates-demo-import"); + const previewButtons = document.querySelectorAll(".open-template-live-preview"); + const livePreviewModal = document.querySelector(".template-live-preview-modal"); + const iframeWrapper = document.querySelector(".template-preview-iframe-wrapper"); + const iframe = document.getElementById("template-preview-iframe"); + const livePreviewCloseModal = document.querySelector(".live-preview-close-modal"); + const deviceSwitchers = document.querySelectorAll(".template-preview-device-switcher li"); + const previewTemplateName = document.querySelector(".preview-modal-template-name"); + const loadingIndicator = document.querySelector(".loading-indicator"); // Add this line + + if (templatesDemoImportRoot) { + // Open live preview modal + templatesDemoImportRoot.addEventListener('click', (event) => { + if (event.target && event.target.matches('.open-template-live-preview')) { + loadingIndicator.style.display = "block"; + let previewBtn = event.target; + const url = previewBtn.getAttribute("data-url"); + const singleTemplate = previewBtn.closest(".tutorowl-single-template"); + const templateName = singleTemplate.querySelector('.tutorowl-template-name span'); + previewTemplateName.innerText = templateName.innerText; + livePreviewModal.style.display = "flex"; + iframe.src = url; + } + }); + + // Hide loading indicator when iframe is fully loaded + iframe.addEventListener('load', function () { + loadingIndicator.style.display = "none"; + }); + + // Close live preview modal + livePreviewCloseModal?.addEventListener("click", function () { + resetPreviewModal(); + }); + + // Close modal when clicking outside content + livePreviewModal?.addEventListener("click", function (event) { + if (event.target === livePreviewModal) { + resetPreviewModal(); + } + }); + + // Device switcher + deviceSwitchers.forEach((deviceSwitcher) => { + deviceSwitcher.addEventListener("click", function () { + removeActiveClassFromDeviceList(deviceSwitchers); + deviceSwitcher.classList.add("active"); + let width = this.getAttribute("data-width"); + let height = this.getAttribute("data-height"); + iframeWrapper.style.width = width; + iframeWrapper.style.height = height; + loadingIndicator.style.display = "block"; + }); + }); + + // Detect 'Escape' key and close modal + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' || event.keyCode === 27) { + resetPreviewModal(); + } + }); + + // Reset preview modal + function resetPreviewModal() { + livePreviewModal.style.display = "none"; + iframe.src = ""; + iframeWrapper.style.width = "100%"; + iframeWrapper.style.height = "100%"; + removeActiveClassFromDeviceList(deviceSwitchers); + deviceSwitchers[0].classList.add("active"); + loadingIndicator.style.display = "none"; + } + + // Remove active class from device list + function removeActiveClassFromDeviceList(deviceSwitchers) { + deviceSwitchers.forEach((deviceSwitcher) => { + deviceSwitcher.classList.remove("active"); + }); + } + } +}); diff --git a/assets/react/admin-dashboard/tutor-admin.js b/assets/react/admin-dashboard/tutor-admin.js index bfa9b5d747..4b0d529665 100644 --- a/assets/react/admin-dashboard/tutor-admin.js +++ b/assets/react/admin-dashboard/tutor-admin.js @@ -15,6 +15,7 @@ import './segments/multiple_email_input'; import './quiz-attempts'; import './wp-events-subscriber'; import './segments/manage-api-keys'; +import './template-import'; document.querySelectorAll('.tutor-control-button').forEach(function (button) { button.addEventListener('click', function (event) { diff --git a/assets/scss/admin-dashboard/index.scss b/assets/scss/admin-dashboard/index.scss index b0d8e808f6..2616375480 100644 --- a/assets/scss/admin-dashboard/index.scss +++ b/assets/scss/admin-dashboard/index.scss @@ -8,4 +8,4 @@ @import '../modules/quiz-attempts.scss'; @import '../front/select_dd_search'; @import '../modules/certificate-list.scss'; -@import '../modules/quiz-icons.scss'; +@import '../modules/quiz-icons.scss'; \ No newline at end of file diff --git a/assets/scss/admin-dashboard/template-import.scss b/assets/scss/admin-dashboard/template-import.scss new file mode 100644 index 0000000000..83e9f6bac4 --- /dev/null +++ b/assets/scss/admin-dashboard/template-import.scss @@ -0,0 +1,508 @@ +body { + background-color: #f1f1f1; +} + +body.tutor-backend-tutor-templates.tutor-lms-pro_page_tutor-templates { + font-family: SF Pro Display; +} + +.tutor-templates-demo-import { + background: #f1f1f1; + max-width: 1200px; + margin: 0 auto; +} + +.tutorowl-demo-importer-top { + border-bottom: 1px solid #e0e2ea; + + .tutorowl-demo-importer-top-left { + + .tutorowl-top-left-icon { + width: 40px; + } + } + + .tutorowl-top-left-heading { + line-height: 28px; + } + + .tutorowl-top-left-text { + line-height: 24px; + margin-top: 2px; + } + + .tutorowl-demo-importer-top-right { + .tutorowl-template-search-wrapper { + position: relative; + + svg.tutorowl-template-search-icon { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + } + } + + input { + width: 300px; + height: 40px; + padding: 8px 8px 8px 30px; + gap: 4px; + border-radius: 6px; + border: 1px solid #c3c5cb; + opacity: 0px; + + &::placeholder { + color: #5b616f; + font-size: 16px; + font-weight: 400; + line-height: 24px; + text-align: left; + } + } + } +} + +img { + width: 100%; +} + +.tutorowl-demo-importer-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(374px, 1fr)); + gap: 24px; + + .tutorowl-single-template { + border-radius: 6px; + border: 1px solid #e0e2ea; + background: white; + + &:hover { + box-shadow: 0px 6px 20px 0px #1c31681a; + } + + .tutorowl-single-template-inner { + position: relative; + + .tutorowl-template-preview-img { + img { + border-radius: 6px; + } + } + } + + .tutorowl-single-template-footer { + + .tutorowl-template-name { + line-height: 26px; + } + + a.tutorowl-template-preview { + display: flex; + + &:hover { + svg { + path { + fill: #3e64de; + } + } + } + } + } + } +} + +#tutorowl-import-modal-wrapper { + + h1, + h2, + h3, + h4, + h5, + h6 { + margin: 0; + } + + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.8); + padding: 30px; + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: none; + + .tutorowl-modal-wrapper-overlay { + position: absolute; + background: rgba(0, 0, 0, 0.1); + width: 100%; + height: 100%; + inset: 0 0 0 0; + backdrop-filter: blur(20px); + } + + .tutorowl-modal-content { + z-index: 1; + width: 550px; + min-height: 330px; + background: #fff; + box-shadow: 0px 0px 1px rgba(9, 30, 66, .31), 0px 4px 8px rgba(9, 30, 66, .25); + border-radius: 10px; + padding: 30px; + gap: 32px; + display: grid; + grid-template-columns: 205px 1fr; + position: relative; + + .tutorowl-modal-img { + position: relative; + + img { + width: 100%; + border-radius: 12px; + position: relative; + border: 2px solid #E0E2EA; + } + + .tutorowl-circle { + width: 25px; + height: 25px; + background-color: #22A848; + border-radius: 50%; + position: absolute; + top: -13px; + left: -13px; + display: none; + } + + .tutorowl-checkmark { + width: 12px; + height: 7px; + border: solid #fff; + border-width: 0px 0px 2px 2px; + transform: rotate(316deg); + position: absolute; + top: 44%; + left: 51%; + transform: translate(-50%, -50%) rotate(316deg); + } + } + + .tutorowl-modal-head-subtitle { + margin: 8px 0px 15px; + line-height: 1.3em; + } + + .tutorowl-import-item-wrapper { + display: flex; + flex-direction: column; + gap: 4px; + + .tutorowl-import-item { + display: flex; + align-items: center; + gap: 10px; + background: #F6F8FD; + padding: 12px 8px; + border-radius: 6px; + } + } + + &.tutorowl-template-importing { + .tutorowl-installation-progress-wrapper { + display: flex; + } + + .tutorowl-modal-head-subtitle { + display: none; + } + } + + .tutorowl-modal-footer { + display: flex; + } + + &.tutorowl-template-imported { + + .tutorowl-modal-img { + img { + border: 2px solid #22A848; + } + } + + .tutorowl-circle { + display: block; + } + + .tutorowl-installation-progress-wrapper, + .tutorowl-import-item, + .tutorowl-modal-head, + .tutorowl-modal-footer { + display: none; + } + + .tutorowl-success-block-wrapper { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + align-items: end; + } + } + + .tutorowl-import-modal-close { + position: absolute; + top: 15px; + right: 15px; + cursor: pointer; + + &:hover { + svg { + path { + fill: #000000; + } + } + } + } + } +} + +.tutorowl-success-block-wrapper { + display: none; + border-radius: 6px; + animation: fadeIn 0.4s linear; + + .tutorowl-success-heading { + + >svg { + margin-right: 4px; + } + + h3 { + margin-left: 10px; + line-height: 28px; + color: rgba(33, 35, 39, 1); + } + } +} + +@keyframes rotate { + 100% { + transform: rotate(360deg); + } +} + +@keyframes dash { + 0% { + stroke-dasharray: 1, 150; + stroke-dashoffset: 0; + } + + 50% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -35; + } + + 100% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -124; + } +} + +.tutorowl-import-item { + position: relative; + + svg { + width: 15px; + height: 15px; + border-radius: 50%; + } + + .svg-spinner { + fill: #22a848; + } + + .svg-spinner { + &.active { + background: none; + fill: none; + stroke-width: 5; + position: relative; + + .path { + stroke: #22a848; + stroke-linecap: round; + animation: dash 1.5s ease-in-out infinite; + } + } + } + + .svg-spinner-inner { + position: absolute; + top: 14px; + left: 8px; + width: 15px; + height: 15px; + + .path { + stroke: #ddd; + stroke-width: 5px; + } + } + + svg.svg-circle { + width: 15px; + height: 15px; + position: relative; + z-index: 1; + display: none; + } +} + +.progress-circle { + animation: progress 2s ease-out repeat; +} + +@keyframes progress { + from { + stroke-dashoffset: 283; + } + + to { + stroke-dashoffset: 0; + } +} + +.tutorowl-installation-progress-wrapper { + display: none; + flex-direction: column; + margin-bottom: 13px; + + .tutorowl-import-percentage-number { + width: 32px; + height: 20px; + padding: 2px 4px 2px 4px; + border-radius: 12px; + background: #ECFDF3; + font-size: 11px; + font-weight: 700; + line-height: 16px; + text-align: center; + margin-bottom: 4px; + } +} + +.tutorowl-installation-progress-wrapper .tutorowl-progress { + position: relative; + overflow: hidden; + box-shadow: inset 0px 0px 2px 1px rgba(0, 0, 0, 0.05); + background: rgba(0, 0, 0, 0.10); + height: 8px; + border-radius: 5px; + width: 100%; + margin-bottom: 0; +} + +.tutorowl-installation-progress-wrapper .tutorowl-progress-status { + border-radius: 20px; + color: #fff; + position: absolute; + height: 100%; + width: 0; + transition: 0.2s linear; + background: #239C46; +} + +// template preview scss +.template-live-preview-modal { + display: none; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + padding: 70px 30px 30px; + z-index: 999999; + background: #fff; +} + +.template-live-preview-frame { + width: 100%; + height: 100vh; + // box-shadow: 0px -4px 40px 20px #1C31681A; + position: relative; +} + +.template-preview-frame-header { + display: flex; + justify-content: space-between; + align-items: center; + position: absolute; + top: -45px; + width: 100%; + + h3 { + margin: 0; + } + + ul { + display: flex; + gap: 20px; + margin: 0; + + i { + font-size: 20px; + cursor: pointer; + color: #33333360; + } + + li { + margin: 0; + + &.active { + i { + color: #3e64de; + } + } + } + } +} + +.live-preview-close-modal { + font-size: 20px; + cursor: pointer; + color: #333; + z-index: 1000; +} + +.template-preview-iframe-wrapper { + box-shadow: 0px -4px 40px 20px rgba(28, 49, 104, .1019607843); + width: 100%; + height: 100%; + margin: 0 auto; + + #template-preview-iframe { + width: 100%; + height: 100%; + } +} + + +.device-selector { + margin-bottom: 16px; + display: flex; + gap: 10px; + justify-content: center; +} + +.loading-indicator { + font-size: 40px; + position: absolute; + top: 50%; + left: 50%; + margin: 0 auto; + transform: translate(-40%, -50%); +} \ No newline at end of file diff --git a/classes/Admin.php b/classes/Admin.php index e781129d17..263a8d1cc1 100644 --- a/classes/Admin.php +++ b/classes/Admin.php @@ -145,6 +145,9 @@ public function register_menu() { // Extendable action hook @since 2.2.0. do_action( 'tutor_after_courses_menu' ); + // Templates menu @since 3.3.3 + add_submenu_page( 'tutor', __( 'Templates', 'tutor' ), __( 'Templates', 'tutor' ), 'manage_tutor', 'tutor-templates', array( $this, 'tutor_templates' ) ); + add_submenu_page( 'tutor', __( 'Categories', 'tutor' ), __( 'Categories', 'tutor' ), 'manage_tutor', 'edit-tags.php?taxonomy=course-category&post_type=' . $course_post_type, null ); add_submenu_page( 'tutor', __( 'Tags', 'tutor' ), __( 'Tags', 'tutor' ), 'manage_tutor', 'edit-tags.php?taxonomy=course-tag&post_type=' . $course_post_type, null ); @@ -177,6 +180,15 @@ public function register_menu() { } } + /** + * Tutor template view + * + * @since 3.2.2 + */ + public function tutor_templates() { + include tutor()->path . 'views/templates/templates.php'; + } + /** * Welcome page opt-out * diff --git a/classes/Assets.php b/classes/Assets.php index de365cfe5d..ec3043d195 100644 --- a/classes/Assets.php +++ b/classes/Assets.php @@ -241,6 +241,10 @@ public function admin_scripts() { 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 ); } + + if ( 'tutor-templates' === $page ) { + wp_enqueue_style( 'tutor-template-import', tutor()->url . 'assets/css/tutor-template-import.min.css', array(), TUTOR_VERSION, 'all' ); + } } /** diff --git a/classes/Tutor.php b/classes/Tutor.php index 7e4611a785..d46ffdbbf8 100644 --- a/classes/Tutor.php +++ b/classes/Tutor.php @@ -436,6 +436,7 @@ final class Tutor { */ private $permalink; + /** * Run the TUTOR * diff --git a/composer.json b/composer.json index e05ea5e6f3..2c36940915 100644 --- a/composer.json +++ b/composer.json @@ -30,11 +30,11 @@ "post-install-cmd": [ "composer install --no-dev --working-dir=./ecommerce/PaymentGateways/Paypal", "if [ ! -d \"tutor-droip\" ]; then git clone https://github.com/themeum/tutor-droip.git tutor-droip; fi", - "cd tutor-droip && composer install && npm install" + "cd tutor-droip && composer install && npm install" ], "post-update-cmd": [ - "if [ ! -d \"tutor-droip\" ]; then git clone https://github.com/themeum/tutor-droip.git tutor-droip; fi", - "cd tutor-droip && composer update && npm install" - ] - } -} + "if [ ! -d \"tutor-droip\" ]; then git clone https://github.com/themeum/tutor-droip.git tutor-droip; fi", + "cd tutor-droip && composer update && npm install" + ] + } +} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 39ce0457a6..115703d02b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -55,6 +55,13 @@ var scss_blueprints = { mode: 'expanded', destination: 'tutor-frontend-dashboard.min.css', }, + + tutor_import: { + src: 'assets/scss/admin-dashboard/template-import.scss', + mode: 'expanded', + destination: 'tutor-template-import.min.css' + }, + }; var task_keys = Object.keys(scss_blueprints); @@ -151,7 +158,7 @@ gulp.task('watch', function () { if (e.history[0].includes('/front/')) { gulp.parallel('tutor_front')(); } else if (e.history[0].includes('/admin-dashboard/')) { - gulp.parallel('tutor_admin', 'tutor_setup')(); + gulp.parallel('tutor_admin', 'tutor_setup', 'tutor_import')(); } else if (e.history[0].includes('/frontend-dashboard/')) { gulp.parallel('tutor_front_dashboard')(); } else if (e.history[0].includes('modules/')) { diff --git a/helpers/TemplateHelper.php b/helpers/TemplateHelper.php new file mode 100644 index 0000000000..8e7e39cb6f --- /dev/null +++ b/helpers/TemplateHelper.php @@ -0,0 +1,99 @@ + + * @link https://tutor.com + * @since 3.3.3 + */ + +namespace Tutor\Helpers; + +use Tutor\Traits\JsonResponse; + +/** + * TemplateHelper methods + */ +class TemplateHelper { + + use JsonResponse; + + /** + * Template list endpoint. + * + * @var string + */ + public static $template_list_endpoint = 'https://tutorlms.com/wp-json/themeum-products/v1/tutor/theme-templates'; + + /** + * Template download endpoint. + * + * @var string + */ + public static $template_download_endpoint = 'https://tutorlms.com/wp-json/themeum-products/v1/tutor/theme-template-download'; + + /** + * Get Template list. + * + * @throws \Exception If there is an error fetching or decoding the templates. + */ + public static function get_template_list() { + try { + $response = wp_remote_get( + self::$template_list_endpoint, + array( + 'headers' => array( + 'Secret-Key' => 't344d5d71sae7dcb546b8cf55e594808', + ), + ) + ); + $response_status_code = wp_remote_retrieve_response_code( $response ); + if ( is_wp_error( $response ) ) { + throw new \Exception( 'Failed to fetch templates: ' . $response->get_error_message() ); + } + $template_list = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( json_last_error() !== JSON_ERROR_NONE ) { + throw new \Exception( 'Failed to decode JSON response: ' . json_last_error_msg() ); + } + if ( 200 !== $response_status_code ) { + throw new \Exception( 'Failed to fetch templates: ' . $template_list['response'] ); + } + return $template_list['body_response']; + } catch ( \Exception $e ) { + return array(); + } + } + + /** + * Get Template download url + * + * @param string $template_id The ID of the template to download. + */ + public static function get_template_download_url( $template_id ) { + $template_download_endpoint = 'https://tutorlms.com/wp-json/themeum-products/v1/tutor/theme-template-download'; + $tutor_license_info = get_option( 'tutor_license_info' ); + $website_url = get_site_url(); + $args = array( + 'body' => array( + 'slug' => $template_id, + 'website_url' => $website_url, + 'license_key' => $tutor_license_info['license_key'] ?? '', + ), + 'headers' => array( + 'Secret-Key' => 't344d5d71sae7dcb546b8cf55e594808', + ), + ); + $response = wp_remote_post( self::$template_download_endpoint, $args ); + $response_body = wp_remote_retrieve_body( $response ); + $data = json_decode( $response_body, true ); + if ( is_wp_error( $response ) ) { + self::json_response( $data['response'], null, 400 ); + } + if ( empty( $data['body_response'] ) ) { + self::json_response( $data['response'], null, 400 ); + } + $template_download_url = $data['body_response']; + return $template_download_url; + } +} diff --git a/tutor.php b/tutor.php index ea52e33137..498232c306 100644 --- a/tutor.php +++ b/tutor.php @@ -30,7 +30,7 @@ /** * Load tutor text domain for translation */ -add_action( 'init', fn () => load_plugin_textdomain( 'tutor', false, basename( dirname( __FILE__ ) ) . '/languages' ) ); +add_action( 'init', fn () => load_plugin_textdomain( 'tutor', false, basename( __DIR__ ) . '/languages' ) ); if ( ! function_exists( 'tutor' ) ) { /** diff --git a/views/templates/droip-layouts.json b/views/templates/droip-layouts.json new file mode 100644 index 0000000000..939276988e --- /dev/null +++ b/views/templates/droip-layouts.json @@ -0,0 +1,42 @@ +[ + { + "ID": 1, + "name": "Business", + "builder_type": "droip", + "src": "https://github.com/nur-alam/owl/raw/refs/heads/main/files/business-template.zip", + "preview_image": "https://raw.githubusercontent.com/nur-alam/owl/refs/heads/main/files/beatlab-template-preview-img.jpg", + "preview_url": "https://preview.tutorlms.com/marketplace/" + }, + { + "ID": 2, + "name": "Music", + "builder_type": "droip", + "src": "https://github.com/nur-alam/owl/raw/refs/heads/main/files/photography-templates.zip", + "preview_image": "https://raw.githubusercontent.com/nur-alam/owl/refs/heads/main/files/edumax-template-preview-img.png", + "preview_url": "https://preview.tutorlms.com/singlecourse/" + }, + { + "ID": 3, + "name": "Photography", + "builder_type": "droip", + "src": "https://github.com/nur-alam/owl/raw/refs/heads/main/files/photography-templates.zip", + "preview_image": "https://raw.githubusercontent.com/nur-alam/owl/refs/heads/main/files/beatlab-template-preview-img.jpg", + "preview_url": "https://preview.tutorlms.com/university/" + }, + { + "ID": 4, + "name": "Photography", + "builder_type": "droip", + "src": "https://github.com/nur-alam/owl/raw/refs/heads/main/files/photography-templates.zip", + "preview_image": "https://raw.githubusercontent.com/nur-alam/owl/refs/heads/main/files/edumax-template-preview-img.png", + "preview_url": "https://preview.tutorlms.com/marketplace/" + }, + { + "ID": 5, + "name": "Entrepreneurship", + "builder_type": "droip", + "src": "https://github.com/nur-alam/owl/raw/refs/heads/main/files/photography-templates.zip", + "preview_image": "https://raw.githubusercontent.com/nur-alam/owl/refs/heads/main/files/beatlab-template-preview-img.jpg", + "preview_url": "https://preview.tutorlms.com/university/" + } +] \ No newline at end of file diff --git a/views/templates/templates-list.php b/views/templates/templates-list.php new file mode 100644 index 0000000000..524d8e1f8d --- /dev/null +++ b/views/templates/templates-list.php @@ -0,0 +1,46 @@ + + * @link https://tutor.com + * @since 3.3.3 + */ + +use Tutor\Helpers\TemplateHelper; + +$template_list = TemplateHelper::get_template_list(); + +$i = 0; +if ( ! empty( $template_list ) ) { + foreach ( $template_list as $key => $template ) { + $template = (object) $template; + ?> +