From 6592864bc42ce9d2084bb43bca6f952b0baa10dc Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Fri, 8 Dec 2023 14:51:05 +0100 Subject: [PATCH 1/2] feat: introduced Authentification Context --- app/(tabs)/_layout.tsx | 16 +++- app/(tabs)/home/index.tsx | 9 ++- app/_layout.tsx | 12 +++ app/contexts/AuthContext.tsx | 137 +++++++++++++++++++++++++++++++++++ app/index.jsx | 9 --- app/index.tsx | 14 ++++ app/login.tsx | 22 ++++++ package-lock.json | 9 +++ package.json | 3 +- 9 files changed, 215 insertions(+), 16 deletions(-) create mode 100644 app/_layout.tsx create mode 100644 app/contexts/AuthContext.tsx delete mode 100644 app/index.jsx create mode 100644 app/index.tsx create mode 100644 app/login.tsx diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index fb022e3..2987b71 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,15 +1,22 @@ import { Tabs } from "expo-router/tabs"; -import {StyleSheet, View} from "react-native" +import {StyleSheet, View, Text} from "react-native" import { FontAwesome } from "@expo/vector-icons"; import {useThemeColor} from "../../hooks/hooks"; -import React from "react"; +import React, { useEffect } from "react"; +import { Redirect } from "expo-router"; +import { useAuth } from "../contexts/AuthContext"; export default function Layout() { // const selectedColor: string = useThemeColor( "tabIconSelected"); // const defaultColor: string = useThemeColor("tabIconDefault"); // const backgroundColor: string = useThemeColor("backgroundColor"); // const tabBarColor: string = useThemeColor("tabBarColor"); + const {authState} = useAuth() + useEffect(()=>{ + console.log(authState, "in root Layout") + },[authState]) + const styles = StyleSheet.create({ sceneContainer: { @@ -27,6 +34,11 @@ export default function Layout() { tabBarStyle: styles.tabBar, } + if(!authState?.authenticated){ + return ( + ) + } + return ( + {plusShow && { router.push("/(tabs)/home/addItem") }}/>} @@ -61,7 +62,7 @@ export default function Page() { data={data} ListHeaderComponent={ <> - console.log("hello")}> + } @@ -72,7 +73,7 @@ export default function Page() { scrollEventThrottle={20} > - + ); } \ No newline at end of file diff --git a/app/_layout.tsx b/app/_layout.tsx new file mode 100644 index 0000000..42cdecd --- /dev/null +++ b/app/_layout.tsx @@ -0,0 +1,12 @@ +import { Slot } from 'expo-router' +import React from 'react' +import { AuthProvider } from './contexts/AuthContext' + +export default function _layout() { + console.log("layout called") + return ( + + + + ) +} \ No newline at end of file diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx new file mode 100644 index 0000000..236c962 --- /dev/null +++ b/app/contexts/AuthContext.tsx @@ -0,0 +1,137 @@ +import { createContext, useContext, useEffect, useState } from "react"; +import { Platform } from "react-native"; +import * as SecureStore from 'expo-secure-store' + +interface AuthProps { + authState?: {email: string | null; authenticated: boolean | null; session: string | null}; + onRegister?: (email: string, password: string) => Promise; + onLogin?: (email: string, password: string) => Promise + onLogout?: () => void +} + +const SESSION_KEY = 'my_session' +const USER_KEY = "email" +const AuthContext = createContext({}) + +export const useAuth = ()=>{ + return useContext(AuthContext) +} + +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); + } + } +} + +async function getStorageItemAsync(key: string) : Promise { + if(Platform.OS === 'web'){ + return localStorage.getItem(key) + }else{ + return SecureStore.getItemAsync(key) + } +} + +export const AuthProvider = ({children}: any) => { + const [authState, setAuthState] = useState<{ + email: string | null; + authenticated: boolean | null; + session : string | null; + isLoading: boolean; + }>({ + email: null, authenticated: null, session: null, isLoading: false + }); + + useEffect(() => { + const loadSession = async () => { + setAuthState((prev) => { + return { + ...prev, + isLoading: true + } + }) + + + const session = await getStorageItemAsync(SESSION_KEY) + const email = await getStorageItemAsync(USER_KEY) + + if(session && email){ + setAuthState({ + email: email, + authenticated: true, + session: session, + isLoading: false + }) + } + setAuthState((prev) => { + return { + ...prev, + isLoading: false + } + }) + } + loadSession() + },[]) + + + const register = async (email: string, password: string) => { + //registration Logic goes here + } + + const login = async (email: string, password: string) => { + + //login functionality goes here + console.log("login called") + setAuthState((prev) => { + return{ + ...prev, + isLoading: true + } + }) + + setAuthState({ + email: email, + authenticated: true, + session: "super duper session token das wir vlt mal brauchen", + isLoading: false + + }) + + await setStorageItemAsync(SESSION_KEY, "super duper session token das wir vlt mal brauchen") + await setStorageItemAsync(USER_KEY, email) + } + + const logout = async () => { + await setStorageItemAsync(SESSION_KEY, null) + await setStorageItemAsync(USER_KEY, null) + + setAuthState({ + email: null, + authenticated: false, + session: null, + isLoading: false + }); + }; + + + const value = { + onRegister: register, + onLogin: login, + onLogout: logout, + authState + } + return {children} +} \ No newline at end of file diff --git a/app/index.jsx b/app/index.jsx deleted file mode 100644 index 05e453f..0000000 --- a/app/index.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { View, Text } from 'react-native' -import React from 'react' -import { Redirect } from 'expo-router' - -export default function index() { - return ( - - ) -} \ No newline at end of file diff --git a/app/index.tsx b/app/index.tsx new file mode 100644 index 0000000..094049b --- /dev/null +++ b/app/index.tsx @@ -0,0 +1,14 @@ + +import React, { useEffect } from 'react' +import { Redirect } from 'expo-router' +import { useAuth } from './contexts/AuthContext' + +export default function index() { + const {authState} = useAuth() + + + return ( + + + ) +} \ No newline at end of file diff --git a/app/login.tsx b/app/login.tsx new file mode 100644 index 0000000..cb8113c --- /dev/null +++ b/app/login.tsx @@ -0,0 +1,22 @@ +import { View, Text } from 'react-native' +import React, { useEffect } from 'react' +import { useAuth } from './contexts/AuthContext' +import { Redirect } from 'expo-router'; + +export default function login() { + const {authState, onLogin} = useAuth(); + useEffect(() => { + login() + }, []) + + + const login = async() => { + onLogin!("test", "test"); + } + return ( + + login + + + ) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a72517a..6e8bf8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "expo-font": "~11.4.0", "expo-linking": "~5.0.2", "expo-router": "^2.0.0", + "expo-secure-store": "~12.3.1", "expo-splash-screen": "~0.20.5", "expo-status-bar": "~1.6.0", "expo-system-ui": "~2.4.0", @@ -9008,6 +9009,14 @@ } } }, + "node_modules/expo-secure-store": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-12.3.1.tgz", + "integrity": "sha512-XLIgWDiIuiR0c+AA4NCWWibAMHCZUyRcy+lQBU49U6rvG+xmd3YrBJfQjfqAPyLroEqnLPGTWUX57GyRsfDOQw==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-splash-screen": { "version": "0.20.5", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.20.5.tgz", diff --git a/package.json b/package.json index 4a2fb5a..914e2df 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "react-native-gesture-handler": "~2.12.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", - "react-native-web": "~0.19.6" + "react-native-web": "~0.19.6", + "expo-secure-store": "~12.3.1" }, "devDependencies": { "@babel/core": "^7.20.0", From e111800ca2df93bac382069b964a6c06ca33516d Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Fri, 8 Dec 2023 15:44:32 +0100 Subject: [PATCH 2/2] feat: login page integrated --- app/login.tsx | 46 ++++++++++++---- components/{index.jsx => index.tsx} | 7 ++- components/login/input.tsx | 85 +++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 12 deletions(-) rename components/{index.jsx => index.tsx} (79%) create mode 100644 components/login/input.tsx diff --git a/app/login.tsx b/app/login.tsx index cb8113c..501e82f 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -1,22 +1,46 @@ -import { View, Text } from 'react-native' -import React, { useEffect } from 'react' +import { View, Text, SafeAreaView, TextInput, Button} from 'react-native' +import React, { useEffect, useState } from 'react' import { useAuth } from './contexts/AuthContext' import { Redirect } from 'expo-router'; +import { FontAwesome } from "@expo/vector-icons"; +import { Input } from '../components'; export default function login() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState("") const {authState, onLogin} = useAuth(); - useEffect(() => { - login() - }, []) + // const {authState, onLogin} = useAuth(); + // useEffect(() => { + // login() + // }, []) const login = async() => { - onLogin!("test", "test"); + await onLogin!(email, password); } + + return ( - - login - - - ) + + + {authState?.authenticated && + } + } + onChangeHandler={setEmail} + /> + } + rightIcon={} + onChangeHandler={setPassword} + secure = {true} + /> + + + + ); } \ No newline at end of file diff --git a/components/index.jsx b/components/index.tsx similarity index 79% rename from components/index.jsx rename to components/index.tsx index 403df5a..871d5ce 100644 --- a/components/index.jsx +++ b/components/index.tsx @@ -6,10 +6,15 @@ import Welcome from "./home/Welcome/Welcome" import Plus from "./common/plus/plus" import SearchBar from "./common/searchBar/SearchBar" + +//login +import Input from "./login/input" + export { ExpenseItem, Welcome, Plus, - SearchBar + SearchBar, + Input, } diff --git a/components/login/input.tsx b/components/login/input.tsx new file mode 100644 index 0000000..6e6ec48 --- /dev/null +++ b/components/login/input.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import {View, Text, TextInput, StyleSheet} from 'react-native'; +import { NativeSyntheticEvent } from 'react-native/Libraries/Types/CoreEventTypes'; +import { TextInputEndEditingEventData } from 'react-native/Libraries/Components/TextInput/TextInput'; + +interface InputProps { + label? : string, + outlined? : boolean, + placeholder?: string, + leftIcon?: any, + rightIcon?: any, + numLines?: number, + onChangeHandler?: (text: string)=>void, + secure?: boolean, + validate?: (e : NativeSyntheticEvent)=>void, // ob die line "gecheckt" werden soll + errorMessage? : any, //error msg + errorColor?: string, + bgColor?: string, +} + +function Input({ + label, + outlined, + placeholder, + leftIcon, + rightIcon, + numLines, + onChangeHandler, + secure, + validate, + errorMessage, + errorColor, + bgColor +} : InputProps) { + const containerBorder = outlined ? styles.outlined : styles.standard + if(numLines == undefined){ + numLines = 1 + } + return ( + + {label} + + {leftIcon} + + 1 ? true : false} + numberOfLines={numLines} + style={{flex: 4}} + /> + {rightIcon} + + {errorMessage} + + + ) +} + +const styles = StyleSheet.create({ + label: { + fontWeight: '500', + }, + container: { + padding: 10, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'white' + }, + outlined: { + borderColor: 'darkgrey', + borderRadius: 4, + borderWidth: 1, + }, + standard: { + borderBottomColor: 'darkgrey', + borderBottomWidth: 1, + }, + +}) + +export default Input; \ No newline at end of file