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 ( + +