diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 907c059..0e98e99 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -3,6 +3,9 @@ import { Tabs } from 'expo-router' import React from 'react' export default function TabLayout() { + const isAdmin = localStorage.getItem('session') === 'admin' + console.log(isAdmin) + return ( ( ) }} /> ( + logout()}>Logout + + ) +} + +const styles = StyleSheet.create({ + wrapper: { + flex: 1, + backgroundColor: '#25292e', + padding: 5 + } +}) diff --git a/app/_layout.tsx b/app/_layout.tsx index bb24b00..5bcf4c3 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,9 +1,29 @@ import { Stack } from 'expo-router' +import { SessionProvider, useSession } from './ctx' +import { SplashScreenController } from './splash' -export default function RootLayout() { +export default function Root() { + // Set up the auth context and render our layout inside of it. + return ( + + + + + ) +} + +// Separate this into a new component so it can access the SessionProvider context later +function RootNavigator() { + const { session } = useSession() return ( - + + + + + + + ) } diff --git a/app/ctx.tsx b/app/ctx.tsx new file mode 100644 index 0000000..9875a26 --- /dev/null +++ b/app/ctx.tsx @@ -0,0 +1,46 @@ +import { createContext, use, type PropsWithChildren } from 'react' +import { useStorageState } from './useStorageState' + +const AuthContext = createContext<{ + login: () => void + logout: () => void + session?: string | null + isLoading: boolean +}>({ + login: () => null, + logout: () => null, + session: null, + isLoading: false +}) + +// This hook can be used to access the user info. +export function useSession() { + const value = use(AuthContext) + if (!value) { + throw new Error('useSession must be wrapped in a ') + } + + return value +} + +export function SessionProvider({ children }: PropsWithChildren) { + const [[isLoading, session], setSession] = useStorageState('session') + + return ( + { + // Perform sign-in logic here + setSession('admin') + }, + logout: () => { + setSession(null) + }, + session, + isLoading + }} + > + {children} + + ) +} diff --git a/app/(tabs)/login.tsx b/app/login.tsx similarity index 51% rename from app/(tabs)/login.tsx rename to app/login.tsx index 4918ee5..0d38a2b 100644 --- a/app/(tabs)/login.tsx +++ b/app/login.tsx @@ -1,44 +1,54 @@ -import React from 'react'; -import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import { router } from 'expo-router' +import React from 'react' +import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native' +import { useSession } from './ctx' export default function LoginScreen() { - - const [username, setUsername] = React.useState(''); - const [password, setPassword] = React.useState(''); - - function login(username:string, password:string) { - console.log(username, password); - } + const [username, setUsername] = React.useState('') + const [password, setPassword] = React.useState('') + const { login } = useSession() return ( Login - Username + Username - Password + Password login(username, password)} + onPress={() => { + login() + if (localStorage.getItem('session') === 'success') { + router.replace('/') + } + }} > Login + { + router.navigate('/signup') + }} + > + Sign Up + - ); + ) } const styles = StyleSheet.create({ @@ -46,21 +56,29 @@ const styles = StyleSheet.create({ flex: 1, backgroundColor: '#25292e', justifyContent: 'center', - alignItems: 'center', + alignItems: 'center' }, text: { + color: '#fff' + }, + textlabel: { color: '#fff', + marginBottom: 3 }, input: { backgroundColor: '#fff', borderRadius: 5, padding: 10, - marginBottom: 15, + marginBottom: 15 }, button: { backgroundColor: '#1e90ff', padding: 12, borderRadius: 5, alignItems: 'center', + marginBottom: 12 + }, + signup: { + alignItems: 'center' } -}); +}) diff --git a/app/signup.tsx b/app/signup.tsx new file mode 100644 index 0000000..cf49756 --- /dev/null +++ b/app/signup.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import { StyleSheet, View } from 'react-native' + +export default function SignupScreen() { + return +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#25292e', + justifyContent: 'center', + alignItems: 'center' + } +}) diff --git a/app/splash.tsx b/app/splash.tsx new file mode 100644 index 0000000..ae2c116 --- /dev/null +++ b/app/splash.tsx @@ -0,0 +1,12 @@ +import { SplashScreen } from 'expo-router' +import { useSession } from './ctx' + +export function SplashScreenController() { + const { isLoading } = useSession() + + if (!isLoading) { + SplashScreen.hideAsync() + } + + return null +} diff --git a/app/useStorageState.tsx b/app/useStorageState.tsx new file mode 100644 index 0000000..d99532d --- /dev/null +++ b/app/useStorageState.tsx @@ -0,0 +1,65 @@ +import * as SecureStore from 'expo-secure-store' +import { useCallback, useEffect, useReducer } from 'react' +import { Platform } from 'react-native' + +type UseStateHook = [[boolean, T | null], (value: T | null) => void] + +function useAsyncState(initialValue: [boolean, T | null] = [true, null]): UseStateHook { + return useReducer( + (state: [boolean, T | null], action: T | null = null): [boolean, T | null] => [false, action], + initialValue + ) as UseStateHook +} + +export async function setStorageItemAsync(key: string, value: string | null) { + if (Platform.OS === 'web') { + try { + if (value === null) { + localStorage.removeItem(key) + } else { + localStorage.setItem(key, value) + } + } catch (e) { + console.error('Local storage is unavailable:', e) + } + } else { + if (value == null) { + await SecureStore.deleteItemAsync(key) + } else { + await SecureStore.setItemAsync(key, value) + } + } +} + +export function useStorageState(key: string): UseStateHook { + // Public + const [state, setState] = useAsyncState() + + // Get + useEffect(() => { + if (Platform.OS === 'web') { + try { + if (typeof localStorage !== 'undefined') { + setState(localStorage.getItem(key)) + } + } catch (e) { + console.error('Local storage is unavailable:', e) + } + } else { + SecureStore.getItemAsync(key).then((value) => { + setState(value) + }) + } + }, [key, setState]) + + // Set + const setValue = useCallback( + (value: string | null) => { + setState(value) + setStorageItemAsync(key, value) + }, + [key, setState] + ) + + return [state, setValue] +}