improvement: error handeling structure changed and unified all across the app. user no longer sees inappropriate messages.
This commit is contained in:
@@ -88,13 +88,34 @@ apiClient.interceptors.response.use(
|
||||
}
|
||||
}
|
||||
|
||||
const responseData = error.response?.data as
|
||||
| { success?: false; error?: { code?: string; statusCode?: number; details?: ApiError['details'] } }
|
||||
| { message?: string | string[] }
|
||||
| undefined;
|
||||
|
||||
if (responseData && 'error' in responseData && responseData.error?.code) {
|
||||
const apiError: ApiError = {
|
||||
statusCode: responseData.error.statusCode ?? error.response?.status ?? 500,
|
||||
code: responseData.error.code,
|
||||
details: responseData.error.details,
|
||||
};
|
||||
return Promise.reject(apiError);
|
||||
}
|
||||
|
||||
const apiError: ApiError = {
|
||||
statusCode: error.response?.status || 500,
|
||||
message:
|
||||
(error.response?.data as any)?.message ||
|
||||
error.message ||
|
||||
'An unexpected error occurred',
|
||||
error: (error.response?.data as any)?.error,
|
||||
code:
|
||||
error.response?.status === 401
|
||||
? 'AUTH_UNAUTHORIZED'
|
||||
: error.response?.status === 403
|
||||
? 'PERMISSION_DENIED'
|
||||
: error.response?.status === 404
|
||||
? 'NOT_FOUND'
|
||||
: error.response?.status === 409
|
||||
? 'CONFLICT'
|
||||
: (error.response?.status ?? 500) >= 500
|
||||
? 'INTERNAL_ERROR'
|
||||
: 'BAD_REQUEST',
|
||||
};
|
||||
|
||||
return Promise.reject(apiError);
|
||||
|
||||
@@ -16,6 +16,21 @@ import {
|
||||
rememberAccessTokenExpiresAt,
|
||||
startProactiveSessionRefresh,
|
||||
} from '@/lib/auth/proactiveRefresh';
|
||||
import { asApiError, legacyStatusCode, type ApiError } from '@/types/api';
|
||||
|
||||
function toApiError(err: unknown): ApiError {
|
||||
return (
|
||||
asApiError(err) ?? {
|
||||
statusCode: legacyStatusCode(err) ?? 500,
|
||||
code:
|
||||
legacyStatusCode(err) === 401
|
||||
? 'AUTH_UNAUTHORIZED'
|
||||
: legacyStatusCode(err) === 403
|
||||
? 'PERMISSION_DENIED'
|
||||
: 'INTERNAL_ERROR',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
@@ -23,7 +38,7 @@ interface AuthContextType {
|
||||
currentOrganization: Organization | null;
|
||||
isLoading: boolean;
|
||||
isAuthReady: boolean;
|
||||
error: string | null;
|
||||
apiError: ApiError | null;
|
||||
registerTrial: (
|
||||
email: string,
|
||||
password: string,
|
||||
@@ -56,7 +71,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [currentOrganization, setCurrentOrganization] = useState<Organization | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isAuthReady, setIsAuthReady] = useState(false); // ✅ KEY FIX
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [apiError, setApiError] = useState<ApiError | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -135,9 +150,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const status =
|
||||
(err as { statusCode?: number })?.statusCode ??
|
||||
(err as { response?: { status?: number } })?.response?.status;
|
||||
const status = legacyStatusCode(err);
|
||||
|
||||
// Access token may have expired while refresh cookie is still valid (e.g. JWT_EXPIRES_IN=15m).
|
||||
if (status === 401) {
|
||||
@@ -251,7 +264,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setApiError(null);
|
||||
|
||||
const response = await authApi.registerTrial({
|
||||
email,
|
||||
@@ -282,7 +295,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('errorRegistrationFailed'));
|
||||
setApiError(toApiError(err));
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -297,7 +310,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setApiError(null);
|
||||
|
||||
const response = await authApi.login({ email, password, rememberMe });
|
||||
|
||||
@@ -325,7 +338,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('errorLoginFailed'));
|
||||
setApiError(toApiError(err));
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -348,7 +361,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
setUser(null);
|
||||
setOrganizations([]);
|
||||
setCurrentOrganization(null);
|
||||
setError(null);
|
||||
setApiError(null);
|
||||
clearAccessTokenExpiresAt();
|
||||
setIsAuthReady(true);
|
||||
router.replace('/');
|
||||
@@ -387,7 +400,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
setApiError(toApiError(err));
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -402,7 +415,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setApiError(null);
|
||||
|
||||
const createResponse = await authApi.createOrganization({
|
||||
organizationName,
|
||||
@@ -420,7 +433,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return createResponse.data.organization.id as string;
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('errorCreateOrganization'));
|
||||
setApiError(toApiError(err));
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -431,7 +444,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await checkAuth();
|
||||
}, [checkAuth]);
|
||||
|
||||
const clearError = useCallback(() => setError(null), []);
|
||||
const clearError = useCallback(() => setApiError(null), []);
|
||||
|
||||
const setUserLanguage = useCallback((language: string) => {
|
||||
const normalized = isAppLocale(language) ? language : 'en';
|
||||
@@ -445,7 +458,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
currentOrganization,
|
||||
isLoading,
|
||||
isAuthReady,
|
||||
error,
|
||||
apiError,
|
||||
registerTrial,
|
||||
login,
|
||||
logout,
|
||||
@@ -461,7 +474,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
currentOrganization,
|
||||
isLoading,
|
||||
isAuthReady,
|
||||
error,
|
||||
apiError,
|
||||
registerTrial,
|
||||
login,
|
||||
logout,
|
||||
|
||||
Reference in New Issue
Block a user