diff --git a/app.json b/app.json index eeb32c5..8c0b335 100644 --- a/app.json +++ b/app.json @@ -5,7 +5,7 @@ "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/icon.png", - "jsEngine" : "hermes", + "jsEngine": "hermes", "scheme": "routine-app", "userInterfaceStyle": "automatic", "newArchEnabled": true, @@ -53,9 +53,9 @@ "extra": { "router": {}, "eas": { - "projectId": "57ef7f6c-5fc1-457a-96af-95ecc3e1ccd1" + "projectId": "4c7b89c2-7073-4c7d-b355-94ae9b09c3ce" } }, - "owner": "rojyqaxyl" + "owner": "walterwhite919" } } diff --git a/app/(tabs)/home.tsx b/app/(tabs)/home.tsx index f9893a0..abb8721 100644 --- a/app/(tabs)/home.tsx +++ b/app/(tabs)/home.tsx @@ -8,31 +8,22 @@ import { TouchableOpacity, } from "react-native"; import { authClient } from "@/lib/auth-client"; -import { useState, useEffect } from "react"; -import { WeekDay } from "@/lib/types/routine"; +import { useState, useMemo } from "react"; import { useRoutine } from "@/lib/api/routine"; import Header from "@/components/header"; -import { RoutineCard } from "@/components/routine-card"; +import { DAYS, getTodayIndex, getWeekDates } from "@/lib/utils/routine"; +import { RoutineContent } from "@/components/routine-content"; export default function Home() { - const { isRefetching, isPending } = authClient.useSession(); + const { isPending } = authClient.useSession(); const [refreshing, setRefreshing] = useState(false); - const [activeDayIndex, setActiveDayIndex] = useState(() => { - const today = new Date().getDay(); - return today === 6 ? 0 : today > 5 ? 0 : today; - }); - const [todayRoutine, setTodayRoutine] = useState( - undefined - ); + const [activeDayIndex, setActiveDayIndex] = useState(getTodayIndex); const { data: routineData, isLoading, error, refetch } = useRoutine(); - useEffect(() => { - if (routineData?.week) { - const routineForToday = routineData.week[activeDayIndex]; - setTodayRoutine(routineForToday); - } - }, [activeDayIndex, routineData]); + const todayString = new Date().toDateString(); + const weekDates = useMemo(() => getWeekDates(), [todayString]); + const todayRoutine = routineData?.week[activeDayIndex]; const onRefresh = async () => { setRefreshing(true); @@ -40,7 +31,7 @@ export default function Home() { setRefreshing(false); }; - if (isRefetching || isPending) { + if (isPending) { return ( @@ -51,18 +42,6 @@ export default function Home() { ); } - const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri"]; - - const today = new Date(); - const dayOfWeek = today.getDay(); - const sunday = new Date(today); - sunday.setDate(today.getDate() - dayOfWeek); - const weekDates = days.map((_, index) => { - const date = new Date(sunday); - date.setDate(sunday.getDate() + index); - return date.getDate(); - }); - return ( - {days.map((day, index) => { + {DAYS.map((day, index) => { const isActiveDay = index === activeDayIndex; return ( )} - {!isLoading && - (routineData && - routineData.week.every((day: WeekDay) => day.slots.length === 0) ? ( - - - No classes scheduled this week - - - ) : todayRoutine?.slots.length ? ( - todayRoutine.slots.map((slot, idx) => ( - - )) - ) : routineData ? ( - - - No classes scheduled for this day - - - ) : null)} + diff --git a/app/(tabs)/setting.tsx b/app/(tabs)/setting.tsx index 3dda2d7..da0e052 100644 --- a/app/(tabs)/setting.tsx +++ b/app/(tabs)/setting.tsx @@ -3,15 +3,14 @@ import { ScrollView, Text, View, - TouchableOpacity, Switch, Alert, } from "react-native"; import { authClient } from "@/lib/auth-client"; import { useColorScheme } from "@/lib/use-color-scheme"; -import Ionicons from "@expo/vector-icons/Ionicons"; import { useProfile } from "@/lib/api/profile"; import { useRouter } from "expo-router"; +import { SettingItem } from "@/components/setting-item"; export default function Setting() { const router = useRouter(); @@ -39,75 +38,6 @@ export default function Setting() { ]); }; - const SettingItem = ({ - icon, - label, - value, - onPress, - showChevron = true, - isDestructive = false, - rightElement, - isLast = false, - }: { - icon: keyof typeof Ionicons.glyphMap; - label: string; - value?: string; - onPress?: () => void; - showChevron?: boolean; - isDestructive?: boolean; - rightElement?: React.ReactNode; - isLast?: boolean; - }) => ( - - - - - - - - {label} - - - {value && ( - - {value} - - )} - {rightElement} - {showChevron && !rightElement && ( - - )} - - {!isLast && } - - ); - return ( diff --git a/app/_layout.tsx b/app/_layout.tsx index 2ea951a..9020184 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -18,7 +18,15 @@ import { SafeAreaView } from "react-native-safe-area-context"; import { GroupsProvider } from "@/context/groupContext"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, + retry: 2, + refetchOnWindowFocus: false, + }, + }, +}); const LIGHT_THEME: Theme = { ...DefaultTheme, diff --git a/components/profile-display.tsx b/components/profile-display.tsx index 034688f..546699b 100644 --- a/components/profile-display.tsx +++ b/components/profile-display.tsx @@ -1,12 +1,14 @@ -import { TouchableOpacity, Text, View } from "react-native"; +import { TouchableOpacity, Text, View, ActivityIndicator } from "react-native"; import Ionicons from "@expo/vector-icons/Ionicons"; -import {ProfileDisplayProps} from "@/lib/types/profile" +import { ProfileDisplayProps } from "@/lib/types/profile"; import { useProfile } from "@/lib/api/profile"; export function ProfileDisplay({ onEditPress }: ProfileDisplayProps) { - const { data: profile } = useProfile(); + const { data: profile, isLoading, isError } = useProfile(); - if (!profile) { + if (isLoading) return ; + + if (isError || !profile) { return null; } diff --git a/components/routine-content.tsx b/components/routine-content.tsx new file mode 100644 index 0000000..e60340a --- /dev/null +++ b/components/routine-content.tsx @@ -0,0 +1,44 @@ +import { RoutineCard } from "@/components/routine-card"; +import { Text, View } from "react-native"; +import type { RoutineContentProps } from "@/lib/types/routine"; + +export function RoutineContent({ + isLoading, + routineData, + todayRoutine, +}: RoutineContentProps) { + if (isLoading || !routineData) return null; + + const isWeekEmpty = routineData.week.every((day) => day.slots.length === 0); + + if (isWeekEmpty) { + return ( + + + No classes scheduled this week + + + ); + } + + if (!todayRoutine?.slots.length) { + return ( + + + No classes scheduled for this day + + + ); + } + + return ( + <> + {todayRoutine.slots.map((slot) => ( + + ))} + + ); +} \ No newline at end of file diff --git a/components/setting-item.tsx b/components/setting-item.tsx new file mode 100644 index 0000000..0f99350 --- /dev/null +++ b/components/setting-item.tsx @@ -0,0 +1,72 @@ +import { View, Text, TouchableOpacity } from "react-native"; +import Ionicons from "@expo/vector-icons/Ionicons"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import type { SettingItemProps } from "@/lib/types/setting"; + +export function SettingItem({ + icon, + label, + value, + onPress, + showChevron = true, + isDestructive = false, + rightElement, + isLast = false, + isDark, +}: SettingItemProps) { + const { isDarkColorScheme } = useColorScheme(); + const effectiveDark = isDark ?? isDarkColorScheme; + + return ( + + + + + + + + {label} + + + {value && ( + + {value} + + )} + {rightElement} + {showChevron && !rightElement && ( + + )} + + {!isLast && } + + ); +} diff --git a/components/sign-in.tsx b/components/sign-in.tsx index f5d7c1f..a07b5d6 100644 --- a/components/sign-in.tsx +++ b/components/sign-in.tsx @@ -32,9 +32,9 @@ export default function SignIn() { if (result.error) { Toast.error( - result.error.message + result.error.message?.includes("verify") ? "Check Email to Verify First" - : "Invalid credentials" + : result.error.message || "Invalid credentials" ); console.log("Error while logging in: ", result.error.message); console.log(result.error); diff --git a/lib/types/routine.ts b/lib/types/routine.ts index 82988d6..c973f79 100644 --- a/lib/types/routine.ts +++ b/lib/types/routine.ts @@ -1,34 +1,39 @@ export interface Slot { - startTime: string; - endTime: string; - moduleCode: string; - moduleName: string; - classType: string; - room: string; - teacher: { - name: string; - email: string; - }; - isActive: boolean; - isJoinedClass: boolean; - joinedGroups?: string[]; + startTime: string; + endTime: string; + moduleCode: string; + moduleName: string; + classType: string; + room: string; + teacher: { + name: string; + email: string; + }; + isActive: boolean; + isJoinedClass: boolean; + joinedGroups?: string[]; } - export interface WeekDay { - day: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; - slots: Slot[]; + day: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; + slots: Slot[]; } export interface RoutineData { - groupId: string; - groupName: string; - courseName: string; - week: WeekDay[]; + groupId: string; + groupName: string; + courseName: string; + week: WeekDay[]; } export interface RoutineResponse { - success: boolean; - message: string; - data: RoutineData; + success: boolean; + message: string; + data: RoutineData; +} + +export interface RoutineContentProps { + isLoading: boolean; + routineData: RoutineData | undefined; + todayRoutine: WeekDay | undefined; } diff --git a/lib/types/setting.ts b/lib/types/setting.ts new file mode 100644 index 0000000..31c953b --- /dev/null +++ b/lib/types/setting.ts @@ -0,0 +1,14 @@ +import type { ComponentProps } from "react"; +import type Ionicons from "@expo/vector-icons/Ionicons"; + +export interface SettingItemProps { + icon: ComponentProps["name"]; + label: string; + value?: string; + onPress?: () => void; + showChevron?: boolean; + isDestructive?: boolean; + rightElement?: React.ReactNode; + isLast?: boolean; + isDark?: boolean; +} diff --git a/lib/utils/routine.ts b/lib/utils/routine.ts new file mode 100644 index 0000000..523afe0 --- /dev/null +++ b/lib/utils/routine.ts @@ -0,0 +1,18 @@ +export const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri"] as const; + +export function getTodayIndex(): number { + const day = new Date().getDay(); + return day >= 6 ? 0 : day; +} + +export function getWeekDates(): number[] { + const today = new Date(); + const sunday = new Date(today); + sunday.setDate(today.getDate() - today.getDay()); + + return DAYS.map((_, index) => { + const date = new Date(sunday); + date.setDate(sunday.getDate() + index); + return date.getDate(); + }); +}