Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
518e342
feat(tour): add Alpine component with modal integration
b-l-i-n-d Jun 25, 2026
8a9c31c
feat(tour): add tour styles with directional slide animations
b-l-i-n-d Jun 25, 2026
8d9495f
feat(tour): create tour modal templates
b-l-i-n-d Jun 25, 2026
b4eb2bb
feat(dashboard): register tour component and include in dashboard
b-l-i-n-d Jun 25, 2026
093c57c
feat: Add tour assets
b-l-i-n-d Jun 26, 2026
292e86e
style(tour): Update tour modal padding
b-l-i-n-d Jun 26, 2026
0f03436
fix(dashboard): resolve tour modal close event listener not firing
b-l-i-n-d Jun 26, 2026
c5abd74
feat(tour): move tour component to shared location
b-l-i-n-d Jun 26, 2026
2f5295f
feat(learning-area): add tour support to learning area
b-l-i-n-d Jun 26, 2026
0c06551
feat(dashboard): show tour on all dashboard pages
b-l-i-n-d Jun 26, 2026
f7223f3
feat(dashboard): only show tour for student users
b-l-i-n-d Jun 26, 2026
d1e3a8d
style(tour): add inset box-shadow to tour slide images
b-l-i-n-d Jun 26, 2026
2350382
fix(tour): use ::after pseudo-element for visible inset box-shadow
b-l-i-n-d Jun 26, 2026
3ef47f3
style(tour): update inset box-shadow values
b-l-i-n-d Jun 26, 2026
4efce4e
style(tour): reduce inset box-shadow spread to 0
b-l-i-n-d Jun 26, 2026
acc9ce6
fix(tour): scope localStorage key to user ID
b-l-i-n-d Jun 26, 2026
7366ad7
refactor(tour): extract localStorage key prefix to constant
b-l-i-n-d Jun 26, 2026
34cf97f
fix(tour): correct slide 2 small screen shadow to left+bottom
b-l-i-n-d Jun 26, 2026
6ded400
fix(tour): use data-attribute selectors instead of :nth-child
b-l-i-n-d Jun 26, 2026
75802bd
fix(tour): Update shadow style in slide image
b-l-i-n-d Jun 26, 2026
74403bb
feat(tour): add server-side tour completion endpoint
b-l-i-n-d Jun 29, 2026
91d8734
feat(tour): expose is_tour_completed to JS config
b-l-i-n-d Jun 29, 2026
319ca24
refactor(tour): replace localStorage with server-side tour check
b-l-i-n-d Jun 29, 2026
fd88ba7
refactor(tour): move tour templates from dashboard to shared
b-l-i-n-d Jun 29, 2026
d83b696
chore: Use error message method for utils
b-l-i-n-d Jun 29, 2026
dbeb0e4
asset(tour): Update tour assets
b-l-i-n-d Jun 29, 2026
8aaaa34
refactor: Use guard clause
b-l-i-n-d Jul 1, 2026
9a920bc
refactor: Use external assets
b-l-i-n-d Jul 1, 2026
a7eb566
Merge branch 4.0.0-dev into v4-dashboard-onboarding
b-l-i-n-d Jul 1, 2026
148c2c0
Merge branch 4.0.0-dev into v4-dashboard-onboarding
b-l-i-n-d Jul 1, 2026
c1bd31d
refactor: Add check for logged in user
b-l-i-n-d Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/core/ts/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const defaultTutorConfig = {
wp_rest_nonce: '',
is_admin: '',
is_admin_bar_showing: '',
is_tour_completed: '',
max_upload_size: '',
content_change_event: '',
is_tutor_course_edit: '',
Expand Down
3 changes: 3 additions & 0 deletions assets/core/ts/utils/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ const endpoints = {
// Instructor Dashboard
SAVE_INSTRUCTOR_HOME_SECTIONS_ORDER: 'tutor_save_instructor_home_sections_order',
SAVE_INSTRUCTOR_HOME_SECTIONS_VISIBILITY: 'tutor_save_instructor_home_sections_visibility',

// Tour
COMPLETE_TOUR: 'tutor_complete_tour',
} as const;

export default endpoints;
83 changes: 83 additions & 0 deletions assets/src/js/frontend/components/tour.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { tutorConfig } from '@Core/ts/config/config';
import { TUTOR_CUSTOM_EVENTS } from '@Core/ts/constant';

interface SlideData {
title: string;
imageLarge: string;
imageSmall: string;
}

const SLIDE_DIRECTION = {
NEXT: 'next',
BACK: 'back',
};

const tour = ({ slidesData, modalId }: { slidesData: SlideData[]; modalId: string }) => {
const { modal, api, endpoints } = window.TutorCore;
const isTourComplete = Number(tutorConfig.is_tour_completed);

return {
currentSlide: 0,
slides: slidesData || [],
isOpen: false,
slideDirection: SLIDE_DIRECTION.NEXT,
$nextTick: undefined as ((callback: () => void) => void) | undefined,
_onModalClose: undefined as EventListener | undefined,

init() {
this._onModalClose = ((e: CustomEvent) => {
if (e.detail?.id === modalId) {
this.isOpen = false;
}
}) as EventListener;

document.addEventListener(TUTOR_CUSTOM_EVENTS.MODAL_CLOSE, this._onModalClose);

if (!isTourComplete) {
this.isOpen = true;
this.$nextTick?.(() => {
modal.showModal(modalId);
});
}
},

destroy() {
document.removeEventListener(TUTOR_CUSTOM_EVENTS.MODAL_CLOSE, this._onModalClose!);
},

next() {
this.slideDirection = SLIDE_DIRECTION.NEXT;
if (this.currentSlide < this.slides.length - 1) {
this.currentSlide++;
} else {
this.skip();
}
},

back() {
this.slideDirection = SLIDE_DIRECTION.BACK;
if (this.currentSlide > 0) {
this.currentSlide--;
}
},

skip() {
// Fire-and-forget AJAX call to save user meta
api.wpPost(endpoints.COMPLETE_TOUR);

this.isOpen = false;
modal.closeModal(modalId);
},
};
};

export const initializeTour = () => {
window.TutorComponentRegistry.register({
type: 'component',
meta: {
name: 'tour',
component: tour,
},
});
window.TutorComponentRegistry.initWithAlpine(window.Alpine);
};
2 changes: 2 additions & 0 deletions assets/src/js/frontend/dashboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Initializes dashboard functionality based on current page

import { initializeReviews } from '@FrontendComponents/reviews';
import { initializeTour } from '@FrontendComponents/tour';
import { initializeCommon } from '@FrontendServices/common';

import { initializeHeader } from './header';
Expand Down Expand Up @@ -74,6 +75,7 @@ const getCurrentPage = (): string => {
const initializeDashboard = () => {
initializeHeader();
initializeCommon();
initializeTour();

const currentPage = getCurrentPage();

Expand Down
3 changes: 3 additions & 0 deletions assets/src/js/frontend/learning-area/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Initializes learning area functionality based on current page

import { initializeReviews } from '@FrontendComponents/reviews';
import { initializeTour } from '@FrontendComponents/tour';
import { initializeCommon } from '@FrontendServices/common';

import { initializeCommon as initializeLearningAreaCommon } from './common';
import { initializeLesson } from './lesson';
import { initializeCourseCourseInfo } from './pages/course-info';
Expand All @@ -14,6 +16,7 @@ const initializeLearningArea = () => {
initializeLearningAreaCommon();
initializeCommon();
initializeSidebar();
initializeTour();
initializeReviews();
const { pathname, search } = window.location;

Expand Down
1 change: 1 addition & 0 deletions assets/src/js/v3/@types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ declare global {
wp_rest_nonce: string;
is_admin: string;
is_admin_bar_showing: string;
is_tour_completed: string;
max_upload_size: string; // in bytes
content_change_event: string;
is_tutor_course_edit: string;
Expand Down
1 change: 1 addition & 0 deletions assets/src/scss/frontend/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
@forward 'quiz-attempt-details';
@forward 'quiz-summary';
@forward 'player';
@forward 'tour';
158 changes: 158 additions & 0 deletions assets/src/scss/frontend/components/_tour.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
@use '@Core/scss/tokens' as *;
@use '@Core/scss/mixins' as *;

.tutor-tour-modal {
&.tutor-modal-content {
max-width: 100%;
padding: $tutor-spacing-6;
}

.tutor-tour-images {
position: relative;
overflow: hidden;
min-height: 366px;
width: 100%;
border-radius: $tutor-radius-2xl;
background: $tutor-surface-l1-hover;

.tutor-tour-slide-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;

picture,
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: $tutor-radius-2xl;
}

&::after {
content: '';
position: absolute;
inset: 0;
border-radius: $tutor-radius-2xl;
pointer-events: none;
}

&[class*='tutor-slide-4']::after {
box-shadow: inset 10px 0px 20px 0px $tutor-surface-l1-hover;
}

@include tutor-breakpoint-up(sm) {
&[class*='tutor-slide-0']::after,
&[class*='tutor-slide-1']::after,
&[class*='tutor-slide-2']::after,
&[class*='tutor-slide-3']::after {
box-shadow: inset -6px -6px 20px 0px $tutor-surface-l1-hover;
}
}

@include tutor-breakpoint-down(sm) {
&[class*='tutor-slide-0']::after,
&[class*='tutor-slide-2']::after,
&[class*='tutor-slide-3']::after {
box-shadow: inset -6px -6px 20px 0px $tutor-surface-l1-hover;
}

&[class*='tutor-slide-1']::after {
box-shadow: inset 6px -6px 20px 0px $tutor-surface-l1-hover;
}
}
}
}

.tutor-tour-dot {
width: 6px;
height: 6px;
border-radius: $tutor-radius-full;
background: $tutor-icon-disabled;
display: inline-block;
@include tutor-transition(background);

&.is-active {
background: $tutor-icon-brand;
}
}

.tutor-transition-slide-enter {
@include tutor-transition((transform, opacity));
}

.tutor-transition-slide-enter-start {
transform: translateX(100%);
opacity: 0;
}

.tutor-transition-slide-enter-end {
transform: translateX(0);
opacity: 1;
}

.tutor-transition-slide-leave {
transition:
transform 0.3s ease-in,
opacity 0.3s ease-in;
}

.tutor-transition-slide-leave-start {
transform: translateX(0);
opacity: 1;
}

.tutor-transition-slide-leave-end {
transform: translateX(-100%);
opacity: 0;
}

.tutor-tour-images[data-direction='back'] {
.tutor-transition-slide-enter-start {
transform: translateX(-100%);
}

.tutor-transition-slide-leave-end {
transform: translateX(100%);
}
}

[dir='rtl'] & {
.tutor-transition-slide-enter-start {
transform: translateX(-100%);
}

.tutor-transition-slide-leave-end {
transform: translateX(100%);
}

.tutor-tour-images[data-direction='back'] {
.tutor-transition-slide-enter-start {
transform: translateX(100%);
}

.tutor-transition-slide-leave-end {
transform: translateX(-100%);
}
}
}
}

.tutor-tour-skip-wrapper {
.tutor-tour-skip-btn {
color: $tutor-text-primary-inverse;

&:hover {
color: $tutor-text-primary;
}
}
}

.tutor-modal-content:has(.tutor-tour-modal) {
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 0 !important;
overflow: visible !important;
}
1 change: 1 addition & 0 deletions classes/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
function ( $file ) {
return basename( $file, '.svg' );
},
glob( tutor()->path . 'assets/icons/kids/*.svg' ) ?: array()

Check failure on line 184 in classes/Assets.php

View workflow job for this annotation

GitHub Actions / WPCS

Using short ternaries is not allowed as they are rarely used correctly
);
}

Expand Down Expand Up @@ -224,6 +224,7 @@
'course_slug' => tutor_utils()->get_option( 'course_permalink_base', 'courses' ),
'lesson_slug' => tutor_utils()->get_option( 'lesson_permalink_base', 'lessons' ),
'quiz_slug' => tutor_utils()->get_option( 'quiz_permalink_base', 'quizzes' ),
'is_tour_completed' => (bool) get_user_meta( get_current_user_id(), User::TOUR_COMPLETED_META, true ),
);
}

