Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c56f224
Add QnA edit/update, replies loader & UI
sazedul-haque Feb 3, 2026
99b40b6
Merge branch 'refactor-dashboard-comments' into refactor-dashboard-qna
sazedul-haque Feb 4, 2026
a9ae2c1
Use secondary icon color for button svgs
sazedul-haque Feb 5, 2026
8683242
Form actions style fixed
sazedul-haque Feb 5, 2026
34215c4
Merge branch 'refactor-dashboard-comments' into refactor-dashboard-qna
sazedul-haque Feb 5, 2026
3d20729
Use ghost button style and refine disabled state
sazedul-haque Feb 5, 2026
11e1ab1
Add tooltips to QnA actions; update tooltip styles
sazedul-haque Feb 5, 2026
e7534a1
Heading added for smaller devices
sazedul-haque Feb 5, 2026
563f467
QnA Edit option added to the list
sazedul-haque Feb 5, 2026
388a07e
Translators comment added
sazedul-haque Feb 5, 2026
3534af3
Refactor QnA frontend and split templates
sazedul-haque Feb 6, 2026
27e5fa4
Discussions: unify errors and fix templates
sazedul-haque Feb 6, 2026
9c94962
Handle Q&A AJAX errors and unify responses
sazedul-haque Feb 6, 2026
3cabf89
Preserve original error in mutation handlers
sazedul-haque Feb 6, 2026
a49ad2e
Register Q&A menu in Q_And_A, remove from Template
sazedul-haque Feb 6, 2026
491846c
Merge branch 'refactor-dashboard-comments' into refactor-dashboard-qna
b-l-i-n-d Feb 17, 2026
b53d66b
Address QnA PR review comments
b-l-i-n-d Feb 17, 2026
8d90983
Q&A menu adding condition updated
shewa12 Feb 18, 2026
13891e9
Used the null coalesce operator
shewa12 Feb 18, 2026
fc2de02
Refactored: Used null coalesce operator instead of isset
shewa12 Feb 18, 2026
2ed8905
QNA component file renamed
shewa12 Feb 18, 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
5 changes: 3 additions & 2 deletions assets/core/scss/components/_tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@
@include tutor-typography('tiny', 'regular', 'primary-inverse');
position: fixed;
z-index: 1070;
max-width: 200px;
max-width: 180px;
padding: $tutor-spacing-4;
background-color: $tutor-surface-dark;
border-radius: $tutor-radius-sm;
box-shadow: $tutor-shadow-md;
word-wrap: break-word;
text-align: start;
text-align: center;

&-large {
max-width: 320px;
padding: $tutor-spacing-5;
text-align: start;
}

&-arrow-start {
Expand Down
2 changes: 1 addition & 1 deletion assets/core/scss/mixins/_buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
color: $tutor-text-primary;

svg {
color: $tutor-icon-idle;
color: $tutor-icon-secondary;
}

&:hover:not(:disabled) {
Expand Down
11 changes: 3 additions & 8 deletions assets/core/ts/services/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,16 @@ export class QueryService {

return result;
} catch (err) {
const error = {
message: (err as Error).message || 'Mutation failed',
code: (err as { code?: string }).code,
} as TError;

(this as unknown as MutationState<TData, TVariables, TError>).error = error;
(this as unknown as MutationState<TData, TVariables, TError>).error = err as TError;
(this as unknown as MutationState<TData, TVariables, TError>).isError = true;

if (options.onError) {
options.onError(error, variables);
options.onError(err as TError, variables);
}

// onSettled callback - always called
if (options.onSettled) {
options.onSettled(null, error, variables);
options.onSettled(null, err as TError, variables);
}

throw err;
Expand Down
3 changes: 3 additions & 0 deletions assets/icons/read.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 66 additions & 12 deletions assets/src/js/frontend/dashboard/pages/discussions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,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 = {
Expand All @@ -39,10 +50,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',
};
Expand All @@ -65,6 +78,8 @@ const discussionsPage = () => {
qnaSingleActionMutation: null as MutationState<unknown, unknown> | null,
deleteQnAMutation: null as MutationState<unknown, unknown> | null,
replyQnAMutation: null as MutationState<unknown, unknown> | null,
updateQnAMutation: null as MutationState<unknown, UpdateQnAPayload> | null,
loadQnARepliesMutation: null as MutationState<unknown, unknown> | null,
currentAction: null as string | null,
currentQuestionId: null as number | null,
isSolved: false,
Expand Down Expand Up @@ -139,7 +154,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;
Expand All @@ -162,10 +177,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(convertToErrorMessage(error));
Expand All @@ -174,9 +195,33 @@ 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 = `${FORM_ID_PREFIXES.QNA_REPLY}${payload.question_id}`;
if (form.hasForm(formId)) {
form.reset(formId);
}
},
onError: (error: Error) => {
toast.error(convertToErrorMessage(error));
},
});

// 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(convertToErrorMessage(error));
Expand All @@ -191,12 +236,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,
});
Expand Down Expand Up @@ -234,14 +281,18 @@ 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);
},

replyQnA(payload: ReplyQnAPayload) {
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<string, string> = {}) {
this.currentAction = action;
this.currentQuestionId = questionId;
Expand Down Expand Up @@ -278,15 +329,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);
}
});
}
Expand Down
Loading
Loading