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": { "@/*": [ "./*"