diff --git a/app/addCalendarEvents.tsx b/app/addCalendarEvents.tsx index 8c30428..420f486 100644 --- a/app/addCalendarEvents.tsx +++ b/app/addCalendarEvents.tsx @@ -1,7 +1,7 @@ import React from 'react'; import axios from 'axios'; -import { IPAddr, repeatingData } from './constants'; -import GenericAddPageForm from './addEventPage'; +import { IPAddr, repeatingData } from '../constants/constants'; +import GenericAddPageForm from './genericAddEventPage'; import { cLog } from './log' export default function AddCalendarEvents() { diff --git a/app/addFinanceEvents.tsx b/app/addFinanceEvents.tsx index 7fc490d..48d3f90 100644 --- a/app/addFinanceEvents.tsx +++ b/app/addFinanceEvents.tsx @@ -1,7 +1,7 @@ import React from 'react'; import axios from 'axios'; -import { IPAddr, repeatingData } from './constants'; -import GenericAddPageForm from './addEventPage'; +import { IPAddr, repeatingData } from '../constants/constants'; +import GenericAddPageForm from './genericAddEventPage'; import { cLog } from './log' export default function AddFinanceEvents() { diff --git a/app/addHealthEvents.tsx b/app/addHealthEvents.tsx index 9548b60..7390f09 100644 --- a/app/addHealthEvents.tsx +++ b/app/addHealthEvents.tsx @@ -1,7 +1,7 @@ import React from 'react'; import axios from 'axios'; -import { IPAddr, repeatingData } from './constants'; -import GenericAddPageForm from './addEventPage'; +import { IPAddr, repeatingData } from '../constants/constants'; +import GenericAddPageForm from './genericAddEventPage'; import { cLog } from './log' export default function AddHealthEvents() { diff --git a/app/addMeals.tsx b/app/addMeals.tsx index c093ee0..097de33 100644 --- a/app/addMeals.tsx +++ b/app/addMeals.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react' import axios from 'axios'; -import { IPAddr, repeatingData } from './constants'; -import GenericAddPageForm from './addEventPage'; +import { IPAddr, repeatingData } from '../constants/constants'; +import GenericAddPageForm from './genericAddEventPage'; import { cLog } from './log' import AsyncStorage from '@react-native-async-storage/async-storage'; diff --git a/app/another.tsx b/app/another.tsx index 6eb953b..6af6112 100644 --- a/app/another.tsx +++ b/app/another.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text } from 'react-native'; -import { styles } from './styles'; +import { styles } from '../assets/styles/styles'; export default function AnotherScreen() { return ( diff --git a/app/calendarEvents.tsx b/app/calendarEvents.tsx index ef4b3f7..b2f2603 100644 --- a/app/calendarEvents.tsx +++ b/app/calendarEvents.tsx @@ -1,15 +1,11 @@ -import React, { useState, useCallback } from 'react'; -import { View, Text, TouchableOpacity, Alert } from 'react-native'; +import React, { useState } from 'react'; +import GenericMainPageForm from './genericMainPage'; +import { Alert } from 'react-native'; import * as WebBrowser from 'expo-web-browser'; import * as Linking from 'expo-linking'; import axios from 'axios'; -import { IPAddr, formatTime } from './constants'; -import { styles } from './styles'; -import GenericMainPageForm from './mainPageTemplate'; -import { Task } from '@/components/Types'; -import { useFocusEffect } from '@react-navigation/native'; +import { IPAddr } from '../constants/constants'; import { cLog } from './log' -import AsyncStorage from '@react-native-async-storage/async-storage'; const CLIENT_ID = process.env.EXPO_PUBLIC_CLIENT_ID || ''; const CLIENT_SECRET = process.env.EXPO_PUBLIC_CLIENT_SECRET || ''; @@ -20,22 +16,17 @@ const AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'; WebBrowser.maybeCompleteAuthSession(); export default function CalendarTracker() { - const [tasks, setTasks] = useState([]); - const [isGoogleCalendarLinked, setIsGoogleCalendarLinked] = useState(false); + const [isGoogleCalendarLinked, setIsGoogleCalendarLinked] = useState(false); const handlePress = async () => { try { cLog("Initiating OAuth login..."); - const authUrl = `${AUTH_URI}?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=https://www.googleapis.com/auth/calendar&access_type=offline&prompt=consent`; - const result = await WebBrowser.openAuthSessionAsync(authUrl, REDIRECT_URI); cLog("WebBrowser result:" + result); - if (result.type === 'success' && result.url) { const authCodeMatch = result.url.match(/code=([^&]*)/); const authCode = authCodeMatch ? authCodeMatch[1] : null; - if (authCode) { cLog("Received authorization code:" + authCode); getAccessToken(authCode); @@ -61,13 +52,11 @@ export default function CalendarTracker() { params.append('client_secret', CLIENT_SECRET); params.append('redirect_uri', REDIRECT_URI); params.append('grant_type', 'authorization_code'); - const response = await axios.post(TOKEN_URI, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }); - if (response.data.access_token) { cLog("Received access token:" + response.data.access_token); linkGoogleCalendar(1, response.data.access_token); @@ -90,12 +79,11 @@ export default function CalendarTracker() { userId: userId, accessToken: token, }); - if (response.status === 200 && response.data.includes("successfully")) { cLog('Google Calendar linked successfully:' + response.data); Alert.alert('Google Calendar linked successfully!'); setIsGoogleCalendarLinked(true); - fetchEvents(); + // fetchEvents(); } else { console.error("Failed to link Google Calendar"); Alert.alert('Failed to link Google Calendar'); @@ -106,56 +94,18 @@ export default function CalendarTracker() { } }; - const fetchEvents = async () => { - try { - const hit = `${IPAddr}/get_calendar_events/` + (await AsyncStorage.getItem('userId')); - cLog('Fetching calendar events from:' + hit); - const response = await axios.get(hit); - - if (response.status === 200 && response.data) { - const { events, googleCalendarLinked } = response.data; - - setIsGoogleCalendarLinked(googleCalendarLinked); - const formattedEvents = events.map((event: any) => ({ - id: `${event.id}-${event.event_date}-${event.event_time}`, - title: `${event.title} at ${event.event_date}, ${formatTime(event.event_time)}`, - done: false, - icon: 'calendar-outline', - event: event - })).slice(0, 20);; - - setTasks(formattedEvents); - } else { - console.error('Failed to fetch events'); - } - } catch (error) { - console.error('Error fetching events:', error); - } - }; - - - useFocusEffect( - useCallback(() => { - fetchEvents(); - }, []) - ); - return ( - - - {!isGoogleCalendarLinked && ( - - - Link to Google Calendar - - ) - } - + `${event.id}-${event.event_date}-${event.event_time}`} + eventIconFunc={() => 'calendar-outline'} + handlePress={handlePress} + isGoogleCalendarLinked={isGoogleCalendarLinked} + setIsGoogleCalendarLinked={setIsGoogleCalendarLinked} + /> ); } \ No newline at end of file diff --git a/app/finance.tsx b/app/finance.tsx index 2b9a327..3f2ea01 100644 --- a/app/finance.tsx +++ b/app/finance.tsx @@ -1,44 +1,14 @@ -import { IPAddr, formatTime } from './constants'; -import { Task } from '../components/Types'; -import axios from 'axios'; -import GenericMainPageForm from './mainPageTemplate'; -import React, { useState, useCallback } from 'react'; -import { useFocusEffect } from '@react-navigation/native'; -import { cLog } from './log' -import AsyncStorage from '@react-native-async-storage/async-storage'; +import React from 'react'; +import GenericMainPageForm from './genericMainPage'; export default function FinanceTracker() { - const [tasks, setTasks] = useState([]); - - const fetchEvents = async () => { - const hit = IPAddr + '/get_finance_events/' + (await AsyncStorage.getItem('userId')); - cLog('Fetching finance events from:' + hit); - axios.get(hit) - .then(response => { - const events = response.data.map((event: any) => ({ - id: event.id.toString(), - title: `${event.title} at ${event.event_date}, ${formatTime(event.event_time)}`, - done: false, - icon: 'wallet-outline', - event: event - })).slice(0, 10);; - setTasks(events); - }) - .catch(error => console.error('Error fetching events:', error)); - } - - useFocusEffect( - useCallback(() => { - fetchEvents(); - }, []) - ); - return ( ); -} + hitAddress={`/get_finance_events/`} + eventIconFunc={() => 'wallet-outline'} + /> + ); +} \ No newline at end of file diff --git a/app/addEventPage.tsx b/app/genericAddEventPage.tsx similarity index 98% rename from app/addEventPage.tsx rename to app/genericAddEventPage.tsx index c2bfc95..d02920a 100644 --- a/app/addEventPage.tsx +++ b/app/genericAddEventPage.tsx @@ -2,14 +2,14 @@ import React, { useCallback, useState } from 'react'; import { View, TextInput, Text, TouchableOpacity, ScrollView } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import { Dropdown } from 'react-native-element-dropdown'; -import { styles } from './styles'; +import { styles } from '../assets/styles/styles'; import { RootStackParamList } from '@/components/Types'; import { StackNavigationProp } from '@react-navigation/stack'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; import MultiSelect from 'react-native-multiple-select'; import { cLog } from './log'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { verifyToken } from './constants'; +import { verifyToken } from '../constants/constants'; interface FormProps { title: string; diff --git a/app/genericMainPage.tsx b/app/genericMainPage.tsx new file mode 100644 index 0000000..1e0728d --- /dev/null +++ b/app/genericMainPage.tsx @@ -0,0 +1,189 @@ +import React, { useCallback, useState } from 'react'; +import { View, Text, FlatList, TouchableOpacity } from 'react-native'; +import BouncyCheckbox from "react-native-bouncy-checkbox"; +import { Calendar } from 'react-native-calendars'; +import { styles } from '../assets/styles/styles'; +import { Ionicons } from "@expo/vector-icons"; +import { EventProps, GoogleCalendarProps, NavigationProps, RootStackParamList, Task } from '../components/Types'; +import { useFocusEffect, useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { cLog } from './log'; +import { formatTime, getPageFromEventType, getPageName, IPAddr, verifyToken } from '../constants/constants'; +import axios from 'axios'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +interface FormProps extends EventProps, GoogleCalendarProps, NavigationProps { + title: string; + header?: string; + hitAddress: string; + sliceRange?: number; +} + +const GenericMainPageForm: React.FC = ({ + title, + header = 'Upcoming Events', + nextPage, + thisPage, + hitAddress, + googleCalendar = false, + eventIdFunc = (event) => event.id.toString(), + eventTitleFunc = (event) => `${event.title} at ${event.event_date}, ${formatTime(event.event_time)}`, + eventIconFunc, + handlePress, + sliceRange = 10, + isGoogleCalendarLinked = false, + setIsGoogleCalendarLinked = () => { }, +}) => { + const [selectedDate, setSelectedDate] = useState(''); + const [isCalendarVisible, setIsCalendarVisible] = useState(true); + const [tasks, setTasks] = useState([]); + + type Prop = StackNavigationProp; + const navigation = useNavigation(); + + const handleDayPress = (day: { dateString: React.SetStateAction; }) => { + setSelectedDate(day.dateString); + }; + + const handleViewPress = (item: Task) => { + // cLog(item); + const route = { ...item, thisPage }; + if (route.thisPage === 'index') { + route.thisPage = getPageFromEventType(route.event.event_type) as keyof RootStackParamList; + } + cLog("Route:", route); + navigation.navigate(getPageName(route.thisPage) as any, { event: route }); + } + + const renderTask = ({ item }: { item: Task }) => ( + handleViewPress(item)}> + + + + + + {item.title} + + { item.done = isChecked; }} + /> + + + ); + + const fetchEvents = async () => { + try { + const userId = await AsyncStorage.getItem('userId'); + const hit = `${IPAddr}${hitAddress}${userId}`; + cLog(`Fetching events from: ${hit}`); + + const response = await axios.get(hit); + + if (response.status === 200 && response.data) { + const { events, googleCalendarLinked } = response.data; + cLog(`response.data:`); + cLog(response.data); + cLog(`events:`); + cLog(events); + + if (googleCalendar) { + setIsGoogleCalendarLinked(googleCalendarLinked); + } + + const eventsArray = Array.isArray(events) ? events : response.data; + cLog(`eventsArray:`); + cLog(eventsArray); + const formattedEvents = eventsArray.map((event: any) => ({ + id: eventIdFunc(event), + title: eventTitleFunc(event), + done: false, + icon: eventIconFunc(event) as Task['icon'], + event: event, + })).slice(0, sliceRange); + + setTasks(formattedEvents); + } else { + console.error('Failed to fetch events'); + } + } catch (error) { + console.error('Error fetching events:', error); + } + }; + + + useFocusEffect( + useCallback(() => { + verifyToken(navigation); + fetchEvents(); + }, []) + ); + + return ( + + + + + {title} + {header} + + item.id} + /> + + + Events + setIsCalendarVisible(!isCalendarVisible)}> + + + + {isCalendarVisible && ( + + )} + {title !== 'Home' && + navigation.navigate(nextPage as any)} + hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }} + > + + + + + } + + {googleCalendar && !isGoogleCalendarLinked && ( + + + Link to Google Calendar + + ) + } + + ); +}; + +export default GenericMainPageForm; diff --git a/app/genericViewEventPage.tsx b/app/genericViewEventPage.tsx index d64806f..5255168 100644 --- a/app/genericViewEventPage.tsx +++ b/app/genericViewEventPage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { View, Text, TextInput, Button, StyleSheet } from 'react-native'; import { Dropdown } from 'react-native-element-dropdown'; -import { getUserId, } from './constants'; +import { getUserId, } from '../constants/constants'; import { cLog } from './log'; import { RouteProp, useRoute } from '@react-navigation/native'; import { RootStackParamList } from '@/components/Types'; diff --git a/app/healthTracker.tsx b/app/healthTracker.tsx index e32ea76..c1bb996 100644 --- a/app/healthTracker.tsx +++ b/app/healthTracker.tsx @@ -1,45 +1,14 @@ -import React, { useState, useCallback } from 'react'; -import { formatTime, IPAddr } from './constants'; -import { Task } from '../components/Types'; -import axios from 'axios'; -import GenericMainPageForm from './mainPageTemplate'; -import { useFocusEffect } from '@react-navigation/native'; -import { cLog } from './log' -import AsyncStorage from '@react-native-async-storage/async-storage'; +import React from 'react'; +import GenericMainPageForm from './genericMainPage'; export default function HealthTracker() { - const [tasks, setTasks] = useState([]); - - const fetchEvents = async () => { - const hit = IPAddr + '/get_health_events/' + (await AsyncStorage.getItem('userId')); - cLog('Fetching health events from:' + hit); - axios.get(hit) - .then(response => { - const events = response.data.map((event: any) => ({ - id: event.id.toString(), - title: `${event.title} at ${event.event_date}, ${formatTime(event.event_time)}`, - done: false, - icon: 'fitness-outline', - event: event - })).slice(0, 10); - setTasks(events); - }) - .catch(error => console.error('Error fetching events:', error)); - }; - - useFocusEffect( - useCallback(() => { - fetchEvents(); - }, []) - ); - return ( 'fitness-outline'} /> ); } diff --git a/app/home.tsx b/app/home.tsx index 237e0a4..135be18 100644 --- a/app/home.tsx +++ b/app/home.tsx @@ -1,42 +1,21 @@ -import GenericMainPageForm from './mainPageTemplate'; import React, { useState, useCallback } from 'react'; -import { formatTime, getEventIcon, IPAddr } from './constants'; -import { Task } from '../components/Types'; -import axios from 'axios'; +import GenericMainPageForm from './genericMainPage'; +import { formatTime, getEventIcon } from '../constants/constants'; import { useFocusEffect } from '@react-navigation/native'; -import { cLog } from './log' import AsyncStorage from '@react-native-async-storage/async-storage'; export default function TaskScreen() { - const [tasks, setTasks] = useState([]); const [header, setHeader] = useState(null); - const fetchAllEvents = async () => { - const hit = IPAddr + '/get_all_events/' + (await AsyncStorage.getItem('userId')); - cLog('Fetching all events from:' + hit); - axios.get(hit) - .then(response => { - const events = response.data.map((event: any) => ({ - id: event.id.toString(), - title: `${event.title} at ${event.eventDate}, ${formatTime(event.eventTime)}`, - done: false, - icon: getEventIcon(event.event_type), - event: event - })).slice(0, 10);; - setTasks(events); - }) - .catch(error => console.error('Error fetching events:', error)); - } - - const fetchHeader = async () => { - const name = await AsyncStorage.getItem('name'); - setHeader(name); - }; - useFocusEffect( useCallback(() => { - fetchHeader(); - fetchAllEvents(); + const fetchUserData = async () => { + const name = await AsyncStorage.getItem('name'); + if (name && name !== 'null') { + setHeader(name); + } + }; + fetchUserData(); }, []) ); @@ -46,7 +25,9 @@ export default function TaskScreen() { header={`Welcome ${header && header !== 'null' ? header : 'User'}!`} nextPage='home' thisPage='home' - tasks={tasks} + hitAddress={`/get_all_events/`} + eventTitleFunc = {(event) => `${event.title} at ${event.eventDate}, ${formatTime(event.eventTime)}`} + eventIconFunc={(event) => getEventIcon(event.event_type)} /> ); } diff --git a/app/index.tsx b/app/index.tsx index 1b01bf9..0301dde 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,8 +1,8 @@ import React, { useState, useCallback } from 'react'; -import { IPAddr } from './constants'; -import { RootStackParamList } from '../components/Types'; +import { IPAddr } from '../constants/constants'; +import { RootStackParamList, UserInfo } from '../components/Types'; import axios from 'axios'; -import { styles } from './styles'; +import { styles } from '../assets/styles/styles'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; import { cLog } from './log' import AsyncStorage from "@react-native-async-storage/async-storage"; @@ -12,63 +12,59 @@ import { Button, TextInput, View, Text } from 'react-native'; export default function TaskScreen() { type Prop = StackNavigationProp; const navigation = useNavigation(); - const [userName, setUsername] = useState(''); - const [password, setPassword] = useState(''); + const [credentials, setCredentials] = useState({ userName: '', password: '' }); - const checkLogin = async () => { + const updateAsyncStorage = async (userInfo: UserInfo) => { try { - const storedToken = await AsyncStorage.getItem('token') || ''; - const storedUserName = await AsyncStorage.getItem('userName') || ''; - - // Check if we have a valid token and username - if (!storedToken || !storedUserName) { - console.log('No valid token or username found'); - return; - } - - const hit = IPAddr + '/checkLogin'; - const response = await axios.put(hit, { userName: storedUserName, token: storedToken }); - - cLog('Login response:', response.data); - if (!response.data) { - return; - } - - const userInfo = response.data.split(","); - await AsyncStorage.setItem('userId', userInfo[0]); - await AsyncStorage.setItem('userName', userInfo[1]); - await AsyncStorage.setItem('email', userInfo[2]); - await AsyncStorage.setItem('name', userInfo[3]); - await AsyncStorage.setItem('token', userInfo[4]); + await AsyncStorage.multiSet([ + ['userId', userInfo.userId], + ['userName', userInfo.userName], + ['email', userInfo.email], + ['name', userInfo.name], + ['token', userInfo.token], + ]); + } catch (error) { + console.error('Error updating AsyncStorage:', error); + } + }; - cLog('User id saved:', response.data.userId); + const handleLoginResponse = async (responseData: string) => { + if (responseData) { + const userInfo = parseUserInfo(responseData); + await updateAsyncStorage(userInfo); navigation.navigate('home'); - } catch (error) { - console.error('Error logging in:', error); } - } + }; - const handleLogin = async (userName: string, password: string) => { + const performLoginRequest = async (url: string, data: object) => { try { - const hit = IPAddr + '/login'; - const response = await axios.put(hit, { userName, password }); + const response = await axios.put(url, data); cLog('Login response:', response.data); - if (!response.data) { - return; - } - const userInfo = response.data.split(","); - await AsyncStorage.setItem('userId', userInfo[0]); - await AsyncStorage.setItem('userName', userInfo[1]); - await AsyncStorage.setItem('email', userInfo[2]); - await AsyncStorage.setItem('name', userInfo[3]); - await AsyncStorage.setItem('token', userInfo[4]); - cLog('User id saved:', response.data.userId); - navigation.navigate('home'); + await handleLoginResponse(response.data); } catch (error) { - console.error('Error logging in:', error); + console.error('Error during login request:', error); + } + }; + + const checkLogin = async () => { + const [storedToken, storedUserName] = await AsyncStorage.multiGet(['token', 'userName']); + if (storedToken[1] && storedUserName[1]) { + await performLoginRequest(`${IPAddr}/checkLogin`, { userName: storedUserName[1], token: storedToken[1] }); + } else { + console.log('No valid token or username found'); } + }; + + const handleLogin = async () => { + const { userName, password } = credentials; + performLoginRequest(`${IPAddr}/login`, { userName, password }) } + const parseUserInfo = (data: string): UserInfo => { + const [userId, userName, email, name, token] = data.split(","); + return { userId, userName, email, name, token }; + }; + useFocusEffect( useCallback(() => { checkLogin(); @@ -83,16 +79,16 @@ export default function TaskScreen() { style={styles.loginPageInput} placeholder="Enter Username" autoCapitalize="none" - onChangeText={(text) => setUsername(text)} + onChangeText={(text) => setCredentials({ ...credentials, userName: text })} /> Password setPassword(text)} + onChangeText={(text) => setCredentials({ ...credentials, password: text })} /> -