diff --git a/app/(auth)/index.tsx b/app/(auth)/index.tsx index 1631ef2..19a94e6 100644 --- a/app/(auth)/index.tsx +++ b/app/(auth)/index.tsx @@ -6,11 +6,10 @@ import { TouchableOpacity, StyleSheet, Dimensions, - Alert, + Alert, Platform, } from 'react-native'; import {useRouter} from "expo-router"; import {supabase} from "@/lib/supabaseClient"; -import * as SecureStore from 'expo-secure-store'; const { width } = Dimensions.get('window'); // Get the current screen width @@ -44,31 +43,13 @@ export default function LoginPage() { if (data?.user) { console.log("Signed in user:", data.user); + console.log("Session data:", data.session); - // Fetch role using the user ID directly - if (!supabase) { - Alert.alert("Error", "Supabase client is not initialized."); - return; - } - const { data: roleData, error: roleError } = await supabase - .from('profiles') - .select('role') - .eq('profile_id', data.user.id) - .single(); - - if (roleError) { - console.error('Error fetching role:', roleError); - } else { - console.log('User role:', roleData); - await SecureStore.setItemAsync('role', roleData.role?.toLowerCase()); - } - - // Navigate after everything is ready + // Navigate after successful login router.replace('/(tabs)'); } } - await signInWithEmail() } diff --git a/app/(auth)/signuppage.tsx b/app/(auth)/signuppage.tsx index 6427821..b290842 100644 --- a/app/(auth)/signuppage.tsx +++ b/app/(auth)/signuppage.tsx @@ -97,7 +97,6 @@ export default function SignUpPage() { } try { - // Sign up with Supabase const { data: authData, error: authError } = await supabase.auth.signUp({ email: formData.email, password: formData.password, @@ -116,16 +115,21 @@ export default function SignUpPage() { } if (authData.user) { - Alert.alert( - 'Success', - 'Account created successfully! Please check your email for verification.', - [ - { - text: 'OK', - onPress: () => router.replace('/(auth)'), - }, - ] - ); + if (Platform.OS === 'web') { + window.alert('Account created successfully! Please check your email for verification.'); + router.replace('/(auth)'); + } else { + Alert.alert( + 'Success', + 'Account created successfully! Please check your email for verification.', + [ + { + text: 'OK', + onPress: () => router.replace('/(auth)'), + }, + ] + ); + } } } catch (error) { console.error('Sign up error:', error); @@ -218,7 +222,6 @@ export default function SignUpPage() { Sign Up diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index ca5d88a..10b8ddd 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,86 +1,117 @@ +import { Stack } from 'expo-router'; +import { View, TouchableOpacity, Text, StyleSheet } from 'react-native'; +import { useRouter, usePathname } from 'expo-router'; import FontAwesome from '@expo/vector-icons/FontAwesome'; -import { Tabs } from 'expo-router'; -import {useEffect, useState} from "react"; -import * as SecureStore from "expo-secure-store"; +import { useEffect, useState } from 'react'; +import { supabase } from '@/lib/supabaseClient'; export default function TabLayout() { + const router = useRouter(); + const pathname = usePathname(); const [role, setRole] = useState(null); useEffect(() => { const fetchRole = async () => { - const storedRole = await SecureStore.getItemAsync('role'); - setRole(storedRole); + if (supabase) { + const { data: { session } } = await supabase.auth.getSession(); + if (session?.user) { + const { data: roleData } = await supabase + .from('profiles') + .select('role') + .eq('profile_id', session.user.id) + .single(); + + if (roleData?.role) { + setRole(roleData.role.toLowerCase()); + } + } + } }; fetchRole(); }, []); - const isAdmin = role === 'administrator' || role === 'supervisor'; + + const isAdmin = role === 'admin'; + + const tabs = [ + { name: 'index', title: 'Schedule', icon: 'calendar-check-o' }, + ...(isAdmin ? [{ name: 'add-shift', title: 'Add Shift', icon: 'calendar-plus-o' }] : []), + { name: 'coworkers', title: 'Coworkers', icon: 'users' }, + { name: 'notifications', title: 'Notifications', icon: 'bell' }, + { name: 'profileView', title: 'Profile', icon: 'user' }, + ]; + + const handleTabPress = (tabName: string) => { + if (tabName === 'index') { + router.push('/(tabs)' as any); + } else { + router.push(`/(tabs)/${tabName}` as any); + } + }; + + const isTabActive = (tabName: string) => { + if (tabName === 'index') { + return pathname === '/(tabs)/' || pathname === '/(tabs)/index'; + } + return pathname === `/(tabs)/${tabName}`; + }; + return ( - - , - }} - /> - , - href: isAdmin ? null : '/(tabs)/add-shift' - }} - /> - , - }} - /> - , - }} - /> - , - }} - /> - {/* These screens are not direct tabs and are handled by file-system routing or other navigation methods */} - {/* Remove the explicit definition for shift-details-page/[id] */} - {/* - - - - - */} - + + + + + + + + + + {/* Custom Bottom Tab Bar */} + + {tabs.map((tab) => { + const isActive = isTabActive(tab.name); + return ( + handleTabPress(tab.name)} + > + + + {tab.title} + + + ); + })} + + ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + tabBar: { + flexDirection: 'row', + backgroundColor: '#fff', + borderTopWidth: 1, + borderTopColor: '#E5E5EA', + paddingBottom: 20, + paddingTop: 10, + }, + tab: { + flex: 1, + alignItems: 'center', + paddingVertical: 8, + }, + tabText: { + fontSize: 12, + marginTop: 4, + color: '#8E8E93', + }, + activeTabText: { + color: '#007AFF', + }, +}); \ No newline at end of file diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index e4b471d..d6e40b7 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -101,9 +101,15 @@ export default function UserDashboard() { useEffect(() => { const fetchShiftData = async () => { if (supabase != null) { - const shifts = await getAll(supabase, 'shifts'); - if (shifts) - setShiftData(shifts as Shift[]); + // Check if user is authenticated + const { data: { session } } = await supabase.auth.getSession(); + if (session && session.user) { + const shifts = await getAll(supabase, 'shifts'); + if (shifts) + setShiftData(shifts as Shift[]); + } else { + console.log('No authenticated session found'); + } } } fetchShiftData(); diff --git a/app/(tabs)/profileView.tsx b/app/(tabs)/profileView.tsx index 0a6014f..3be75bb 100644 --- a/app/(tabs)/profileView.tsx +++ b/app/(tabs)/profileView.tsx @@ -23,19 +23,31 @@ const ProfileView = () => { const [error, setError] = useState(null); const router = useRouter(); - // Using the profile_int_id provided - const profileIntId = 5; - const fetchProfileById = useCallback(async () => { setLoading(true); setError(null); try { if (supabase) { + // Get the current session + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError) { + console.error('Error getting session:', sessionError); + setError(sessionError.message); + return; + } + + if (!session || !session.user) { + setError('No active session found. Please log in again.'); + return; + } + + // Fetch profile using the user's ID from the session const { data, error } = await supabase .from('profiles') .select('*') - .eq('profile_int_id', profileIntId) + .eq('profile_id', session.user.id) .single(); if (error) { @@ -59,9 +71,23 @@ const ProfileView = () => { fetchProfileById(); }, [fetchProfileById]); - const handleLogout = () => { - router.replace('/(auth)'); // Navigate to the login page - console.log('Logout pressed'); + const handleLogout = async () => { + try { + if (supabase) { + const { error } = await supabase.auth.signOut(); + if (error) { + console.error('Error signing out:', error); + } else { + console.log('Successfully signed out'); + router.replace('/(auth)'); + } + } else { + router.replace('/(auth)'); + } + } catch (error) { + console.error('Error during logout:', error); + router.replace('/(auth)'); + } }; const handleEditProfile = () => { diff --git a/app/_layout.tsx b/app/_layout.tsx index 2d1da3c..b84d344 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,5 +1,28 @@ import { Stack } from "expo-router"; -import { Slot } from "expo-router"; +import { useEffect } from "react"; +import { useColorScheme } from "react-native"; +import * as SplashScreen from "expo-splash-screen"; + +// Keep the splash screen visible while we fetch resources +SplashScreen.preventAutoHideAsync(); + export default function RootLayout() { - return (); + const colorScheme = useColorScheme(); + + useEffect(() => { + SplashScreen.hideAsync(); + }, []); + + return ( + + + + + + + + + + + ); } diff --git a/app/index.tsx b/app/index.tsx index ff2954c..31c9a71 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -94,4 +94,4 @@ const styles = StyleSheet.create({ textAlign: "left", marginTop: 20, }, -}); \ No newline at end of file +}); \ No newline at end of file diff --git a/hooks/shift-navigation.tsx b/hooks/shift-navigation.tsx index 2192153..8455397 100644 --- a/hooks/shift-navigation.tsx +++ b/hooks/shift-navigation.tsx @@ -16,11 +16,7 @@ export const useShiftNavigation = (currentShiftId: number) => { }; const showAlert = (title: string, message: string): void => { - if (Platform.OS === 'web') { - window.alert(`${title}\n${message}`); - } else { - Alert.alert(title, message); - } + Alert.alert(title, message); }; const goToPreviousShift = () => { diff --git a/lib/expoSecureStoreAdapter.ts b/lib/expoSecureStoreAdapter.ts index 887a100..43e39a3 100644 --- a/lib/expoSecureStoreAdapter.ts +++ b/lib/expoSecureStoreAdapter.ts @@ -1,34 +1,132 @@ import * as SecureStore from "expo-secure-store"; +import { Platform } from 'react-native'; + +const isWeb = Platform.OS === 'web'; +const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; + +// Cookie storage for web platform +const getCookie = (name: string): string | null => { + if (!isWeb || !isBrowser) return null; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + const cookieValue = parts.pop()?.split(';').shift(); + return cookieValue ? decodeURIComponent(cookieValue) : null; + } + return null; +}; + +const setCookie = (name: string, value: string, days = 7): void => { + if (!isWeb || !isBrowser) return; + const expires = new Date(); + expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000); + document.cookie = `${name}=${encodeURIComponent(value)};expires=${expires.toUTCString()};path=/`; +}; + +const deleteCookie = (name: string): void => { + if (!isWeb || !isBrowser) return; + document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`; +}; + +// Memory storage as fallback for non-browser environments +const memoryStorage = new Map(); export const ExpoSecureStoreAdapter = { getItem: async (key: string) => { try { - const value = await SecureStore.getItemAsync(key); + let value; + if (isWeb) { + if (isBrowser) { + value = getCookie(key); + } else { + value = memoryStorage.get(key); + } + } else { + value = await SecureStore.getItemAsync(key); + } + if (value) { - const { access_token, refresh_token } = JSON.parse(value); - return JSON.stringify({access_token, refresh_token, token_type: 'bearer', user: null}) + // Try to parse as JSON first (for session data) + try { + const parsed = JSON.parse(value); + if (parsed.access_token && parsed.refresh_token) { + // This is session data + const { access_token, refresh_token } = parsed; + return JSON.stringify({access_token, refresh_token, token_type: 'bearer', user: null}) + } else { + // This is not session data, return as simple string + return value; + } + } catch { + // Not valid JSON, return as simple string + return value; + } } return null; } catch (error) { - console.error('Error getting item from Securestore:', error); + console.error('Error getting item from storage:', error); return null; } }, setItem: async (key: string, value: string): Promise => { try { - const session = JSON.parse(value); - const { access_token, refresh_token } = session; - // Store only the tokens in SecureStore - await SecureStore.setItemAsync(key, JSON.stringify({ access_token, refresh_token })); + let storageValue; + + // Try to parse as JSON first (for session data) + try { + const session = JSON.parse(value); + if (session.access_token && session.refresh_token) { + // This is session data + const { access_token, refresh_token } = session; + storageValue = JSON.stringify({ access_token, refresh_token }); + } else { + // This is not session data, store as simple string + storageValue = value; + } + } catch { + // Not valid JSON, store as simple string + storageValue = value; + } + + if (isWeb) { + if (isBrowser) { + setCookie(key, storageValue); + } else { + memoryStorage.set(key, storageValue); + } + } else { + await SecureStore.setItemAsync(key, storageValue); + } } catch (error) { - console.error('Error setting item to Securestore:', error); + console.error('Error setting item to storage:', error); } }, removeItem: async (key: string): Promise => { try { - await SecureStore.deleteItemAsync(key); + if (isWeb) { + if (isBrowser) { + deleteCookie(key); + } else { + memoryStorage.delete(key); + } + } else { + await SecureStore.deleteItemAsync(key); + } } catch (error) { - console.error('Error deleting item from Securestore:', error); + console.error('Error deleting item from storage:', error); } }, -}; \ No newline at end of file +}; + +if (isWeb) { + // Polyfill for Supabase Auth JS expecting these methods + (ExpoSecureStoreAdapter as any).setValueWithKeyAsync = async (key: string, value: string) => { + return ExpoSecureStoreAdapter.setItem(key, value); + }; + (ExpoSecureStoreAdapter as any).getValueWithKeyAsync = async (key: string) => { + return ExpoSecureStoreAdapter.getItem(key); + }; + (ExpoSecureStoreAdapter as any).deleteValueWithKeyAsync = async (key: string) => { + return ExpoSecureStoreAdapter.removeItem(key); + }; +} \ No newline at end of file diff --git a/lib/supabaseClient.ts b/lib/supabaseClient.ts index 65460f0..00263c9 100644 --- a/lib/supabaseClient.ts +++ b/lib/supabaseClient.ts @@ -1,7 +1,4 @@ -// @ts-ignore -import * as SecureStore from 'expo-secure-store'; import { createClient } from '@supabase/supabase-js'; -import {ExpoSecureStoreAdapter} from "@/lib/expoSecureStoreAdapter"; const SUPABASE_URL = process.env.EXPO_PUBLIC_SUPABASE_URL; const SUPABASE_ANON_KEY = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY; @@ -13,7 +10,6 @@ if (!SUPABASE_URL || !SUPABASE_ANON_KEY) { export const supabase = SUPABASE_URL && SUPABASE_ANON_KEY ? createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { auth: { - storage: ExpoSecureStoreAdapter, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false,