diff --git a/App.tsx b/App.tsx
new file mode 100644
index 0000000..0ab9266
--- /dev/null
+++ b/App.tsx
@@ -0,0 +1,10 @@
+import { AuthProvider } from './app/src/context/AuthContext';
+import AppNavigator from './app/src/navigation/AppNavigator';
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/app.json b/app.json
index 7fa6261..e064e49 100644
--- a/app.json
+++ b/app.json
@@ -20,11 +20,9 @@
},
"web": {
"bundler": "metro",
- "output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
- "expo-router",
[
"expo-splash-screen",
{
@@ -34,9 +32,6 @@
"backgroundColor": "#ffffff"
}
]
- ],
- "experiments": {
- "typedRoutes": true
- }
+ ]
}
}
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
deleted file mode 100644
index cfbc1e2..0000000
--- 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 5f472c7..0000000
--- a/app/(tabs)/explore.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { StyleSheet } from 'react-native';
-
-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 section of the app is under construction š§
-
- );
-}
-
-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 1081e2a..0000000
--- a/app/(tabs)/index.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Image } from 'expo-image';
-import { 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 to Experient!
-
-
-
- );
-}
-
-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 215b0ed..0000000
--- 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
deleted file mode 100644
index 8d506f7..0000000
--- a/app/_layout.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-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 { useColorScheme } from '@/hooks/useColorScheme';
-
-export default function RootLayout() {
- const colorScheme = useColorScheme();
- const [loaded] = useFonts({
- SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
- });
-
- if (!loaded) {
- // Async font loading only occurs in development.
- return null;
- }
-
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/app/src/context/AuthContext.tsx b/app/src/context/AuthContext.tsx
new file mode 100644
index 0000000..2a02acb
--- /dev/null
+++ b/app/src/context/AuthContext.tsx
@@ -0,0 +1,74 @@
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import { createContext, useEffect, useReducer } from 'react';
+
+type State = {
+ isLoading: boolean;
+ isAuthenticated: boolean;
+};
+
+type Action =
+ | {type: 'RESTORE'}
+ | {type: 'LOGIN'}
+ | {type: 'LOGOUT'};
+
+const initialState: State = {
+ isLoading: true,
+ isAuthenticated: false,
+};
+
+function reducer(state: State, action: Action): State {
+ switch (action.type) {
+ case 'RESTORE':
+ return {isLoading: false, isAuthenticated: true};
+ case 'LOGIN':
+ return {isLoading: false, isAuthenticated: true};
+ case 'LOGOUT':
+ return {isLoading: false, isAuthenticated: false};
+ default:
+ return state;
+ }
+}
+
+export const AuthContext = createContext(null);
+
+export const AuthProvider: React.FC<{children: React.ReactNode}> = ({children}) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ useEffect(() => {
+ const bootstrap = async () => {
+ const accessToken = await AsyncStorage.getItem('accessToken');
+ const expiry = await AsyncStorage.getItem('tokenExpiry');
+
+ if (accessToken && expiry && Date.now() < Number(expiry)) {
+ dispatch({type: 'RESTORE'});
+ } else {
+ dispatch({type: 'LOGOUT'});
+ }
+ };
+
+ bootstrap();
+ }, []);
+
+ const login = async (access: string, refresh: string) => {
+ const expiry = Date.now() + 60 * 60 * 1000; // 1 hour
+
+ await AsyncStorage.multiSet([
+ ['accessToken', access],
+ ['refreshToken', refresh],
+ ['tokenExpiry', expiry.toString()],
+ ]);
+
+ dispatch({type: 'LOGIN'});
+ };
+
+ const logout = async () => {
+ await AsyncStorage.clear();
+ dispatch({type: 'LOGOUT'});
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/app/src/navigation/AppNavigator.tsx b/app/src/navigation/AppNavigator.tsx
new file mode 100644
index 0000000..a81ce9d
--- /dev/null
+++ b/app/src/navigation/AppNavigator.tsx
@@ -0,0 +1,24 @@
+import { NavigationContainer } from '@react-navigation/native';
+import { useContext } from 'react';
+import { ActivityIndicator, View } from 'react-native';
+import { AuthContext } from '../context/AuthContext';
+import AppStack from './AppStack';
+import AuthStack from './AuthStack';
+
+export default function AppNavigator() {
+ const {isLoading, isAuthenticated} = useContext(AuthContext);
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {isAuthenticated ? : }
+
+ );
+}
diff --git a/app/src/navigation/AppStack.tsx b/app/src/navigation/AppStack.tsx
new file mode 100644
index 0000000..04b43ea
--- /dev/null
+++ b/app/src/navigation/AppStack.tsx
@@ -0,0 +1,12 @@
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import HomeScreen from '../screens/HomeScreen';
+
+const Stack = createNativeStackNavigator();
+
+export default function AppStack() {
+ return (
+
+
+
+ );
+}
diff --git a/app/src/navigation/AuthStack.tsx b/app/src/navigation/AuthStack.tsx
new file mode 100644
index 0000000..d97162a
--- /dev/null
+++ b/app/src/navigation/AuthStack.tsx
@@ -0,0 +1,12 @@
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import LoginScreen from '../screens/LoginScreen';
+
+const Stack = createNativeStackNavigator();
+
+export default function AuthStack() {
+ return (
+
+
+
+ );
+}
diff --git a/app/src/screens/HomeScreen.tsx b/app/src/screens/HomeScreen.tsx
new file mode 100644
index 0000000..ba2089e
--- /dev/null
+++ b/app/src/screens/HomeScreen.tsx
@@ -0,0 +1,13 @@
+import { useContext } from 'react';
+import { Button, View } from 'react-native';
+import { AuthContext } from '../context/AuthContext';
+
+export default function HomeScreen() {
+ const {logout} = useContext(AuthContext);
+
+ return (
+
+
+
+ );
+}
diff --git a/app/src/screens/LoginScreen.tsx b/app/src/screens/LoginScreen.tsx
new file mode 100644
index 0000000..6226741
--- /dev/null
+++ b/app/src/screens/LoginScreen.tsx
@@ -0,0 +1,43 @@
+import { useContext, useState } from 'react';
+import { Alert, Button, StyleSheet, TextInput, View } from 'react-native';
+import { AuthContext } from '../context/AuthContext';
+import { mockLoginRequest } from '../services/authService';
+
+export default function LoginScreen() {
+ const {login} = useContext(AuthContext);
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+
+ const onSubmit = async () => {
+ try {
+ const res = await mockLoginRequest(username, password);
+ await login(res.accessToken, res.refreshToken);
+ } catch {
+ Alert.alert('Login failed');
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {flex: 1, justifyContent: 'center', padding: 16},
+ input: {borderWidth: 1, marginBottom: 12, padding: 10},
+});
diff --git a/app/src/services/authService.ts b/app/src/services/authService.ts
new file mode 100644
index 0000000..19dfd73
--- /dev/null
+++ b/app/src/services/authService.ts
@@ -0,0 +1,43 @@
+export const mockLoginRequest = async (username: string, password: string) => {
+ await new Promise(res => setTimeout(res, 800));
+
+ if (!username || !password) {
+ throw new Error('Invalid credentials');
+ }
+
+ const setCookieHeaders = [
+ 'accessToken=abc123; Path=/; HttpOnly; Secure; SameSite=Lax',
+ 'refreshToken=xyz789; Path=/; HttpOnly; Secure; SameSite=Lax',
+ ];
+
+ const parseCookie = (cookie: string) =>
+ cookie.split(';')[0].split('=');
+
+ const cookies = Object.fromEntries(
+ setCookieHeaders.map(parseCookie),
+ );
+
+ return {
+ user: {
+ username: 'VShah',
+ active: true,
+ roleId: 20,
+ dateCreated: '2018-03-02T00:00:00.000Z',
+ dateModified: '2018-03-02T00:00:00.000Z',
+ lastName: 'Shah',
+ firstName: 'Viraj',
+ displayName: 'Viraj Shah',
+ jiraUsername: 'viraj.shah',
+ intacctUserId: 'EE-00112',
+ userId: 41,
+ emailAddress: 'vshah@experient.com',
+ openAtCurWeeksTimesheet: true,
+ activeInterviewer: true,
+ createIntacctTimesheet: true,
+ roleName: 'Developer',
+ },
+ accessToken: cookies.accessToken,
+ refreshToken: cookies.refreshToken,
+ };
+ };
+
\ No newline at end of file
diff --git a/components/Collapsible.tsx b/components/Collapsible.tsx
deleted file mode 100644
index 55bff2f..0000000
--- 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 dfbd23e..0000000
--- 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 7f3981c..0000000
--- 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 eb6ea61..0000000
--- 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 5df1d75..0000000
--- 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/ThemedText.tsx b/components/ThemedText.tsx
deleted file mode 100644
index 9d214a2..0000000
--- 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 4d2cb09..0000000
--- 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 9177f4d..0000000
--- 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 b7ece6b..0000000
--- 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 495b2d4..0000000
--- 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 70d1c3c..0000000
--- 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
deleted file mode 100644
index 14e6784..0000000
--- a/constants/Colors.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * 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,
- },
-};
diff --git a/hooks/useColorScheme.ts b/hooks/useColorScheme.ts
deleted file mode 100644
index 17e3c63..0000000
--- 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 7eb1c1b..0000000
--- 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 0608e73..0000000
--- 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 3e10742..224b198 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,11 @@
"version": "1.0.0",
"dependencies": {
"@expo/vector-icons": "^14.1.0",
+ "@react-native-async-storage/async-storage": "^2.2.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/elements": "^2.3.8",
- "@react-navigation/native": "^7.1.6",
+ "@react-navigation/native": "^7.1.28",
+ "@react-navigation/native-stack": "^7.12.0",
"expo": "~53.0.9",
"expo-blur": "~14.1.4",
"expo-constants": "~17.1.6",
@@ -19,7 +21,6 @@
"expo-haptics": "~14.1.4",
"expo-image": "~2.1.7",
"expo-linking": "~7.1.5",
- "expo-router": "~5.0.6",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "~2.2.3",
"expo-symbols": "~0.4.4",
@@ -30,7 +31,7 @@
"react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
- "react-native-safe-area-context": "5.4.0",
+ "react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5"
@@ -2111,6 +2112,8 @@
"resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-5.0.4.tgz",
"integrity": "sha512-r694MeO+7Vi8IwOsDIDzH/Q5RPMt1kUDYbiTJwnO15nIqiDwlE8HU55UlRhffKZy6s5FmxQsZ8HA+T8DqUW8cQ==",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"peerDependencies": {
"react-native": "*"
}
@@ -2189,18 +2192,6 @@
"integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==",
"license": "MIT"
},
- "node_modules/@expo/server": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@expo/server/-/server-0.6.2.tgz",
- "integrity": "sha512-ko+dq+1WEC126/iGVv3g+ChFCs9wGyKtGlnYphwrOQbFBBqX19sn6UV0oUks6UdhD+MyzUv+w/TOdktdcI0Cgg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "debug": "^4.3.4",
- "source-map-support": "~0.5.21",
- "undici": "^6.18.2 || ^7.0.0"
- }
- },
"node_modules/@expo/spawn-async": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz",
@@ -2742,37 +2733,16 @@
"node": ">=14"
}
},
- "node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
- "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-slot": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
- "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "node_modules/@react-native-async-storage/async-storage": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
+ "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2"
+ "merge-options": "^3.0.4"
},
"peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
+ "react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native/assets-registry": {
@@ -3070,17 +3040,18 @@
}
},
"node_modules/@react-navigation/core": {
- "version": "7.9.2",
- "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.9.2.tgz",
- "integrity": "sha512-lqCyKMWWaSwGK4VV3wRXXEKvl5IKrVH207Kp77TLCnITnd4KQIdgjzzJ/Pr62ugki3VTAErq1vg0yRlcXciCbg==",
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.14.0.tgz",
+ "integrity": "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g==",
"license": "MIT",
"dependencies": {
- "@react-navigation/routers": "^7.3.7",
+ "@react-navigation/routers": "^7.5.3",
"escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"query-string": "^7.1.3",
"react-is": "^19.1.0",
- "use-latest-callback": "^0.2.3",
+ "use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
@@ -3088,16 +3059,18 @@
}
},
"node_modules/@react-navigation/elements": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.4.2.tgz",
- "integrity": "sha512-cudKLsRtOB+i8iDzfBKypdqiHsDy1ruqCfYAtwKEclDmLsxu3/90YXoBtoPyFNyIpsn3GtsJzZsrYWQh78xSWg==",
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.5.tgz",
+ "integrity": "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g==",
"license": "MIT",
"dependencies": {
- "color": "^4.2.3"
+ "color": "^4.2.3",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"@react-native-masked-view/masked-view": ">= 0.2.0",
- "@react-navigation/native": "^7.1.9",
+ "@react-navigation/native": "^7.1.28",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0"
@@ -3109,16 +3082,16 @@
}
},
"node_modules/@react-navigation/native": {
- "version": "7.1.9",
- "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.9.tgz",
- "integrity": "sha512-/A0oBwZIeD23o4jsnB0fEyKmKS+l4LAbJP/ioVvsGEubGp+sc5ouQNranOh7JwR0R1eX0MjcsLKprEwB+nztdw==",
+ "version": "7.1.28",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.28.tgz",
+ "integrity": "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==",
"license": "MIT",
"dependencies": {
- "@react-navigation/core": "^7.9.2",
+ "@react-navigation/core": "^7.14.0",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
- "use-latest-callback": "^0.2.3"
+ "use-latest-callback": "^0.2.4"
},
"peerDependencies": {
"react": ">= 18.2.0",
@@ -3126,16 +3099,18 @@
}
},
"node_modules/@react-navigation/native-stack": {
- "version": "7.3.13",
- "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.3.13.tgz",
- "integrity": "sha512-udH+HumX0PmaT6QQTqjU3ciiCwifBGtnw1+6B1bVEDw83q80WHotlMitaf8Enbuf7oWrxwB+Eow4tV5MJXgQtQ==",
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.12.0.tgz",
+ "integrity": "sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ==",
"license": "MIT",
"dependencies": {
- "@react-navigation/elements": "^2.4.2",
+ "@react-navigation/elements": "^2.9.5",
+ "color": "^4.2.3",
+ "sf-symbols-typescript": "^2.1.0",
"warn-once": "^0.1.1"
},
"peerDependencies": {
- "@react-navigation/native": "^7.1.9",
+ "@react-navigation/native": "^7.1.28",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
@@ -3143,9 +3118,9 @@
}
},
"node_modules/@react-navigation/routers": {
- "version": "7.3.7",
- "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.3.7.tgz",
- "integrity": "sha512-5ffgrefOs2zWqcCVX+OKn+RDx0puopQtxqetegFrTfWQ6pGXdY/5v4kBpPwaOFrNEeE/LPbHt9IJaJuvyhB7RA==",
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
+ "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11"
@@ -3284,6 +3259,7 @@
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/json5": {
@@ -3898,45 +3874,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/ajv-formats": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
- "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/ajv-formats/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-formats/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT"
- },
"node_modules/anser": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
@@ -4853,12 +4790,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "license": "MIT"
- },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -6395,60 +6326,6 @@
"invariant": "^2.2.4"
}
},
- "node_modules/expo-router": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.0.6.tgz",
- "integrity": "sha512-/44G3liB7LMMDoUO+lN5TS8XvZrAhLtq7cVGoilO2QkoSBjFQfxFA9VYOVWVlu2R80tN6dM3cgsEuoA275FGQg==",
- "license": "MIT",
- "dependencies": {
- "@expo/metro-runtime": "5.0.4",
- "@expo/server": "^0.6.2",
- "@radix-ui/react-slot": "1.2.0",
- "@react-navigation/bottom-tabs": "^7.3.10",
- "@react-navigation/native": "^7.1.6",
- "@react-navigation/native-stack": "^7.3.10",
- "client-only": "^0.0.1",
- "invariant": "^2.2.4",
- "react-fast-compare": "^3.2.2",
- "react-native-is-edge-to-edge": "^1.1.6",
- "schema-utils": "^4.0.1",
- "semver": "~7.6.3",
- "server-only": "^0.0.1",
- "shallowequal": "^1.1.0"
- },
- "peerDependencies": {
- "@react-navigation/drawer": "^7.3.9",
- "expo": "*",
- "expo-constants": "*",
- "expo-linking": "*",
- "react-native-reanimated": "*",
- "react-native-safe-area-context": "*",
- "react-native-screens": "*"
- },
- "peerDependenciesMeta": {
- "@react-navigation/drawer": {
- "optional": true
- },
- "@testing-library/jest-native": {
- "optional": true
- },
- "react-native-reanimated": {
- "optional": true
- }
- }
- },
- "node_modules/expo-router/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/expo-splash-screen": {
"version": "0.30.8",
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.8.tgz",
@@ -6725,22 +6602,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/fast-uri": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
- "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
@@ -7915,6 +7776,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -8931,6 +8801,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge-options": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
+ "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -10466,12 +10348,6 @@
"react": "^19.0.0"
}
},
- "node_modules/react-fast-compare": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
- "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
- "license": "MIT"
- },
"node_modules/react-freeze": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
@@ -10485,9 +10361,9 @@
}
},
"node_modules/react-is": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
- "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
+ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
"license": "MIT"
},
"node_modules/react-native": {
@@ -11150,59 +11026,6 @@
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT"
},
- "node_modules/schema-utils": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
- "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/schema-utils/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/schema-utils/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/schema-utils/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT"
- },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -11341,12 +11164,6 @@
"node": ">= 0.8"
}
},
- "node_modules/server-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
- "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
- "license": "MIT"
- },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -11417,12 +11234,6 @@
"node": ">=10"
}
},
- "node_modules/shallowequal": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
- "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
- "license": "MIT"
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -12636,18 +12447,18 @@
}
},
"node_modules/use-latest-callback": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz",
- "integrity": "sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==",
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
+ "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/use-sync-external-store": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
- "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
diff --git a/package.json b/package.json
index b602dcb..84db154 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "experientapp",
- "main": "expo-router/entry",
+ "main": "node_modules/expo/AppEntry.js",
"version": "1.0.0",
"scripts": {
"start": "expo start",
@@ -12,9 +12,11 @@
},
"dependencies": {
"@expo/vector-icons": "^14.1.0",
+ "@react-native-async-storage/async-storage": "^2.2.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/elements": "^2.3.8",
- "@react-navigation/native": "^7.1.6",
+ "@react-navigation/native": "^7.1.28",
+ "@react-navigation/native-stack": "^7.12.0",
"expo": "~53.0.9",
"expo-blur": "~14.1.4",
"expo-constants": "~17.1.6",
@@ -22,7 +24,6 @@
"expo-haptics": "~14.1.4",
"expo-image": "~2.1.7",
"expo-linking": "~7.1.5",
- "expo-router": "~5.0.6",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "~2.2.3",
"expo-symbols": "~0.4.4",
@@ -33,7 +34,7 @@
"react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
- "react-native-safe-area-context": "5.4.0",
+ "react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5"
@@ -41,9 +42,9 @@
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.10",
- "typescript": "~5.8.3",
"eslint": "^9.25.0",
- "eslint-config-expo": "~9.2.0"
+ "eslint-config-expo": "~9.2.0",
+ "typescript": "~5.8.3"
},
"private": true
}
diff --git a/scripts/reset-project.js b/scripts/reset-project.js
deleted file mode 100755
index 51dff15..0000000
--- 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 909e901..d9acc9d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
+ "jsx": "react-native",
"strict": true,
"paths": {
"@/*": [
@@ -10,8 +11,6 @@
},
"include": [
"**/*.ts",
- "**/*.tsx",
- ".expo/types/**/*.ts",
- "expo-env.d.ts"
+ "**/*.tsx"
]
}