Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
db1f684
chore: update Storybook addons and core package to version 10.1.2
nur-alam Dec 1, 2025
4f16c9a
Merge branch 'dashboard-skeleton' of github.com:themeum/tutor into st…
nur-alam Dec 2, 2025
2ba46e8
Update dashboard styles and components for improved layout and functi…
nur-alam Dec 4, 2025
2dca47e
Merge branch '4.0.0-dev' of github.com:themeum/tutor into student-cou…
nur-alam Dec 4, 2025
12894f7
Refactor courses dashboard: add new page navigation component and upd…
nur-alam Dec 12, 2025
8019f20
Merge branch '4.0.0-dev' of github.com:themeum/tutor into student-cou…
nur-alam Dec 12, 2025
eec3977
Refactor course templates: improve structure and readability
nur-alam Dec 12, 2025
2a67cd8
Refactor dashboard course templates: enhance styles, improve structur…
nur-alam Dec 15, 2025
b9a5e64
Merge branch '4.0.0-dev' of github.com:themeum/tutor into student-cou…
nur-alam Dec 15, 2025
817eb32
Refactor course dashboard: rename routes, keep enrolled courses templ…
nur-alam Dec 16, 2025
a75dc70
Fix variable name typo in enrolled courses template
nur-alam Dec 17, 2025
ca24112
Refactor course navigation: rename 'enrolled-courses' to 'courses', u…
nur-alam Dec 17, 2025
80c7c7d
Refactor courses dashboard: simplify navigation by removing unnecessa…
nur-alam Dec 22, 2025
f8a189c
Merge branch '4.0.0-dev' of github.com:themeum/tutor into student-cou…
nur-alam Dec 22, 2025
5d75b8d
Merge branch '4.0.0-dev' of github.com:themeum/tutor into student-cou…
nur-alam Dec 23, 2025
3fca256
Add EmptyState component for no courses found scenario
nur-alam Dec 23, 2025
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
21 changes: 21 additions & 0 deletions assets/scss/frontend/dashboard/_courses.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@use '@Core/scss/tokens' as *;

.tutor-dashboard-courses-wrapper {
background: $tutor-surface-base;
border-radius: $tutor-radius-3xl;
border: 1px solid $tutor-border-idle;

.tutor-dashboard-courses {
border-top: 1px solid $tutor-border-idle;
}

.tutor-courses-thumb {
position: relative;
flex-basis: 200px;
.tutor-badge {
top: 4px;
right: 4px;
z-index: 1;
}
}
}
1 change: 1 addition & 0 deletions assets/scss/frontend/dashboard/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

// Page components
@forward 'overview';
@forward 'courses';
@forward 'my-courses';
@forward 'assignments';
@forward 'quiz-attempts';
Expand Down
11 changes: 4 additions & 7 deletions assets/scss/frontend/dashboard/_progress-card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
gap: $tutor-spacing-5;
border: 1px solid $tutor-border-idle;
border-radius: $tutor-radius-2xl;
height: 128px;
transition: all 0.2s ease;
cursor: pointer;
overflow: hidden;
Expand All @@ -22,9 +21,8 @@

&-thumbnail {
position: relative;
flex: 0 0 auto;
max-width: 220px;
aspect-ratio: 220 / 112;
flex: 0 0 200px;
aspect-ratio: 16 / 9;
border-radius: $tutor-radius-lg;
overflow: hidden;

Expand All @@ -41,7 +39,7 @@
}

&-content {
flex: 1 1 auto;
flex: 1 1 calc(100% - 224px);
@include tutor-flex(column, stretch, space-between);
}

Expand Down Expand Up @@ -171,5 +169,4 @@
padding: $tutor-spacing-6 $tutor-spacing-4 0;
}
}
}

}
2 changes: 1 addition & 1 deletion classes/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@
"SELECT count(umeta_id)
FROM {$wpdb->usermeta}
WHERE user_id = %d
AND meta_key IN ('{$in_ids}')

Check failure on line 814 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Use placeholders and $wpdb->prepare(); found interpolated variable {$in_ids} at AND meta_key IN ('{$in_ids}')

",
$user_id
)
Expand Down Expand Up @@ -1359,7 +1359,7 @@
AND post_type = %s
AND post_parent = %d
AND post_author = %d
{$status_clause};

