Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
031e916
Add learning mood preference to user settings and update Utils for re…
nur-alam Mar 28, 2026
8c44429
Merge branch 'kids-mood-v4' of github.com:themeum/tutor into user-pre…
nur-alam Mar 28, 2026
fa4ae56
Refactor learning mood retrieval in UserPreference and Utils classes …
nur-alam Mar 28, 2026
ee3239d
Refactor is_kids_mode method in Utils class to improve clarity by rea…
nur-alam Mar 29, 2026
9fbd82f
Add learning mood icon and update related references in the codebase
nur-alam Mar 30, 2026
e2599da
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Mar 30, 2026
ba627df
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Mar 30, 2026
c1b96ef
refactor: add reset preferences functionality & Update theme attribut…
nur-alam Mar 30, 2026
6a69e3e
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Mar 30, 2026
bb74ebf
feat: implement reset preferences feature
nur-alam Mar 31, 2026
49936f7
refactor: remove reset preferences SVG and update preferences reset l…
nur-alam Mar 31, 2026
78288e6
mend
nur-alam Mar 31, 2026
9b37d4a
update reset preference modal illustrations
nur-alam Apr 1, 2026
97d5960
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Apr 1, 2026
d9b3a42
refactor: simplify learning mode preference retrieval and update plac…
nur-alam Apr 1, 2026
6df6512
refactor: restrict learning mood setting visibility to student view a…
nur-alam Apr 2, 2026
4c377ae
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Apr 2, 2026
3e6b2e3
refactor: simplify admin kids mode check by removing redundant variable
nur-alam Apr 2, 2026
4569e9a
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Apr 3, 2026
54257d7
fix: correct kids mode detection and user preference handling
nur-alam Apr 3, 2026
f07dd29
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Apr 3, 2026
a9574e5
fix: prevent undefined variable warning in user preference handling
nur-alam Apr 3, 2026
239401e
Merge branch '4.0.0-dev' of github.com:themeum/tutor into user-prefer…
nur-alam Apr 3, 2026
fc1c67f
fix: remove boolean type cast from auto_play_next preference
nur-alam Apr 3, 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
2 changes: 1 addition & 1 deletion assets/core/scss/themes/_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

@use '../tokens' as *;

