├── .eslintrc ├── src ├── stacks │ ├── index.js │ ├── AppStack.js │ ├── RootStack.js │ └── AuthStack.js ├── utils │ ├── index.js │ └── options.js ├── shared │ ├── index.js │ ├── constants.js │ └── styledComponents.js ├── screens │ ├── app │ │ ├── Home.js │ │ ├── Profile.js │ │ └── Settings.js │ ├── index.js │ ├── Loading.js │ └── auth │ │ ├── ForgetPassword.js │ │ ├── ConfirmPassword.js │ │ ├── ConfirmSignUp.js │ │ ├── SignUp.js │ │ └── SignIn.js └── components │ └── Spinner.js ├── assets ├── icon.png └── splash.png ├── babel.config.js ├── App.js ├── .expo-shared └── assets.json ├── .gitignore ├── README.md ├── app.json └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["wesbos"] 3 | } 4 | -------------------------------------------------------------------------------- /src/stacks/index.js: -------------------------------------------------------------------------------- 1 | export { default as RootStack } from './RootStack'; 2 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youneshenniwrites/authentication/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youneshenniwrites/authentication/HEAD/assets/splash.png -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { tabOptions } from './options'; 2 | export { stackOptions } from './options'; 3 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { createAppContainer } from 'react-navigation'; 2 | 3 | import { RootStack } from './src/stacks'; 4 | 5 | export default createAppContainer(RootStack); 6 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # Lock file 17 | package-lock.json -------------------------------------------------------------------------------- /src/shared/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | titles, 3 | messages, 4 | colors, 5 | placeholders, 6 | routes, 7 | buttons, 8 | } from './constants'; 9 | export { 10 | Container, 11 | Centered, 12 | TextInput, 13 | Button, 14 | Text, 15 | } from './styledComponents'; 16 | -------------------------------------------------------------------------------- /src/screens/app/Home.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import { FlexCentered, Text } from '../../shared/styledComponents'; 5 | 6 | const Home = () => ( 7 | 8 | Home 9 | 10 | ); 11 | export default Home; 12 | -------------------------------------------------------------------------------- /src/screens/app/Profile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import { FlexCentered, Text } from '../../shared/styledComponents'; 5 | 6 | const Profile = () => ( 7 | 8 | Profile 9 | 10 | ); 11 | export default Profile; 12 | -------------------------------------------------------------------------------- /src/components/Spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ActivityIndicator } from 'react-native'; 3 | 4 | import { FlexCentered } from '../shared/styledComponents'; 5 | 6 | const Spinner = () => ( 7 | 8 | 9 | 10 | ); 11 | export default Spinner; 12 | -------------------------------------------------------------------------------- /src/stacks/AppStack.js: -------------------------------------------------------------------------------- 1 | import { createMaterialTopTabNavigator } from 'react-navigation-tabs'; 2 | 3 | import { Home, Profile, Settings } from '../screens'; 4 | import { tabOptions } from '../utils/options'; 5 | 6 | const AppStack = createMaterialTopTabNavigator( 7 | { Home, Profile, Settings }, 8 | tabOptions 9 | ); 10 | 11 | export default AppStack; 12 | -------------------------------------------------------------------------------- /src/stacks/RootStack.js: -------------------------------------------------------------------------------- 1 | import { createSwitchNavigator } from 'react-navigation'; 2 | 3 | import { Loading } from '../screens'; 4 | import AuthStack from './AuthStack'; 5 | import AppStack from './AppStack'; 6 | 7 | const RootStack = createSwitchNavigator( 8 | { 9 | Loading, 10 | App: AppStack, 11 | Auth: AuthStack, 12 | }, 13 | { 14 | initialRouteName: 'Loading', 15 | } 16 | ); 17 | 18 | export default RootStack; 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [WIP] User Authentication With React Native and FOO Back End 2 | 3 | ![auth flow](https://user-images.githubusercontent.com/26605247/71269676-cef47980-2347-11ea-93c8-0837d63c5389.png) 4 | 5 | ## Features 6 | 7 | * Sign in 8 | * Sing up 9 | * Remember user 10 | * Confirm user by SMS or Email link 11 | * Reset password 12 | 13 | ## Technologies 14 | 15 | * Expo 16 | * React navigation 17 | * Styled components 18 | * Back End to be decided 19 | -------------------------------------------------------------------------------- /src/screens/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './app/Home'; 2 | export { default as Loading } from './Loading'; 3 | export { default as Profile } from './app/Profile'; 4 | export { default as Settings } from './app/Settings'; 5 | export { default as SignIn } from './auth/SignIn'; 6 | export { default as SignUp } from './auth/SignUp'; 7 | export { default as ForgetPassword } from './auth/ForgetPassword'; 8 | export { default as ConfirmPassword } from './auth/ConfirmPassword'; 9 | export { default as ConfirmSignUp } from './auth/ConfirmSignUp'; 10 | -------------------------------------------------------------------------------- /src/utils/options.js: -------------------------------------------------------------------------------- 1 | import { colors } from '../shared'; 2 | 3 | export const tabOptions = { 4 | tabBarPosition: 'bottom', 5 | tabBarOptions: { 6 | indicatorStyle: { 7 | height: 0, 8 | }, 9 | activeTintColor: colors.blue, 10 | inactiveTintColor: colors.dark, 11 | style: { 12 | backgroundColor: colors.bright, 13 | }, 14 | }, 15 | }; 16 | 17 | export const stackOptions = title => ({ 18 | title, 19 | headerTintColor: colors.dark, 20 | headerBackTitle: 'Back', 21 | headerStyle: { 22 | backgroundColor: colors.creamy, 23 | borderBottomWidth: 0, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /src/screens/Loading.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, { useEffect, useCallback } from 'react'; 3 | import { AsyncStorage } from 'react-native'; 4 | 5 | import { routes } from '../shared/constants'; 6 | import Spinner from '../components/Spinner'; 7 | 8 | const Loading = ({ navigation }) => { 9 | const isUserLoggedIn = useCallback(async () => { 10 | const userToken = await AsyncStorage.getItem('userToken'); 11 | navigation.navigate(userToken ? routes.app : routes.auth); 12 | }, [navigation]); 13 | 14 | useEffect(() => { 15 | isUserLoggedIn(); 16 | }, [isUserLoggedIn]); 17 | 18 | return ; 19 | }; 20 | 21 | export default Loading; 22 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "ReactNativeAuth", 4 | "slug": "ReactNativeAuth", 5 | "privacy": "public", 6 | "sdkVersion": "36.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "web" 11 | ], 12 | "version": "1.0.0", 13 | "orientation": "portrait", 14 | "icon": "./assets/icon.png", 15 | "splash": { 16 | "image": "./assets/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "updates": { 21 | "fallbackToCacheTimeout": 0 22 | }, 23 | "assetBundlePatterns": [ 24 | "**/*" 25 | ], 26 | "ios": { 27 | "supportsTablet": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/screens/auth/ForgetPassword.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import { 5 | Container, 6 | Centered, 7 | TextInput, 8 | Button, 9 | Text, 10 | colors, 11 | placeholders, 12 | buttons, 13 | routes, 14 | } from '../../shared'; 15 | 16 | const ForgetPassword = ({ navigation: { navigate } }) => ( 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | ); 28 | 29 | export default ForgetPassword; 30 | -------------------------------------------------------------------------------- /src/screens/auth/ConfirmPassword.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import { 5 | Container, 6 | Centered, 7 | TextInput, 8 | Button, 9 | Text, 10 | colors, 11 | placeholders, 12 | buttons, 13 | } from '../../shared'; 14 | 15 | const ConfirmPassword = () => ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | ); 28 | 29 | export default ConfirmPassword; 30 | -------------------------------------------------------------------------------- /src/screens/auth/ConfirmSignUp.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import { 5 | Container, 6 | TextInput, 7 | Text, 8 | Button, 9 | Centered, 10 | } from '../../shared/styledComponents'; 11 | import { colors, placeholders, routes, buttons } from '../../shared/constants'; 12 | 13 | const ConfirmSignUp = ({ navigation: { navigate } }) => ( 14 | 15 | 16 | 17 | 18 | 19 | 22 | 25 | 26 | 27 | ); 28 | 29 | export default ConfirmSignUp; 30 | -------------------------------------------------------------------------------- /src/stacks/AuthStack.js: -------------------------------------------------------------------------------- 1 | import { createStackNavigator } from 'react-navigation-stack'; 2 | 3 | import { 4 | SignIn, 5 | SignUp, 6 | ForgetPassword, 7 | ConfirmPassword, 8 | ConfirmSignUp, 9 | } from '../screens'; 10 | import { stackOptions } from '../utils'; 11 | import { titles } from '../shared'; 12 | 13 | const AuthStack = createStackNavigator({ 14 | SignIn: { 15 | screen: SignIn, 16 | navigationOptions: stackOptions(titles.login), 17 | }, 18 | SignUp: { 19 | screen: SignUp, 20 | navigationOptions: stackOptions(titles.register), 21 | }, 22 | ConfirmSignUp: { 23 | screen: ConfirmSignUp, 24 | navigationOptions: stackOptions(titles.confirm), 25 | }, 26 | ForgetPassword: { 27 | screen: ForgetPassword, 28 | navigationOptions: stackOptions(titles.forget), 29 | }, 30 | ConfirmPassword: { 31 | screen: ConfirmPassword, 32 | navigationOptions: stackOptions(titles.newPassword), 33 | }, 34 | }); 35 | 36 | export default AuthStack; 37 | -------------------------------------------------------------------------------- /src/screens/auth/SignUp.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import { 5 | Container, 6 | Centered, 7 | TextInput, 8 | Button, 9 | Text, 10 | messages, 11 | colors, 12 | placeholders, 13 | routes, 14 | buttons, 15 | } from '../../shared'; 16 | 17 | const SignUp = ({ navigation: { navigate } }) => ( 18 | 19 | 20 | 21 | 22 | 26 | 27 | navigate(routes.login)}>{messages.already} 28 | 29 | 30 | 33 | 34 | 35 | ); 36 | 37 | export default SignUp; 38 | -------------------------------------------------------------------------------- /src/screens/app/Settings.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import { AsyncStorage } from 'react-native'; 4 | 5 | import { 6 | Container, 7 | Button, 8 | Text, 9 | TextInput, 10 | Centered, 11 | } from '../../shared/styledComponents'; 12 | import { routes, colors, buttons, placeholders } from '../../shared/constants'; 13 | 14 | const Settings = ({ navigation }) => { 15 | const signOutAsync = async () => { 16 | await AsyncStorage.clear(); 17 | navigation.navigate(routes.loader); 18 | }; 19 | return ( 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | 34 | ); 35 | }; 36 | export default Settings; 37 | -------------------------------------------------------------------------------- /src/shared/constants.js: -------------------------------------------------------------------------------- 1 | export const titles = { 2 | login: 'Sign in', 3 | register: 'Sign up', 4 | confirm: 'Confirm user', 5 | forget: 'Forget password', 6 | newPassword: 'New password', 7 | }; 8 | 9 | export const messages = { 10 | forget: 'Forget password ?', 11 | register: 'Create an account', 12 | already: 'Already a member ?', 13 | }; 14 | 15 | export const colors = { 16 | bright: '#fafafa', 17 | dark: '#33395F', 18 | blue: '#1760E3', 19 | creamy: '#e6ebf0', 20 | grey: '#b5b1b3', 21 | }; 22 | 23 | export const buttons = { 24 | login: 'Sign in', 25 | logout: 'Sing out', 26 | code: 'Send code', 27 | confirm: 'Confirm', 28 | send: 'Send code', 29 | resend: 'Resend code', 30 | }; 31 | 32 | export const placeholders = { 33 | username: 'Username', 34 | password: 'Password', 35 | code: 'Confirmation code', 36 | oldPassword: 'Old password', 37 | newPassword: 'New password', 38 | email: 'Email', 39 | phone: 'Phone', 40 | }; 41 | 42 | export const routes = { 43 | auth: 'Auth', 44 | app: 'App', 45 | loader: 'Loading', 46 | profile: 'Profile', 47 | register: 'SignUp', 48 | confirm: 'ConfirmSignUp', 49 | login: 'SignIn', 50 | forget: 'ForgetPassword', 51 | password: 'ConfirmPassword', 52 | }; 53 | -------------------------------------------------------------------------------- /src/screens/auth/SignIn.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import { AsyncStorage } from 'react-native'; 4 | 5 | import { 6 | Container, 7 | Centered, 8 | TextInput, 9 | Button, 10 | Text, 11 | messages, 12 | colors, 13 | placeholders, 14 | routes, 15 | buttons, 16 | } from '../../shared'; 17 | 18 | const SignIn = ({ navigation: { navigate } }) => { 19 | // TODO: move this to utils 20 | const signInAsync = async () => { 21 | await AsyncStorage.setItem('userToken', 'abc'); 22 | navigate(routes.app); 23 | }; 24 | 25 | return ( 26 | 27 | 28 | 29 | 30 | navigate(routes.forget)}> 31 | {messages.forget} 32 | 33 | 34 | 35 | navigate(routes.register)}> 36 | {messages.register} 37 | 38 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default SignIn; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "lint": "eslint .", 10 | "lint:fix": "eslint . --fix" 11 | }, 12 | "dependencies": { 13 | "expo": "~36.0.0", 14 | "react": "~16.9.0", 15 | "react-dom": "~16.9.0", 16 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz", 17 | "react-native-gesture-handler": "^1.5.2", 18 | "react-native-reanimated": "^1.4.0", 19 | "react-native-web": "~0.11.7", 20 | "react-navigation": "^4.0.10", 21 | "react-navigation-drawer": "^2.3.3", 22 | "react-navigation-stack": "^1.10.3", 23 | "react-navigation-tabs": "^2.6.2", 24 | "styled-components": "^4.4.1" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.0.0", 28 | "babel-eslint": "^9.0.0", 29 | "babel-preset-expo": "~8.0.0", 30 | "eslint": "^5.16.0", 31 | "eslint-config-airbnb": "^17.1.1", 32 | "eslint-config-prettier": "^4.3.0", 33 | "eslint-config-wesbos": "0.0.19", 34 | "eslint-plugin-html": "^5.0.5", 35 | "eslint-plugin-import": "^2.19.1", 36 | "eslint-plugin-jsx-a11y": "^6.2.3", 37 | "eslint-plugin-prettier": "^3.1.2", 38 | "eslint-plugin-react": "^7.17.0", 39 | "eslint-plugin-react-hooks": "^1.7.0", 40 | "prettier": "^1.19.1" 41 | }, 42 | "private": true 43 | } 44 | -------------------------------------------------------------------------------- /src/shared/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { Dimensions } from 'react-native'; 3 | import { colors } from './constants'; 4 | 5 | const { width } = Dimensions.get('screen'); 6 | 7 | export const Container = styled.SafeAreaView` 8 | flex: 1; 9 | align-items: center; 10 | justify-content: space-between; 11 | background-color: ${colors.creamy}; 12 | `; 13 | 14 | export const Centered = styled.View` 15 | align-items: center; 16 | justify-content: center; 17 | background-color: ${colors.creamy}; 18 | `; 19 | 20 | export const FlexCentered = styled(Centered)` 21 | flex: 1; 22 | `; 23 | 24 | export const TextInput = styled.TextInput.attrs(props => ({ 25 | autoCorrect: false, 26 | autoCapitalize: 'none', 27 | returnKeyType: 'done', 28 | placeholder: props.placeholder, 29 | secureTextEntry: props.password, 30 | }))` 31 | font-size: 18px; 32 | border-color: ${colors.bright}; 33 | border-width: 0.5px; 34 | border-radius: 30px; 35 | background-color: ${colors.bright}; 36 | height: 55px; 37 | width: ${width * 0.9}px; 38 | padding: 16px; 39 | padding-left: 24px; 40 | margin-bottom: 16px; 41 | margin-top: 16px; 42 | `; 43 | 44 | export const Button = styled.TouchableOpacity` 45 | background-color: ${colors.blue}; 46 | width: ${width * 0.9}px; 47 | height: 55px; 48 | padding: 16px; 49 | border-radius: 30px; 50 | align-items: center; 51 | justify-content: center; 52 | margin-bottom: 16px; 53 | margin-top: 16px; 54 | `; 55 | 56 | export const Text = styled.Text` 57 | font-size: 18px; 58 | font-weight: 500; 59 | color: ${props => (props.color ? props.color : colors.dark)}; 60 | `; 61 | --------------------------------------------------------------------------------