Files
RoadtripDJ/src/screens/auth/LoginScreen.tsx
2026-05-17 23:36:26 +01:00

303 lines
8.9 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, SafeAreaView, KeyboardAvoidingView, Platform, ScrollView, Alert, ActivityIndicator } from 'react-native';
import { Car, Music } from 'lucide-react-native';
import { colors } from '../../utils/colors';
import { supabase } from '../../services/supabase';
import { makeRedirectUri } from 'expo-auth-session';
import * as QueryParams from 'expo-auth-session/build/QueryParams';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
import { handleAuthRedirectUrl } from '../../auth/authRedirect';
import { addAuthDebugEvent, getAuthDebugEvents, clearAuthDebugEvents } from '../../debug/authDebug';
import { clearSpotifyTokens } from '../../auth/spotifyToken';
// @ts-ignore
export default function LoginScreen({ navigation }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleLogin = async () => {
if (!email || !password) {
Alert.alert('Erro', 'Por favor preenche todos os campos.');
return;
}
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
Alert.alert('Erro no login', error.message);
}
setLoading(false);
};
const handleAuthDebug = async () => {
const events = await getAuthDebugEvents();
Alert.alert('Auth Debug Events', JSON.stringify(events, null, 2));
};
const handleResetAuth = async () => {
await supabase.auth.signOut();
await clearSpotifyTokens();
await clearAuthDebugEvents();
Alert.alert('Reset', 'Auth state cleared.');
};
const handleSpotifyLogin = async () => {
try {
const redirectTo = "roadtripdj://auth/callback";
await addAuthDebugEvent({ event: 'login_button_pressed', redirectTo });
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'spotify',
options: {
redirectTo,
skipBrowserRedirect: true,
scopes: "user-read-email user-read-private playlist-modify-public playlist-modify-private",
queryParams: {
show_dialog: "true"
}
}
});
await addAuthDebugEvent({
event: 'oauth_url_created',
success: !!data?.url,
host: data?.url ? data.url.split('?')[0] : null
});
if (error) {
throw error;
}
if (data?.url) {
const result = await WebBrowser.openAuthSessionAsync(data.url, redirectTo);
await addAuthDebugEvent({ event: 'web_browser_closed', type: result.type });
if (result.type === 'success' && result.url) {
await addAuthDebugEvent({ event: 'web_browser_success_url_received', success: true });
await addAuthDebugEvent({ event: 'processing_web_browser_success_url' });
await handleAuthRedirectUrl(result.url);
const { data: sessionData } = await supabase.auth.getSession();
await addAuthDebugEvent({
event: 'post_browser_getSession',
user_exists: Boolean(sessionData.session?.user),
provider_token_exists: Boolean((sessionData.session as any)?.provider_token)
});
}
}
} catch (e: any) {
console.error('🚀 [LoginScreen] OAuth Error:', e);
Alert.alert('Erro de Autenticação', e.message);
}
};
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}
>
<ScrollView contentContainerStyle={styles.scrollContent}>
{/* Header Section */}
<View style={styles.headerContainer}>
<View style={styles.iconWrapper}>
<View style={styles.carIconContainer}>
<Car color={colors.white} size={32} />
</View>
<View style={styles.musicIconContainer}>
<Music color={colors.primary} size={16} />
</View>
</View>
<Text style={styles.title}>Roadtrip DJ</Text>
<Text style={styles.subtitle}>O teu guia de carros e música</Text>
</View>
{/* Form Card */}
<View style={styles.card}>
<TextInput
style={styles.input}
placeholder="Email"
placeholderTextColor={colors.textSecondary}
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
placeholder="Password"
placeholderTextColor={colors.textSecondary}
secureTextEntry={true}
value={password}
onChangeText={setPassword}
/>
<TouchableOpacity
style={styles.primaryButton}
onPress={handleLogin}
disabled={loading}
>
{loading ? (
<ActivityIndicator color={colors.white} />
) : (
<Text style={styles.primaryButtonText}>Entrar</Text>
)}
</TouchableOpacity>
<TouchableOpacity style={styles.spotifyButton} onPress={handleSpotifyLogin}>
{/* Note: In a real app we would use an actual Spotify SVG logo. Using Music icon for now as a placeholder for the Spotify logo. */}
<Music color={colors.white} size={20} style={styles.spotifyIcon} />
<Text style={styles.spotifyButtonText}>Entrar com Spotify</Text>
</TouchableOpacity>
<TouchableOpacity style={{ padding: 10, alignItems: 'center' }} onPress={handleAuthDebug}>
<Text style={{ color: colors.textSecondary }}>Auth Debug</Text>
</TouchableOpacity>
<TouchableOpacity style={{ padding: 10, alignItems: 'center', marginBottom: 10 }} onPress={handleResetAuth}>
<Text style={{ color: 'red' }}>Reset Auth</Text>
</TouchableOpacity>
<View style={styles.footerContainer}>
<Text style={styles.footerText}>Não tens conta? </Text>
<TouchableOpacity onPress={() => navigation.navigate('Register')}>
<Text style={styles.footerLink}>Criar conta</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.primary,
},
keyboardView: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
paddingTop: 60,
paddingBottom: 40,
},
headerContainer: {
alignItems: 'center',
marginBottom: 40,
},
iconWrapper: {
position: 'relative',
marginBottom: 20,
width: 100,
height: 100,
alignItems: 'center',
justifyContent: 'center',
},
carIconContainer: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
alignItems: 'center',
justifyContent: 'center',
},
musicIconContainer: {
position: 'absolute',
bottom: 5,
right: 5,
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#000000',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 32,
fontWeight: '800',
color: colors.white,
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: colors.white,
fontWeight: '500',
},
card: {
backgroundColor: colors.white,
width: '100%',
borderRadius: 24,
padding: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 5,
},
input: {
backgroundColor: colors.inputBackground,
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 16,
fontSize: 16,
marginBottom: 16,
color: colors.textMain,
},
primaryButton: {
backgroundColor: colors.primary,
borderRadius: 12,
paddingVertical: 16,
alignItems: 'center',
marginBottom: 16,
},
primaryButtonText: {
color: colors.white,
fontSize: 16,
fontWeight: 'bold',
},
spotifyButton: {
backgroundColor: colors.spotify,
borderRadius: 12,
paddingVertical: 16,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 24,
},
spotifyIcon: {
marginRight: 8,
},
spotifyButtonText: {
color: colors.white,
fontSize: 16,
fontWeight: 'bold',
},
footerContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
footerText: {
color: colors.textSecondary,
fontSize: 14,
},
footerLink: {
color: colors.textMain,
fontSize: 14,
fontWeight: 'bold',
},
});