[data-theme='dark'] {
[data-tutor-theme='dark'] {
// =============================================================================
// SURFACE COLORS
// =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion assets/core/scss/themes/_light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@use '../tokens' as *;

:root,
[data-theme="light"] {
[data-tutor-theme="light"] {
// =============================================================================
// SURFACE COLORS
// =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion assets/core/ts/components/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const TUTOR_CALENDAR_VALUES = {
apply: 'apply',
clear: 'clear',
calendarZIndex: '100001',
themeAttrDetect: 'body[data-theme]',
themeAttrDetect: 'body[data-tutor-theme]',
calendarClasses: 'vc tutor-vc-calendar',
} as const;

Expand Down
2 changes: 1 addition & 1 deletion assets/core/ts/services/Preference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class PreferenceService {
private readonly BASE_FONT_SIZE = 16;
private readonly SCALE_PERCENTAGE_BASE = 100;
private readonly STYLE_ID = 'tutor-font-scale';
private readonly DATA_THEME_ATTR = 'data-theme';
private readonly DATA_THEME_ATTR = 'data-tutor-theme';

constructor() {
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
Expand Down
3 changes: 3 additions & 0 deletions assets/icons/learning-mood.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/illustrations/kids-reset-preference.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/illustrations/reset-preference.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions assets/src/js/frontend/dashboard/pages/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,15 @@ interface PreferencesFormProps {
auto_play_next: boolean;
theme: string;
font_scale: number;
learning_mood: boolean;
formId?: string;
}

interface ResetPreferencesPayload {
formId: string;
modalId: string;
}

interface UpdateNotificationProps {
[key: string]: boolean | string;
}
Expand All @@ -77,6 +83,7 @@ interface ResetPasswordResponse {
const settings = () => {
const query = window.TutorCore.query;
const form = window.TutorCore.form;
const modal = window.TutorCore.modal;
const toast = window.TutorCore.toast;

return {
Expand All @@ -90,6 +97,7 @@ const settings = () => {
resetPasswordMutation: null as MutationState<ResetPasswordResponse> | null,
handleUpdateNotification: null as MutationState<unknown, unknown> | null,
savePreferencesMutation: null as MutationState<TutorMutationResponse<PreferencesFormProps>> | null,
resetPreferencesMutation: null as MutationState<TutorMutationResponse<PreferencesFormProps>> | null,

init() {
if (!this.$el) {
Expand All @@ -103,6 +111,7 @@ const settings = () => {
this.handleSaveBillingInfo = this.handleSaveBillingInfo.bind(this);
this.handleSaveWithdrawMethod = this.handleSaveWithdrawMethod.bind(this);
this.handleResetPassword = this.handleResetPassword.bind(this);
this.handleResetPreferences = this.handleResetPreferences.bind(this);

this.handleUpdateNotification = query.useMutation(this.updateNotification, {
onSuccess: (data: TutorMutationResponse<string>, payload: UpdateNotificationProps) => {
Expand Down Expand Up @@ -190,6 +199,16 @@ const settings = () => {
toast.error(convertToErrorMessage(error));
},
});

this.resetPreferencesMutation = query.useMutation(this.resetPreferences, {
onSuccess: (data: TutorMutationResponse<PreferencesFormProps>) => {
toast.success(data?.message ?? __('Preferences reset successfully', 'tutor'));
window.location.reload();
},
onError: (error: Error) => {
toast.error(convertToErrorMessage(error));
},
});
},

async updatePreferences(payload: PreferencesFormProps) {
Expand All @@ -198,6 +217,16 @@ const settings = () => {
>;
},

async resetPreferences(payload: ResetPreferencesPayload) {
return wpAjaxInstance.post(endpoints.RESET_USER_PREFERENCES, payload) as unknown as Promise<
TutorMutationResponse<PreferencesFormProps>
>;
},

handleResetPreferences(formId: string, modalId: string) {
this.resetPreferencesMutation?.mutate({ formId, modalId });
},

async updateNotification(payload: UpdateNotificationProps) {
const transformedPayload = Object.keys(payload).reduce(
(formattedPayload, key) => {
Expand Down
1 change: 1 addition & 0 deletions assets/src/js/v3/shared/icons/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export const icons = [
'landscape',
'landscapeFilled',
'league',
'learningMood',
'left',
'lesson',
'lessonCompleted',
Expand Down
1 change: 1 addition & 0 deletions assets/src/js/v3/shared/utils/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ const endpoints = {
RESET_PASSWORD: 'tutor_profile_password_reset',
UPDATE_PROFILE_NOTIFICATION: 'tutor_save_notification_preference',
UPDATE_USER_PREFERENCES: 'tutor_save_user_preferences',
RESET_USER_PREFERENCES: 'tutor_reset_user_preferences',
REMOVE_DEVICE_MANUALLY: 'tutor_remove_device_manually',
REMOVE_ALL_ACTIVE_LOGINS: 'tutor_remove_all_active_logins',

Expand Down
14 changes: 6 additions & 8 deletions assets/src/scss/frontend/dashboard/settings/_preferences.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
@use '@Core/scss/mixins' as *;

.tutor-preferences {
&-section-header {
@include tutor-typography('h5', 'semibold', 'primary', 'heading');
margin: $tutor-spacing-none $tutor-spacing-none $tutor-spacing-4 $tutor-spacing-none;

@include tutor-breakpoint-down(sm) {
@include tutor-typography('medium', 'semibold', 'primary', 'heading');
}
&-reset-default {
@include tutor-flex(row, center, flex-end);
color: $tutor-text-secondary;
gap: $tutor-spacing-2;
cursor: pointer;
}

&-setting-item {
Expand Down Expand Up @@ -47,4 +45,4 @@
flex-shrink: 0;
}
}
}
}
1 change: 1 addition & 0 deletions classes/Icon.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ final class Icon {
const LANDSCAPE = 'landscape';
const LANDSCAPE_FILLED = 'landscape-filled';
const LEAGUE = 'league';
const LEARNING_MOOD = 'learning-mood';
const LEFT = 'left';
const LESSON = 'lesson';
const LESSON_COMPLETED = 'lesson-completed';
Expand Down
115 changes: 98 additions & 17 deletions classes/UserPreference.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use Tutor\Cache\TutorCache;
use Tutor\Helpers\HttpHelper;
use Tutor\Options_V2;
use Tutor\Traits\JsonResponse;

/**
Expand Down Expand Up @@ -113,6 +114,7 @@ public function __construct( $register_hooks = true ) {
}

add_action( 'wp_ajax_tutor_save_user_preferences', array( $this, 'ajax_save_user_preferences' ) );
add_action( 'wp_ajax_tutor_reset_user_preferences', array( $this, 'ajax_reset_user_preferences' ) );
add_filter( 'body_class', array( $this, 'add_theme_attribute' ) );
add_action( 'wp_head', array( $this, 'apply_font_scale' ) );
}
Expand Down Expand Up @@ -152,7 +154,7 @@ public function add_theme_attribute( $classes ) {
if ( ! in_array( $theme, self::THEMES, true ) ) {
$theme = self::DEFAULT_THEME;
}
echo ' data-theme="' . esc_attr( $theme ) . '"';
echo ' data-tutor-theme="' . esc_attr( $theme ) . '"';
return $classes;
}

Expand Down Expand Up @@ -226,10 +228,15 @@ public function save_preferences( array $prefs, $user_id = 0 ) {
return false;
}

$current_preferences = $this->get_preferences( $user_id );
$preferences = array_merge( $current_preferences, $prefs );
// Get from database.
$current_preferences = get_user_meta( $user_id, self::META_KEY, true );
if ( ! is_array( $current_preferences ) ) {
$current_preferences = array();
}

$preferences = apply_filters( 'tutor_user_preference_data', $preferences, $user_id );
$combined_preferences = array_merge( $current_preferences, $prefs );

$preferences = apply_filters( 'tutor_user_preference_data', $combined_preferences, $user_id );

update_user_meta( $user_id, self::META_KEY, $preferences );

Expand All @@ -248,24 +255,49 @@ public function save_preferences( array $prefs, $user_id = 0 ) {
public function ajax_save_user_preferences() {
tutor_utils()->check_nonce();

if ( ! is_user_logged_in() ) {
$auto_play_next = Input::post( 'auto_play_next', null );
$theme = Input::post( 'theme', null );
$font_scale = Input::post( 'font_scale', null );
$learning_mood = Input::post( 'learning_mood', null );

$preferences_settings = array();

if ( null !== $auto_play_next ) {
$default_auto_play_next = (bool) tutor_utils()->get_option( 'autoload_next_course_content' );
$auto_play_next = 'true' === $auto_play_next ? true : false;
if ( $auto_play_next !== $default_auto_play_next ) {
$preferences_settings['auto_play_next'] = $auto_play_next;
}
}

if ( null !== $theme ) {
$preferences_settings['theme'] = $theme;
}

if ( null !== $font_scale ) {
$preferences_settings['font_scale'] = (int) $font_scale;
}

if ( null !== $learning_mood ) {
// Validate learning_mood against allowed values.
$allowed_moods = array( Options_V2::LEARNING_MODE_MODERN, Options_V2::LEARNING_MODE_KIDS );
if ( ! in_array( $learning_mood, $allowed_moods, true ) ) {
$learning_mood = Options_V2::LEARNING_MODE_MODERN;
}
$default_learning_mood = tutor_utils()->get_option( 'learning_mode', Options_V2::LEARNING_MODE_MODERN );
if ( $learning_mood !== $default_learning_mood ) {
$preferences_settings['learning_mood'] = $learning_mood;
}
}

if ( empty( $preferences_settings ) ) {
$this->json_response(
tutor_utils()->error_message( 'forbidden' ),
__( 'No changes detected', 'tutor' ),
null,
HttpHelper::STATUS_UNAUTHORIZED
HttpHelper::STATUS_OK
);
}

$auto_play_next = Input::post( 'auto_play_next', false, INPUT::TYPE_BOOL );
$theme = Input::post( 'theme', self::DEFAULT_THEME );
$font_scale = Input::post( 'font_scale', self::DEFAULT_FONT_SCALE, INPUT::TYPE_INT );

$preferences_settings = array(
'auto_play_next' => $auto_play_next,
'theme' => $theme,
'font_scale' => $font_scale,
);

$preference_data = $this->save_preferences( $preferences_settings, get_current_user_id() );

if ( false === $preference_data ) {
Expand All @@ -282,6 +314,34 @@ public function ajax_save_user_preferences() {
);
}

/**
* AJAX handler: reset current user's preferences back to defaults.
*
* @since 4.0.0
*
* @return void
*/
public function ajax_reset_user_preferences() {
tutor_utils()->check_nonce();

$user_id = get_current_user_id();
if ( ! $user_id ) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need

$this->json_response(
__( 'Failed to reset preferences', 'tutor' ),
null,
HttpHelper::STATUS_NOT_FOUND
);
}

// Delete user meta.
delete_user_meta( $user_id, self::META_KEY );

$this->json_response(
__( 'Preferences reset successfully', 'tutor' ),
null
);
}

/**
* Get default preferences.
*
Expand All @@ -297,6 +357,7 @@ private static function get_default_preferences() {
'auto_play_next' => (bool) tutor_utils()->get_option( 'autoload_next_course_content' ),
'theme' => self::DEFAULT_THEME,
'font_scale' => self::DEFAULT_FONT_SCALE,
'learning_mood' => tutor_utils()->get_option( 'learning_mode', Options_V2::LEARNING_MODE_MODERN ),
)
);

Expand Down Expand Up @@ -327,6 +388,26 @@ public static function get_theme_options() {
);
}

/**
* Get learning mood options for UI selects.
*
* @since 4.0.0
*
* @return array<int,array{label:string,value:string}>
*/
public static function get_learning_mood_options() {
return array(
array(
'label' => __( 'Modern', 'tutor' ),
'value' => Options_V2::LEARNING_MODE_MODERN,
),
array(
'label' => __( 'Kids', 'tutor' ),
'value' => Options_V2::LEARNING_MODE_KIDS,
),
);
}

/**
* Get font scale options for UI selects.
*
Expand Down
10 changes: 8 additions & 2 deletions classes/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -11132,13 +11132,19 @@ public static function get_icon_by_post_type( $post_type ): string {
}

/**
* Is kids mode active?
* Is kids mode active
*
* @since 4.0.0
*
* @return bool
*/
public static function is_kids_mode(): bool {
public function is_kids_mode(): bool {
$user_id = get_current_user_id();
if ( $user_id && User::is_student_view() ) {
$user_learning_mood = UserPreference::get( 'learning_mood', Options_V2::LEARNING_MODE_MODERN );
return Options_V2::LEARNING_MODE_KIDS === $user_learning_mood;
}

return Options_V2::LEARNING_MODE_KIDS === tutor_utils()->get_option( 'learning_mode' ) && User::is_student_view();
}

Expand Down
Loading
Loading