From b58d5c214938138411c3929ed19c6c07e2064410 Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Wed, 12 Oct 2022 06:43:55 +0200 Subject: [PATCH 1/4] app: src folder added --- app/src/contexts/AuthContext.js | 3 + app/src/hooks/useConnectMeteor.js | 49 ++++++++++++++ app/src/hooks/useLogin.js | 92 +++++++++++++++++++++++++++ app/src/hooks/useLoginMeteor.js | 47 ++++++++++++++ app/src/screens/HomeScreen.js | 25 ++++++++ app/src/screens/LoginScreen.js | 50 +++++++++++++++ app/src/screens/RegistrationScreen.js | 46 ++++++++++++++ app/src/screens/ScreenNavigator.js | 47 ++++++++++++++ app/src/storage/Credentials.js | 15 +++++ app/src/styles/inputStyles.js | 12 ++++ app/src/utils/debounce.js | 12 ++++ app/src/utils/throttle.js | 10 +++ 12 files changed, 408 insertions(+) create mode 100644 app/src/contexts/AuthContext.js create mode 100644 app/src/hooks/useConnectMeteor.js create mode 100644 app/src/hooks/useLogin.js create mode 100644 app/src/hooks/useLoginMeteor.js create mode 100644 app/src/screens/HomeScreen.js create mode 100644 app/src/screens/LoginScreen.js create mode 100644 app/src/screens/RegistrationScreen.js create mode 100644 app/src/screens/ScreenNavigator.js create mode 100644 app/src/storage/Credentials.js create mode 100644 app/src/styles/inputStyles.js create mode 100644 app/src/utils/debounce.js create mode 100644 app/src/utils/throttle.js diff --git a/app/src/contexts/AuthContext.js b/app/src/contexts/AuthContext.js new file mode 100644 index 0000000..f5d4674 --- /dev/null +++ b/app/src/contexts/AuthContext.js @@ -0,0 +1,3 @@ +import { createContext } from 'react' + +export const AuthContext = createContext() diff --git a/app/src/hooks/useConnectMeteor.js b/app/src/hooks/useConnectMeteor.js new file mode 100644 index 0000000..95ac675 --- /dev/null +++ b/app/src/hooks/useConnectMeteor.js @@ -0,0 +1,49 @@ +import { useEffect, useState } from 'react' +import Meteor from '@meteorrn/core' +import * as SecureStore from 'expo-secure-store' +import config from '../../config.json' + +Meteor.connect(config.backend.url, { + // store login token in SecureStore! + AsyncStorage: { + getItem: SecureStore.getItemAsync, + setItem: SecureStore.setItemAsync, + removeItem: SecureStore.deleteItemAsync + } +}) + +Meteor.isVerbose = true + +export const useConnectMeteor = () => { + const [connected, setConnected] = useState(null) + const [connectionError, setConnectionError] = useState(null) + + // TODO try useMemo and useTracker and see if this is functional + + useEffect(() => { + const onError = (e) => setConnectionError(e) + const onConnected = () => { + if (connected !== true) { + setConnected(true) + } + } + const onDisconnected = () => { + Meteor.ddp.autoConnect = true + if (connected !== false) { + setConnected(false) + } + Meteor.reconnect() + } + Meteor.ddp.on('error', onError) + Meteor.ddp.on('connected', onConnected) + Meteor.ddp.on('disconnected',onDisconnected) + + return () => { + Meteor.ddp.off('error', onError) + Meteor.ddp.off('connected', onConnected) + Meteor.ddp.off('disconnected',onDisconnected) + } + }, []) + + return { connected, connectionError } +} \ No newline at end of file diff --git a/app/src/hooks/useLogin.js b/app/src/hooks/useLogin.js new file mode 100644 index 0000000..e620b12 --- /dev/null +++ b/app/src/hooks/useLogin.js @@ -0,0 +1,92 @@ +import React, { useReducer, useEffect, useMemo } from 'react'; +import Meteor from '@meteorrn/core' + +const initialState = { + isLoading: true, + isSignout: false, + userToken: null +} + +// (state, action) => newState +const reducer = (state, action) => { + switch (action.type) { + case 'RESTORE_TOKEN': + return { + ...state, + userToken: action.token, + isLoading: false + } + case 'SIGN_IN': + return { + ...state, + isSignOut: false, + userToken: action.token + } + case 'SIGN_OUT': + return { + ...state, + isSignout: true, + userToken: null + } + } +} + +const Data = Meteor.getData() + +export const useLogin = () => { + const [state, dispatch] = useReducer(reducer, initialState, undefined) + + // MeteorRN loads the token on connection automatically, + // but we need to "know" that for our auth workflow + useEffect(() => { + const handleOnLogin = () => dispatch({ type: 'RESTORE_TOKEN', token: Meteor.getAuthToken() }) + Data.on('onLogin', handleOnLogin) + return () => Data.off('onLogin', handleOnLogin) + }, []) + + const authContext = useMemo(() => ({ + signIn: ({ email, password, onError }) => { + Meteor.loginWithPassword(email, password, async (err) => { + if (err) { + if (err.message === 'Match failed [400]') { + err.message = 'Login failed, please check your credentials and retry.' + } + return onError(err) + } + const token = Meteor.getAuthToken() + const type = 'SIGN_IN' + dispatch({ type, token }) + }) + }, + signOut: () => { + Meteor.logout(err => { + if (err) { + // TODO display error, merge into the above workflow + return console.error(err) + } + dispatch({ type: 'SIGN_OUT' }) + }) + }, + signUp: ({ email, password, onError }) => { + Meteor.call('register', { email, password }, (err, res) => { + console.debug('on register', err, res) + if (err) { + return onError(err) + } + Meteor.loginWithPassword(email, password, async (err) => { + if (err) { + if (err.message === 'Match failed [400]') { + err.message = 'Login failed, please check your credentials and retry.' + } + return onError(err) + } + const token = Meteor.getAuthToken() + const type = 'SIGN_IN' + dispatch({ type, token }) + }) + }) + } + }), []) + + return { state, authContext } +} \ No newline at end of file diff --git a/app/src/hooks/useLoginMeteor.js b/app/src/hooks/useLoginMeteor.js new file mode 100644 index 0000000..9e8b15a --- /dev/null +++ b/app/src/hooks/useLoginMeteor.js @@ -0,0 +1,47 @@ +import Meteor from '@meteorrn/core' +import { useEffect, useState } from 'react' +import { Credentials } from '../storage/Credentials' + +export const useLoginMeteor = () => { + const [registered, setRegistered] = useState(false) + const [loggedIn, setLoggedIn] = useState(false) + const [loggingIn, setLoggingIn] = useState(false) + const [loginError, setLoginError] = useState(null) + + useEffect(() => { + const onConnected = async () => { + const user = Meteor.user() + if (user) { + setRegistered(true) + setLoggedIn(true) + setLoggingIn(false) + return + } + + const { email, password, exist } = Credentials.get() + if (!exist) { + setRegistered(false) + setLoggedIn(false) + setLoggingIn(false) + return + } + + setLoggingIn(true) + Meteor.loginWithPassword(email, password, err => { + setLoginError(err ?? null) + setRegistered(true) + setLoggedIn(!err) + setLoggingIn(false) + }) + } + Meteor.ddp.on('connected', onConnected) + return () => Meteor.ddp.off('connected', onConnected) + }, []) + + return { + registered, + loggedIn, + loggingIn, + loginError + } +} diff --git a/app/src/screens/HomeScreen.js b/app/src/screens/HomeScreen.js new file mode 100644 index 0000000..995b5ff --- /dev/null +++ b/app/src/screens/HomeScreen.js @@ -0,0 +1,25 @@ +import React, { useContext, useState } from 'react' +import { View, Text, Button } from 'react-native' +import { AuthContext } from '../contexts/AuthContext' + +export const HomeScreen = () => { + const [error, setError] = useState(null) + const { signOut } = useContext(AuthContext) + const onError = err => setError(err) + const handleSignOut = () => signOut({ onError }) + + const renderError = () => { + if (!error) { return null } + return ( + + {error.message} + + ) + } + + return ( + +