├── ForgetPassword.js ├── Login.js ├── Navbar.js ├── Register.js ├── ResetPassword.js ├── RestrictedPage.js ├── Server.js ├── Style.js ├── cli.js ├── icons ├── ic_action_more.png ├── ic_action_more@2x.png ├── ic_action_more@3x.png ├── ic_action_refresh.png ├── ic_action_refresh@2x.png ├── ic_action_refresh@3x.png ├── ic_action_search.png ├── ic_action_search@2x.png └── ic_action_search@3x.png ├── index.js ├── package.json ├── preview.gif └── readme.md /ForgetPassword.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | ScrollView, 6 | Text, 7 | TouchableOpacity, 8 | TouchableHighlight, 9 | View, 10 | TextInput, 11 | ToastAndroid 12 | } from 'react-native'; 13 | 14 | import api from './Server'; 15 | import styles from './Style'; 16 | 17 | export default class extends Component { 18 | constructor(props) { 19 | super(props); 20 | 21 | this.state = { 22 | data: { 23 | phone: undefined, 24 | role: 2 25 | }, 26 | loading: false 27 | }; 28 | } 29 | 30 | render() { 31 | let fields = [ 32 | {ref: 'phone', placeholder: 'Phone Number', keyboardType: 'numeric', secureTextEntry: false, style: [styles.inputText]}, 33 | ]; 34 | 35 | return( 36 | 37 | 38 | {'FORGET PASSWORD'} 39 | 40 | 41 | this.onFocus({...fields[0]})} onChangeText={(text) => this.state.data.phone = text} /> 42 | 43 | this.gotoRoute('reset')}> 44 | {'Reset password?'} 45 | 46 | this.onSubmit(fields)}> 47 | {this.state.loading ? 'Please Wait . . .' : 'Submit'} 48 | 49 | 50 | ); 51 | } 52 | 53 | onFocus(argument) { 54 | setTimeout(() => { 55 | let scrollResponder = this.refs.forgetForm.getScrollResponder(); 56 | scrollResponder.scrollResponderScrollNativeHandleToKeyboard( 57 | React.findNodeHandle(this.refs[argument.ref]), 110, true 58 | ); 59 | }, 50); 60 | } 61 | 62 | onSubmit() { 63 | if (this.state.loading) { 64 | ToastAndroid.show('Please Wait . . .', ToastAndroid.SHORT); 65 | return null; 66 | } 67 | 68 | let valid = true; 69 | 70 | Object.keys(this.state.data).map((val, key) => { 71 | if ([null, undefined, 'null', 'undefined', ''].indexOf(this.state.data[val]) > -1) valid = false; 72 | }); 73 | 74 | if (!valid) return null; 75 | 76 | this.setState({loading: true}); 77 | 78 | api.auth.forget(this.state.data) 79 | .then((response) => { 80 | if (!response.ok) throw Error(response.statusText || response._bodyText); 81 | return response.json(); 82 | }) 83 | .then((responseData) => { 84 | console.log(responseData); 85 | ToastAndroid.show(JSON.stringify(responseData), ToastAndroid.LONG); 86 | }) 87 | .catch((error) => { 88 | console.log(error); 89 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.LONG); 90 | }) 91 | .done(() => { 92 | this.setState({loading: false}); 93 | }); 94 | } 95 | 96 | goBack() { 97 | if (this.props.navigator) { 98 | this.props.navigator.pop(); 99 | } 100 | } 101 | 102 | gotoRoute(name) { 103 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 104 | this.props.navigator.push({name: name}); 105 | } 106 | } 107 | 108 | replaceRoute(name) { 109 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 110 | this.props.navigator.replace({name: name}); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | ScrollView, 6 | Text, 7 | View, 8 | TextInput, 9 | TouchableHighlight, 10 | TouchableOpacity, 11 | ToastAndroid, 12 | AsyncStorage, 13 | Navigator, 14 | Image 15 | } from 'react-native'; 16 | 17 | import styles from './Style'; 18 | import api, {host, key} from './Server'; 19 | import Register from './Register'; 20 | import RestrictedPage from './RestrictedPage'; 21 | 22 | export default class extends Component { 23 | constructor(props) { 24 | super(props); 25 | 26 | this.state = { 27 | data: { 28 | email: undefined, 29 | password: undefined, 30 | role: 0 31 | }, 32 | loading: false, 33 | session: undefined 34 | }; 35 | } 36 | 37 | render() { 38 | let fields = [ 39 | {ref: 'email', placeholder: 'Email', keyboardType: 'email-address', secureTextEntry: false, style: [styles.inputText]}, 40 | {ref: 'password', placeholder: 'Password', keyboardType: 'default', secureTextEntry: true, style: [styles.inputText]}, 41 | ]; 42 | 43 | return ( 44 | 45 | 46 | {'LOGIN'} 47 | 48 | 49 | this.onFocus({...fields[0]})} onChangeText={(text) => this.state.data.email = text} /> 50 | 51 | 52 | this.onFocus({...fields[1]})} onChangeText={(text) => this.state.data.password = text} /> 53 | 54 | this.gotoRoute('forget')}> 55 | {'Forget password?'} 56 | 57 | this.onSubmit()}> 58 | {this.state.loading ? 'Please Wait . . .' : 'Submit'} 59 | 60 | 61 | {'Doesn\'t have an account? '} 62 | this.goBack()}> 63 | {'Register'} 64 | 65 | 66 | 67 | ); 68 | } 69 | 70 | onFocus(argument) { 71 | setTimeout(() => { 72 | let scrollResponder = this.refs.loginFormC.getScrollResponder(); 73 | scrollResponder.scrollResponderScrollNativeHandleToKeyboard( 74 | React.findNodeHandle(this.refs[argument.ref]), 110, true 75 | ); 76 | }, 50); 77 | } 78 | 79 | onSubmit() { 80 | if (this.state.loading) { 81 | ToastAndroid.show('Please Wait . . .', ToastAndroid.SHORT); 82 | return; 83 | } 84 | 85 | let valid = true; 86 | 87 | Object.keys(this.state.data).map((val, key) => { 88 | if ([null, undefined, 'null', 'undefined', ''].indexOf(this.state.data[val]) > -1) valid = false; 89 | }); 90 | 91 | if (!valid) return null; 92 | 93 | this.setState({loading: true}); 94 | 95 | api.auth.login(this.state.data) 96 | .then((response) => { 97 | if (!response.ok) throw Error(response.statusText || response._bodyText); 98 | return response.json(); 99 | }) 100 | .then((responseData) => { 101 | console.log(responseData); 102 | ToastAndroid.show(JSON.stringify(responseData), ToastAndroid.LONG); 103 | this.onSuccess(responseData).done(() => this.replaceRoute('restricted')); 104 | }) 105 | .catch((error) => { 106 | console.log(error); 107 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.LONG); 108 | }) 109 | .done(() => { 110 | this.setState({loading: false}); 111 | }); 112 | } 113 | 114 | async onSuccess(data) { 115 | try { 116 | await AsyncStorage.setItem(key, JSON.stringify(data)); 117 | } catch (error) { 118 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.LONG); 119 | } 120 | } 121 | 122 | goBack() { 123 | if (this.props.navigator) { 124 | this.props.navigator.pop(); 125 | } 126 | } 127 | 128 | gotoRoute(name) { 129 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 130 | this.props.navigator.push({name: name}); 131 | } 132 | } 133 | 134 | replaceRoute(name) { 135 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 136 | this.props.navigator.replace({name: name}); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Navbar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | StyleSheet, 6 | ToastAndroid, 7 | View, 8 | Text, 9 | TextInput, 10 | PropTypes, 11 | AsyncStorage 12 | } from 'react-native'; 13 | 14 | import ToolbarAndroid from 'ToolbarAndroid'; 15 | 16 | import {key} from './Server'; 17 | 18 | export default class extends Component { 19 | static propTypes = { 20 | onSearch: PropTypes.func, 21 | onRefresh: PropTypes.func, 22 | onLogout: PropTypes.func 23 | }; 24 | 25 | constructor(props) { 26 | super(props); 27 | 28 | this.state = { 29 | search: false, 30 | query: undefined, 31 | actions: actions, 32 | session: undefined 33 | }; 34 | } 35 | 36 | componentWillMount() { 37 | let temp = []; 38 | 39 | this.state.actions.map((val, key) => { 40 | if (!val.auth) { 41 | temp.push(val); 42 | } 43 | }); 44 | 45 | if (this.state.actions.length > temp.length) { 46 | AsyncStorage.getItem(key) 47 | .then((value) => { 48 | if (value !== null) { 49 | this.state.session = value; 50 | } else { 51 | this.state.actions = temp; 52 | } 53 | }) 54 | .catch((error) => { 55 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.SHORT); 56 | }) 57 | .done(); 58 | } 59 | } 60 | 61 | render() { 62 | return ( 63 | 64 | 69 | {this.state.search ? this.renderSearch() : this.renderTitle()} 70 | 71 | 72 | ); 73 | } 74 | 75 | renderTitle() { 76 | return ( 77 | 78 | {this.props.title} 79 | 80 | ); 81 | } 82 | 83 | renderSearch() { 84 | return ( 85 | 86 | this.state.query = text} 93 | onSubmitEditing={() => this.onSearch()} 94 | /> 95 | 96 | ); 97 | } 98 | 99 | onActionSelected(position) { 100 | switch (position) { 101 | case 0: this.onSearch(); break; 102 | case 1: this.onRefresh(); break; 103 | case 5: this.onLogout(); break; 104 | default: ToastAndroid.show(`${actions[position].title} selected.`, ToastAndroid.SHORT); 105 | } 106 | } 107 | 108 | onSearch() { 109 | this.props.onSearch && this.props.onSearch(); 110 | 111 | if (this.state.query) ToastAndroid.show(`${this.state.query} not found`, ToastAndroid.SHORT); 112 | this.setState({search: !this.state.search, query: undefined}); 113 | } 114 | 115 | onRefresh() { 116 | this.props.onRefresh && this.props.onRefresh(); 117 | } 118 | 119 | onLogout() { 120 | this.props.onLogout && this.props.onLogout(); 121 | } 122 | } 123 | 124 | const icons = { 125 | more: require('./icons/ic_action_more.png'), 126 | search: require('./icons/ic_action_search.png'), 127 | refresh: require('./icons/ic_action_refresh.png'), 128 | }; 129 | 130 | const actions = [ 131 | {title: 'Search', icon: icons.search, show: 'always'}, 132 | {title: 'Refresh', icon: icons.refresh, show: 'ifRoom'}, 133 | {title: 'Single Sign On'}, 134 | {title: 'Notifications'}, 135 | {title: 'Profile', auth: true}, 136 | {title: 'Logout', auth: true}, 137 | ]; 138 | 139 | const styles = StyleSheet.create({ 140 | container: { 141 | flex: 1, 142 | alignItems: 'stretch', 143 | }, 144 | toolbar: { 145 | height: 60, 146 | backgroundColor: '#00796B' 147 | }, 148 | titleContainer: { 149 | flex: 1, 150 | justifyContent: 'center', 151 | backgroundColor: 'transparent', 152 | alignItems: 'center', 153 | }, 154 | title: { 155 | alignSelf: 'flex-start', 156 | backgroundColor: 'transparent', 157 | fontSize: 20, 158 | color: '#ffffff' 159 | } 160 | }); 161 | -------------------------------------------------------------------------------- /Register.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | ScrollView, 6 | Text, 7 | TouchableOpacity, 8 | TouchableHighlight, 9 | View, 10 | TextInput, 11 | ToastAndroid 12 | } from 'react-native'; 13 | 14 | import api from './Server'; 15 | import styles from './Style'; 16 | 17 | export default class extends Component { 18 | constructor(props) { 19 | super(props); 20 | 21 | this.state = { 22 | data: { 23 | name: undefined, 24 | phone: undefined, 25 | password: undefined, 26 | passwordd: undefined, 27 | role: 'patient' 28 | }, 29 | loading: false, 30 | messages: [] 31 | }; 32 | } 33 | 34 | render() { 35 | let fields = [ 36 | {ref: 'name', placeholder: 'Full Name', keyboardType: 'default', secureTextEntry: false, message: '* Full Name cannot be blank', style: [styles.inputText]}, 37 | {ref: 'phone', placeholder: 'Phone Number', keyboardType: 'numeric', secureTextEntry: false, message: '* Phone Number cannot be blank', style: [styles.inputText]}, 38 | {ref: 'password', placeholder: 'Password', keyboardType: 'default', secureTextEntry: true, message: '* Password cannot be blank', style: [styles.inputText]}, 39 | {ref: 'passwordd', placeholder: 'Password Confirmation', keyboardType: 'default', secureTextEntry: true, message: '* Password Confirmation cannot be blank', style: [styles.inputText]}, 40 | ]; 41 | 42 | return( 43 | 44 | 45 | REGISTER 46 | 47 | 48 | {this.renderMessages()} 49 | 50 | 51 | this.onFocus({...fields[0]})} onChangeText={(text) => this.state.data.name = text} /> 52 | 53 | 54 | this.onFocus({...fields[1]})} onChangeText={(text) => this.state.data.phone = text} /> 55 | 56 | 57 | this.onFocus({...fields[2]})} onChangeText={(text) => this.state.data.password = text} /> 58 | 59 | 60 | this.onFocus({...fields[3]})} onChangeText={(text) => this.state.data.passwordd = text} /> 61 | 62 | this.onSubmit(fields)}> 63 | {this.state.loading ? 'Please Wait . . .' : 'Submit'} 64 | 65 | 66 | {'Have an account? '} 67 | this.gotoRoute('login')}> 68 | {'Login'} 69 | 70 | 71 | 72 | ); 73 | } 74 | 75 | renderMessages() { 76 | if (this.state.messages.length > 0) { 77 | let messages = this.state.messages.map((val, key) => { 78 | if (val.message) return {val.message}; 79 | }); 80 | 81 | return messages; 82 | } 83 | } 84 | 85 | onFocus(argument) { 86 | setTimeout(() => { 87 | let scrollResponder = this.refs.registerFormC.getScrollResponder(); 88 | scrollResponder.scrollResponderScrollNativeHandleToKeyboard( 89 | React.findNodeHandle(this.refs[argument.ref]), 110, true 90 | ); 91 | }, 50); 92 | } 93 | 94 | onSubmit(argument) { 95 | if (this.state.loading) { 96 | ToastAndroid.show('Please Wait . . .', ToastAndroid.SHORT); 97 | return null; 98 | } 99 | 100 | let keys = Object.keys(this.state.data).map((val, key) => { 101 | if ([null, undefined, 'null', 'undefined', ''].indexOf(this.state.data[val]) > -1) return val; 102 | }); 103 | 104 | this.setState({messages: []}); 105 | 106 | argument.map((val, key) => { 107 | if (keys.indexOf(val.ref) > -1) this.setState({messages: this.state.messages.concat(val)}); 108 | }); 109 | 110 | if (this.state.messages.length > 0) return null; 111 | 112 | this.gotoRoute('login'); return; // for demo only 113 | 114 | this.setState({loading: true}); 115 | 116 | api.auth.register(this.state.data) 117 | .then((response) => { 118 | if (!response.ok) throw Error(response.statusText || response._bodyText); 119 | return response.json(); 120 | }) 121 | .then((responseData) => { 122 | console.log(responseData); 123 | ToastAndroid.show(JSON.stringify(responseData), ToastAndroid.LONG); 124 | this.replaceRoute('login'); 125 | }) 126 | .catch((error) => { 127 | console.log(error); 128 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.LONG); 129 | }) 130 | .done(() => { 131 | this.setState({loading: false}); 132 | }); 133 | } 134 | 135 | goBack() { 136 | if (this.props.navigator) { 137 | this.props.navigator.pop(); 138 | } 139 | } 140 | 141 | gotoRoute(name) { 142 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 143 | this.props.navigator.push({name: name}); 144 | } 145 | } 146 | 147 | replaceRoute(name) { 148 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 149 | this.props.navigator.replace({name: name}); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /ResetPassword.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | ScrollView, 6 | Text, 7 | TouchableOpacity, 8 | TouchableHighlight, 9 | View, 10 | TextInput, 11 | ToastAndroid 12 | } from 'react-native'; 13 | 14 | import api from './Server'; 15 | import styles from './Style'; 16 | 17 | export default class extends Component { 18 | constructor(props) { 19 | super(props); 20 | 21 | this.state = { 22 | data: { 23 | phone: undefined, 24 | resetPassCode: undefined, 25 | password: undefined, 26 | passwordd: undefined, 27 | role: 2 28 | }, 29 | loading: false, 30 | messages: [] 31 | }; 32 | } 33 | 34 | render() { 35 | let fields = [ 36 | {ref: 'phone', placeholder: 'Phone Number', keyboardType: 'numeric', secureTextEntry: false, message: '* Phone number cannot be blank', style: [styles.inputText]}, 37 | {ref: 'resetPassCode', placeholder: 'Reset Code', keyboardType: 'default', secureTextEntry: false, message: '* Reset Code cannot be blank', style: [styles.inputText]}, 38 | {ref: 'password', placeholder: 'Password', keyboardType: 'default', secureTextEntry: true, message: '* Password cannot be blank', style: [styles.inputText]}, 39 | {ref: 'passwordd', placeholder: 'Password Confirmation', keyboardType: 'default', secureTextEntry: true, message: '* Password Confirmation cannot be blank', style: [styles.inputText]}, 40 | ]; 41 | 42 | return( 43 | 44 | 45 | {'RESET PASSWORD'} 46 | 47 | 48 | {this.renderMessages()} 49 | 50 | 51 | this.onFocus({...fields[0]})} onChangeText={(text) => this.state.data.phone = text} /> 52 | 53 | 54 | this.onFocus({...fields[1]})} onChangeText={(text) => this.state.data.resetPassCode = text} /> 55 | 56 | 57 | this.onFocus({...fields[2]})} onChangeText={(text) => this.state.data.password = text} /> 58 | 59 | 60 | this.onFocus({...fields[3]})} onChangeText={(text) => this.state.data.passwordd = text} /> 61 | 62 | this.onSubmit(fields)}> 63 | {this.state.loading ? 'Please Wait . . .' : 'Submit'} 64 | 65 | 66 | ); 67 | } 68 | 69 | renderMessages() { 70 | if (this.state.messages.length > 0) { 71 | let messages = this.state.messages.map((val, key) => { 72 | if (val.message) return {val.message}; 73 | }); 74 | 75 | return messages; 76 | } 77 | } 78 | 79 | onFocus(argument) { 80 | setTimeout(() => { 81 | let scrollResponder = this.refs.resetForm.getScrollResponder(); 82 | scrollResponder.scrollResponderScrollNativeHandleToKeyboard( 83 | React.findNodeHandle(this.refs[argument.ref]), 110, true 84 | ); 85 | }, 50); 86 | } 87 | 88 | onSubmit(argument) { 89 | if (this.state.loading) { 90 | ToastAndroid.show('Please Wait . . .', ToastAndroid.SHORT); 91 | return null; 92 | } 93 | 94 | let keys = Object.keys(this.state.data).map((val, key) => { 95 | if ([null, undefined, 'null', 'undefined', ''].indexOf(this.state.data[val]) > -1) return val; 96 | }); 97 | 98 | this.setState({messages: []}); 99 | 100 | argument.map((val, key) => { 101 | if (keys.indexOf(val.ref) > -1) this.setState({messages: this.state.messages.concat(val)}); 102 | }); 103 | 104 | if (this.state.messages.length > 0) return null; 105 | 106 | this.setState({loading: true}); 107 | 108 | api.auth.reset(this.state.data) 109 | .then((response) => { 110 | if (!response.ok) throw Error(response.statusText || response._bodyText); 111 | return response.json(); 112 | }) 113 | .then((responseData) => { 114 | console.log(responseData); 115 | ToastAndroid.show(JSON.stringify(responseData), ToastAndroid.LONG); 116 | this.replaceRoute('login'); 117 | }) 118 | .catch((error) => { 119 | console.log(error); 120 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.LONG); 121 | }) 122 | .done(() => { 123 | this.setState({loading: false}); 124 | }); 125 | } 126 | 127 | goBack() { 128 | if (this.props.navigator) { 129 | this.props.navigator.pop(); 130 | } 131 | } 132 | 133 | gotoRoute(name) { 134 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 135 | this.props.navigator.push({name: name}); 136 | } 137 | } 138 | 139 | replaceRoute(name) { 140 | if (this.props.navigator && this.props.navigator.getCurrentRoutes()[this.props.navigator.getCurrentRoutes().length-1].name != name) { 141 | this.props.navigator.replace({name: name}); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /RestrictedPage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | View, 6 | Text, 7 | AsyncStorage, 8 | ToastAndroid, 9 | ScrollView, 10 | ProgressBarAndroid 11 | } from 'react-native'; 12 | 13 | import Login from './Login'; 14 | import Navbar from './Navbar'; 15 | import styles from './Style'; 16 | import {key} from './Server'; 17 | 18 | export default class extends Component { 19 | constructor(props) { 20 | super(props); 21 | 22 | this.state = { 23 | session: undefined, 24 | loading: true 25 | }; 26 | } 27 | 28 | componentWillMount() { 29 | this.loadSession().done(() => this.setState({loading: false})); 30 | } 31 | 32 | async loadSession() { 33 | try { 34 | let value = await AsyncStorage.getItem(key); 35 | 36 | if (value !== null) this.setState({session: JSON.parse(value)}); 37 | } catch (error) { 38 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.LONG); 39 | } 40 | } 41 | 42 | render() { 43 | if (this.state.loading) return this.renderLoading(); 44 | if (!this.state.session) return this.renderLogin(); 45 | 46 | return this.renderScene(); 47 | } 48 | 49 | renderScene() { 50 | return ( 51 | 52 | this.onLogout().done(() => this.setState({session: undefined}))} 56 | /> 57 | 58 | 59 | {JSON.stringify(this.state.session)} 60 | 61 | 62 | 63 | ); 64 | } 65 | 66 | renderLogin() { 67 | return ; 68 | } 69 | 70 | renderLoading() { 71 | return ; 72 | } 73 | 74 | async onLogout() { 75 | try { 76 | await AsyncStorage.removeItem(key); 77 | ToastAndroid.show('Logout successfully!', ToastAndroid.SHORT); 78 | } catch (error) { 79 | ToastAndroid.show(String(error).replace('Error: ',''), ToastAndroid.SHORT); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const key = '@lussatech:session'; 4 | export const host = 'http://aprs.lussa.net'; 5 | export default { 6 | auth: { 7 | login: function (data) { 8 | let url = `${host}/auth/login`, 9 | opt = { 10 | method: 'post', 11 | headers: { 12 | 'Accept': 'application/json', 13 | 'Content-Type': 'application/json' 14 | }, 15 | body: JSON.stringify(data) 16 | }; 17 | 18 | return fetch(url, opt); 19 | }, 20 | register: function (data) { 21 | let url = `${host}/auth/register`, 22 | opt = { 23 | method: 'post', 24 | headers: { 25 | 'Accept': 'application/json', 26 | 'Content-Type': 'application/json' 27 | }, 28 | body: JSON.stringify(data) 29 | }; 30 | 31 | return fetch(url, opt); 32 | }, 33 | forget: function (data) { 34 | let url = `${host}/auth/forget`, 35 | opt = { 36 | method: 'post', 37 | headers: { 38 | 'Accept': 'application/json', 39 | 'Content-Type': 'application/json' 40 | }, 41 | body: JSON.stringify(data) 42 | }; 43 | 44 | return fetch(url, opt); 45 | }, 46 | reset: function (data) { 47 | let url = `${host}/auth/reset`, 48 | opt = { 49 | method: 'post', 50 | headers: { 51 | 'Accept': 'application/json', 52 | 'Content-Type': 'application/json' 53 | }, 54 | body: JSON.stringify(data) 55 | }; 56 | 57 | return fetch(url, opt); 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /Style.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import {StyleSheet} from 'react-native'; 4 | 5 | export default StyleSheet.create({ 6 | titleContainer: { 7 | backgroundColor: '#00BFA5', 8 | justifyContent: 'center', 9 | alignItems: 'center', 10 | padding: 10, 11 | height: 100 12 | }, 13 | title: { 14 | color: '#FFFFFF', 15 | fontWeight: 'bold', 16 | fontSize: 27 17 | }, 18 | button: { 19 | backgroundColor: '#26a69a', 20 | padding: 15, 21 | marginTop: 20, 22 | justifyContent: 'center', 23 | alignSelf: 'stretch' 24 | }, 25 | buttonDisabled: { 26 | backgroundColor: '#2bbbad', 27 | padding: 15, 28 | marginTop: 20, 29 | justifyContent: 'center', 30 | alignSelf: 'stretch' 31 | }, 32 | buttonText: { 33 | fontSize: 15, 34 | color: 'white', 35 | alignSelf: 'center' 36 | }, 37 | container: { 38 | flex: 1, 39 | alignItems: 'stretch', 40 | }, 41 | welcome: { 42 | fontSize: 20, 43 | textAlign: 'center', 44 | margin: 10, 45 | }, 46 | instructions: { 47 | textAlign: 'center', 48 | color: '#333333', 49 | marginBottom: 5, 50 | }, 51 | toolbar: { 52 | height: 60, 53 | backgroundColor: '#D6D2D2' 54 | }, 55 | message: { 56 | color: 'red', 57 | marginLeft: 5 58 | }, 59 | inputContainer: { 60 | borderBottomWidth: 1, 61 | borderBottomColor: 'transparent' 62 | }, 63 | inputText: { 64 | backgroundColor: '#FFFFFF', 65 | height: 60 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs') 4 | , path = require('path') 5 | , source = path.resolve('node_modules', 'react-native-base-authentication') 6 | , target = path.resolve(process.cwd(), 'lib') 7 | , ignore = ['cli.js', 'package.json', 'readme.md', 'preview.gif']; 8 | 9 | function run() { 10 | try { 11 | var packages = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf8')) || undefined; 12 | } catch (e) { 13 | console.error('package.json couldn\'t be found, maybe `%s` is not the root of project directory', process.cwd()); 14 | process.exit(1); 15 | } finally { 16 | var hasReactNative = packages.dependencies.hasOwnProperty('react-native'); 17 | 18 | if (hasReactNative) { 19 | writing(source, target); 20 | } else { 21 | console.error('react-native dependencies couldn\'t be found, maybe `%s` is not the root of react-native project directory', process.cwd()); 22 | process.exit(1); 23 | } 24 | } 25 | } 26 | 27 | function writing(source, target) { 28 | if (!fs.existsSync(target)) { 29 | fs.mkdirSync(target); 30 | } 31 | 32 | copyFolderSync(source, target); 33 | } 34 | 35 | function copyFileSync(source, target) { 36 | var targetFile = target; 37 | 38 | if (ignore.indexOf(path.basename(source)) > -1) return; 39 | 40 | if(fs.existsSync(target)) { 41 | if(fs.lstatSync(target).isDirectory()) { 42 | targetFile = path.join(target, path.basename(source)); 43 | } 44 | } 45 | 46 | fs.writeFileSync(targetFile, fs.readFileSync(source)); 47 | } 48 | 49 | function copyFolderSync(source, target) { 50 | var files = []; 51 | var targetFolder = path.join(target, path.basename(source)); 52 | 53 | if (!fs.existsSync(targetFolder)) { 54 | fs.mkdirSync(targetFolder); 55 | } 56 | 57 | if(fs.lstatSync(source).isDirectory()) { 58 | files = fs.readdirSync(source); 59 | files.forEach(function (file) { 60 | var curSource = path.join(source, file); 61 | 62 | if (fs.lstatSync(curSource).isDirectory()) { 63 | copyFolderSync(curSource, targetFolder); 64 | } else { 65 | copyFileSync(curSource, targetFolder); 66 | } 67 | }); 68 | } 69 | } 70 | 71 | module.exports = { 72 | run: run 73 | }; 74 | -------------------------------------------------------------------------------- /icons/ic_action_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_more.png -------------------------------------------------------------------------------- /icons/ic_action_more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_more@2x.png -------------------------------------------------------------------------------- /icons/ic_action_more@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_more@3x.png -------------------------------------------------------------------------------- /icons/ic_action_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_refresh.png -------------------------------------------------------------------------------- /icons/ic_action_refresh@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_refresh@2x.png -------------------------------------------------------------------------------- /icons/ic_action_refresh@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_refresh@3x.png -------------------------------------------------------------------------------- /icons/ic_action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_search.png -------------------------------------------------------------------------------- /icons/ic_action_search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_search@2x.png -------------------------------------------------------------------------------- /icons/ic_action_search@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/icons/ic_action_search@3x.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | AppRegistry, 5 | Component, 6 | StyleSheet, 7 | Text, 8 | View, 9 | Navigator, 10 | TouchableHighlight, 11 | ScrollView, 12 | ToastAndroid, 13 | BackAndroid 14 | } from 'react-native'; 15 | 16 | import ForgetPassword from './ForgetPassword'; 17 | import Login from './Login'; 18 | import Navbar from './Navbar'; 19 | import Register from './Register'; 20 | import ResetPassword from './ResetPassword'; 21 | import RestrictedPage from './RestrictedPage'; 22 | import Server, {host as Host, key as Key} from './Server'; 23 | import Style from './Style'; 24 | 25 | export {ForgetPassword, Login, Navbar, Register, ResetPassword, RestrictedPage, Server, Host, Key, Style}; 26 | 27 | export default class extends Component { 28 | constructor(props) { 29 | super(props); 30 | } 31 | 32 | render() { 33 | return ( 34 | { 38 | return route.sceneConfig ? route.sceneConfig : Navigator.SceneConfigs.HorizontalSwipeJump; 39 | }} 40 | /> 41 | ); 42 | } 43 | 44 | renderScene(route, navigator) { 45 | _navigator = navigator; 46 | switch (route.name) { 47 | case 'login': 48 | return 49 | break; 50 | case 'forget': 51 | return 52 | break; 53 | case 'reset': 54 | return 55 | break; 56 | case 'restricted': 57 | return 58 | break; 59 | default: 60 | return 61 | } 62 | } 63 | } 64 | 65 | let _navigator; 66 | 67 | BackAndroid.addEventListener('hardwareBackPress', () => { 68 | if (_navigator.getCurrentRoutes().length === 1 ) { 69 | return false; 70 | } 71 | _navigator.pop(); 72 | return true; 73 | }); 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-base-authentication", 3 | "version": "1.0.3", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Lussa Teknologi", 9 | "license": "ISC", 10 | "description": "an authentication, policy and session management template for react native to connect with your server API", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/lussatech/react-native-base-authentication.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/lussatech/react-native-base-authentication/issues" 17 | }, 18 | "homepage": "https://github.com/lussatech/react-native-base-authentication" 19 | } 20 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lussatech/react-native-base-authentication/e1c07024be98fbd0412a1c0cdafb8c40dc78bff9/preview.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![react-native-base-authentication](https://raw.githubusercontent.com/lussatech/react-native-base-authentication/master/preview.gif) 2 | 3 | ### Installation 4 | npm i react-native-base-authentication 5 | 6 | ### Generate Files 7 | Before generate library files to your react-native-project, make sure that `lussatech-cli` is installed globally in your machine, otherwise use this command to install it: 8 | 9 | npm i lussatech-cli -g 10 | 11 | If `lussatech-cli` have been installed, change directory to your react-native-project and run this command: 12 | 13 | lussatech generate react-native-base-authentication 14 | 15 | then the library files will be added automatically inside your react-native-project, e.g. 16 | 17 | react-native-project 18 | |_ ... 19 | |_ lib 20 | |_ react-native-base-authentication 21 | |_ ... 22 | |_ index.js 23 | |_ ... 24 | 25 | ### Usage 26 | ```javascript 27 | ... 28 | import BaseAuth, { // sample app 29 | /* available components */ 30 | Navbar, // sample navigation bar 31 | Login, // sample login view 32 | Register, // sample register view 33 | ForgetPassword, // sample forget password view 34 | ResetPassword, // sample reset password view 35 | RestrictedPage, // sample restricted view 36 | /* available constants */ 37 | Server, // sample api end-point 38 | Host, // sample host for api end-point 39 | Key, // sample key for asynstorage 40 | Style // sample styles 41 | } from './lib/react-native-base-authentication'; 42 | 43 | class Name extends Component { 44 | render() { 45 | return ( 46 | // sample calling component 47 | ); 48 | } 49 | } 50 | ... 51 | ``` 52 | 53 | ###### Manage API end-point 54 | To manage api end-point, update `Server.js` based on your api end-point, e.g. 55 | 56 | ```javascript 57 | # lib/react-native-base-authentication/Server.js 58 | 59 | ... 60 | export const key = '@lussatech:session'; // key for asynstorage 61 | export const host = 'http://example.com'; // host for api end-point 62 | export default { 63 | auth: { 64 | login: function (data) { 65 | let url = `${host}/auth/login`, // api url for login 66 | opt = { // optional second argument 67 | method: 'post', // to customize the HTTP request 68 | headers: { 69 | 'Accept': 'application/json', 70 | 'Content-Type': 'application/json' 71 | }, 72 | body: JSON.stringify(data) 73 | }; 74 | 75 | return fetch(url, opt); 76 | }, 77 | ... 78 | }, 79 | ... 80 | }; 81 | ... 82 | ``` 83 | 84 | ###### Customize navigation bar 85 | To customize navigation bar, update `Navbar.js` based on your need, e.g. 86 | 87 | ```javascript 88 | # lib/react-native-base-authentication/Navbar.js 89 | 90 | ... 91 | export default class extends Component { 92 | /* to validate props value */ 93 | static propTypes = { 94 | onLogout: PropTypes.func, 95 | ... 96 | }; 97 | 98 | constructor(props) { 99 | super(props); 100 | 101 | this.state = { 102 | actions: actions, 103 | ... 104 | }; 105 | } 106 | 107 | componentWillMount() { 108 | let temp = []; 109 | 110 | this.state.actions.map((val, key) => { 111 | if (!val.auth) temp.push(val); 112 | }); 113 | 114 | if (this.state.actions.length > temp.length) { 115 | AsyncStorage.getItem(Key) // check session at asynstorage 116 | .then((value) => { 117 | if (value !== null) this.state.session = value; 118 | else this.state.actions = temp; // if no session, hide auth menu 119 | }) 120 | .catch((error) => { 121 | ... 122 | }) 123 | .done(); 124 | } 125 | } 126 | 127 | /* when a menu is selected */ 128 | onActionSelected(position) { 129 | switch (position) { 130 | ... 131 | case 5: this.onLogout(); break; 132 | default: ToastAndroid.show(`${actions[position].title} selected.`, ToastAndroid.SHORT); 133 | } 134 | } 135 | ... 136 | 137 | /* when selected menu is `Logout` */ 138 | onLogout() { 139 | /* calling onLogout props action if available */ 140 | this.props.onLogout && this.props.onLogout(); 141 | } 142 | ... 143 | } 144 | 145 | /* list of menu */ 146 | const actions = [ 147 | {title: 'Search', icon: icons.search, show: 'always'}, 148 | {title: 'Refresh', icon: icons.refresh, show: 'ifRoom'}, 149 | ... 150 | /* only authenticated user can see this menu */ 151 | {title: 'Profile', auth: true}, 152 | {title: 'Logout', auth: true}, 153 | ]; 154 | ... 155 | ``` 156 | 157 | then include the navigation bar inside your react-native-project, e.g. 158 | 159 | ```javascript 160 | # lib/react-native-base-authentication/RestrictedPage.js 161 | 162 | ... 163 | render() { 164 | return ( 165 | 166 | this.onLogout().done())} /> 167 | 168 | ... 169 | 170 | 171 | ); 172 | } 173 | 174 | async onLogout() { 175 | ... 176 | } 177 | ... 178 | ``` 179 | 180 | #### Customize views 181 | To customize views, update `ForgetPassword.js`, `Login.js`, `Register.js`, `ResetPassword.js` and `RestrictedPage.js` based on your need, e.g. 182 | 183 | ```javascript 184 | # lib/react-native-base-authentication/Login.js 185 | 186 | ... 187 | export default class extends Component { 188 | constructor(props) { 189 | super(props); 190 | 191 | this.state = { 192 | data: { 193 | email: undefined, 194 | password: undefined, 195 | ... 196 | }, 197 | ... 198 | }; 199 | } 200 | 201 | render() { 202 | let fields = [ 203 | {ref: 'email', placeholder: 'Email', keyboardType: 'email-address', secureTextEntry: false, style: [styles.inputText]}, 204 | {ref: 'password', placeholder: 'Password', keyboardType: 'default', secureTextEntry: true, style: [styles.inputText]}, 205 | ... 206 | ]; 207 | 208 | return( 209 | ... 210 | 211 | this.state.data.email = text} /> 212 | 213 | 214 | this.state.data.password = text} /> 215 | 216 | ... 217 | this.onSubmit()}> 218 | {'Submit'} 219 | 220 | ... 221 | ); 222 | } 223 | 224 | onSubmit() { 225 | ... 226 | api.auth.login(this.state.data) // call api url for login 227 | .then((response) => { 228 | if (!response.ok) throw Error(response.statusText || response._bodyText); 229 | return response.json(); 230 | }) 231 | .then((responseData) => { 232 | this.onSuccess(responseData).done(); // store session at asynstorage 233 | }) 234 | .catch((error) => { 235 | ... 236 | }) 237 | .done(() => { 238 | ... 239 | }); 240 | } 241 | 242 | async onSuccess(data) { 243 | try { 244 | await AsyncStorage.setItem(Key, JSON.stringify(data)); // save response data on asynstorage as session 245 | ... 246 | } catch (error) { 247 | ... 248 | } 249 | } 250 | } 251 | ... 252 | ``` 253 | 254 | ```javascript 255 | # lib/react-native-base-authentication/RestrictedPage.js 256 | 257 | ... 258 | export default class extends Component { 259 | ... 260 | componentWillMount() { 261 | this.loadSession().done(); // check session at asynstorage 262 | } 263 | 264 | render() { 265 | if (!this.state.session) return this.renderLogin(); // if no session at asynstorage, render login page 266 | ... 267 | } 268 | 269 | async loadSession() { 270 | try { 271 | let value = await AsyncStorage.getItem(Key); 272 | 273 | if (value !== null) this.setState({session: JSON.parse(value)}); 274 | } catch (error) { 275 | ... 276 | } 277 | } 278 | 279 | async onLogout() { 280 | try { 281 | await AsyncStorage.removeItem(key); 282 | } catch (error) { 283 | ... 284 | } 285 | } 286 | ... 287 | } 288 | ... 289 | ``` 290 | --------------------------------------------------------------------------------