Expand Down
17 changes: 17 additions & 0 deletions classes/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class User {
const PROFILE_BIO_META = '_tutor_profile_bio';
const PROFILE_JOB_TITLE_META = '_tutor_profile_job_title';
const TUTOR_STUDENT_META = '_is_tutor_student';
const TOUR_COMPLETED_META = '_tutor_tour_completed';
const APPLICATION_SOURCE_META = '_tutor_application_source';
const INSTRUCTOR_APPROVAL_NOTICE_META = 'tutor_instructor_show_approval_message';

Expand Down Expand Up @@ -115,6 +116,7 @@ public function __construct( $register_hooks = true ) {

add_action( 'wp_ajax_tutor_user_list', array( $this, 'ajax_user_list' ) );
add_action( 'wp_ajax_tutor_switch_profile', array( $this, 'ajax_switch_profile' ) );
add_action( 'wp_ajax_tutor_complete_tour', array( $this, 'ajax_complete_tour' ) );
}

/**
Expand Down Expand Up @@ -854,4 +856,19 @@ public static function is_student_view(): bool {
public static function can_switch_mode( int $user_id = 0 ): bool {
return self::is_admin( $user_id ) || self::is_instructor( $user_id );
}

/**
* Mark dashboard tour as completed for the current user.
*
* @since 4.0.0
*
* @return void JSON response.
*/
public function ajax_complete_tour() {
tutor_utils()->check_nonce();

update_user_meta( $user_id, self::TOUR_COMPLETED_META, true );

$this->json_response( __( 'Tour completed', 'tutor' ) );
}
}
5 changes: 5 additions & 0 deletions templates/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
$user = get_user_by( 'ID', $user_id );
$enable_profile_completion = tutor_utils()->get_option( 'enable_profile_completion' );
$is_instructor = tutor_utils()->is_instructor();
$is_tour_completed = get_user_meta( $user_id, User::TOUR_COMPLETED_META, true );

