feat: add dedicated notifications page

Replace the bell popover with an RTL notification inbox, inline Markdown details, read filters, and explicit destination and delete actions. Remove retired email-link screens and client calls.
This commit is contained in:
2026-09-26 12:23:18 +03:30
parent b49747e822
commit c714c32a58
15 changed files with 308 additions and 440 deletions

View File

@@ -261,28 +261,6 @@ class ApiClient {
});
}
async verifyEmail(token: string): Promise<Types.MessageSchema> {
const url = `${this.baseUrl}/api/auth/verify-email/${encodeURIComponent(token)}`;
const response = await fetch(url, { method: 'GET' });
if (response.ok) {
return response.json() as Promise<Types.MessageSchema>;
}
const data = (await response.json().catch(() => ({}))) as ApiErrorBody;
const errMsg: string =
(data && (data.error || data.detail)) || 'خطای ناشناخته رخ داد';
throw new Error(errMsg);
}
async resendVerification(email: string) {
return this.request<Types.MessageSchema>(
`/api/auth/resend-verification?email=${encodeURIComponent(email)}`,
{ method: 'POST' }
);
}
async getProfile() {
return this.request<Types.UserProfileSchema>('/api/auth/profile'
);
@@ -324,34 +302,6 @@ class ApiClient {
});
}
async requestPasswordReset(email: string) {
return this.request<Types.MessageSchema>('/api/auth/request-password-reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
}
async resetPasswordConfirm(token: string, new_password: string) {
return this.request<Types.MessageSchema>('/api/auth/reset-password-confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, new_password }),
});
}
async getLegacyVerifyEmailMessage(token: string) {
return this.request<Types.MessageSchema>(`/api/auth/verify-email/${encodeURIComponent(token)}`);
}
async getLegacyResetTokenMessage(token: string) {
return this.request<Types.MessageSchema>('/api/auth/reset-password-confirm', {
method: 'POST',
body: JSON.stringify({ token }),
});
}
async checkUsername(username: string) {
return this.request<Types.UsernameCheckSchema>(
`/api/auth/check-username?username=${encodeURIComponent(username)}`
@@ -1259,27 +1209,22 @@ class ApiClient {
});
}
async subscribeNewsletter(email: string) {
return this.request<{ message: string, success: boolean }>(
`/api/communications/newsletter/subscribe/`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: email }),
}
);
}
async getNotifications(params?: { limit?: number; offset?: number; type?: string }) {
async getNotifications(params?: { limit?: number; offset?: number; type?: string; status?: 'all' | 'unread' | 'read' }) {
const query = new URLSearchParams();
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
if (params?.type) query.set('type', params.type);
if (params?.status) query.set('status', params.status);
return this.request<Types.NotificationListSchema>(
`/api/notifications/${query.toString() ? `?${query.toString()}` : ''}`
);
}
async getNotification(id: string) {
return this.request<Types.NotificationDetailSchema>(`/api/notifications/${encodeURIComponent(id)}`);
}
async markNotificationSeen(id: string) {
return this.request<Types.NotificationSeenResponseSchema>('/api/notifications/mark-seen', {
method: 'POST',