diff --git a/src/api/errors.ts b/src/api/errors.ts index 1da7531..48a8766 100644 --- a/src/api/errors.ts +++ b/src/api/errors.ts @@ -63,7 +63,62 @@ export function isShortDomainError(error: MaybeError): error is ShortDomainError return typeof error === 'string' } +/** + * Express-validator error format + */ +interface ValidationError { + type: string + value: string + msg: string + path: string + location: string +} + +/** + * Service error format (e.g., ZeroBounce) + */ +interface ServiceErrorResponse { + status: 'error' + message: string +} + +function isValidationErrorArray(data: unknown): data is ValidationError[] { + return Array.isArray(data) && data.length > 0 && 'msg' in data[0] && 'path' in data[0] +} + +function isServiceErrorResponse(data: unknown): data is ServiceErrorResponse { + return typeof data === 'object' && data !== null && 'status' in data && 'message' in data +} + +/** + * Extract error message from ApiError + * Handles multiple formats: + * 1. { errors: [{ msg, path }] } - express-validator + * 2. { status: "error", message: "..." } - service errors (ZeroBounce) + * 3. { error: "..." } - single error + * 4. { errors: { base: [...] } } - base errors + */ export function extractErrorMessage(apiError: ApiError): string { + const responseData = apiError.responseData + + // Check for service error format: { status: "error", message: "..." } + if (isServiceErrorResponse(responseData)) { + return responseData.message + } + + // Check for express-validator format: { errors: [{ msg, path }] } + if (responseData && typeof responseData === 'object' && 'errors' in responseData) { + const errors = (responseData as { errors: unknown }).errors + if (isValidationErrorArray(errors)) { + const emailError = errors.find(e => e.path === 'email') + if (emailError) { + return emailError.msg + } + return errors[0].msg + } + } + + // Fallback to original logic for other formats const body = isSingleErrorResponse(apiError.body) ? [apiError.body.error] : apiError.body.errors const errors = Array.isArray(body) ? body : body.base const firstError = errors.at(0) @@ -78,3 +133,24 @@ export function extractErrorMessage(apiError: ApiError): string { } return firstError.title } + +/** + * Extract email suggestion from error message + * Parses "Did you mean user@gmail.com?" format + */ +export function extractEmailSuggestion(apiError: ApiError): string | null { + const responseData = apiError.responseData + + if (responseData && typeof responseData === 'object' && 'errors' in responseData) { + const errors = (responseData as { errors: unknown }).errors + if (isValidationErrorArray(errors)) { + const emailError = errors.find(e => e.path === 'email') + if (emailError) { + const match = emailError.msg.match(/Did you mean (.+)\?/) + return match ? match[1] : null + } + } + } + + return null +} diff --git a/src/hooks/authentication/use-authentication.ts b/src/hooks/authentication/use-authentication.ts index aacc1d4..16d9ccb 100644 --- a/src/hooks/authentication/use-authentication.ts +++ b/src/hooks/authentication/use-authentication.ts @@ -1,4 +1,5 @@ import { ErrorPayload, useApi } from "@/api"; +import { ApiError, extractErrorMessage, extractEmailSuggestion } from "@/api/errors"; import { EGender, ESourceAuthorization, ICreateAuthorizePayload, ICreateAuthorizeResponse } from "@/api/resources/User"; import { useAuth } from "@/auth"; import { getClientTimezone } from "@/locales"; @@ -23,6 +24,7 @@ export const useAuthentication = () => { const { updateSession } = useSession(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [suggestedEmail, setSuggestedEmail] = useState(null); const [token, setToken] = useState(null); const { user, signUp } = useAuth(); const locale = i18n.language; @@ -215,8 +217,17 @@ export const useAuthentication = () => { metricService.reachGoal(EGoals.ROSE_VIDEO_CREATION_START, [EMetrics.YANDEX, EMetrics.KLAVIYO]); } dispatch(actions.status.update("registred")); - } catch (error) { - setError((error as Error).message); + } catch (err) { + // Extract error message from API error + if (err instanceof ApiError) { + const message = extractErrorMessage(err); + const suggestion = extractEmailSuggestion(err); + setError(message); + setSuggestedEmail(suggestion); + } else { + setError((err as Error).message); + setSuggestedEmail(null); + } } finally { setIsLoading(false); } @@ -251,31 +262,44 @@ export const useAuthentication = () => { metricService.reachGoal(EGoals.ROSE_VIDEO_CREATION_START, [EMetrics.YANDEX, EMetrics.KLAVIYO]); } dispatch(actions.status.update("registred")); - } catch (error) { - setError((error as Error).message); + } catch (err) { + if (err instanceof ApiError) { + setError(extractErrorMessage(err)); + } else { + setError((err as Error).message); + } } finally { setIsLoading(false); } }, [api, dispatch, getAuthorizationPayload, signUp]) + const clearError = useCallback(() => { + setError(null); + setSuggestedEmail(null); + }, []); + return useMemo( () => ({ isLoading, error, + suggestedEmail, token, user, authorization, authorizationWithPassword, - anonymousAuthorization + anonymousAuthorization, + clearError }), [ isLoading, error, + suggestedEmail, token, user, authorization, authorizationWithPassword, - anonymousAuthorization + anonymousAuthorization, + clearError ] ); }