// URLS.
$current_url = tutor()->current_url;
Expand Down Expand Up @@ -149,6 +150,10 @@
</div>
</div>
</div>
<?php if ( User::is_student_view() && ! $is_tour_completed ) : ?>
<?php tutor_load_template( 'shared.tour' ); ?>
<?php endif; ?>

<?php do_action( 'tutor_dashboard/after/wrap' ); ?>
<?php if ( ! $is_by_short_code && ! defined( 'OTLMS_VERSION' ) ) : ?>
<?php wp_footer(); ?>
Expand Down
7 changes: 7 additions & 0 deletions templates/learning-area/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Tutor\Models\EnrollmentModel;
use TUTOR\Quiz;
use TUTOR\Template;
use TUTOR\User;

$current_user_id = get_current_user_id();
$tutor_current_post = get_post();
Expand All @@ -37,6 +38,7 @@
$tutor_is_course_instructor = tutor_utils()->is_instructor_of_this_course( $current_user_id, $tutor_course_id, true );
$tutor_is_course_completed = tutor_utils()->is_completed_course( $tutor_course_id, $current_user_id );
$tutor_can_complete_course = CourseModel::can_complete_course( $tutor_course_id, $current_user_id ) && ! $tutor_is_course_completed;
$is_tour_completed = get_user_meta( $current_user_id, User::TOUR_COMPLETED_META, true );

$tutor_course_progress = tutor_utils()->get_course_completed_percent( $tutor_course_id, $current_user_id );
$tutor_completion_mode = tutor_utils()->get_option( 'course_completion_process' );
Expand Down Expand Up @@ -171,6 +173,11 @@ class="tutor-btn tutor-btn-outline tutor-btn-small tutor-btn-icon tutor-expand-b
</button>
</div>
</div>
<?php
if ( is_user_logged_in() && ! $is_tour_completed ) {
tutor_load_template( 'shared.tour' );
}
?>
<?php wp_footer(); ?>

<?php
Expand Down
Loading
Loading