mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 09:35:42 +00:00
authentication error handling
This commit is contained in:
parent
b5ae55da9e
commit
36d93c270a
3 changed files with 100 additions and 12 deletions
|
|
@ -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)',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue