diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
deleted file mode 100644
index cfbc1e23..00000000
--- a/app/(tabs)/_layout.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Tabs } from 'expo-router';
-import React from 'react';
-import { Platform } from 'react-native';
-
-import { HapticTab } from '@/components/HapticTab';
-import { IconSymbol } from '@/components/ui/IconSymbol';
-import TabBarBackground from '@/components/ui/TabBarBackground';
-import { Colors } from '@/constants/Colors';
-import { useColorScheme } from '@/hooks/useColorScheme';
-
-export default function TabLayout() {
- const colorScheme = useColorScheme();
-
- return (
-
- ,
- }}
- />
- ,
- }}
- />
-
- );
-}
diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx
deleted file mode 100644
index d4fbcaaa..00000000
--- a/app/(tabs)/explore.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { Image } from 'expo-image';
-import { Platform, StyleSheet } from 'react-native';
-
-import { Collapsible } from '@/components/Collapsible';
-import { ExternalLink } from '@/components/ExternalLink';
-import ParallaxScrollView from '@/components/ParallaxScrollView';
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
-import { IconSymbol } from '@/components/ui/IconSymbol';
-
-export default function TabTwoScreen() {
- return (
-
- }>
-
- Explore
-
- This app includes example code to help you get started.
-
-
- This app has two screens:{' '}
- app/(tabs)/index.tsx and{' '}
- app/(tabs)/explore.tsx
-
-
- The layout file in app/(tabs)/_layout.tsx{' '}
- sets up the tab navigator.
-
-
- Learn more
-
-
-
-
- You can open this project on Android, iOS, and the web. To open the web version, press{' '}
- w in the terminal running this project.
-
-
-
-
- For static images, you can use the @2x and{' '}
- @3x suffixes to provide files for
- different screen densities
-
-
-
- Learn more
-
-
-
-
- Open app/_layout.tsx to see how to load{' '}
-
- custom fonts such as this one.
-
-
-
- Learn more
-
-
-
-
- This template has light and dark mode support. The{' '}
- useColorScheme() hook lets you inspect
- what the user's current color scheme is, and so you can adjust UI colors accordingly.
-
-
- Learn more
-
-
-
-
- This template includes an example of an animated component. The{' '}
- components/HelloWave.tsx component uses
- the powerful react-native-reanimated{' '}
- library to create a waving hand animation.
-
- {Platform.select({
- ios: (
-
- The components/ParallaxScrollView.tsx{' '}
- component provides a parallax effect for the header image.
-
- ),
- })}
-
-
- );
-}
-
-const styles = StyleSheet.create({
- headerImage: {
- color: '#808080',
- bottom: -90,
- left: -35,
- position: 'absolute',
- },
- titleContainer: {
- flexDirection: 'row',
- gap: 8,
- },
-});
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
deleted file mode 100644
index 462e8cd1..00000000
--- a/app/(tabs)/index.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Image } from 'expo-image';
-import { Platform, StyleSheet } from 'react-native';
-
-import { HelloWave } from '@/components/HelloWave';
-import ParallaxScrollView from '@/components/ParallaxScrollView';
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
-
-export default function HomeScreen() {
- return (
-
- }>
-
- Welcome!
-
-
-
- Step 1: Try it
-
- Edit app/(tabs)/index.tsx to see changes.
- Press{' '}
-
- {Platform.select({
- ios: 'cmd + d',
- android: 'cmd + m',
- web: 'F12',
- })}
- {' '}
- to open developer tools.
-
-
-
- Step 2: Explore
-
- {`Tap the Explore tab to learn more about what's included in this starter app.`}
-
-
-
- Step 3: Get a fresh start
-
- {`When you're ready, run `}
- npm run reset-project to get a fresh{' '}
- app directory. This will move the current{' '}
- app to{' '}
- app-example.
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- titleContainer: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: 8,
- },
- stepContainer: {
- gap: 8,
- marginBottom: 8,
- },
- reactLogo: {
- height: 178,
- width: 290,
- bottom: 0,
- left: 0,
- position: 'absolute',
- },
-});
diff --git a/app/+not-found.tsx b/app/+not-found.tsx
deleted file mode 100644
index 215b0ed1..00000000
--- a/app/+not-found.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Link, Stack } from 'expo-router';
-import { StyleSheet } from 'react-native';
-
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
-
-export default function NotFoundScreen() {
- return (
- <>
-
-
- This screen does not exist.
-
- Go to home screen!
-
-
- >
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- padding: 20,
- },
- link: {
- marginTop: 15,
- paddingVertical: 15,
- },
-});
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 8d506f7e..d4fe122b 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,29 +1,48 @@
-import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
-import { useFonts } from 'expo-font';
-import { Stack } from 'expo-router';
-import { StatusBar } from 'expo-status-bar';
-import 'react-native-reanimated';
+import { Manrope_400Regular, Manrope_500Medium, Manrope_700Bold, useFonts } from "@expo-google-fonts/manrope";
+import { SplashScreen, Stack } from "expo-router";
+import { StatusBar } from "expo-status-bar";
+import { useEffect } from "react";
+import { useColorScheme } from "react-native";
+import { AuthProvider } from "../context/AuthContext";
+import { useProtectedRoute } from "../context/ProtectedRoute";
-import { useColorScheme } from '@/hooks/useColorScheme';
+function RootLayoutNav() {
+ // Use the route guard to protect routes
+ useProtectedRoute();
+
+ return (
+ <>
+
+
+ >
+ );
+}
export default function RootLayout() {
const colorScheme = useColorScheme();
- const [loaded] = useFonts({
- SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
+
+ const [fontsLoaded] = useFonts({
+ Manrope_400Regular,
+ Manrope_500Medium,
+ Manrope_700Bold,
});
- if (!loaded) {
- // Async font loading only occurs in development.
+ useEffect(() => {
+ if (fontsLoaded) {
+ SplashScreen.hideAsync();
+ }
+ }, [fontsLoaded]);
+
+ if (!fontsLoaded) {
return null;
}
return (
-
-
-
-
-
-
-
+
+
+
);
}
diff --git a/app/account/_layout.tsx b/app/account/_layout.tsx
new file mode 100644
index 00000000..67bb081a
--- /dev/null
+++ b/app/account/_layout.tsx
@@ -0,0 +1,12 @@
+import { Stack } from "expo-router";
+
+export default function AccountLayout() {
+ return (
+
+ );
+}
diff --git a/app/account/index.tsx b/app/account/index.tsx
new file mode 100644
index 00000000..9c4d5008
--- /dev/null
+++ b/app/account/index.tsx
@@ -0,0 +1,210 @@
+import { Ionicons } from "@expo/vector-icons";
+import { Stack, router } from "expo-router";
+import { useState } from "react";
+import { Pressable, ScrollView, StyleSheet, Switch, Text, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import TabBar from "../../components/TabBar";
+import Colors from "../../constants/Colors";
+import { useAuth } from "../../context/AuthContext";
+
+// Setting item component
+const SettingItem = ({
+ icon,
+ title,
+ rightElement
+}: {
+ icon: string,
+ title: string,
+ rightElement?: React.ReactNode
+}) => {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {rightElement || }
+
+
+ );
+};
+
+export default function AccountScreen() {
+ const { logout, user } = useAuth();
+ const [isDarkMode, setIsDarkMode] = useState(true);
+
+ const handleLogout = () => {
+ logout();
+ router.replace("/");
+ };
+
+ return (
+ <>
+
+
+
+ Account
+
+
+
+
+
+
+
+ {user?.name || "Sophia Carter"}
+
+
+ {user?.email || "sophia.carter@email.com"}
+
+
+
+
+
+ Settings
+
+
+
+
+
+ }
+ />
+
+
+
+ Support
+
+
+
+
+
+
+
+
+
+ Log Out
+
+
+
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: Colors.background,
+ },
+ header: {
+ paddingVertical: 16,
+ paddingHorizontal: 16,
+ paddingBottom: 8,
+ alignItems: 'center',
+ },
+ headerTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ textAlign: "center",
+ },
+ scrollView: {
+ flex: 1,
+ },
+ profileContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 16,
+ gap: 16,
+ },
+ profileAvatar: {
+ width: 128,
+ height: 128,
+ borderRadius: 64,
+ backgroundColor: "#3A3A3C",
+ },
+ profileInfo: {
+ justifyContent: 'center',
+ },
+ profileName: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 22,
+ },
+ profileEmail: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 16,
+ },
+ sectionContainer: {
+ paddingHorizontal: 16,
+ paddingTop: 16,
+ paddingBottom: 8,
+ },
+ sectionTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ marginBottom: 8,
+ },
+ settingItemContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ },
+ settingLeft: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 16,
+ },
+ iconContainer: {
+ width: 40,
+ height: 40,
+ borderRadius: 8,
+ backgroundColor: Colors.button.secondary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ settingTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 16,
+ },
+ settingRight: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ logoutContainer: {
+ paddingHorizontal: 16,
+ paddingVertical: 24,
+ },
+ logoutButton: {
+ backgroundColor: "#FF3B30",
+ padding: 16,
+ borderRadius: 24,
+ alignItems: 'center',
+ },
+ logoutText: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 16,
+ },
+});
diff --git a/app/activity/_layout.tsx b/app/activity/_layout.tsx
new file mode 100644
index 00000000..6e22ed2b
--- /dev/null
+++ b/app/activity/_layout.tsx
@@ -0,0 +1,12 @@
+import { Stack } from "expo-router";
+
+export default function ActivityLayout() {
+ return (
+
+ );
+}
diff --git a/app/activity/index.tsx b/app/activity/index.tsx
new file mode 100644
index 00000000..97561350
--- /dev/null
+++ b/app/activity/index.tsx
@@ -0,0 +1,118 @@
+import { Stack } from "expo-router";
+import { ScrollView, StyleSheet, Text, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import TabBar from "../../components/TabBar";
+import Colors from "../../constants/Colors";
+
+// Activity component for displaying each activity item
+const ActivityItem = ({
+ title,
+ date,
+ color
+}: {
+ title: string,
+ date: string,
+ color: string
+}) => {
+ return (
+
+
+
+ {title}
+ {date}
+
+
+ );
+};
+
+export default function ActivityScreen() {
+ // Sample activity data
+ const activities = [
+ { id: 1, title: "You added 'Dinner at The Italian Place'", date: "10/20/24", color: "#6A7FDB" },
+ { id: 2, title: "You added 'Movie Tickets'", date: "10/19/24", color: "#8A4FFF" },
+ { id: 3, title: "You added 'Weekend Getaway'", date: "10/18/24", color: "#FF745C" },
+ { id: 4, title: "You added 'Grocery Shopping'", date: "10/17/24", color: "#45CB85" },
+ { id: 5, title: "You added 'Coffee at The Daily Grind'", date: "10/16/24", color: "#F5A623" },
+ { id: 6, title: "You added 'Lunch at The Bistro'", date: "10/15/24", color: "#D0021B" },
+ { id: 7, title: "You added 'Gas for Road Trip'", date: "10/14/24", color: "#9013FE" },
+ { id: 8, title: "You added 'Concert Tickets'", date: "10/13/24", color: "#4A90E2" },
+ { id: 9, title: "You added 'Brunch at The Cafe'", date: "10/12/24", color: "#50E3C2" },
+ { id: 10, title: "You added 'Drinks at The Bar'", date: "10/11/24", color: "#B8E986" },
+ ];
+
+ return (
+ <>
+
+
+
+ Activity
+
+
+
+ {activities.map((activity) => (
+
+ ))}
+
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: Colors.background,
+ },
+ header: {
+ paddingVertical: 16,
+ paddingHorizontal: 16,
+ paddingBottom: 8,
+ alignItems: 'center',
+ },
+ headerTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ textAlign: "center",
+ },
+ scrollView: {
+ flex: 1,
+ },
+ activityItemContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 8,
+ paddingHorizontal: 16,
+ gap: 16,
+ },
+ colorIndicator: {
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ },
+ activityInfoContainer: {
+ justifyContent: 'center',
+ flex: 1,
+ },
+ activityTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_500Medium",
+ fontSize: 16,
+ },
+ activityDate: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 14,
+ },
+});
diff --git a/app/friends/_layout.tsx b/app/friends/_layout.tsx
new file mode 100644
index 00000000..4d6eedad
--- /dev/null
+++ b/app/friends/_layout.tsx
@@ -0,0 +1,12 @@
+import { Stack } from "expo-router";
+
+export default function FriendsLayout() {
+ return (
+
+ );
+}
diff --git a/app/friends/index.tsx b/app/friends/index.tsx
new file mode 100644
index 00000000..d04a8040
--- /dev/null
+++ b/app/friends/index.tsx
@@ -0,0 +1,139 @@
+import { Ionicons } from "@expo/vector-icons";
+import { Stack } from "expo-router";
+import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import TabBar from "../../components/TabBar";
+import Colors from "../../constants/Colors";
+
+// Friend component for displaying each friend item
+const FriendItem = ({
+ name,
+ status,
+ color
+}: {
+ name: string,
+ status: string,
+ color: string
+}) => {
+ return (
+
+
+
+ {name}
+ {status}
+
+
+ );
+};
+
+export default function FriendsScreen() {
+ // Sample friends data
+ const friends = [
+ { id: 1, name: "Liam Carter", status: "You owe $10.00", color: "#6A7FDB" },
+ { id: 2, name: "Sophia Bennett", status: "You owe $25.00", color: "#8A4FFF" },
+ { id: 3, name: "Ethan Harper", status: "You owe $15.00", color: "#FF745C" },
+ { id: 4, name: "Olivia Hayes", status: "You owe $5.00", color: "#45CB85" },
+ { id: 5, name: "Noah Foster", status: "You owe $30.00", color: "#F5A623" },
+ { id: 6, name: "Ava Mitchell", status: "You owe $20.00", color: "#D0021B" },
+ ];
+
+ return (
+ <>
+
+
+
+
+ Friends
+
+
+
+
+
+
+
+
+
+ {friends.map((friend) => (
+
+ ))}
+
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: Colors.background,
+ },
+ header: {
+ paddingVertical: 16,
+ paddingHorizontal: 16,
+ paddingBottom: 8,
+ },
+ headerContentWrapper: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ headerTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ textAlign: "center",
+ },
+ headerRight: {
+ width: 48,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ addButton: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ backgroundColor: Colors.button.secondary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ scrollView: {
+ flex: 1,
+ },
+ friendItemContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 8,
+ paddingHorizontal: 16,
+ gap: 16,
+ },
+ colorIndicator: {
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ },
+ friendInfoContainer: {
+ justifyContent: 'center',
+ },
+ friendName: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_500Medium",
+ fontSize: 16,
+ },
+ friendStatus: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 14,
+ },
+});
diff --git a/app/groups/[id].tsx b/app/groups/[id].tsx
new file mode 100644
index 00000000..4cabda6d
--- /dev/null
+++ b/app/groups/[id].tsx
@@ -0,0 +1,243 @@
+import { Ionicons } from "@expo/vector-icons";
+import { Stack, router, useLocalSearchParams } from "expo-router";
+import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import Colors from "../../constants/Colors";
+
+// Expense component for displaying each expense item
+const ExpenseItem = ({
+ icon,
+ title,
+ paidBy,
+ amount
+}: {
+ icon: string,
+ title: string,
+ paidBy: string,
+ amount: string
+}) => {
+ return (
+
+
+
+
+
+
+ {title}
+ {paidBy}
+
+
+
+ {amount}
+
+
+ );
+};
+
+// Member component for displaying each member
+const MemberItem = ({
+ name,
+ status,
+ color
+}: {
+ name: string,
+ status: string,
+ color: string
+}) => {
+ return (
+
+
+
+ {name}
+ {status}
+
+
+ );
+};
+
+export default function GroupDetailsScreen() {
+ const { id } = useLocalSearchParams();
+
+ // Mock data for the screen - in a real app, this would come from an API
+ const groupTitle = id === "1" ? "Vacation Crew" :
+ id === "2" ? "Apartment Mates" :
+ id === "3" ? "Road Trip Buddies" : "Family Getaway";
+
+ // Mock members
+ const members = [
+ { id: 1, name: "You", status: "", color: "#6A7FDB" },
+ { id: 2, name: "Sophia", status: "Owes you $12.50", color: "#8A4FFF" },
+ { id: 3, name: "Ethan", status: "You owe $12.50", color: "#FF745C" },
+ ];
+
+ // Mock expenses
+ const expenses = [
+ { id: 1, icon: "ticket-outline", title: "Eiffel Tower Tickets", paidBy: "Paid by Liam", amount: "$50" },
+ { id: 2, icon: "restaurant-outline", title: "Dinner at Le Jules Verne", paidBy: "Paid by Sophia", amount: "$100" },
+ { id: 3, icon: "bed-outline", title: "Hotel Accommodation", paidBy: "Paid by Ethan", amount: "$150" },
+ ];
+
+ return (
+ <>
+ (
+ router.back()} style={{ marginRight: 16 }}>
+
+
+ ),
+ }}
+ />
+
+
+
+ Group Members
+ {members.map((member) => (
+
+ ))}
+
+
+
+ Expenses
+ {expenses.map((expense) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: Colors.background,
+ },
+ scrollView: {
+ flex: 1,
+ },
+ sectionContainer: {
+ paddingHorizontal: 16,
+ paddingTop: 16,
+ paddingBottom: 8,
+ },
+ sectionTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ marginBottom: 8,
+ },
+ memberItemContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ paddingVertical: 8,
+ gap: 16,
+ },
+ memberAvatar: {
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ },
+ memberInfoContainer: {
+ flex: 1,
+ justifyContent: "center",
+ },
+ memberName: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_500Medium",
+ fontSize: 16,
+ },
+ memberStatus: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 14,
+ },
+ expenseItemContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ paddingVertical: 8,
+ },
+ expenseLeft: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: 16,
+ },
+ iconContainer: {
+ width: 48,
+ height: 48,
+ borderRadius: 8,
+ backgroundColor: Colors.button.secondary,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ expenseInfoContainer: {
+ justifyContent: "center",
+ },
+ expenseTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_500Medium",
+ fontSize: 16,
+ },
+ expensePaidBy: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 14,
+ },
+ expenseRight: {
+ justifyContent: "center",
+ },
+ expenseAmount: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 16,
+ },
+ fabContainer: {
+ position: "absolute",
+ bottom: 20,
+ right: 20,
+ },
+ fab: {
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ backgroundColor: Colors.primary,
+ justifyContent: "center",
+ alignItems: "center",
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.25,
+ shadowRadius: 3.84,
+ elevation: 5,
+ },
+});
diff --git a/app/groups/_layout.tsx b/app/groups/_layout.tsx
new file mode 100644
index 00000000..578adc9d
--- /dev/null
+++ b/app/groups/_layout.tsx
@@ -0,0 +1,12 @@
+import { Stack } from "expo-router";
+
+export default function GroupsLayout() {
+ return (
+
+ );
+}
diff --git a/app/groups/index.tsx b/app/groups/index.tsx
new file mode 100644
index 00000000..562028c0
--- /dev/null
+++ b/app/groups/index.tsx
@@ -0,0 +1,147 @@
+import { Ionicons } from "@expo/vector-icons";
+import { router } from "expo-router";
+import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import TabBar from "../../components/TabBar";
+import Colors from "../../constants/Colors";
+import { useAuth } from "../../context/AuthContext";
+
+// Group component for displaying each group item
+const GroupItem = ({
+ id,
+ title,
+ balance,
+ color
+}: {
+ id: number,
+ title: string,
+ balance: string,
+ color: string
+}) => {
+ return (
+ router.push(`/groups/${id}`)}
+ >
+
+
+ {title}
+ {balance}
+
+
+ );
+};
+
+export default function GroupsScreen() {
+ const { user, logout } = useAuth();
+
+ // Handle logout
+ const handleLogout = () => {
+ logout();
+ router.replace("/");
+ };
+
+ // Sample group data
+ const groups = [
+ { id: 1, title: "Vacation Crew", balance: "Total balance: $120", color: "#6A7FDB" },
+ { id: 2, title: "Apartment Mates", balance: "Total balance: $350", color: "#8A4FFF" },
+ { id: 3, title: "Road Trip Buddies", balance: "Total balance: $80", color: "#FF745C" },
+ { id: 4, title: "Family Getaway", balance: "Total balance: $200", color: "#45CB85" },
+ ];
+
+ return (
+
+
+
+
+
+ Groups
+
+
+
+
+
+
+ {groups.map((group) => (
+
+ ))}
+
+ {/* Import and use the TabBar component */}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: Colors.background,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ paddingBottom: 8,
+ },
+ headerLeft: {
+ width: 48,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ headerTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ textAlign: "center",
+ },
+ headerRight: {
+ width: 48,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ addButton: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ backgroundColor: Colors.button.secondary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ scrollView: {
+ flex: 1,
+ },
+ groupItemContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 8,
+ paddingHorizontal: 16,
+ gap: 16,
+ },
+ colorIndicator: {
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ },
+ groupInfoContainer: {
+ justifyContent: 'center',
+ },
+ groupTitle: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_500Medium",
+ fontSize: 16,
+ },
+ groupBalance: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_400Regular",
+ fontSize: 14,
+ }, // TabBar styles are now in the TabBar component
+});
diff --git a/app/index.tsx b/app/index.tsx
new file mode 100644
index 00000000..3cfd88fe
--- /dev/null
+++ b/app/index.tsx
@@ -0,0 +1,166 @@
+import { router } from "expo-router";
+import { useEffect } from "react";
+import { ActivityIndicator, ImageBackground, Pressable, StyleSheet, Text, View } from "react-native";
+import Colors from "../constants/Colors";
+import { useAuth } from "../context/AuthContext";
+
+export default function Index() {
+ const { user, login, isLoading } = useAuth();
+
+ // If user is already authenticated, redirect to groups screen
+ useEffect(() => {
+ if (user) {
+ router.replace("/groups");
+ }
+ }, [user]);
+
+ const handleLogIn = async () => {
+ // For demo, we'll use a mock login
+ const success = await login("demo@example.com", "password");
+ if (success) {
+ router.replace("/groups");
+ }
+ };
+
+ const handleSignUp = () => {
+ // In a real app, navigate to sign up form
+ // For now, use same login functionality
+ handleLogIn();
+ };
+
+ const handleEmailSignIn = () => {
+ // In a real app, navigate to email sign in form
+ // For now, use same login functionality
+ handleLogIn();
+ };
+
+ return (
+
+
+
+
+ Splitwiser
+
+
+
+ Get started
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+ Log in
+ )}
+
+
+
+ Sign up
+
+
+
+ Sign in with an email
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ backgroundImage: {
+ flex: 1,
+ width: "100%",
+ },
+ overlay: {
+ flex: 1,
+ backgroundColor: 'rgba(18, 23, 18, 0.5)',
+ justifyContent: "space-between",
+ paddingBottom: 20,
+ },
+ titleContainer: {
+ width: "100%",
+ alignItems: "center",
+ paddingTop: 16,
+ paddingBottom: 8,
+ },
+ title: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 18,
+ textAlign: "center",
+ },
+ getStartedContainer: {
+ width: "100%",
+ alignItems: "center",
+ paddingVertical: 20,
+ paddingHorizontal: 16,
+ },
+ getStartedText: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 28,
+ textAlign: "center",
+ },
+ buttonContainer: {
+ width: "100%",
+ gap: 12,
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ },
+ loginButton: {
+ backgroundColor: Colors.button.primary,
+ padding: 16,
+ borderRadius: 24,
+ width: "100%",
+ alignItems: "center",
+ },
+ loginButtonText: {
+ color: Colors.background,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 16,
+ },
+ signUpButton: {
+ backgroundColor: Colors.button.secondary,
+ padding: 16,
+ borderRadius: 24,
+ width: "100%",
+ alignItems: "center",
+ },
+ signUpButtonText: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 16,
+ },
+ emailButton: {
+ backgroundColor: Colors.button.secondary,
+ padding: 16,
+ borderRadius: 24,
+ width: "100%",
+ alignItems: "center",
+ },
+ emailButtonText: {
+ color: Colors.text.primary,
+ fontFamily: "Manrope_700Bold",
+ fontSize: 16,
+ },
+});
diff --git a/assets/images/login-background.png b/assets/images/login-background.png
new file mode 100644
index 00000000..cbafd82f
Binary files /dev/null and b/assets/images/login-background.png differ
diff --git a/components/AuthGuard.tsx b/components/AuthGuard.tsx
new file mode 100644
index 00000000..f6280c37
--- /dev/null
+++ b/components/AuthGuard.tsx
@@ -0,0 +1,7 @@
+import { useProtectedRoute } from "../context/ProtectedRoute";
+
+export default function AuthLayout() {
+ useProtectedRoute();
+
+ return null;
+}
diff --git a/components/Collapsible.tsx b/components/Collapsible.tsx
deleted file mode 100644
index 55bff2f9..00000000
--- a/components/Collapsible.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { PropsWithChildren, useState } from 'react';
-import { StyleSheet, TouchableOpacity } from 'react-native';
-
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
-import { IconSymbol } from '@/components/ui/IconSymbol';
-import { Colors } from '@/constants/Colors';
-import { useColorScheme } from '@/hooks/useColorScheme';
-
-export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
- const [isOpen, setIsOpen] = useState(false);
- const theme = useColorScheme() ?? 'light';
-
- return (
-
- setIsOpen((value) => !value)}
- activeOpacity={0.8}>
-
-
- {title}
-
- {isOpen && {children}}
-
- );
-}
-
-const styles = StyleSheet.create({
- heading: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: 6,
- },
- content: {
- marginTop: 6,
- marginLeft: 24,
- },
-});
diff --git a/components/ExternalLink.tsx b/components/ExternalLink.tsx
deleted file mode 100644
index dfbd23ea..00000000
--- a/components/ExternalLink.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Href, Link } from 'expo-router';
-import { openBrowserAsync } from 'expo-web-browser';
-import { type ComponentProps } from 'react';
-import { Platform } from 'react-native';
-
-type Props = Omit, 'href'> & { href: Href & string };
-
-export function ExternalLink({ href, ...rest }: Props) {
- return (
- {
- if (Platform.OS !== 'web') {
- // Prevent the default behavior of linking to the default browser on native.
- event.preventDefault();
- // Open the link in an in-app browser.
- await openBrowserAsync(href);
- }
- }}
- />
- );
-}
diff --git a/components/HapticTab.tsx b/components/HapticTab.tsx
deleted file mode 100644
index 7f3981cb..00000000
--- a/components/HapticTab.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs';
-import { PlatformPressable } from '@react-navigation/elements';
-import * as Haptics from 'expo-haptics';
-
-export function HapticTab(props: BottomTabBarButtonProps) {
- return (
- {
- if (process.env.EXPO_OS === 'ios') {
- // Add a soft haptic feedback when pressing down on the tabs.
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
- }
- props.onPressIn?.(ev);
- }}
- />
- );
-}
diff --git a/components/HelloWave.tsx b/components/HelloWave.tsx
deleted file mode 100644
index eb6ea61a..00000000
--- a/components/HelloWave.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useEffect } from 'react';
-import { StyleSheet } from 'react-native';
-import Animated, {
- useAnimatedStyle,
- useSharedValue,
- withRepeat,
- withSequence,
- withTiming,
-} from 'react-native-reanimated';
-
-import { ThemedText } from '@/components/ThemedText';
-
-export function HelloWave() {
- const rotationAnimation = useSharedValue(0);
-
- useEffect(() => {
- rotationAnimation.value = withRepeat(
- withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
- 4 // Run the animation 4 times
- );
- }, [rotationAnimation]);
-
- const animatedStyle = useAnimatedStyle(() => ({
- transform: [{ rotate: `${rotationAnimation.value}deg` }],
- }));
-
- return (
-
- š
-
- );
-}
-
-const styles = StyleSheet.create({
- text: {
- fontSize: 28,
- lineHeight: 32,
- marginTop: -6,
- },
-});
diff --git a/components/ParallaxScrollView.tsx b/components/ParallaxScrollView.tsx
deleted file mode 100644
index 5df1d75f..00000000
--- a/components/ParallaxScrollView.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import type { PropsWithChildren, ReactElement } from 'react';
-import { StyleSheet } from 'react-native';
-import Animated, {
- interpolate,
- useAnimatedRef,
- useAnimatedStyle,
- useScrollViewOffset,
-} from 'react-native-reanimated';
-
-import { ThemedView } from '@/components/ThemedView';
-import { useBottomTabOverflow } from '@/components/ui/TabBarBackground';
-import { useColorScheme } from '@/hooks/useColorScheme';
-
-const HEADER_HEIGHT = 250;
-
-type Props = PropsWithChildren<{
- headerImage: ReactElement;
- headerBackgroundColor: { dark: string; light: string };
-}>;
-
-export default function ParallaxScrollView({
- children,
- headerImage,
- headerBackgroundColor,
-}: Props) {
- const colorScheme = useColorScheme() ?? 'light';
- const scrollRef = useAnimatedRef();
- const scrollOffset = useScrollViewOffset(scrollRef);
- const bottom = useBottomTabOverflow();
- const headerAnimatedStyle = useAnimatedStyle(() => {
- return {
- transform: [
- {
- translateY: interpolate(
- scrollOffset.value,
- [-HEADER_HEIGHT, 0, HEADER_HEIGHT],
- [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
- ),
- },
- {
- scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),
- },
- ],
- };
- });
-
- return (
-
-
-
- {headerImage}
-
- {children}
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- },
- header: {
- height: HEADER_HEIGHT,
- overflow: 'hidden',
- },
- content: {
- flex: 1,
- padding: 32,
- gap: 16,
- overflow: 'hidden',
- },
-});
diff --git a/components/TabBar.tsx b/components/TabBar.tsx
new file mode 100644
index 00000000..0bf9f6ea
--- /dev/null
+++ b/components/TabBar.tsx
@@ -0,0 +1,113 @@
+import { Ionicons } from "@expo/vector-icons";
+import { router, usePathname } from "expo-router";
+import { Pressable, StyleSheet, Text, View } from "react-native";
+import Colors from "../constants/Colors";
+
+export default function TabBar() {
+ const pathname = usePathname();
+
+ const isActive = (path: string) => {
+ if (path === '/groups' && pathname.startsWith('/groups')) {
+ return true;
+ }
+ return pathname === path;
+ };
+
+ return (
+
+ router.push("/groups")}
+ >
+
+
+ Groups
+
+
+
+ router.push("/friends")}
+ >
+
+
+ Friends
+
+
+
+ router.push("/activity")}
+ >
+
+
+ Activity
+
+
+
+ router.push("/account")}
+ >
+
+
+ Account
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ tabBar: {
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ paddingBottom: 12,
+ backgroundColor: "#1F261C",
+ borderTopWidth: 1,
+ borderTopColor: "#2E3829",
+ },
+ tabBarItem: {
+ alignItems: 'center',
+ gap: 4,
+ },
+ tabBarLabel: {
+ color: Colors.text.secondary,
+ fontFamily: "Manrope_500Medium",
+ fontSize: 12,
+ },
+ activeTab: {
+ color: Colors.text.primary,
+ },
+});
diff --git a/components/ThemedText.tsx b/components/ThemedText.tsx
deleted file mode 100644
index 9d214a2b..00000000
--- a/components/ThemedText.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { StyleSheet, Text, type TextProps } from 'react-native';
-
-import { useThemeColor } from '@/hooks/useThemeColor';
-
-export type ThemedTextProps = TextProps & {
- lightColor?: string;
- darkColor?: string;
- type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
-};
-
-export function ThemedText({
- style,
- lightColor,
- darkColor,
- type = 'default',
- ...rest
-}: ThemedTextProps) {
- const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
-
- return (
-
- );
-}
-
-const styles = StyleSheet.create({
- default: {
- fontSize: 16,
- lineHeight: 24,
- },
- defaultSemiBold: {
- fontSize: 16,
- lineHeight: 24,
- fontWeight: '600',
- },
- title: {
- fontSize: 32,
- fontWeight: 'bold',
- lineHeight: 32,
- },
- subtitle: {
- fontSize: 20,
- fontWeight: 'bold',
- },
- link: {
- lineHeight: 30,
- fontSize: 16,
- color: '#0a7ea4',
- },
-});
diff --git a/components/ThemedView.tsx b/components/ThemedView.tsx
deleted file mode 100644
index 4d2cb09d..00000000
--- a/components/ThemedView.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { View, type ViewProps } from 'react-native';
-
-import { useThemeColor } from '@/hooks/useThemeColor';
-
-export type ThemedViewProps = ViewProps & {
- lightColor?: string;
- darkColor?: string;
-};
-
-export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
- const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
-
- return ;
-}
diff --git a/components/ui/IconSymbol.ios.tsx b/components/ui/IconSymbol.ios.tsx
deleted file mode 100644
index 9177f4da..00000000
--- a/components/ui/IconSymbol.ios.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
-import { StyleProp, ViewStyle } from 'react-native';
-
-export function IconSymbol({
- name,
- size = 24,
- color,
- style,
- weight = 'regular',
-}: {
- name: SymbolViewProps['name'];
- size?: number;
- color: string;
- style?: StyleProp;
- weight?: SymbolWeight;
-}) {
- return (
-
- );
-}
diff --git a/components/ui/IconSymbol.tsx b/components/ui/IconSymbol.tsx
deleted file mode 100644
index b7ece6b3..00000000
--- a/components/ui/IconSymbol.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-// Fallback for using MaterialIcons on Android and web.
-
-import MaterialIcons from '@expo/vector-icons/MaterialIcons';
-import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
-import { ComponentProps } from 'react';
-import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';
-
-type IconMapping = Record['name']>;
-type IconSymbolName = keyof typeof MAPPING;
-
-/**
- * Add your SF Symbols to Material Icons mappings here.
- * - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
- * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
- */
-const MAPPING = {
- 'house.fill': 'home',
- 'paperplane.fill': 'send',
- 'chevron.left.forwardslash.chevron.right': 'code',
- 'chevron.right': 'chevron-right',
-} as IconMapping;
-
-/**
- * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
- * This ensures a consistent look across platforms, and optimal resource usage.
- * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
- */
-export function IconSymbol({
- name,
- size = 24,
- color,
- style,
-}: {
- name: IconSymbolName;
- size?: number;
- color: string | OpaqueColorValue;
- style?: StyleProp;
- weight?: SymbolWeight;
-}) {
- return ;
-}
diff --git a/components/ui/TabBarBackground.ios.tsx b/components/ui/TabBarBackground.ios.tsx
deleted file mode 100644
index 495b2d4e..00000000
--- a/components/ui/TabBarBackground.ios.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
-import { BlurView } from 'expo-blur';
-import { StyleSheet } from 'react-native';
-
-export default function BlurTabBarBackground() {
- return (
-
- );
-}
-
-export function useBottomTabOverflow() {
- return useBottomTabBarHeight();
-}
diff --git a/components/ui/TabBarBackground.tsx b/components/ui/TabBarBackground.tsx
deleted file mode 100644
index 70d1c3c0..00000000
--- a/components/ui/TabBarBackground.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-// This is a shim for web and Android where the tab bar is generally opaque.
-export default undefined;
-
-export function useBottomTabOverflow() {
- return 0;
-}
diff --git a/constants/Colors.ts b/constants/Colors.ts
index 14e67844..6dc2d67c 100644
--- a/constants/Colors.ts
+++ b/constants/Colors.ts
@@ -1,26 +1,12 @@
-/**
- * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
- * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
- */
-
-const tintColorLight = '#0a7ea4';
-const tintColorDark = '#fff';
-
-export const Colors = {
- light: {
- text: '#11181C',
- background: '#fff',
- tint: tintColorLight,
- icon: '#687076',
- tabIconDefault: '#687076',
- tabIconSelected: tintColorLight,
- },
- dark: {
- text: '#ECEDEE',
- background: '#151718',
- tint: tintColorDark,
- icon: '#9BA1A6',
- tabIconDefault: '#9BA1A6',
- tabIconSelected: tintColorDark,
+export default {
+ primary: "#4FD12B",
+ background: "#121712",
+ text: {
+ primary: "#FFFFFF",
+ secondary: "#A6B5A1",
},
+ button: {
+ primary: "#4FD12B",
+ secondary: "#2E3829",
+ }
};
diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx
new file mode 100644
index 00000000..1338efe3
--- /dev/null
+++ b/context/AuthContext.tsx
@@ -0,0 +1,95 @@
+import { createContext, ReactNode, useContext, useState } from 'react';
+
+// Define the shape of our user data
+interface User {
+ id: string;
+ email: string;
+ name?: string;
+}
+
+// Define the shape of our auth context
+interface AuthContextType {
+ user: User | null;
+ isLoading: boolean;
+ login: (email: string, password: string) => Promise;
+ signup: (email: string, password: string, name?: string) => Promise;
+ logout: () => void;
+}
+
+// Create context with default values
+const AuthContext = createContext({
+ user: null,
+ isLoading: false,
+ login: async () => false,
+ signup: async () => false,
+ logout: () => {},
+});
+
+// Custom hook to use auth context
+export const useAuth = () => useContext(AuthContext);
+
+// Provider component
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ // In a real app, this would make an API call to your server
+ const login = async (email: string, password: string) => {
+ setIsLoading(true);
+
+ try {
+ // Simulate API call
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // For demo purposes, any non-empty email/password will work
+ if (email && password) {
+ setUser({
+ id: '1',
+ email,
+ name: email.split('@')[0],
+ });
+ return true;
+ }
+ return false;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // In a real app, this would make an API call to your server
+ const signup = async (email: string, password: string, name?: string) => {
+ setIsLoading(true);
+
+ try {
+ // Simulate API call
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // For demo purposes, any non-empty email/password will work
+ if (email && password) {
+ setUser({
+ id: '1',
+ email,
+ name: name || email.split('@')[0],
+ });
+ return true;
+ }
+ return false;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const logout = () => {
+ setUser(null);
+ };
+
+ const value = {
+ user,
+ isLoading,
+ login,
+ signup,
+ logout,
+ };
+
+ return {children};
+}
diff --git a/context/ProtectedRoute.tsx b/context/ProtectedRoute.tsx
new file mode 100644
index 00000000..64391d31
--- /dev/null
+++ b/context/ProtectedRoute.tsx
@@ -0,0 +1,27 @@
+import { useRouter, useSegments } from "expo-router";
+import { useEffect } from "react";
+import { useAuth } from "./AuthContext";
+
+export function useProtectedRoute(protectedPaths: string[] = ["groups", "friends", "activity", "account"]) {
+ const { user } = useAuth();
+ const segments = useSegments();
+ const router = useRouter();
+
+ useEffect(() => {
+ const inAuthGroup = segments[0] === "(auth)";
+
+ if (!user) {
+ // If the user is not signed in and the initial segment is not in the auth group,
+ // redirect to the sign-in page.
+ if (protectedPaths.includes(segments[0])) {
+ router.replace("/");
+ }
+ } else {
+ // If the user is signed in and the initial segment is in the auth group,
+ // redirect to the groups page.
+ if (segments.length === 0 || segments[0] === "") {
+ router.replace("/groups");
+ }
+ }
+ }, [user, segments]);
+}
diff --git a/hooks/useColorScheme.ts b/hooks/useColorScheme.ts
deleted file mode 100644
index 17e3c63e..00000000
--- a/hooks/useColorScheme.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { useColorScheme } from 'react-native';
diff --git a/hooks/useColorScheme.web.ts b/hooks/useColorScheme.web.ts
deleted file mode 100644
index 7eb1c1b7..00000000
--- a/hooks/useColorScheme.web.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useColorScheme as useRNColorScheme } from 'react-native';
-
-/**
- * To support static rendering, this value needs to be re-calculated on the client side for web
- */
-export function useColorScheme() {
- const [hasHydrated, setHasHydrated] = useState(false);
-
- useEffect(() => {
- setHasHydrated(true);
- }, []);
-
- const colorScheme = useRNColorScheme();
-
- if (hasHydrated) {
- return colorScheme;
- }
-
- return 'light';
-}
diff --git a/hooks/useThemeColor.ts b/hooks/useThemeColor.ts
deleted file mode 100644
index 0608e731..00000000
--- a/hooks/useThemeColor.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Learn more about light and dark modes:
- * https://docs.expo.dev/guides/color-schemes/
- */
-
-import { Colors } from '@/constants/Colors';
-import { useColorScheme } from '@/hooks/useColorScheme';
-
-export function useThemeColor(
- props: { light?: string; dark?: string },
- colorName: keyof typeof Colors.light & keyof typeof Colors.dark
-) {
- const theme = useColorScheme() ?? 'light';
- const colorFromProps = props[theme];
-
- if (colorFromProps) {
- return colorFromProps;
- } else {
- return Colors[theme][colorName];
- }
-}
diff --git a/package-lock.json b/package-lock.json
index 57ab4007..32694fa2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "splitwiser",
"version": "1.0.0",
"dependencies": {
+ "@expo-google-fonts/manrope": "^0.4.1",
"@expo/ngrok": "^4.1.3",
"@expo/vector-icons": "^14.1.0",
"@react-navigation/bottom-tabs": "^7.3.10",
@@ -1605,6 +1606,11 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@expo-google-fonts/manrope": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@expo-google-fonts/manrope/-/manrope-0.4.1.tgz",
+ "integrity": "sha512-3Ydkj5dS4M6/xgjk7UT/+9wq1xnqo2LO9E5K/EIaynY2phJOGwFzvtAE9EEImqXj96TEg2jzZ095Mmac5w8nvQ=="
+ },
"node_modules/@expo/cli": {
"version": "0.24.14",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.14.tgz",
diff --git a/package.json b/package.json
index f5f9574f..b51ceaec 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"lint": "expo lint"
},
"dependencies": {
+ "@expo-google-fonts/manrope": "^0.4.1",
"@expo/ngrok": "^4.1.3",
"@expo/vector-icons": "^14.1.0",
"@react-navigation/bottom-tabs": "^7.3.10",
diff --git a/scripts/reset-project.js b/scripts/reset-project.js
deleted file mode 100755
index 51dff15a..00000000
--- a/scripts/reset-project.js
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * This script is used to reset the project to a blank state.
- * It deletes or moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example based on user input and creates a new /app directory with an index.tsx and _layout.tsx file.
- * You can remove the `reset-project` script from package.json and safely delete this file after running it.
- */
-
-const fs = require("fs");
-const path = require("path");
-const readline = require("readline");
-
-const root = process.cwd();
-const oldDirs = ["app", "components", "hooks", "constants", "scripts"];
-const exampleDir = "app-example";
-const newAppDir = "app";
-const exampleDirPath = path.join(root, exampleDir);
-
-const indexContent = `import { Text, View } from "react-native";
-
-export default function Index() {
- return (
-
- Edit app/index.tsx to edit this screen.
-
- );
-}
-`;
-
-const layoutContent = `import { Stack } from "expo-router";
-
-export default function RootLayout() {
- return ;
-}
-`;
-
-const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout,
-});
-
-const moveDirectories = async (userInput) => {
- try {
- if (userInput === "y") {
- // Create the app-example directory
- await fs.promises.mkdir(exampleDirPath, { recursive: true });
- console.log(`š /${exampleDir} directory created.`);
- }
-
- // Move old directories to new app-example directory or delete them
- for (const dir of oldDirs) {
- const oldDirPath = path.join(root, dir);
- if (fs.existsSync(oldDirPath)) {
- if (userInput === "y") {
- const newDirPath = path.join(root, exampleDir, dir);
- await fs.promises.rename(oldDirPath, newDirPath);
- console.log(`ā”ļø /${dir} moved to /${exampleDir}/${dir}.`);
- } else {
- await fs.promises.rm(oldDirPath, { recursive: true, force: true });
- console.log(`ā /${dir} deleted.`);
- }
- } else {
- console.log(`ā”ļø /${dir} does not exist, skipping.`);
- }
- }
-
- // Create new /app directory
- const newAppDirPath = path.join(root, newAppDir);
- await fs.promises.mkdir(newAppDirPath, { recursive: true });
- console.log("\nš New /app directory created.");
-
- // Create index.tsx
- const indexPath = path.join(newAppDirPath, "index.tsx");
- await fs.promises.writeFile(indexPath, indexContent);
- console.log("š app/index.tsx created.");
-
- // Create _layout.tsx
- const layoutPath = path.join(newAppDirPath, "_layout.tsx");
- await fs.promises.writeFile(layoutPath, layoutContent);
- console.log("š app/_layout.tsx created.");
-
- console.log("\nā
Project reset complete. Next steps:");
- console.log(
- `1. Run \`npx expo start\` to start a development server.\n2. Edit app/index.tsx to edit the main screen.${
- userInput === "y"
- ? `\n3. Delete the /${exampleDir} directory when you're done referencing it.`
- : ""
- }`
- );
- } catch (error) {
- console.error(`ā Error during script execution: ${error.message}`);
- }
-};
-
-rl.question(
- "Do you want to move existing files to /app-example instead of deleting them? (Y/n): ",
- (answer) => {
- const userInput = answer.trim().toLowerCase() || "y";
- if (userInput === "y" || userInput === "n") {
- moveDirectories(userInput).finally(() => rl.close());
- } else {
- console.log("ā Invalid input. Please enter 'Y' or 'N'.");
- rl.close();
- }
- }
-);
diff --git a/tsconfig.json b/tsconfig.json
index 909e9010..ac69e9cd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,7 @@
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
+ "jsx": "react-jsx",
"paths": {
"@/*": [
"./*"