fix: prevent duplicate channel action toasts (#5015)

* fix: prevent duplicate channel action toasts

* fix: localize api error fallbacks
This commit is contained in:
yyhhyyyyyy
2026-05-26 10:20:54 +08:00
committed by GitHub
parent a64f26d1d2
commit ad224ecf5b
6 changed files with 182 additions and 81 deletions
+33 -27
View File
@@ -16,11 +16,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import axios from 'axios'
import i18next from 'i18next'
import axios, { type AxiosRequestConfig } from 'axios'
import { t } from 'i18next'
import { toast } from 'sonner'
import { useAuthStore } from '@/stores/auth-store'
declare module 'axios' {
export interface AxiosRequestConfig {
skipBusinessError?: boolean
skipErrorHandler?: boolean
disableDuplicate?: boolean
}
}
export type ApiRequestConfig = AxiosRequestConfig
// ============================================================================
// Axios Instance Configuration
// ============================================================================
@@ -46,14 +56,11 @@ export const api = axios.create({
const inFlightGet = new Map<string, Promise<unknown>>()
const originalGet = api.get.bind(api)
api.get = ((url: string, config = {}) => {
const disableDuplicate = (config as unknown as Record<string, unknown>)
?.disableDuplicate
api.get = ((url: string, config: ApiRequestConfig = {}) => {
const disableDuplicate = config.disableDuplicate
if (disableDuplicate) return originalGet(url, config)
const params = (config as unknown as Record<string, unknown>)?.params
? JSON.stringify((config as unknown as Record<string, unknown>).params)
: '{}'
const params = config.params ? JSON.stringify(config.params) : '{}'
const key = `${url}?${params}`
// Return existing in-flight request if available
@@ -72,8 +79,7 @@ api.get = ((url: string, config = {}) => {
// Handle business logic errors and HTTP errors globally
api.interceptors.response.use(
(response) => {
const skipBusiness = (response.config as unknown as Record<string, unknown>)
?.skipBusinessError
const skipBusiness = response.config.skipBusinessError
// Unified business response format: { success, message, data }
if (
@@ -84,7 +90,7 @@ api.interceptors.response.use(
) {
if (!response.data.success) {
// Show error toast for business failures
const msg = response.data.message || 'Request failed'
const msg = response.data.message || t('Request failed')
toast.error(msg)
}
}
@@ -92,23 +98,23 @@ api.interceptors.response.use(
},
(error) => {
const skip = error?.config?.skipErrorHandler
if (!skip) {
const status = error?.response?.status
const status = error?.response?.status
if (status === 401) {
// Unauthorized: clear auth state and show toast
toast.error(i18next.t('Session expired!'))
try {
useAuthStore.getState().auth.reset()
} catch {
/* empty */
}
} else {
// Other errors: show error message from response or default
const msg =
error?.response?.data?.message || error?.message || 'Request error'
toast.error(msg)
if (status === 401) {
try {
useAuthStore.getState().auth.reset()
} catch {
/* empty */
}
if (!skip) {
toast.error(t('Session expired!'))
}
} else if (!skip) {
// Other errors: show error message from response or default
const msg =
error?.response?.data?.message || error?.message || t('Request failed')
toast.error(msg)
}
return Promise.reject(error)
}
@@ -175,7 +181,7 @@ export async function getSelf() {
const res = await api.get('/api/user/self', {
// Avoid global 401 toast during guards/preloads
skipErrorHandler: true,
} as Record<string, unknown>)
})
return res.data
}