Fix Spotify auth, playlist creation, and Expo config
This commit is contained in:
3
app.json
3
app.json
@@ -14,7 +14,8 @@
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.epvc.roadtripdj"
|
||||
},
|
||||
"android": {
|
||||
"package": "com.eduardo12345122.roadtripdj",
|
||||
|
||||
BIN
screenshot_login.png
Normal file
BIN
screenshot_login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
@@ -45,3 +45,52 @@ export async function clearSpotifyTokens() {
|
||||
console.error('Error clearing Spotify tokens:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshSpotifyToken() {
|
||||
const refreshToken = await getSpotifyRefreshToken();
|
||||
if (!refreshToken) {
|
||||
console.log('[SpotifyToken] No refresh token found, cannot refresh.');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[SpotifyToken] Refreshing Spotify access token...');
|
||||
const SPOTIFY_CLIENT_ID = "7fa1e7acbf7e44f18bf28d74f14fb9cb";
|
||||
|
||||
// Construct urlencoded body safely
|
||||
const bodyDetails = {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_id: SPOTIFY_CLIENT_ID,
|
||||
};
|
||||
const formBody = Object.keys(bodyDetails)
|
||||
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(bodyDetails[key as keyof typeof bodyDetails]))
|
||||
.join('&');
|
||||
|
||||
const res = await fetch('https://accounts.spotify.com/api/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formBody,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.access_token) {
|
||||
console.log('[SpotifyToken] Spotify access token refreshed successfully!');
|
||||
await setSpotifyToken(data.access_token);
|
||||
if (data.refresh_token) {
|
||||
await setSpotifyRefreshToken(data.refresh_token);
|
||||
}
|
||||
return data.access_token;
|
||||
}
|
||||
} else {
|
||||
const errText = await res.text();
|
||||
console.error('[SpotifyToken] Refresh request failed:', res.status, errText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[SpotifyToken] Error refreshing Spotify token:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -48,12 +48,28 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
if (!session) {
|
||||
setIsDemoMode(false);
|
||||
setIsSpotifyAuthenticated(false);
|
||||
} else {
|
||||
const isSpotify = !!session.user?.user_metadata?.spotify_id;
|
||||
setIsSpotifyAuthenticated(isSpotify);
|
||||
setIsDemoMode(false);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
if (!session) {
|
||||
setIsDemoMode(false);
|
||||
setIsSpotifyAuthenticated(false);
|
||||
} else {
|
||||
const isSpotify = !!session.user?.user_metadata?.spotify_id;
|
||||
setIsSpotifyAuthenticated(isSpotify);
|
||||
setIsDemoMode(false);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Car, Music } from 'lucide-react-native';
|
||||
import { colors } from '../../utils/colors';
|
||||
import { supabase } from '../../services/supabase';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import * as Linking from 'expo-linking';
|
||||
import { makeRedirectUri, useAuthRequest, exchangeCodeAsync, DiscoveryDocument } from 'expo-auth-session';
|
||||
import { clearSpotifyTokens, setSpotifyToken, setSpotifyRefreshToken } from '../../auth/spotifyToken';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
@@ -24,11 +23,14 @@ export default function LoginScreen({ navigation }) {
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Temporary Emergency Fix: Hardcoded Client ID
|
||||
// Direct Spotify App Client ID
|
||||
const SPOTIFY_CLIENT_ID = "7fa1e7acbf7e44f18bf28d74f14fb9cb";
|
||||
|
||||
// Configure Direct Spotify OAuth
|
||||
const redirectUri = "exp://192.168.1.7:8081/--/auth/callback";
|
||||
// Configure Dynamic Redirect URI
|
||||
const redirectUri = makeRedirectUri({
|
||||
scheme: 'roadtripdj',
|
||||
path: 'auth/callback',
|
||||
});
|
||||
|
||||
const [request, response, promptAsync] = useAuthRequest(
|
||||
{
|
||||
@@ -79,69 +81,88 @@ export default function LoginScreen({ navigation }) {
|
||||
await setSpotifyRefreshToken(tokenResult.refreshToken);
|
||||
}
|
||||
|
||||
// 1 & 2. Check for Supabase session or create an anonymous one
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
let supabaseUserId = sessionData?.session?.user?.id;
|
||||
// Fetch real Spotify Profile
|
||||
console.log("SPOTIFY_PROFILE_FETCH_START");
|
||||
const profileRes = await fetch('https://api.spotify.com/v1/me', {
|
||||
headers: { Authorization: `Bearer ${tokenResult.accessToken}` }
|
||||
});
|
||||
|
||||
console.log("SUPABASE_SESSION_EXISTS:", !!sessionData?.session);
|
||||
|
||||
if (!sessionData?.session) {
|
||||
const { data, error } = await supabase.auth.signInAnonymously();
|
||||
if (error) {
|
||||
console.error("Error creating anonymous session:", error);
|
||||
throw error;
|
||||
}
|
||||
supabaseUserId = data.user?.id;
|
||||
if (!profileRes.ok) {
|
||||
throw new Error(`Failed to fetch Spotify profile: ${profileRes.statusText}`);
|
||||
}
|
||||
|
||||
const profile = await profileRes.json();
|
||||
console.log("SPOTIFY_PROFILE_NAME:", profile.display_name);
|
||||
console.log("SPOTIFY_PROFILE_EMAIL_EXISTS:", !!profile.email);
|
||||
console.log("SPOTIFY_USER_ID:", profile.id);
|
||||
console.log("SPOTIFY_ACCESS_TOKEN_EXISTS:", !!tokenResult.accessToken);
|
||||
|
||||
console.log("SUPABASE_USER_ID:", supabaseUserId);
|
||||
console.log("TRIP_INSERT_USER_ID:", supabaseUserId);
|
||||
const email = profile.email || `spotify_${profile.id}@roadtripdj.local`;
|
||||
const password = `SpotifySecure_${profile.id}`;
|
||||
|
||||
// 7 & 8. Fetch Spotify Profile and update Supabase User Metadata
|
||||
try {
|
||||
console.log("SPOTIFY_PROFILE_FETCH_START");
|
||||
const profileRes = await fetch('https://api.spotify.com/v1/me', {
|
||||
headers: { Authorization: `Bearer ${tokenResult.accessToken}` }
|
||||
});
|
||||
|
||||
if (profileRes.ok) {
|
||||
const profile = await profileRes.json();
|
||||
console.log("SPOTIFY_PROFILE_NAME:", profile.display_name);
|
||||
console.log("SPOTIFY_PROFILE_EMAIL_EXISTS:", !!profile.email);
|
||||
console.log("SPOTIFY_USER_ID:", profile.id);
|
||||
console.log("SPOTIFY_ACCESS_TOKEN_EXISTS:", !!tokenResult.accessToken);
|
||||
|
||||
// Force session refresh to ensure AuthContext picks up the new metadata instantly
|
||||
const { data: updatedUser } = await supabase.auth.updateUser({
|
||||
data: {
|
||||
display_name: profile.display_name,
|
||||
name: profile.display_name,
|
||||
spotify_id: profile.id,
|
||||
email: profile.email,
|
||||
avatar_url: profile.images?.[0]?.url
|
||||
}
|
||||
});
|
||||
|
||||
// Explicitly refresh session if needed
|
||||
if (updatedUser) {
|
||||
await supabase.auth.refreshSession();
|
||||
// Attempt sign in or automatic registration
|
||||
let { data: sessionData, error: signInError } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (signInError) {
|
||||
console.log("Spotify user does not exist or login failed, signing up...", signInError.message);
|
||||
|
||||
// Sign up user with Spotify metadata
|
||||
const { error: signUpError } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
display_name: profile.display_name || 'Viajante',
|
||||
name: profile.display_name || 'Viajante',
|
||||
spotify_id: profile.id,
|
||||
email: profile.email || null,
|
||||
avatar_url: profile.images?.[0]?.url || null,
|
||||
}
|
||||
}
|
||||
} catch (profileError) {
|
||||
console.error("Error fetching Spotify profile:", profileError);
|
||||
}
|
||||
});
|
||||
|
||||
if (signUpError) {
|
||||
console.error("SignUp error:", signUpError);
|
||||
throw signUpError;
|
||||
}
|
||||
|
||||
// Log in again after sign up
|
||||
const { data: newSession, error: newSignInError } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
if (newSignInError) throw newSignInError;
|
||||
sessionData = newSession;
|
||||
} else {
|
||||
// Update metadata with latest Spotify data
|
||||
await supabase.auth.updateUser({
|
||||
data: {
|
||||
display_name: profile.display_name || 'Viajante',
|
||||
name: profile.display_name || 'Viajante',
|
||||
spotify_id: profile.id,
|
||||
email: profile.email || null,
|
||||
avatar_url: profile.images?.[0]?.url || null,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Force session refresh for app state
|
||||
await supabase.auth.refreshSession();
|
||||
|
||||
console.log("LOGIN_MODE:", "spotify");
|
||||
console.log("ENTERING_SPOTIFY_MODE");
|
||||
|
||||
// Trigger app main flow as Spotify authenticated user
|
||||
// Trigger app main flow
|
||||
enableSpotifyMode();
|
||||
} else {
|
||||
Alert.alert('Erro de Autenticação', 'Não foi possível obter o token de acesso do Spotify.');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('🚀 [LoginScreen] Token Exchange Error:', e);
|
||||
Alert.alert('Erro de Autenticação', 'Não foi possível trocar o código pelo token.');
|
||||
Alert.alert('Erro de Autenticação', e.message || 'Não foi possível trocar o código pelo token.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -165,10 +186,6 @@ export default function LoginScreen({ navigation }) {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleAuthDebug = async () => {
|
||||
// Placeholder function for debug logic
|
||||
};
|
||||
|
||||
const handleResetAuth = async () => {
|
||||
await supabase.auth.signOut();
|
||||
await clearSpotifyTokens();
|
||||
@@ -178,23 +195,7 @@ export default function LoginScreen({ navigation }) {
|
||||
const handleSpotifyLogin = async () => {
|
||||
try {
|
||||
console.log("SPOTIFY_CLIENT_ID_EXISTS:", !!SPOTIFY_CLIENT_ID);
|
||||
console.log("SPOTIFY_CLIENT_ID_LENGTH:", SPOTIFY_CLIENT_ID.length);
|
||||
console.log("SPOTIFY_CLIENT_ID_FIRST_6:", SPOTIFY_CLIENT_ID.slice(0, 6));
|
||||
console.log("SPOTIFY_REDIRECT_URI:", redirectUri);
|
||||
console.log("FULL_SPOTIFY_AUTH_URL:", request?.url);
|
||||
|
||||
if (
|
||||
!SPOTIFY_CLIENT_ID ||
|
||||
SPOTIFY_CLIENT_ID.includes("PASTE_") ||
|
||||
SPOTIFY_CLIENT_ID.includes("HERE")
|
||||
) {
|
||||
Alert.alert(
|
||||
'Aviso',
|
||||
'Cole o Client ID real no arquivo LoginScreen.tsx.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await promptAsync();
|
||||
} catch (e: any) {
|
||||
console.error('🚀 [LoginScreen] OAuth Error:', e);
|
||||
@@ -205,10 +206,13 @@ export default function LoginScreen({ navigation }) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
style={styles.keyboardView}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* Header Section */}
|
||||
<View style={styles.headerContainer}>
|
||||
<View style={styles.iconWrapper}>
|
||||
@@ -265,9 +269,6 @@ export default function LoginScreen({ navigation }) {
|
||||
style={[styles.primaryButton, { backgroundColor: '#333', marginBottom: 24 }]}
|
||||
onPress={() => {
|
||||
console.log("DEMO_BYPASS_PRESSED");
|
||||
console.log("LOGIN_MODE:", "demo");
|
||||
console.log("ENTERING_DEMO_MODE");
|
||||
console.log("AVAILABLE_ROUTE_NAMES", navigation.getState()?.routeNames);
|
||||
enableDemoMode();
|
||||
}}
|
||||
>
|
||||
@@ -275,12 +276,8 @@ export default function LoginScreen({ navigation }) {
|
||||
</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>
|
||||
<Text style={{ color: 'red', fontWeight: '600' }}>Reset Auth</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.footerContainer}>
|
||||
@@ -306,15 +303,13 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: 60,
|
||||
paddingBottom: 40,
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 40,
|
||||
marginBottom: 30,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
iconWrapper: {
|
||||
position: 'relative',
|
||||
@@ -357,10 +352,16 @@ const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: colors.white,
|
||||
width: '100%',
|
||||
borderRadius: 24,
|
||||
borderTopLeftRadius: 32,
|
||||
borderTopRightRadius: 32,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
padding: 24,
|
||||
paddingBottom: Platform.OS === 'ios' ? 40 : 24,
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOffset: { width: 0, height: -4 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 12,
|
||||
elevation: 5,
|
||||
@@ -407,6 +408,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 10,
|
||||
},
|
||||
footerText: {
|
||||
color: colors.textSecondary,
|
||||
|
||||
@@ -1,21 +1,171 @@
|
||||
import React, { useState } from 'react';
|
||||
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 WebBrowser from 'expo-web-browser';
|
||||
import * as QueryParams from 'expo-auth-session/build/QueryParams';
|
||||
import { makeRedirectUri, useAuthRequest, exchangeCodeAsync, DiscoveryDocument } from 'expo-auth-session';
|
||||
import { clearSpotifyTokens, setSpotifyToken, setSpotifyRefreshToken } from '../../auth/spotifyToken';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
// Direct Spotify OAuth Endpoints
|
||||
const discovery: DiscoveryDocument = {
|
||||
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
|
||||
tokenEndpoint: 'https://accounts.spotify.com/api/token',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
export default function RegisterScreen({ navigation }) {
|
||||
const { enableSpotifyMode } = useAuth();
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Direct Spotify App Client ID
|
||||
const SPOTIFY_CLIENT_ID = "7fa1e7acbf7e44f18bf28d74f14fb9cb";
|
||||
|
||||
// Configure Dynamic Redirect URI
|
||||
const redirectUri = makeRedirectUri({
|
||||
scheme: 'roadtripdj',
|
||||
path: 'auth/callback',
|
||||
});
|
||||
|
||||
const [request, response, promptAsync] = useAuthRequest(
|
||||
{
|
||||
clientId: SPOTIFY_CLIENT_ID,
|
||||
scopes: ['user-read-email', 'user-read-private', 'playlist-modify-public', 'playlist-modify-private'],
|
||||
usePKCE: true,
|
||||
redirectUri,
|
||||
},
|
||||
discovery
|
||||
);
|
||||
|
||||
// Handle Spotify OAuth Response
|
||||
useEffect(() => {
|
||||
if (response) {
|
||||
console.log("SPOTIFY_AUTH_RESULT (Register):", response);
|
||||
if (response.type === 'success') {
|
||||
const { code } = response.params;
|
||||
console.log("SPOTIFY_CODE_EXISTS (Register):", !!code);
|
||||
exchangeCodeForTokens(code);
|
||||
} else if (response.type === 'error') {
|
||||
Alert.alert('Erro de Autenticação', response.error?.message || 'Falha ao logar com o Spotify');
|
||||
}
|
||||
}
|
||||
}, [response]);
|
||||
|
||||
const exchangeCodeForTokens = async (code: string) => {
|
||||
if (!request?.codeVerifier) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const tokenResult = await exchangeCodeAsync(
|
||||
{
|
||||
clientId: SPOTIFY_CLIENT_ID,
|
||||
code,
|
||||
redirectUri,
|
||||
extraParams: {
|
||||
code_verifier: request.codeVerifier,
|
||||
},
|
||||
},
|
||||
discovery
|
||||
);
|
||||
|
||||
console.log("SPOTIFY_TOKEN_RECEIVED (Register):", !!tokenResult.accessToken);
|
||||
|
||||
if (tokenResult.accessToken) {
|
||||
await setSpotifyToken(tokenResult.accessToken);
|
||||
if (tokenResult.refreshToken) {
|
||||
await setSpotifyRefreshToken(tokenResult.refreshToken);
|
||||
}
|
||||
|
||||
// Fetch Spotify Profile
|
||||
console.log("SPOTIFY_PROFILE_FETCH_START (Register)");
|
||||
const profileRes = await fetch('https://api.spotify.com/v1/me', {
|
||||
headers: { Authorization: `Bearer ${tokenResult.accessToken}` }
|
||||
});
|
||||
|
||||
if (!profileRes.ok) {
|
||||
throw new Error(`Failed to fetch Spotify profile: ${profileRes.statusText}`);
|
||||
}
|
||||
|
||||
const profile = await profileRes.json();
|
||||
console.log("SPOTIFY_PROFILE_NAME (Register):", profile.display_name);
|
||||
console.log("SPOTIFY_PROFILE_EMAIL_EXISTS (Register):", !!profile.email);
|
||||
console.log("SPOTIFY_USER_ID (Register):", profile.id);
|
||||
|
||||
const email = profile.email || `spotify_${profile.id}@roadtripdj.local`;
|
||||
const password = `SpotifySecure_${profile.id}`;
|
||||
|
||||
// Attempt sign in or automatic registration
|
||||
let { data: sessionData, error: signInError } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (signInError) {
|
||||
console.log("Spotify user does not exist or login failed, signing up (Register)...", signInError.message);
|
||||
|
||||
// Sign up user with Spotify metadata
|
||||
const { error: signUpError } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
display_name: profile.display_name || 'Viajante',
|
||||
name: profile.display_name || 'Viajante',
|
||||
spotify_id: profile.id,
|
||||
email: profile.email || null,
|
||||
avatar_url: profile.images?.[0]?.url || null,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (signUpError) {
|
||||
console.error("SignUp error:", signUpError);
|
||||
throw signUpError;
|
||||
}
|
||||
|
||||
// Log in again after sign up
|
||||
const { data: newSession, error: newSignInError } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
if (newSignInError) throw newSignInError;
|
||||
sessionData = newSession;
|
||||
} else {
|
||||
// Update metadata with latest Spotify data
|
||||
await supabase.auth.updateUser({
|
||||
data: {
|
||||
display_name: profile.display_name || 'Viajante',
|
||||
name: profile.display_name || 'Viajante',
|
||||
spotify_id: profile.id,
|
||||
email: profile.email || null,
|
||||
avatar_url: profile.images?.[0]?.url || null,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Force session refresh for app state
|
||||
await supabase.auth.refreshSession();
|
||||
|
||||
// Trigger app main flow
|
||||
enableSpotifyMode();
|
||||
} else {
|
||||
Alert.alert('Erro de Autenticação', 'Não foi possível obter o token de acesso do Spotify.');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('🚀 [RegisterScreen] Token Exchange Error:', e);
|
||||
Alert.alert('Erro de Autenticação', e.message || 'Não foi possível trocar o código pelo token.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!name || !email || !password || !confirmPassword) {
|
||||
Alert.alert('Erro', 'Por favor preenche todos os campos.');
|
||||
@@ -39,48 +189,34 @@ export default function RegisterScreen({ navigation }) {
|
||||
|
||||
if (error) {
|
||||
Alert.alert('Erro no registo', error.message);
|
||||
} else {
|
||||
Alert.alert('Sucesso', 'Conta criada com sucesso! Faça login.');
|
||||
navigation.navigate('Login');
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSpotifyAuth = async () => {
|
||||
try {
|
||||
const redirectUri = makeRedirectUri();
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'spotify',
|
||||
options: {
|
||||
redirectTo: redirectUri,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (data?.url) {
|
||||
const res = await WebBrowser.openAuthSessionAsync(data.url, redirectUri);
|
||||
if (res.type === 'success') {
|
||||
const { url } = res;
|
||||
const { params, errorCode } = QueryParams.getQueryParams(url);
|
||||
if (errorCode) throw new Error(errorCode);
|
||||
if (params.access_token && params.refresh_token) {
|
||||
await supabase.auth.setSession({
|
||||
access_token: params.access_token,
|
||||
refresh_token: params.refresh_token,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
Alert.alert('Erro', err?.message || 'Ocorreu um erro no Spotify Auth.');
|
||||
console.log("SPOTIFY_CLIENT_ID_EXISTS (Register):", !!SPOTIFY_CLIENT_ID);
|
||||
console.log("SPOTIFY_REDIRECT_URI (Register):", redirectUri);
|
||||
await promptAsync();
|
||||
} catch (e: any) {
|
||||
console.error('🚀 [RegisterScreen] OAuth Error:', e);
|
||||
Alert.alert('Erro de Autenticação', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
style={styles.keyboardView}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* Header Section */}
|
||||
<View style={styles.headerContainer}>
|
||||
<View style={styles.iconWrapper}>
|
||||
@@ -144,9 +280,8 @@ export default function RegisterScreen({ navigation }) {
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.spotifyButton} onPress={handleSpotifyAuth}>
|
||||
{/* Note: Placeholder Spotify logo */}
|
||||
<Music color={colors.white} size={20} style={styles.spotifyIcon} />
|
||||
<Text style={styles.spotifyButtonText}>Registar com Spotify</Text>
|
||||
<Text style={styles.spotifyButtonText}>Criar conta com Spotify</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.footerContainer}>
|
||||
@@ -172,15 +307,13 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 40,
|
||||
paddingBottom: 40,
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: 60,
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 30,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
iconWrapper: {
|
||||
position: 'relative',
|
||||
@@ -223,10 +356,16 @@ const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: colors.white,
|
||||
width: '100%',
|
||||
borderRadius: 24,
|
||||
borderTopLeftRadius: 32,
|
||||
borderTopRightRadius: 32,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
padding: 24,
|
||||
paddingBottom: Platform.OS === 'ios' ? 40 : 24,
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOffset: { width: 0, height: -4 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 12,
|
||||
elevation: 5,
|
||||
@@ -273,6 +412,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 10,
|
||||
},
|
||||
footerText: {
|
||||
color: colors.textSecondary,
|
||||
|
||||
@@ -4,7 +4,8 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { X, MapPin, ArrowRight, Navigation } from 'lucide-react-native';
|
||||
import { colors } from '../../utils/colors';
|
||||
import { supabase } from '../../services/supabase';
|
||||
import { getSpotifyAccessToken } from '../../auth/spotifyToken';
|
||||
import { getSpotifyAccessToken, refreshSpotifyToken } from '../../auth/spotifyToken';
|
||||
import { OLLAMA_API_URL } from '../../services/ollama';
|
||||
|
||||
// @ts-ignore
|
||||
export default function NewTripScreen({ navigation }) {
|
||||
@@ -56,20 +57,52 @@ export default function NewTripScreen({ navigation }) {
|
||||
console.log(`PLAYLIST_API_CONTENT_TYPE [${label}]:`, res.headers.get("content-type"));
|
||||
console.log(`PLAYLIST_API_RAW_RESPONSE [${label}]:`, rawText.substring(0, 300) + (rawText.length > 300 ? "..." : ""));
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Spotify API returned status ${res.status} for [${label}]: ${rawText.substring(0, 150)}`);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "";
|
||||
if (!contentType.includes("application/json")) {
|
||||
throw new Error(`Playlist API returned non-JSON response for [${label}]: ${rawText.substring(0, 150)}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(rawText);
|
||||
} catch (e) {
|
||||
throw new Error(`Playlist API returned non-JSON response [${label}]: ${rawText}`);
|
||||
throw new Error(`Failed to parse JSON response for [${label}]: ${rawText.substring(0, 150)}`);
|
||||
}
|
||||
};
|
||||
|
||||
// A. Get provider token
|
||||
const providerToken = await getSpotifyAccessToken();
|
||||
let providerToken = await getSpotifyAccessToken();
|
||||
console.log("SPOTIFY_ACCESS_TOKEN_EXISTS:", !!providerToken);
|
||||
|
||||
if (providerToken) {
|
||||
// Proactively check if token is valid, or refresh it
|
||||
console.log("Validating Spotify token...");
|
||||
let testRes = await fetch('https://api.spotify.com/v1/me', {
|
||||
headers: { Authorization: `Bearer ${providerToken}` }
|
||||
});
|
||||
|
||||
if (testRes.status === 401) {
|
||||
console.log("Spotify token is invalid/expired (401), attempting to refresh...");
|
||||
const newToken = await refreshSpotifyToken();
|
||||
if (newToken) {
|
||||
providerToken = newToken;
|
||||
} else {
|
||||
console.log("Failed to refresh Spotify token.");
|
||||
providerToken = null;
|
||||
}
|
||||
} else if (!testRes.ok) {
|
||||
const testErr = await testRes.text();
|
||||
console.error("Spotify validation request failed:", testRes.status, testErr);
|
||||
providerToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!providerToken) {
|
||||
console.log("Spotify token missing, skipping playlist generation.");
|
||||
Alert.alert('Aviso', 'Spotify token missing, please login again');
|
||||
console.log("Spotify token missing or expired, skipping playlist generation.");
|
||||
Alert.alert('Spotify Desligado', 'O token do Spotify expirou ou está em falta. Por favor reconecte o Spotify no Perfil.');
|
||||
} else {
|
||||
// B. Fetch Spotify User ID
|
||||
const spotifyUserRes = await fetch('https://api.spotify.com/v1/me', {
|
||||
@@ -83,7 +116,7 @@ export default function NewTripScreen({ navigation }) {
|
||||
const spotifyUserId = spotifyUserData.id;
|
||||
|
||||
// C. Call Ollama server
|
||||
const ollamaRes = await fetch("http://89.114.196.110:11434/api/chat", {
|
||||
const ollamaRes = await fetch(`${OLLAMA_API_URL}/api/chat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Placeholder for Ollama API logic
|
||||
export const OLLAMA_API_URL = "https://apichat.epvc.pt/";
|
||||
export const OLLAMA_API_URL = "https://apichat.epvc.pt";
|
||||
|
||||
export const generateTripGuide = async (origin: string, destination: string, waypoints: string[], duration: string) => {
|
||||
// Logic to call Ollama
|
||||
|
||||
Reference in New Issue
Block a user