Check failure on line 1362 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Use placeholders and $wpdb->prepare(); found interpolated variable {$status_clause} at {$status_clause};

",
'tutor_enrolled',
$course_id,
Expand Down Expand Up @@ -1392,7 +1392,7 @@
$user_id = $this->get_user_id( $user_id );

// Delete Quiz submissions.
$attempts = \Tutor\Models\QuizModel::get_quiz_attempts_by_course_ids( $start = 0, $limit = 99999999, $course_ids = array( $course_id ), $search_filter = '', $course_filter = '', $date_filter = '', $order_filter = '', $user_id = $user_id, false, true );

Check failure on line 1395 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Assignments must be the first block of code on a line

if ( is_array( $attempts ) ) {
$attempt_ids = array_map(
Expand Down Expand Up @@ -1570,7 +1570,7 @@
ON topic.ID = items.post_parent
WHERE topic.post_parent = %d
AND items.post_status = %s
" . ( $post_type ? " AND items.post_type='{$post_type}' " : '' ) . '

Check failure on line 1573 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Use placeholders and $wpdb->prepare(); found :

Check failure on line 1573 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Use placeholders and $wpdb->prepare(); found interpolated variable {$post_type} at " AND items.post_type='{$post_type}' "

Check failure on line 1573 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Use placeholders and $wpdb->prepare(); found ?

Check failure on line 1573 in classes/Utils.php

View workflow job for this annotation

GitHub Actions / WPCS

Use placeholders and $wpdb->prepare(); found $post_type
ORDER BY topic.menu_order ASC,
items.menu_order ASC;
',
Expand Down Expand Up @@ -9470,7 +9470,7 @@
'title' => __( 'Home', 'tutor' ),
'icon' => Icon::HOME_FILL,
),
'enrolled-courses' => array(
'courses' => array(
'title' => __( 'Courses', 'tutor' ),
'icon' => Icon::COURSES,
),
Expand Down
2 changes: 1 addition & 1 deletion classes/WooCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ public function redirect_to_enrolled_courses( $order_id ) {
// get woo order details.
$order = wc_get_order( $order_id );
$tutor_product = false;
$url = tutor_utils()->tutor_dashboard_url() . 'enrolled-courses/';
$url = tutor_utils()->tutor_dashboard_url() . 'courses/';

/**
* Loop though each WC order item.
Expand Down
2 changes: 1 addition & 1 deletion cypress/config/page-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const frontendUrls = {
dashboard: {
DASHBOARD: dashboardUrl,
MY_PROFILE: `${dashboardUrl}/my-profile/`,
ENROLLED_COURSES: `${dashboardUrl}/enrolled-courses/`,
ENROLLED_COURSES: `${dashboardUrl}/courses/`,
WISHLIST: `${dashboardUrl}/wishlist/`,
REVIEWS: `${dashboardUrl}/reviews/`,
MY_QUIZ_ATTEMPTS: `${dashboardUrl}/my-quiz-attempts/`,
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 24 additions & 44 deletions templates/core-components/nav.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,6 @@

use TUTOR\Icon;

/**
* Expected $args structure:
*
* $args = array(
* 'items' => array(
* array(
* 'type' => 'link', // 'link' or 'dropdown'
* 'label' => 'Wishlist',
* 'icon' => Icon::WISHLIST,
* 'url' => '#',
* 'active' => false,
* ),
* array(
* 'type' => 'dropdown',
* 'icon' => Icon::ENROLLED,
* 'active' => true,
* 'options' => array(
* array(
* 'label' => 'Active',
* 'icon' => Icon::PLAY_LINE,
* 'url' => '#',
* 'active' => false,
* ),
* array(
* 'label' => 'Enrolled',
* 'icon' => Icon::ENROLLED,
* 'url' => '#',
* 'active' => true,
* ),
* ),
* ),
* ),
* 'variant' => 'primary', // 'primary' or 'secondary' (default: 'primary')
* 'size' => 'md', // 'sm', 'md', or 'lg' (default: 'md')
* );
*/

if ( empty( $items ) ) {
return;
}
Expand Down Expand Up @@ -75,32 +38,44 @@
* @since 4.0.0
*
* @param array $options Array of dropdown options.
* @return string The label of the active option, or the first option's label if none are active.
* @return array The label and count of the active option, or the first option's label if none are active.
*/
$get_active_dropdown_label = function ( $options ) {
function get_active_dropdown_label( $options ) {
$active_info = array(
'label' => $options[0]['label'],
'count' => $options[0]['count'] ?? 0,
);
foreach ( $options as $option ) {
if ( ! empty( $option['active'] ) ) {
return $option['label'] ?? '';
$active_info['label'] = $option['label'];
}
if ( ! empty( $option['active'] ) && ! empty( $option['count'] ) ) {
$active_info['count'] = $option['count'];
}
}
return $options[0]['label'] ?? '';
};
return $active_info;
}
?>

<div class="tutor-nav tutor-nav-<?php echo esc_attr( $size ); ?> tutor-nav-<?php echo esc_attr( $variant ); ?>">
<?php foreach ( $items as $item ) : ?>
<?php if ( 'dropdown' === $item['type'] ) : ?>
<?php
$options = $item['options'] ?? array();
$active_label = $get_active_dropdown_label( $options );
$active_info = get_active_dropdown_label( $options );
?>
<div x-data="tutorPopover({ placement: 'bottom-start', offset: 4 })">
<button x-ref="trigger" @click="toggle()"
class="tutor-nav-item<?php echo ! empty( $item['active'] ) ? ' active' : ''; ?>">
<?php if ( ! empty( $item['icon'] ) ) : ?>
<?php tutor_utils()->render_svg_icon( $item['icon'], $icon_size, $icon_size ); ?>
<?php endif; ?>
<?php echo esc_html( $active_label ); ?>
<?php echo esc_html( $active_info['label'] ); ?>
<?php if ( ! empty( $active_info['count'] ) ) : ?>
<span class="tutor-ml-1">
(<?php echo esc_html( $active_info['count'] ); ?>)
</span>
<?php endif; ?>
<?php
tutor_utils()->render_svg_icon(
Icon::CHEVRON_DOWN_2,
Expand All @@ -120,6 +95,11 @@ class="tutor-nav-dropdown-item <?php echo ! empty( $option['active'] ) ? 'active
<?php tutor_utils()->render_svg_icon( $option['icon'], $icon_size, $icon_size ); ?>
<?php endif; ?>
<?php echo esc_html( $option['label'] ?? '' ); ?>
<?php if ( ! empty( $option['count'] ) ) : ?>
<span class="ml-1">
(<?php echo esc_html( $option['count'] ); ?>)
</span>
<?php endif; ?>
</a>
<?php endforeach; ?>
</div>
Expand Down
138 changes: 138 additions & 0 deletions templates/dashboard/courses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* Courses Page
*
* @package Tutor\Templates
* @subpackage Dashboard
* @author Themeum <[email protected]>
* @link https://themeum.com
* @since 1.4.3
*/

use TUTOR\Icon;
use TUTOR\Input;
use Tutor\Components\Pagination;
use Tutor\Models\CourseModel;
use Tutor\Components\EmptyState;

// Pagination.
$per_page = tutor_utils()->get_option( 'pagination_per_page', 10 );
$paged = max( 1, Input::get( 'current_page', 1, Input::TYPE_INT ) );
$offset = ( $per_page * $paged ) - $per_page;

$post_type_query = Input::get( 'type', '' );

$post_type_args = $post_type_query ? array( 'type' => $post_type_query ) : array();

$page_tabs = apply_filters(
'tutor_enrolled_courses_page_tabs',
array(
'courses' => __( 'Enrolled Courses', 'tutor' ),
'courses/active-courses' => __( 'Active Courses', 'tutor' ),
'courses/completed-courses' => __( 'Completed Courses', 'tutor' ),
),
$post_type_query
);

// Default tab set.
( ! isset( $active_tab, $page_tabs[ $active_tab ] ) ) ? $active_tab = 'courses' : 0;

// Get Paginated course list.
$courses_list_array = array(
'courses' => CourseModel::get_enrolled_courses_by_user( get_current_user_id(), array( 'private', 'publish' ), $offset, $per_page ),
'courses/active-courses' => CourseModel::get_active_courses_by_user( null, $offset, $per_page ),
'courses/completed-courses' => CourseModel::get_completed_courses_by_user( null, $offset, $per_page ),
);

// Get Full course list.
$full_course_list_array = array(
'courses' => CourseModel::get_enrolled_courses_by_user( get_current_user_id(), array( 'private', 'publish' ) ),
'courses/active-courses' => CourseModel::get_active_courses_by_user(),
'courses/completed-courses' => CourseModel::get_completed_courses_by_user(),
);

// Count course list based on query param.
$enrolled_course_count = $full_course_list_array['courses'] ? $full_course_list_array['courses']->found_posts : 0;
$active_course_count = $full_course_list_array['courses/active-courses'] ? $full_course_list_array['courses/active-courses']->found_posts : 0;
$completed_course_count = $full_course_list_array['courses/completed-courses'] ? $full_course_list_array['courses/completed-courses']->found_posts : 0;

$courses_tab = array(
array(
'type' => 'dropdown',
'icon' => Icon::ENROLLED,
'active' => ( ( 'courses' === $active_tab ) || ( 'courses/active-courses' === $active_tab ) || ( 'courses/completed-courses' === $active_tab ) ) ? true : false,
'options' => array(
array(
'label' => __( 'Enrolled', 'tutor' ),
'icon' => Icon::ENROLLED,
'url' => esc_url( add_query_arg( $post_type_args, tutor_utils()->get_tutor_dashboard_page_permalink( 'courses' ) ) ),
'active' => 'courses' === $active_tab ? true : false,
'count' => $enrolled_course_count,
),
array(
'label' => __( 'Active', 'tutor' ),
'icon' => Icon::PLAY_LINE,
'url' => esc_url( add_query_arg( $post_type_args, tutor_utils()->get_tutor_dashboard_page_permalink( 'courses/active-courses' ) ) ),
'active' => 'courses/active-courses' === $active_tab ? true : false,
'count' => $active_course_count,
),
array(
'label' => __( 'Complete', 'tutor' ),
'icon' => Icon::COMPLETED_CIRCLE,
'url' => esc_url( add_query_arg( $post_type_args, tutor_utils()->get_tutor_dashboard_page_permalink( 'courses/completed-courses' ) ) ),
'active' => 'courses/completed-courses' === $active_tab ? true : false,
'count' => $completed_course_count,
),
),
),
);

// Prepare course list based on page tab.
$courses_list = $courses_list_array[ $active_tab ];
$paginated_courses_list = $full_course_list_array[ $active_tab ];

?>

<div class="tutor-dashboard-courses-wrapper">

<!-- Courses nav -->
<div class="tutor-dashboard-page-nav tutor-p-6">
<?php
tutor_load_template(
'core-components.nav',
array(
'items' => $courses_tab,
'size' => 'sm',
'variant' => 'primary',
)
);
?>
</div>

<!-- courses list -->
<div class="tutor-dashboard-courses tutor-flex tutor-flex-column tutor-gap-4 tutor-p-6">
<?php
if ( $courses_list && $courses_list->have_posts() ) :
while ( $courses_list->have_posts() ) :
$courses_list->the_post();
tutor_load_template( 'dashboard.courses.course-card' );
endwhile;
?>
<div class="tutor-dashboard-courses-pagination tutor-pt-6">
<?php
Pagination::make()
->current( $paged )
->total( $courses_list->found_posts )
->limit( $per_page )
->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) )
->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) )
->render();
?>
</div>
<?php
else :
EmptyState::make()->title( 'No Courses Found' )->render();
endif;
?>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
* @since 1.4.3
*/

$active_tab = 'enrolled-courses/active-courses';
require dirname( __DIR__ ) . DIRECTORY_SEPARATOR . 'enrolled-courses.php';
$active_tab = 'courses/active-courses';
require dirname( __DIR__ ) . DIRECTORY_SEPARATOR . 'courses.php';

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
* @since 1.4.3
*/

$active_tab = 'enrolled-courses/completed-courses';
require dirname( __DIR__ ) . DIRECTORY_SEPARATOR . 'enrolled-courses.php';
$active_tab = 'courses/completed-courses';
require dirname( __DIR__ ) . DIRECTORY_SEPARATOR . 'courses.php';

Loading
Loading