diff --git a/scratch_db_test.js b/scratch_db_test.js new file mode 100644 index 0000000..91a8d51 --- /dev/null +++ b/scratch_db_test.js @@ -0,0 +1,28 @@ +const supabaseUrl = "https://qyvnryhskgmvgjajqqru.supabase.co"; +const supabaseKey = "sb_publishable_fazCCLmO7XjtryY28ePR-A_CS7aU6fF"; + +async function run() { + try { + const res = await fetch(`${supabaseUrl}/rest/v1/trips`, { + method: "POST", + headers: { + apikey: supabaseKey, + Authorization: `Bearer ${supabaseKey}`, + "Content-Type": "application/json", + Prefer: "return=representation" + }, + body: JSON.stringify({ + title: "Test Trip", + origin: "Lisbon", + destination: "Porto", + waypoints: [] + }) + }); + const data = await res.json(); + console.log("INSERT_RESPONSE:", JSON.stringify(data, null, 2)); + } catch (err) { + console.error("Error inserting:", err); + } +} + +run(); diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 4be467a..5ec2a76 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -6,9 +6,7 @@ interface AuthContextType { user: User | null; session: Session | null; loading: boolean; - isDemoMode: boolean; isSpotifyAuthenticated: boolean; - enableDemoMode: () => void; enableSpotifyMode: () => void; } @@ -16,9 +14,7 @@ const AuthContext = createContext({ user: null, session: null, loading: true, - isDemoMode: false, isSpotifyAuthenticated: false, - enableDemoMode: () => {}, enableSpotifyMode: () => {}, }); @@ -28,18 +24,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const [user, setUser] = useState(null); const [session, setSession] = useState(null); const [loading, setLoading] = useState(true); - const [isDemoMode, setIsDemoMode] = useState(false); const [isSpotifyAuthenticated, setIsSpotifyAuthenticated] = useState(false); - const enableDemoMode = () => { - setIsDemoMode(true); - setIsSpotifyAuthenticated(false); - setUser({ id: '00000000-0000-4000-8000-000000000002', email: 'demo@roadtripdj.com' } as User); - setLoading(false); - }; - const enableSpotifyMode = () => { - setIsDemoMode(false); setIsSpotifyAuthenticated(true); setLoading(false); }; @@ -49,12 +36,10 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { 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); }); @@ -63,12 +48,10 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { 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); }); @@ -77,7 +60,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { }, []); return ( - + {children} ); diff --git a/src/screens/auth/LoginScreen.tsx b/src/screens/auth/LoginScreen.tsx index 0d9c7e6..97681aa 100644 --- a/src/screens/auth/LoginScreen.tsx +++ b/src/screens/auth/LoginScreen.tsx @@ -18,7 +18,7 @@ const discovery: DiscoveryDocument = { // @ts-ignore export default function LoginScreen({ navigation }) { - const { enableDemoMode, enableSpotifyMode } = useAuth(); + const { enableSpotifyMode } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); @@ -305,14 +305,6 @@ export default function LoginScreen({ navigation }) { setLoading(false); }; - const handleResetAuth = async () => { - await supabase.auth.signOut(); - await clearSpotifyTokens(); - setEmail(''); - setPassword(''); - Alert.alert('Reset', 'Auth state cleared.'); - }; - const handleSpotifyLogin = async () => { try { console.log("[SpotifyAuthDebug] 1. Exact redirectUri at login press:", redirectUri); @@ -394,22 +386,6 @@ export default function LoginScreen({ navigation }) { Entrar com Spotify - {__DEV__ && ( - { - console.log("DEMO_BYPASS_PRESSED"); - enableDemoMode(); - }} - > - Continuar sem Spotify (demo) - - )} - - - Reset Auth - - Não tens conta? navigation.navigate('Register')}> @@ -489,7 +465,7 @@ const styles = StyleSheet.create({ padding: 24, paddingBottom: Platform.OS === 'ios' ? 40 : 24, flexGrow: 1, - justifyContent: 'center', + justifyContent: 'flex-start', shadowColor: '#000', shadowOffset: { width: 0, height: -4 }, shadowOpacity: 0.1, diff --git a/src/screens/main/HomeScreen.tsx b/src/screens/main/HomeScreen.tsx index caff438..e41b913 100644 --- a/src/screens/main/HomeScreen.tsx +++ b/src/screens/main/HomeScreen.tsx @@ -176,7 +176,15 @@ export default function HomeScreen({ navigation }: Props) { {loading ? ( ) : trips.length > 0 ? ( - trips.map(trip => ) + trips.map(trip => ( + navigation.navigate('TripDetails', { trip })} + > + + + )) ) : ( Pronto para a próxima? diff --git a/src/screens/trip/TripDetailsScreen.tsx b/src/screens/trip/TripDetailsScreen.tsx index e05027f..66b25a3 100644 --- a/src/screens/trip/TripDetailsScreen.tsx +++ b/src/screens/trip/TripDetailsScreen.tsx @@ -1,361 +1,480 @@ -import React, { useState } from 'react'; -import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ImageBackground, SafeAreaView, FlatList } from 'react-native'; -import { ArrowLeft, Share2, MoreVertical, Navigation, Clock, Music, Compass, MapPin, Play } from 'lucide-react-native'; +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ImageBackground, SafeAreaView, Linking, Alert } from 'react-native'; +import { ArrowLeft, MapPin, Navigation, Music, Play } from 'lucide-react-native'; import { colors } from '../../utils/colors'; import TimelineItem from '../../components/TimelineItem'; -import TrackItem from '../../components/TrackItem'; - -const MOCK_TRACKS = [ - { id: '1', title: 'Born to Run', artist: 'Bruce Springsteen', duration: '4:30' }, - { id: '2', title: 'Hotel California', artist: 'Eagles', duration: '6:30' }, - { id: '3', title: 'Holocene', artist: 'Bon Iver', duration: '5:37' }, - { id: '4', title: 'Ganges', artist: 'The National', duration: '4:12' }, -]; +import { getDestinationLandmarkImage } from '../../services/destinationImage'; // @ts-ignore -export default function TripDetailsScreen({ navigation }) { - const [activeTab, setActiveTab] = useState<'rota' | 'playlist'>('rota'); +export default function TripDetailsScreen({ navigation, route }) { + const { trip } = route.params || {}; - const renderRotaTab = () => ( - - {/* Stats Cards */} - - - {/* @ts-ignore */} - - 314 km - Distância - - - - - 3h 15m - Tempo - - + const [imageUrl, setImageUrl] = useState(trip?.destination_image_url || null); + const [landmarkName, setLandmarkName] = useState(trip?.destination_landmark_name || null); - {/* DJ Guide Card */} - - - - - - O Teu DJ Guide: - - - "Na primeira hora da viagem, ouve Rock clássico para acordar. Quando passares pela zona de Leiria, ouve Indie Folk. Ah, e faz uma paragem na área de serviço de Pombal porque o café lá tem ótimas reviews." - - + useEffect(() => { + if (!trip?.destination || imageUrl) return; - {/* Itinerary */} - - - - Itinerário - + let isMounted = true; + const tripTitle = trip.title || trip.destination || 'Viagem'; - - - - - - + getDestinationLandmarkImage(trip.destination, { tripTitle }).then(result => { + if (!isMounted) return; + if (result.imageUrl) { + setImageUrl(result.imageUrl); + } + if (result.landmarkName) { + setLandmarkName(result.landmarkName); + } + }); - {/* Maps Action */} - - - Abrir no Google Maps - - - ); + return () => { + isMounted = false; + }; + }, [trip?.destination, imageUrl]); - const renderPlaylistTab = () => ( - - {/* Generated Playlist Card */} - - - {/* Mocking large spotify logo with music icon for now */} - - - Playlist Gerada - 45 músicas • 3h 20m de viagem + const handleOpenGoogleMaps = () => { + if (!trip?.origin || !trip?.destination) { + Alert.alert('Erro', 'Dados de rota incompletos.'); + return; + } + const url = `https://www.google.com/maps/dir/?api=1&origin=${encodeURIComponent(trip.origin)}&destination=${encodeURIComponent(trip.destination)}`; + Linking.openURL(url); + }; - - - Ouvir no Spotify - - + const stops = trip?.stops || trip?.waypoints || []; + const hasStops = Array.isArray(stops) && stops.length > 0; - {/* Preview List */} - Pré-visualização (Músicas Iniciais) - - {MOCK_TRACKS.map((track, index) => ( - - ))} + const routeVisual = ( + + + + + ); return ( - - - {/* Header Image */} - + {/* Header */} + + + {trip?.title || trip?.destination || 'Detalhes da Viagem'} + + navigation.goBack()} > - - - navigation.goBack()}> - - - - - - - - - - - + + + - - - ROADTRIP - - Lisbon to Porto Coastline - May 10, 2026 + + {/* Top visual representation */} + {imageUrl ? ( + + + {landmarkName && ( + + + {landmarkName} + + + )} + {routeVisual} - - + + ) : ( + + {routeVisual} + + )} - {/* Content Area overlapping image slightly */} - - - {/* Custom Tabs */} - - setActiveTab('rota')} - > - - Rota & DJ + {/* Content Card overlapping map/image */} + + {/* Trip Name info */} + + NOME DA VIAGEM + + + {trip?.title || trip?.destination || 'Sem título'} - - - setActiveTab('playlist')} - > - - Playlist Spotify - - + - {/* Render Tab Content */} - {activeTab === 'rota' ? renderRotaTab() : renderPlaylistTab()} + {/* Origin & Destination timeline */} + + + + + + + + + PARTIDA + + + {trip?.origin} + + + + + + DESTINO + + + {trip?.destination} + + + + + + + {/* Distance and Duration summary */} + + + Distância + {trip?.distance || '-'} + + + + Duração + {trip?.duration || '-'} + + + + {/* Spotify Playlist Segment */} + {trip?.playlist_url ? ( + + PLAYLIST DO SPOTIFY + + + + + + + + {trip?.title || 'Playlist da Viagem'} + + + {trip?.playlist_url} + + + + Linking.openURL(trip.playlist_url)} + > + + Ouvir no Spotify + + + + ) : null} + + {/* Route Stops / Waypoints */} + + PARAGENS DA ROTA + {hasStops ? ( + + {stops.map((stop: string, index: number) => ( + + ))} + + ) : ( + + + Ainda não existem paragens guardadas para esta viagem. + + + )} + + + {/* Bottom Actions */} + + + + Abrir no Google Maps + + - + ); } const styles = StyleSheet.create({ - container: { + safeArea: { flex: 1, - backgroundColor: colors.background, + backgroundColor: colors.white, }, - headerImage: { - width: '100%', - height: 320, - justifyContent: 'flex-start', + scrollContent: { + flexGrow: 1, }, - headerSafeArea: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0.3)', // Overlay - justifyContent: 'space-between', - }, - headerNav: { + header: { flexDirection: 'row', justifyContent: 'space-between', + alignItems: 'center', paddingHorizontal: 20, paddingTop: 16, - }, - navIconButton: { - width: 40, - height: 40, - borderRadius: 20, - backgroundColor: 'rgba(255,255,255,0.2)', - justifyContent: 'center', - alignItems: 'center', - }, - navRightActions: { - flexDirection: 'row', - }, - headerTitles: { - paddingHorizontal: 20, - paddingBottom: 40, // Room for overlap - }, - tag: { - backgroundColor: colors.primary, - alignSelf: 'flex-start', - paddingHorizontal: 12, - paddingVertical: 4, - borderRadius: 8, - marginBottom: 8, - }, - tagText: { - color: colors.white, - fontSize: 12, - fontWeight: 'bold', - letterSpacing: 1, - }, - mainTitle: { - fontSize: 32, - fontWeight: '800', - color: colors.white, - marginBottom: 4, - lineHeight: 38, - }, - subTitle: { - fontSize: 16, - color: colors.white, - fontWeight: '500', - }, - contentWrapper: { - flex: 1, - backgroundColor: colors.background, - borderTopLeftRadius: 24, - borderTopRightRadius: 24, - marginTop: -24, - minHeight: 500, - }, - tabContainer: { - flexDirection: 'row', + paddingBottom: 16, + backgroundColor: colors.white, + zIndex: 10, borderBottomWidth: 1, borderBottomColor: colors.inputBorder, - paddingHorizontal: 20, - marginTop: 10, }, - tabButton: { - flex: 1, - paddingVertical: 16, - alignItems: 'center', - borderBottomWidth: 2, - borderBottomColor: 'transparent', - }, - tabButtonActive: { - borderBottomColor: colors.primary, - }, - tabText: { - fontSize: 15, - fontWeight: 'bold', - color: colors.textSecondary, - }, - tabTextActive: { - color: colors.primary, - }, - tabContent: { - padding: 20, - }, - statsContainer: { - flexDirection: 'row', - backgroundColor: colors.white, - borderRadius: 20, - padding: 20, - marginBottom: 24, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.05, - shadowRadius: 8, - elevation: 3, - }, - statCard: { - flex: 1, - alignItems: 'center', - }, - dividerVertical: { - width: 1, - backgroundColor: colors.inputBorder, - marginHorizontal: 10, - }, - statValue: { + title: { fontSize: 20, fontWeight: 'bold', color: colors.textMain, - marginBottom: 4, + flex: 1, + marginRight: 16, }, - statLabel: { - fontSize: 13, + backButton: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: colors.inputBackground, + justifyContent: 'center', + alignItems: 'center', + }, + mapArea: { + height: 180, + backgroundColor: '#F0F2F5', // Light map-like gray + justifyContent: 'center', + alignItems: 'center', + }, + imageOverlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.3)', + justifyContent: 'center', + alignItems: 'center', + }, + landmarkTag: { + position: 'absolute', + top: 12, + right: 12, + backgroundColor: 'rgba(0, 0, 0, 0.65)', + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 12, + maxWidth: '80%', + }, + landmarkTagText: { + color: colors.white, + fontSize: 12, + fontWeight: 'bold', + }, + mockRouteVisual: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 40, + width: '100%', + }, + routeDotLarge: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: colors.primary, + borderWidth: 4, + borderColor: colors.white, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + zIndex: 2, + }, + routeLineDashed: { + flex: 1, + height: 4, + borderWidth: 2, + borderColor: colors.primary, + borderStyle: 'dashed', + marginHorizontal: -4, + zIndex: 1, + }, + routePinLarge: { + width: 28, + height: 28, + borderRadius: 14, + backgroundColor: '#000000', + justifyContent: 'center', + alignItems: 'center', + borderWidth: 2, + borderColor: colors.white, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + zIndex: 2, + }, + detailsCard: { + backgroundColor: colors.white, + borderTopLeftRadius: 32, + borderTopRightRadius: 32, + padding: 24, + marginTop: -32, // Overlap map/image + shadowColor: '#000', + shadowOffset: { width: 0, height: -4 }, + shadowOpacity: 0.05, + shadowRadius: 12, + elevation: 10, + }, + inputGroup: { + marginBottom: 20, + }, + inputLabel: { + fontSize: 12, + fontWeight: 'bold', color: colors.textSecondary, + marginBottom: 8, + letterSpacing: 0.5, + }, + textInputReadonly: { + backgroundColor: colors.inputBackground, + borderRadius: 16, + paddingHorizontal: 16, + paddingVertical: 16, + }, + textInputReadonlyText: { + fontSize: 16, + color: colors.textMain, fontWeight: '500', }, - djCard: { - backgroundColor: '#FFF5EB', + boldText: { + fontWeight: 'bold', + }, + routeInputContainer: { + flexDirection: 'row', + marginTop: 10, + }, + routeTimeline: { + alignItems: 'center', + width: 30, + marginTop: 38, + marginRight: 8, + }, + timelineDot: { + width: 10, + height: 10, + borderRadius: 5, + backgroundColor: colors.primary, + }, + timelineLine: { + width: 1, + height: 60, + backgroundColor: colors.inputBorder, + marginVertical: 4, + }, + timelinePin: { + marginTop: 4, + }, + routeInputs: { + flex: 1, + }, + resultsContainer: { + flexDirection: 'row', + marginTop: 24, + paddingTop: 24, + borderTopWidth: 1, + borderTopColor: colors.inputBorder, + marginBottom: 24, + }, + resultItem: { + flex: 1, + alignItems: 'center', + }, + resultDivider: { + width: 1, + backgroundColor: colors.inputBorder, + marginHorizontal: 16, + }, + resultLabel: { + fontSize: 12, + color: colors.textSecondary, + marginBottom: 4, + fontWeight: 'bold', + }, + resultValue: { + fontSize: 18, + color: colors.textMain, + fontWeight: 'bold', + }, + sectionLabel: { + fontSize: 12, + fontWeight: 'bold', + color: colors.textSecondary, + marginBottom: 12, + letterSpacing: 0.5, + }, + playlistSection: { + marginBottom: 28, + }, + spotifyCard: { + backgroundColor: '#E8F5E9', // Light green matches spotify style borderRadius: 20, padding: 20, - marginBottom: 30, borderWidth: 1, - borderColor: '#FFE0C2', + borderColor: '#C8E6C9', }, - djHeader: { + spotifyHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, }, - djIconContainer: { - width: 28, - height: 28, - borderRadius: 14, - backgroundColor: colors.primary, + spotifyLogoContainer: { + width: 44, + height: 44, + borderRadius: 22, + backgroundColor: '#000000', justifyContent: 'center', alignItems: 'center', - marginRight: 10, + marginRight: 12, }, - djTitle: { - fontSize: 18, - fontWeight: 'bold', - color: '#8C3800', + spotifyHeaderTextContainer: { + flex: 1, }, - djText: { - fontSize: 15, - lineHeight: 24, - color: '#A34200', - fontWeight: '500', - }, - itinerarySection: { - marginBottom: 30, - }, - sectionHeader: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 20, - }, - sectionTitle: { - fontSize: 20, + spotifyCardTitle: { + fontSize: 16, fontWeight: 'bold', color: colors.textMain, + marginBottom: 2, + }, + spotifyCardSubtitle: { + fontSize: 13, + color: colors.textSecondary, + }, + spotifyButton: { + backgroundColor: colors.spotify, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 12, + borderRadius: 12, + shadowColor: colors.spotify, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, + }, + spotifyButtonText: { + color: colors.white, + fontSize: 14, + fontWeight: 'bold', + }, + stopsSection: { + marginBottom: 28, }, timelineContainer: { backgroundColor: colors.white, @@ -366,87 +485,40 @@ const styles = StyleSheet.create({ shadowOpacity: 0.05, shadowRadius: 8, elevation: 3, + borderWidth: 1, + borderColor: colors.inputBorder, + }, + noStopsCard: { + backgroundColor: '#FFF5EB', + borderRadius: 16, + padding: 16, + borderWidth: 1, + borderColor: '#FFE0C2', + alignItems: 'center', + }, + noStopsText: { + color: '#A34200', + fontSize: 14, + fontWeight: '500', + textAlign: 'center', + lineHeight: 20, + }, + bottomActions: { + marginTop: 8, + marginBottom: 16, }, mapsButton: { - backgroundColor: '#111827', // Almost black + backgroundColor: colors.inputBackground, flexDirection: 'row', - alignItems: 'center', justifyContent: 'center', + alignItems: 'center', paddingVertical: 18, - borderRadius: 30, // Highly rounded - marginBottom: 40, + borderRadius: 16, }, mapsButtonText: { - color: colors.white, + color: colors.primary, fontSize: 16, fontWeight: 'bold', - }, - - // Playlist Tab Styles - playlistGeneratedCard: { - backgroundColor: '#E8F5E9', // Light green - borderRadius: 24, - padding: 30, - alignItems: 'center', - marginBottom: 30, - borderWidth: 1, - borderColor: '#C8E6C9', - }, - spotifyLogoLarge: { - width: 80, - height: 80, - borderRadius: 40, - backgroundColor: '#000', - justifyContent: 'center', - alignItems: 'center', - marginBottom: 16, - }, - playlistTitle: { - fontSize: 24, - fontWeight: 'bold', - color: colors.textMain, - marginBottom: 8, - }, - playlistSubtitle: { - fontSize: 15, - color: colors.textSecondary, - marginBottom: 24, - }, - spotifyActionBtn: { - backgroundColor: colors.spotify, - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 24, - paddingVertical: 14, - borderRadius: 30, - width: '100%', - justifyContent: 'center', - shadowColor: colors.spotify, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 5, - }, - spotifyActionText: { - color: colors.white, - fontSize: 16, - fontWeight: 'bold', - }, - previewTitle: { - fontSize: 18, - fontWeight: 'bold', - color: colors.textMain, - marginBottom: 16, - }, - previewListCard: { - backgroundColor: colors.white, - borderRadius: 20, - padding: 20, - marginBottom: 40, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.05, - shadowRadius: 8, - elevation: 3, + marginLeft: 8, }, });