From c56f22486691859050459b169aad80104a947ebe Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Tue, 3 Feb 2026 18:08:22 +0600 Subject: [PATCH 01/18] Add QnA edit/update, replies loader & UI Enable inline editing and updating of Q&A questions and replies and add AJAX-powered replies loading. Key changes: - Add new read.svg icon and register it in icons/types and Icon class. - Add endpoints: UPDATE_QNA and LOAD_QNA_REPLIES. - Frontend (assets/src/js/frontend/dashboard/pages/discussions.ts): add updateQnAMutation, loadQnAReplies mutation, support for reply deletion context, reloadReplies now loads QnA or comment replies based on tab, and setEditing supports 'qna' context; update DOM after successful update and reset reply forms. - Backend (classes/Q_And_A.php): include JsonResponse trait, register tutor_qna_update and tutor_qna_load_replies handlers, implement tutor_qna_update to update comment content and load_replies to return rendered replies HTML. - UI/templates: add qna-form.php and qna-replies.php templates; update qna-card.php and qna-single.php to show/hide edit forms, wire up edit/delete actions, and adjust icon sizing/spacing. - Misc: small icon/spinner sizing and class tweaks for consistent UI. These changes let users edit their Q&A posts inline, update content via AJAX, and load reply lists dynamically for a smoother dashboard experience. --- assets/icons/read.svg | 3 + .../frontend/dashboard/pages/discussions.ts | 78 +++++++-- assets/src/js/v3/shared/icons/types.ts | 1 + assets/src/js/v3/shared/utils/endpoints.ts | 2 + classes/Icon.php | 1 + classes/Q_And_A.php | 91 ++++++++-- templates/dashboard/discussions/qna-card.php | 59 +++++-- templates/dashboard/discussions/qna-form.php | 86 ++++++++++ .../dashboard/discussions/qna-replies.php | 112 ++++++++++++ .../dashboard/discussions/qna-single.php | 162 +++++++----------- 10 files changed, 447 insertions(+), 148 deletions(-) create mode 100644 assets/icons/read.svg create mode 100644 templates/dashboard/discussions/qna-form.php create mode 100644 templates/dashboard/discussions/qna-replies.php diff --git a/assets/icons/read.svg b/assets/icons/read.svg new file mode 100644 index 0000000000..69a02a2132 --- /dev/null +++ b/assets/icons/read.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/src/js/frontend/dashboard/pages/discussions.ts b/assets/src/js/frontend/dashboard/pages/discussions.ts index c3e3901289..e13a4c2b44 100644 --- a/assets/src/js/frontend/dashboard/pages/discussions.ts +++ b/assets/src/js/frontend/dashboard/pages/discussions.ts @@ -25,10 +25,21 @@ interface ReplyQnAPayload { question_id: number; answer: string; } +interface UpdateQnAPayload { + question_id: number; + answer: string; +} + +interface DeleteQnAPayload { + question_id: number; + context?: 'question' | 'reply'; +} const FORM_ID_PREFIXES = { COMMENT_EDIT: 'lesson-comment-edit-', COMMENT_REPLY: 'lesson-comment-reply-form-', + QNA_EDIT: 'qna-edit-', + QNA_REPLY: 'qna-reply-form-', }; const MODALS = { @@ -38,10 +49,12 @@ const MODALS = { const ELEMENT_IDS = { COMMENT_TEXT_PREFIX: 'tutor-lesson-comment-text-', + QNA_TEXT_PREFIX: 'tutor-qna-text-', REPLIES_LIST_CONTAINER: 'tutor-discussion-replies-list', }; const URL_PARAMS = { + TAB: 'tab', ID: 'id', ORDER: 'order', }; @@ -64,6 +77,8 @@ const discussionsPage = () => { qnaSingleActionMutation: null as MutationState | null, deleteQnAMutation: null as MutationState | null, replyQnAMutation: null as MutationState | null, + updateQnAMutation: null as MutationState | null, + loadQnARepliesMutation: null as MutationState | null, currentAction: null as string | null, currentQuestionId: null as number | null, isSolved: false, @@ -138,7 +153,7 @@ const discussionsPage = () => { // Q&A single action mutation (read, unread, solved, important, archived). this.qnaSingleActionMutation = this.query.useMutation(this.qnaSingleAction, { - onSuccess: (response, payload: QnASingleActionPayload) => { + onSuccess: (_, payload: QnASingleActionPayload) => { const action = payload.qna_action; if (action === 'solved') { this.isSolved = !this.isSolved; @@ -161,10 +176,16 @@ const discussionsPage = () => { // Q&A delete mutation. this.deleteQnAMutation = this.query.useMutation(this.deleteQnA, { - onSuccess: () => { - const url = new URL(window.location.href); - url.searchParams.delete(URL_PARAMS.ID); - window.location.href = url.toString(); + onSuccess: (_, payload) => { + if (payload.context === 'reply') { + toast.success(__('Reply deleted successfully', 'tutor')); + modal.closeModal(MODALS.QNA_DELETE); + this.reloadReplies(); + } else { + const url = new URL(window.location.href); + url.searchParams.delete(URL_PARAMS.ID); + window.location.href = url.toString(); + } }, onError: (error: Error) => { toast.error(error.message || __('Failed to delete Q&A', 'tutor')); @@ -173,14 +194,38 @@ const discussionsPage = () => { // Q&A reply mutation. this.replyQnAMutation = this.query.useMutation(this.replyQnA, { - onSuccess: () => { + onSuccess: (_, payload) => { toast.success(__('Reply saved successfully', 'tutor')); - window.location.reload(); + this.reloadReplies(); + const formId = `qna-reply-form-${payload.question_id}`; + if (form.hasForm(formId)) { + form.reset(formId); + } }, onError: (error: Error) => { toast.error(error.message || __('Failed to save reply', 'tutor')); }, }); + + // Q&A update mutation. + this.updateQnAMutation = this.query.useMutation(this.updateQnA, { + onSuccess: (_, payload) => { + toast.success(__('Updated successfully', 'tutor')); + + // Update DOM directly for immediate feedback + const element = document.getElementById(`${ELEMENT_IDS.QNA_TEXT_PREFIX}${payload.question_id}`); + if (element) { + element.innerHTML = payload.answer; + } + + if (this.editingId === payload.question_id) { + this.setEditing(null); + } + }, + onError: (error: Error) => { + toast.error(error.message || __('Failed to update', 'tutor')); + }, + }); }, async reloadReplies(order?: string) { @@ -190,12 +235,14 @@ const discussionsPage = () => { const url = new URL(window.location.href); const commentId = parseInt(url.searchParams.get(URL_PARAMS.ID) || '0'); + const tab = url.searchParams.get(URL_PARAMS.TAB) || 'qna'; if (!commentId) return; this.loadingReplies = true; try { - const response = await wpAjaxInstance.post(endpoints.LOAD_DISCUSSION_REPLIES, { + const endpoint = tab === 'qna' ? endpoints.LOAD_QNA_REPLIES : endpoints.LOAD_COMMENT_REPLIES; + const response = await wpAjaxInstance.post(endpoint, { comment_id: commentId, order: this.repliesOrder, }); @@ -233,7 +280,7 @@ const discussionsPage = () => { return wpAjaxInstance.post(endpoints.QNA_SINGLE_ACTION, payload); }, - deleteQnA(payload: { question_id: number }) { + deleteQnA(payload: DeleteQnAPayload) { return wpAjaxInstance.post(endpoints.DELETE_DASHBOARD_QNA, payload); }, @@ -241,6 +288,10 @@ const discussionsPage = () => { return wpAjaxInstance.post(endpoints.CREATE_UPDATE_QNA, payload); }, + updateQnA(payload: UpdateQnAPayload) { + return wpAjaxInstance.post(endpoints.UPDATE_QNA, payload); + }, + async handleQnASingleAction(questionId: number, action: string, extras: Record = {}) { this.currentAction = action; this.currentQuestionId = questionId; @@ -277,15 +328,18 @@ const discussionsPage = () => { } }, - setEditing(id: number | null) { + setEditing(id: number | null, context = 'comment') { + const prefix = context === 'qna' ? FORM_ID_PREFIXES.QNA_EDIT : FORM_ID_PREFIXES.COMMENT_EDIT; + const field = context === 'qna' ? 'answer' : 'comment'; + this.editingId = id; - const formId = id ? `${FORM_ID_PREFIXES.COMMENT_EDIT}${id}` : null; + const formId = id ? `${prefix}${id}` : null; this.editingFormId = formId; if (id && formId) { this.$nextTick?.(() => { if (form.hasForm(formId)) { - form.setFocus(formId, 'comment'); + form.setFocus(formId, field); } }); } diff --git a/assets/src/js/v3/shared/icons/types.ts b/assets/src/js/v3/shared/icons/types.ts index f02cdfbeb0..08fcc61343 100644 --- a/assets/src/js/v3/shared/icons/types.ts +++ b/assets/src/js/v3/shared/icons/types.ts @@ -284,6 +284,7 @@ export const icons = [ 'quizShortAnswer', 'quizTrueFalse', 'ratings', + 'read', 'receiptPercent', 'redo', 'refresh', diff --git a/assets/src/js/v3/shared/utils/endpoints.ts b/assets/src/js/v3/shared/utils/endpoints.ts index f01660523b..0f9e2e76fc 100644 --- a/assets/src/js/v3/shared/utils/endpoints.ts +++ b/assets/src/js/v3/shared/utils/endpoints.ts @@ -92,6 +92,8 @@ const endpoints = { QNA_SINGLE_ACTION: 'tutor_qna_single_action', DELETE_DASHBOARD_QNA: 'tutor_delete_dashboard_question', CREATE_UPDATE_QNA: 'tutor_qna_create_update', + UPDATE_QNA: 'tutor_qna_update', + LOAD_QNA_REPLIES: 'tutor_qna_load_replies', // ASSIGNMENT GET_ASSIGNMENT_DETAILS: 'tutor_assignment_details', diff --git a/classes/Icon.php b/classes/Icon.php index 0cf785ebb3..3921d96fd4 100644 --- a/classes/Icon.php +++ b/classes/Icon.php @@ -300,6 +300,7 @@ final class Icon { const QUIZ_SHORT_ANSWER = 'quiz-short-answer'; const QUIZ_TRUE_FALSE = 'quiz-true-false'; const RATINGS = 'ratings'; + const READ = 'read'; const RECEIPT_PERCENT = 'receipt-percent'; const REDO = 'redo'; const REFRESH = 'refresh'; diff --git a/classes/Q_And_A.php b/classes/Q_And_A.php index 59c59976ba..b1c1b6fe9a 100644 --- a/classes/Q_And_A.php +++ b/classes/Q_And_A.php @@ -11,6 +11,7 @@ namespace TUTOR; use Tutor\Helpers\QueryHelper; +use Tutor\Traits\JsonResponse; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -21,6 +22,7 @@ * @since 1.0.0 */ class Q_And_A { + use JsonResponse; /** * List of all possible Q&A question statuses. @@ -48,27 +50,12 @@ public function __construct( $register_hooks = true ) { } add_action( 'wp_ajax_tutor_qna_create_update', array( $this, 'tutor_qna_create_update' ) ); - - /** - * Delete question - * - * @since v.1.6.4 - */ + add_action( 'wp_ajax_tutor_qna_update', array( $this, 'tutor_qna_update' ) ); add_action( 'wp_ajax_tutor_delete_dashboard_question', array( $this, 'tutor_delete_dashboard_question' ) ); - - /** - * Take action against single qna - * - * @since v2.0.0 - */ add_action( 'wp_ajax_tutor_qna_single_action', array( $this, 'tutor_qna_single_action' ) ); add_action( 'wp_ajax_tutor_qna_bulk_action', array( $this, 'process_bulk_action' ) ); - /** - * Q & A load more - * - * @since v2.0.6 - */ - add_action( 'wp_ajax_tutor_q_and_a_load_more', __CLASS__ . '::load_more' ); + add_action( 'wp_ajax_tutor_q_and_a_load_more', array( $this, 'load_more' ) ); + add_action( 'wp_ajax_tutor_qna_load_replies', array( $this, 'load_replies' ) ); } /** @@ -207,6 +194,34 @@ public function inset_qna( $qna_object ) { return $question_id; } + /** + * Update question [frontend dashboard] + * + * @since v.4.0.0 + */ + public function tutor_qna_update() { + tutor_utils()->checking_nonce(); + + $question_id = Input::post( 'question_id', 0, Input::TYPE_INT ); + if ( ! $question_id || ! tutor_utils()->can_user_manage( 'qa_question', $question_id ) ) { + $this->response_bad_request( tutor_utils()->error_message( 'authorization' ) ); + } + + $qna_text = Input::post( 'answer', '', tutor()->has_pro ? Input::TYPE_KSES_POST : Input::TYPE_TEXTAREA ); + if ( ! $qna_text ) { + $this->response_bad_request( __( 'Empty Content Not Allowed!', 'tutor' ) ); + } + + $data = array( + 'comment_content' => $qna_text, + ); + + global $wpdb; + $wpdb->update( $wpdb->comments, $data, array( 'comment_ID' => $question_id ) ); + + wp_send_json_success( array( 'message' => __( 'Comment edited successfully', 'tutor' ) ) ); + } + /** * Delete question [frontend dashboard] * @@ -433,4 +448,44 @@ public static function load_more() { $html = ob_get_clean(); wp_send_json_success( array( 'html' => $html ) ); } + + /** + * Load replies + * + * @since v4.0.0 + * + * @return void send wp_json response + */ + public function load_replies() { + tutor_utils()->checking_nonce(); + + $comment_id = Input::post( 'comment_id', 0, Input::TYPE_INT ); + $replies_order = Input::post( 'order', 'DESC' ); + $context = Input::post( 'context', 'dashboard' ); + + if ( ! $comment_id ) { + $this->response_bad_request( __( 'Invalid comment ID', 'tutor' ) ); + } + + $user_id = get_current_user_id(); + $replies = tutor_utils()->get_qa_answer_by_question( $comment_id, $replies_order, 'frontend' ); + + $template = 'dashboard.discussions.qna-replies'; + if ( 'learning-area' === $context ) { + $template = 'learning-area.subpages.qna.qna-replies'; + } + + ob_start(); + tutor_load_template( + $template, + array( + 'replies' => $replies, + 'replies_order' => $replies_order, + 'user_id' => $user_id, + ) + ); + $html = ob_get_clean(); + + wp_send_json_success( array( 'html' => $html ) ); + } } diff --git a/templates/dashboard/discussions/qna-card.php b/templates/dashboard/discussions/qna-card.php index b52854b178..b10f8bcdfa 100644 --- a/templates/dashboard/discussions/qna-card.php +++ b/templates/dashboard/discussions/qna-card.php @@ -50,6 +50,7 @@ ?>
id( $question->course_id )->render(); ?>
-
+
+ +user_id ) : ?> +
+ 'qna-edit-' . (int) $question_id, + 'default_value' => $question->comment_content, + 'submit_handler' => '(data) => updateQnAMutation?.mutate({ ...data, question_id: ' . (int) $question->comment_ID . ' })', + 'cancel_handler' => 'setEditing(null)', + 'is_pending' => 'updateQnAMutation?.isPending', + ) + ); + ?> +
+ diff --git a/templates/dashboard/discussions/qna-form.php b/templates/dashboard/discussions/qna-form.php new file mode 100644 index 0000000000..cdc58cbc82 --- /dev/null +++ b/templates/dashboard/discussions/qna-form.php @@ -0,0 +1,86 @@ + + * @link https://themeum.com + * @since 1.0.0 + */ + +use Tutor\Components\Button; +use Tutor\Components\Constants\Size; +use Tutor\Components\Constants\Variant; +use Tutor\Components\InputField; +use Tutor\Components\Constants\InputType; + +defined( 'ABSPATH' ) || exit; + +$form_id = isset( $form_id ) ? $form_id : ''; +$label = isset( $label ) ? $label : ''; +$form_class = isset( $form_class ) ? $form_class : 'tutor-mb-3'; +$default_value = isset( $default_value ) ? $default_value : ''; +$submit_handler = isset( $submit_handler ) ? $submit_handler : ''; +$cancel_handler = isset( $cancel_handler ) ? $cancel_handler : ''; +$is_pending = isset( $is_pending ) ? $is_pending : ''; +$placeholder = isset( $placeholder ) ? $placeholder : __( 'Write your question', 'tutor' ); + +?> + +
+ type( InputType::TEXTAREA ) + ->name( 'answer' ) + ->placeholder( $placeholder ) + ->attr( 'x-bind', "register('answer', { required: '" . esc_js( __( 'Please enter your response.', 'tutor' ) ) . "' })" ) + ->attr( '@keydown', 'handleKeydown($event)' ) + ->attr( '@focus', 'focused = true' ); + + if ( $label ) { + $input->label( $label ); + } + + $input->render(); + ?> + +
+
+ render_svg_icon( \TUTOR\Icon::COMMAND, 12, 12 ); ?> + + render_svg_icon( \TUTOR\Icon::ENTER, 12, 12 ); ?> + +
+
+ label( __( 'Cancel', 'tutor' ) ) + ->variant( Variant::GHOST ) + ->size( Size::X_SMALL ) + ->attr( 'type', 'button' ) + ->attr( '@click', $cancel_handler ) + ->attr( ':disabled', $is_pending ) + ->render(); + + Button::make() + ->label( __( 'Update', 'tutor' ) ) + ->variant( Variant::PRIMARY ) + ->size( Size::X_SMALL ) + ->attr( 'type', 'submit' ) + ->attr( ':disabled', $is_pending ) + ->attr( ':class', "{ 'tutor-btn-loading': {$is_pending} }" ) + ->render(); + ?> +
+
+
diff --git a/templates/dashboard/discussions/qna-replies.php b/templates/dashboard/discussions/qna-replies.php new file mode 100644 index 0000000000..554d888893 --- /dev/null +++ b/templates/dashboard/discussions/qna-replies.php @@ -0,0 +1,112 @@ + + * @link https://themeum.com + * @since 4.0.0 + */ + +defined( 'ABSPATH' ) || exit; + +use Tutor\Components\Avatar; +use Tutor\Components\Button; +use Tutor\Components\Constants\Size; +use Tutor\Components\Constants\Variant; +use Tutor\Components\Popover; +use Tutor\Components\Sorting; +use TUTOR\Icon; +use TUTOR\User; + +?> + +
+
+ + () +
+ order( $replies_order ) + ->on_change( 'reloadReplies' ) + ->bind_active_order( 'repliesOrder' ) + ->render(); + ?> +
+ +
+ +
+
+ user( $reply->user_id )->size( Size::SIZE_40 )->render(); ?> +
+
+ + comment_author ); ?> + + + comment_date_gmt ) ) ) ); + ?> + +
+
+ comment_content ); ?> +
+
+ user_id; + $can_delete = User::is_instructor_view() || $user_id === (int) $reply->user_id; + $has_menu = $can_edit || $can_delete; + ?> + +
+ +
+
+ user_id ) : ?> + + + user_id ) : ?> + + +
+
+
+ +
+ + user_id ) : ?> +
+ 'qna-edit-' . (int) $reply->comment_ID, + 'default_value' => $reply->comment_content, + 'submit_handler' => '(data) => updateQnAMutation?.mutate({ ...data, question_id: ' . (int) $reply->comment_ID . ' })', + 'cancel_handler' => 'setEditing(null)', + 'is_pending' => 'updateQnAMutation?.isPending', + 'placeholder' => __( 'Write your answer', 'tutor' ), + ) + ); + ?> +
+ +
+ +
+ diff --git a/templates/dashboard/discussions/qna-single.php b/templates/dashboard/discussions/qna-single.php index dd8716f750..344f7db2db 100644 --- a/templates/dashboard/discussions/qna-single.php +++ b/templates/dashboard/discussions/qna-single.php @@ -12,15 +12,10 @@ defined( 'ABSPATH' ) || exit; use Tutor\Components\Avatar; -use Tutor\Components\Button; use Tutor\Components\ConfirmationModal; -use Tutor\Components\Constants\InputType; use Tutor\Components\Constants\Size; -use Tutor\Components\Constants\Variant; use Tutor\Components\EmptyState; -use Tutor\Components\InputField; use Tutor\Components\PreviewTrigger; -use Tutor\Components\Sorting; use TUTOR\Icon; use TUTOR\Input; use TUTOR\User; @@ -31,8 +26,8 @@ return; } -$use_id = get_current_user_id(); -$is_user_asker = $use_id === (int) $question->user_id; +$user_id = get_current_user_id(); +$is_user_asker = $user_id === (int) $question->user_id; $replies_order = Input::get( 'order', '' ); $replies = tutor_utils()->get_qa_answer_by_question( $discussion_id, $replies_order, 'frontend' ); @@ -113,10 +108,9 @@ class="tutor-btn tutor-btn-link tutor-btn-x-small tutor-gap-2 tutor-text-subdued
-
- - -
-
+ +
comment_content ); ?>
-
- comment_ID; ?> -
- type( InputType::TEXTAREA ) - ->name( 'answer' ) - ->label( __( 'Reply', 'tutor' ) ) - ->placeholder( __( 'Just drop your response here!', 'tutor' ) ) - ->attr( 'x-bind', "register('answer', { required: '" . esc_js( __( 'Please enter a response', 'tutor' ) ) . "' })" ) - ->attr( '@focus', 'focused = true' ) - ->attr( '@keydown', 'handleKeydown($event)' ) - ->render(); - ?> -
-
- render_svg_icon( Icon::COMMAND, 12, 12 ); ?> - - render_svg_icon( Icon::ENTER, 12, 12 ); ?> - -
-
+ user_id ) : ?> +
label( __( 'Cancel', 'tutor' ) ) - ->variant( Variant::GHOST ) - ->size( Size::X_SMALL ) - ->attr( 'type', 'button' ) - ->attr( '@click', 'reset(); focused = false' ) - ->attr( ':disabled', 'replyQnAMutation?.isPending' ) - ->render(); - - Button::make() - ->label( __( 'Save', 'tutor' ) ) - ->variant( Variant::PRIMARY_SOFT ) - ->size( Size::X_SMALL ) - ->attr( 'type', 'submit' ) - ->attr( ':disabled', 'replyQnAMutation?.isPending' ) - ->attr( ':class', "{ 'tutor-btn-loading': replyQnAMutation?.isPending }" ) - ->render(); + tutor_load_template( + 'dashboard.discussions.qna-form', + array( + 'form_id' => 'qna-edit-' . (int) $question->comment_ID, + 'default_value' => $question->comment_content, + 'submit_handler' => '(data) => updateQnAMutation?.mutate({ ...data, question_id: ' . (int) $question->comment_ID . ' })', + 'cancel_handler' => 'setEditing(null)', + 'is_pending' => 'updateQnAMutation?.isPending', + ) + ); ?>
-
- -
-
- - () -
- order( $replies_order )->render(); ?> +
- -
- -
- user( $reply->user_id )->size( Size::SIZE_40 )->render(); ?> -
-
- - comment_author ); ?> - - - comment_date_gmt ) ) ) ); - ?> - -
-
- comment_content ); ?> -
-
-
- + + 'qna-reply-form-' . $question->comment_ID, + 'submit_handler' => '(data) => replyQnAMutation?.mutate({ ...data, question_id: ' . (int) $question->comment_ID . ', course_id: ' . (int) $question->course_id . ' })', + 'cancel_handler' => 'reset(); focused = false', + 'is_pending' => 'replyQnAMutation?.isPending', + 'placeholder' => __( 'Just drop your response here!', 'tutor' ), + 'label' => __( 'Reply', 'tutor' ), + 'submit_label' => __( 'Save', 'tutor' ), + 'form_class' => 'tutor-discussion-single-reply-form tutor-p-6', + ) + ); + ?> + +
+ $replies, + 'replies_order' => $replies_order, + 'user_id' => $user_id, + ) + ); + ?>
- title( __( 'Delete This Question?', 'tutor' ) ) ->message( __( 'Are you sure you want to delete this question permanently? Please confirm your choice.', 'tutor' ) ) ->confirm_text( __( 'Yes, Delete This', 'tutor' ) ) - ->confirm_handler( 'deleteQnAMutation?.mutate({ question_id: payload?.questionId })' ) + ->confirm_handler( 'deleteQnAMutation?.mutate(payload)' ) ->mutation_state( 'deleteQnAMutation' ) ->render(); ?> From a9ae2c191ba672461e571b7212e63c955c026fb3 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Feb 2026 12:09:28 +0600 Subject: [PATCH 02/18] Use secondary icon color for button svgs Change SVG color in the button mixin from $tutor-icon-idle to $tutor-icon-secondary so button icons use the secondary icon token. This aligns icon styling with the updated design tokens while leaving hover/disabled behavior unchanged. --- assets/core/scss/mixins/_buttons.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/core/scss/mixins/_buttons.scss b/assets/core/scss/mixins/_buttons.scss index 826a86bdc7..b602d1ded9 100644 --- a/assets/core/scss/mixins/_buttons.scss +++ b/assets/core/scss/mixins/_buttons.scss @@ -166,7 +166,7 @@ color: $tutor-text-primary; svg { - color: $tutor-icon-idle; + color: $tutor-icon-secondary; } &:hover:not(:disabled) { From 86832421ad43d730e6dc9c932ea26b347f718b09 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Feb 2026 12:10:07 +0600 Subject: [PATCH 03/18] Form actions style fixed --- templates/dashboard/discussions/qna-card.php | 2 +- templates/dashboard/discussions/qna-form.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/dashboard/discussions/qna-card.php b/templates/dashboard/discussions/qna-card.php index b10f8bcdfa..4b0ecc6983 100644 --- a/templates/dashboard/discussions/qna-card.php +++ b/templates/dashboard/discussions/qna-card.php @@ -149,7 +149,7 @@ class="tutor-btn tutor-btn-link tutor-btn-x-small tutor-btn-icon tutor-text-subd diff --git a/templates/dashboard/discussions/qna-form.php b/templates/dashboard/discussions/qna-form.php index cdc58cbc82..e98c56ca58 100644 --- a/templates/dashboard/discussions/qna-form.php +++ b/templates/dashboard/discussions/qna-form.php @@ -30,7 +30,7 @@
@@ -51,7 +51,7 @@ class="" ?>
From 3d2072996872d2ee225489a52e3316cb380007c7 Mon Sep 17 00:00:00 2001 From: Sazedul Haque Date: Thu, 5 Feb 2026 12:30:35 +0600 Subject: [PATCH 04/18] Use ghost button style and refine disabled state Update Q&A card buttons for solved and important actions: replace `tutor-btn-link` with `tutor-btn-ghost` for consistent styling, and tighten the `:disabled` binding so a button is only disabled when the mutation is pending for that specific action and question ID (prevents unrelated buttons from being disabled while an action is in progress). Changes applied in templates/dashboard/discussions/qna-card.php. --- templates/dashboard/discussions/qna-card.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/dashboard/discussions/qna-card.php b/templates/dashboard/discussions/qna-card.php index 4b0ecc6983..77ecda3769 100644 --- a/templates/dashboard/discussions/qna-card.php +++ b/templates/dashboard/discussions/qna-card.php @@ -100,9 +100,9 @@ class="tutor-discussion-card"