Merge branch '29-integrate-login' into 'main'
Resolve "integrate Login" Closes #29 See merge request thschleicher/interaktive-systeme!10
This commit is contained in:
commit
424407faf3
11 changed files with 330 additions and 17 deletions
|
|
@ -1,15 +1,22 @@
|
||||||
import { Tabs } from "expo-router/tabs";
|
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 { FontAwesome } from "@expo/vector-icons";
|
||||||
import {useThemeColor} from "../../hooks/hooks";
|
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() {
|
export default function Layout() {
|
||||||
// const selectedColor: string = useThemeColor( "tabIconSelected");
|
// const selectedColor: string = useThemeColor( "tabIconSelected");
|
||||||
// const defaultColor: string = useThemeColor("tabIconDefault");
|
// const defaultColor: string = useThemeColor("tabIconDefault");
|
||||||
// const backgroundColor: string = useThemeColor("backgroundColor");
|
// const backgroundColor: string = useThemeColor("backgroundColor");
|
||||||
// const tabBarColor: string = useThemeColor("tabBarColor");
|
// const tabBarColor: string = useThemeColor("tabBarColor");
|
||||||
|
const {authState} = useAuth()
|
||||||
|
useEffect(()=>{
|
||||||
|
console.log(authState, "in root Layout")
|
||||||
|
},[authState])
|
||||||
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
sceneContainer: {
|
sceneContainer: {
|
||||||
|
|
@ -27,6 +34,11 @@ export default function Layout() {
|
||||||
tabBarStyle: styles.tabBar,
|
tabBarStyle: styles.tabBar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!authState?.authenticated){
|
||||||
|
return (
|
||||||
|
<Redirect href={"/"} />)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs sceneContainerStyle={styles.sceneContainer} screenOptions={screenOptions}>
|
<Tabs sceneContainerStyle={styles.sceneContainer} screenOptions={screenOptions}>
|
||||||
<Tabs.Screen name="budget/index" options={
|
<Tabs.Screen name="budget/index" options={
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ import { FlatList, TextInput} from 'react-native-gesture-handler';
|
||||||
import { useRef, useState, useEffect } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useRouter } from "expo-router"
|
import { useRouter } from "expo-router"
|
||||||
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const {onLogout} = useAuth();
|
||||||
const [plusShow, setPlusShow] = useState(true);
|
const [plusShow, setPlusShow] = useState(true);
|
||||||
const prevOffset = useRef(0);
|
const prevOffset = useRef(0);
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
@ -52,7 +53,7 @@ export default function Page() {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<SafeAreaView style={{flex: 1}}>
|
||||||
{plusShow && <Plus onPress={()=>{
|
{plusShow && <Plus onPress={()=>{
|
||||||
router.push("/(tabs)/home/addItem")
|
router.push("/(tabs)/home/addItem")
|
||||||
}}/>}
|
}}/>}
|
||||||
|
|
@ -61,7 +62,7 @@ export default function Page() {
|
||||||
data={data}
|
data={data}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<>
|
<>
|
||||||
<Welcome name="My Dude" image={profile} onPress={()=>console.log("hello")}></Welcome>
|
<Welcome name="My Dude" image={profile} onPress={onLogout!}></Welcome>
|
||||||
<SearchBar placeholder='Type to Search...'></SearchBar>
|
<SearchBar placeholder='Type to Search...'></SearchBar>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +73,7 @@ export default function Page() {
|
||||||
scrollEventThrottle={20}
|
scrollEventThrottle={20}
|
||||||
>
|
>
|
||||||
</FlatList>
|
</FlatList>
|
||||||
</View>
|
</SafeAreaView>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
12
app/_layout.tsx
Normal file
12
app/_layout.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<AuthProvider>
|
||||||
|
<Slot />
|
||||||
|
</AuthProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
137
app/contexts/AuthContext.tsx
Normal file
137
app/contexts/AuthContext.tsx
Normal file
|
|
@ -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<any>;
|
||||||
|
onLogin?: (email: string, password: string) => Promise<any>
|
||||||
|
onLogout?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const SESSION_KEY = 'my_session'
|
||||||
|
const USER_KEY = "email"
|
||||||
|
const AuthContext = createContext<AuthProps>({})
|
||||||
|
|
||||||
|
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<string|null> {
|
||||||
|
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 <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { View, Text } from 'react-native'
|
|
||||||
import React from 'react'
|
|
||||||
import { Redirect } from 'expo-router'
|
|
||||||
|
|
||||||
export default function index() {
|
|
||||||
return (
|
|
||||||
<Redirect href={"(tabs)/home"}></Redirect>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
14
app/index.tsx
Normal file
14
app/index.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<Redirect href={authState?.authenticated ? "/home/" : "/login"}></Redirect>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
46
app/login.tsx
Normal file
46
app/login.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
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();
|
||||||
|
// const {authState, onLogin} = useAuth();
|
||||||
|
// useEffect(() => {
|
||||||
|
// login()
|
||||||
|
// }, [])
|
||||||
|
|
||||||
|
|
||||||
|
const login = async() => {
|
||||||
|
await onLogin!(email, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<SafeAreaView style={{flex: 1, justifyContent: "center"}}>
|
||||||
|
{authState?.authenticated &&
|
||||||
|
<Redirect href={"/"}></Redirect>}
|
||||||
|
<Input
|
||||||
|
placeholder={'Email'}
|
||||||
|
label='Email or Username'
|
||||||
|
leftIcon={<FontAwesome name="user" size={20} />}
|
||||||
|
onChangeHandler={setEmail}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={'Enter Password'}
|
||||||
|
label='Password'
|
||||||
|
leftIcon={<FontAwesome name="key" size={20} />}
|
||||||
|
rightIcon={<FontAwesome name="key" size={20} />}
|
||||||
|
onChangeHandler={setPassword}
|
||||||
|
secure = {true}
|
||||||
|
/>
|
||||||
|
<Button title='login' onPress={login}></Button>
|
||||||
|
</SafeAreaView>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,15 @@ import Welcome from "./home/Welcome/Welcome"
|
||||||
import Plus from "./common/plus/plus"
|
import Plus from "./common/plus/plus"
|
||||||
import SearchBar from "./common/searchBar/SearchBar"
|
import SearchBar from "./common/searchBar/SearchBar"
|
||||||
|
|
||||||
|
|
||||||
|
//login
|
||||||
|
import Input from "./login/input"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ExpenseItem,
|
ExpenseItem,
|
||||||
Welcome,
|
Welcome,
|
||||||
Plus,
|
Plus,
|
||||||
SearchBar
|
SearchBar,
|
||||||
|
Input,
|
||||||
}
|
}
|
||||||
|
|
||||||
85
components/login/input.tsx
Normal file
85
components/login/input.tsx
Normal file
|
|
@ -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<TextInputEndEditingEventData>)=>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 (
|
||||||
|
<View style={{padding:10}}>
|
||||||
|
<Text style={styles.label}>{label}</Text>
|
||||||
|
<View
|
||||||
|
style={[styles.container, containerBorder, {backgroundColor: bgColor ? bgColor : "white"}]}>
|
||||||
|
<View style={{paddingRight:10}}>{leftIcon}</View>
|
||||||
|
|
||||||
|
<TextInput secureTextEntry={secure} placeholder={
|
||||||
|
placeholder ? placeholder : label ? 'Enter ${label}' : ''
|
||||||
|
}
|
||||||
|
onChangeText={onChangeHandler}
|
||||||
|
onEndEditing={validate}
|
||||||
|
multiline={numLines > 1 ? true : false}
|
||||||
|
numberOfLines={numLines}
|
||||||
|
style={{flex: 4}}
|
||||||
|
/>
|
||||||
|
{rightIcon}
|
||||||
|
</View>
|
||||||
|
<Text style={{color: errorColor? errorColor: "red"}}>{errorMessage}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
9
package-lock.json
generated
9
package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
||||||
"expo-font": "~11.4.0",
|
"expo-font": "~11.4.0",
|
||||||
"expo-linking": "~5.0.2",
|
"expo-linking": "~5.0.2",
|
||||||
"expo-router": "^2.0.0",
|
"expo-router": "^2.0.0",
|
||||||
|
"expo-secure-store": "~12.3.1",
|
||||||
"expo-splash-screen": "~0.20.5",
|
"expo-splash-screen": "~0.20.5",
|
||||||
"expo-status-bar": "~1.6.0",
|
"expo-status-bar": "~1.6.0",
|
||||||
"expo-system-ui": "~2.4.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": {
|
"node_modules/expo-splash-screen": {
|
||||||
"version": "0.20.5",
|
"version": "0.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.20.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@
|
||||||
"react-native-gesture-handler": "~2.12.0",
|
"react-native-gesture-handler": "~2.12.0",
|
||||||
"react-native-safe-area-context": "4.6.3",
|
"react-native-safe-area-context": "4.6.3",
|
||||||
"react-native-screens": "~3.22.0",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
|
|
||||||
Reference in a new issue