authentication error handling

This commit is contained in:
tapframe 2026-02-17 02:23:53 +05:30
parent b5ae55da9e
commit 36d93c270a
3 changed files with 100 additions and 12 deletions

View file

@ -11,6 +11,33 @@ import { useToast } from '../contexts/ToastContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width, height } = Dimensions.get('window');
const EMAIL_CONFIRMATION_REQUIRED_PREFIX = '__EMAIL_CONFIRMATION__';
const normalizeAuthErrorMessage = (input: string): string => {
const raw = (input || '').trim();
if (!raw) return 'Authentication failed';
let parsed: any = null;
if (raw.startsWith('{') && raw.endsWith('}')) {
try {
parsed = JSON.parse(raw);
} catch {
parsed = null;
}
}
const code = (parsed?.error_code || parsed?.code || '').toString().toLowerCase();
const message = (parsed?.msg || parsed?.message || raw).toString();
if (code === 'invalid_credentials' || /invalid login credentials/i.test(message)) {
return 'Invalid email or password';
}
if (code === 'email_not_confirmed' || /email not confirmed/i.test(message)) {
return 'Email not confirmed. Check your inbox or Spam/Junk folder, verify your account, then sign in.';
}
return message;
};
const AuthScreen: React.FC = () => {
const { currentTheme } = useTheme();
@ -168,8 +195,19 @@ const AuthScreen: React.FC = () => {
setError(null);
const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password);
if (err) {
setError(err);
showError('Authentication Failed', err);
if (mode === 'signup' && err.startsWith(EMAIL_CONFIRMATION_REQUIRED_PREFIX)) {
setError(null);
setMode('signin');
setPassword('');
setConfirmPassword('');
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
setLoading(false);
return;
}
const cleanError = normalizeAuthErrorMessage(err);
setError(cleanError);
showError('Authentication Failed', cleanError);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
} else {
const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful';
@ -261,7 +299,24 @@ const AuthScreen: React.FC = () => {
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? headerHeight : 0}
>
<Animated.View style={styles.centerContainer}>
<Animated.View
style={[
styles.centerContainer,
keyboardHeight > 0
? {
paddingTop: Platform.OS === 'ios' ? 6 : 10,
transform: [
{
translateY:
Platform.OS === 'ios'
? -Math.min(120, keyboardHeight * 0.35)
: -Math.min(84, keyboardHeight * 0.22),
},
],
}
: null,
]}
>
<Animated.View style={[styles.card, {
backgroundColor: Platform.OS === 'android' ? '#121212' : 'rgba(255,255,255,0.02)',
borderColor: Platform.OS === 'android' ? '#1f1f1f' : 'rgba(255,255,255,0.06)',

View file

@ -11,6 +11,7 @@ export type AuthUser = {
const USER_DATA_KEY = '@user:data';
const USER_SCOPE_KEY = '@user:current';
const EMAIL_CONFIRMATION_REQUIRED_PREFIX = '__EMAIL_CONFIRMATION__';
class AccountService {
private static instance: AccountService;
@ -37,11 +38,18 @@ class AccountService {
async signUpWithEmail(email: string, password: string): Promise<{ user?: AuthUser; error?: string }> {
const result = await supabaseSyncService.signUpWithEmail(email, password);
if (result.error || !result.user) {
return { error: result.error || 'Sign up failed' };
if (result.error) {
return { error: result.error };
}
const mapped = this.mapSupabaseUser(result.user);
const sessionUser = supabaseSyncService.getCurrentSessionUser();
if (!sessionUser) {
return {
error: `${EMAIL_CONFIRMATION_REQUIRED_PREFIX}Account created. Check your email to verify, then sign in.`,
};
}
const mapped = this.mapSupabaseUser(sessionUser);
await this.persistUser(mapped);
try {

View file

@ -193,10 +193,8 @@ class SupabaseSyncService {
await this.setSession(response.session);
}
if (!response.user) {
return { error: 'Signup failed: user not returned by Supabase' };
}
// In email-confirmation mode, Supabase may not establish a session immediately.
// Treat this as a successful signup attempt and let caller handle next UX step.
return { user: response.user };
} catch (error: any) {
return { error: this.extractErrorMessage(error, 'Signup failed') };
@ -768,7 +766,11 @@ class SupabaseSyncService {
private buildRequestError(status: number, parsed: unknown, raw: string): Error {
if (parsed && typeof parsed === 'object') {
const message = (parsed as any).message || (parsed as any).error_description || (parsed as any).error;
const message =
(parsed as any).message ||
(parsed as any).msg ||
(parsed as any).error_description ||
(parsed as any).error;
if (typeof message === 'string' && message.trim().length > 0) {
return new Error(message);
}
@ -781,7 +783,30 @@ class SupabaseSyncService {
private extractErrorMessage(error: unknown, fallback: string): string {
if (error instanceof Error && error.message) {
return error.message;
const raw = error.message.trim();
let parsed: any = null;
if (raw.startsWith('{') && raw.endsWith('}')) {
try {
parsed = JSON.parse(raw);
} catch {
parsed = null;
}
}
const errorCode = (parsed?.error_code || parsed?.code || '').toString().toLowerCase();
const message = (parsed?.msg || parsed?.message || raw).toString().trim();
if (errorCode === 'invalid_credentials') {
return 'Invalid email or password';
}
if (errorCode === 'email_not_confirmed') {
return 'Email not confirmed. Check your inbox or Spam/Junk folder, verify your account, then sign in.';
}
if (message.length > 0) {
return message;
}
}
return fallback;
}