diff --git a/App.js b/App.js
index 9c36aa0..f183f49 100644
--- a/App.js
+++ b/App.js
@@ -1,9 +1,17 @@
import React from 'react';
+import { View } from 'react-native';
+import FlashMessage from 'react-native-flash-message';
import AppNavigator from './src/config/router';
export default class App extends React.Component {
render() {
- return ;
+ return (
+
+
+
+
+ );
}
-};
+}
+
diff --git a/src/config/apiEndpoints.js b/src/config/apiEndpoints.js
index 452c751..4812389 100644
--- a/src/config/apiEndpoints.js
+++ b/src/config/apiEndpoints.js
@@ -3,7 +3,11 @@ const endpoints = {
login: {
method: 'POST',
url: `${baseurl}/auth/signin`
+ },
+ passwordReset: {
+ method: 'POST',
+ url: `${baseurl}/auth/passwordReset`
}
};
-export default endpoints;
\ No newline at end of file
+export default endpoints;
diff --git a/src/config/errors.js b/src/config/errors.js
index e93cf13..fd32e0c 100644
--- a/src/config/errors.js
+++ b/src/config/errors.js
@@ -14,4 +14,4 @@ const errors = {
MISSING_BODY_ATTRIBUTES: 'request/missing-body-attributes'
};
-export default errors;
\ No newline at end of file
+export default errors;
diff --git a/src/config/router.js b/src/config/router.js
index 509d507..de1685f 100644
--- a/src/config/router.js
+++ b/src/config/router.js
@@ -1,12 +1,14 @@
import { createStackNavigator, createAppContainer } from 'react-navigation';
-import WelcomeScreen from '../screens/boarding/WelcomeScreen';
-import LoginScreen from '../screens/boarding/LoginScreen';
+import WelcomeScreen from '../screens/onboaring/WelcomeScreen';
+import LoginScreen from '../screens/auth/LoginScreen';
+import ForgotPasswordScreen from '../screens/auth/ForgotPasswordScreen';
const StackNavigator = createStackNavigator({
welcome: WelcomeScreen,
- login: LoginScreen
+ login: LoginScreen,
+ forgotPassword: ForgotPasswordScreen
});
const AppNavigator = createAppContainer(StackNavigator);
-export default AppNavigator;
\ No newline at end of file
+export default AppNavigator;
diff --git a/src/locales/translations/en.json b/src/locales/translations/en.json
index 3a9b6af..9f09039 100644
--- a/src/locales/translations/en.json
+++ b/src/locales/translations/en.json
@@ -45,7 +45,7 @@
"education_educationSummary_placeholder": "Education Summary",
"education_fieldOfStudy_placeholder": "Field Of Study",
"education_school_placeholder": "College/University",
- "email_labelText": "Email",
+ "email_labelText": "Email Address",
"email_or_tagferid_label": "EMAIL OR TAGFERID",
"email_or_tagferid_placeholder": "username@tagfer.com",
"email_placeholder": "Email Address",
@@ -449,7 +449,7 @@
"title": "Fingerprint Settings"
},
"forgotPasswordScreen": {
- "submitButton": "Request Reset Email",
+ "submitButton": "Request Reset Link",
"title": "Forgot Password"
},
"investment": {
diff --git a/src/screens/auth/ForgotPasswordScreen.js b/src/screens/auth/ForgotPasswordScreen.js
new file mode 100644
index 0000000..fa0174c
--- /dev/null
+++ b/src/screens/auth/ForgotPasswordScreen.js
@@ -0,0 +1,196 @@
+import React from 'react';
+import { Dimensions, Image, TouchableWithoutFeedback, Text, View, Keyboard, KeyboardAvoidingView, Platform } from 'react-native';
+import { Button, FormInput } from 'react-native-elements';
+import { showMessage } from 'react-native-flash-message';
+
+import { FormLabel, Spacer } from '../../components/common';
+import { fetchAuth } from '../../utils/fetch';
+import { strings } from '../../locales/i18n';
+import { getErrorDescription } from '../../utils/errorHandler';
+
+import endpoints from '../../config/apiEndpoints';
+import colors from '../../config/colors.json';
+import logo from '../../../assets/logo-round.png';
+
+export default class ForgotPasswordScreen extends React.Component {
+ static navigationOptions = {
+ header: null
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ email: '',
+ loading: false
+ };
+ }
+
+ /**
+ * Send an API request and display error/sucess message
+ */
+ onRequestResetPress() {
+ Keyboard.dismiss();
+ this.setState({ loading: true });
+ const body = { email: this.state.email };
+ console.log(body);
+
+ fetchAuth(endpoints.passwordReset, body).then(res => {
+ this.setState({ loading: false });
+ console.log(res);
+ if (res.error) {
+ this.showErrorMessage(res.error);
+ } else {
+ this.showSuccessMessage();
+ }
+ }).catch(error => {
+ this.setState({ loading: false });
+ this.showErrorMessage(error);
+ });
+ }
+
+ /**
+ * Simple regex to check if the input is an email.
+ */
+ isNotEmail() {
+ const regex = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/;
+ return !regex.test(this.state.email);
+ }
+
+ /**
+ * Display a flash message at the top of the screen.
+ * @param {String} error one of the error codes in {@see config.error.js}
+ */
+ showErrorMessage(error) {
+ showMessage({
+ message: 'Authentication Error',
+ description: getErrorDescription(error),
+ type: 'danger',
+ icon: 'auto',
+ duration: 4000
+ });
+ }
+
+ /**
+ * Displays a success message
+ */
+ showSuccessMessage() {
+ showMessage({
+ message: 'Password reset link sent',
+ description: 'Please check your email.',
+ type: 'success',
+ icon: 'auto',
+ autoHide: false,
+ onPress: () => this.props.navigation.navigate('login')
+ });
+ }
+
+ renderContent() {
+ return (
+
+
+
+ {/* LOGO */}
+
+
+
+
+ {/* HEADER */}
+
+ Forgot your password?
+ Enter your email address below and we'll get you back on track
+
+
+ {/* TEXT INPUTS */}
+
+
+
+ this.setState({ email })}
+ placeholder={strings('common.contactFields.email_or_tagferid_placeholder')}
+ autoCorrect={false}
+ autoCapitalize='none'
+ inputStyle={styles.inputTextStyle}
+ />
+
+
+ {/* BUTTONS */}
+
+ this.props.navigation.navigate('login')} style={styles.backButtonStyle} >
+ Back to Login
+
+
+
+
+
+
+ );
+ }
+
+ render() {
+ if (Platform.OS === 'android') {
+ return this.renderContent();
+ }
+
+ return (
+
+ {this.renderContent()}
+
+ );
+ }
+}
+
+const SCREEN_WIDTH = Dimensions.get('window').width;
+const styles = {
+ logoContainerStyle: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'center',
+ marginTop: 50,
+ marginBottom: 40
+ },
+ logoStyle: {
+ width: '100%',
+ height: '100%'
+ },
+ headerStyle: {
+ color: colors.headerBlue,
+ fontWeight: 'bold',
+ marginLeft: 20,
+ fontSize: 24
+ },
+ instructions: {
+ color: colors.grey,
+ fontSize: 18,
+ marginLeft: 20,
+ marginRight: 20
+ },
+ buttonStyle: {
+ width: SCREEN_WIDTH * 0.9,
+ marginTop: 15,
+ marginBottom: 30,
+ borderRadius: 5,
+ backgroundColor: colors.buttonBlue
+ },
+ backButtonStyle: {
+ marginLeft: 20,
+ fontSize: 18,
+ color: colors.grey
+ },
+ labelStyle: {
+ color: colors.labelBlue
+ },
+ inputTextStyle: {
+ color: colors.black
+ }
+};
diff --git a/src/screens/boarding/LoginScreen.js b/src/screens/auth/LoginScreen.js
similarity index 92%
rename from src/screens/boarding/LoginScreen.js
rename to src/screens/auth/LoginScreen.js
index 8be9285..2ba98e0 100644
--- a/src/screens/boarding/LoginScreen.js
+++ b/src/screens/auth/LoginScreen.js
@@ -1,12 +1,12 @@
import React from 'react';
import { Image, KeyboardAvoidingView, Platform, Dimensions, View, Text, Keyboard, TouchableWithoutFeedback } from 'react-native';
import { Button, FormInput } from 'react-native-elements';
-import FlashMessage, { showMessage } from 'react-native-flash-message';
+import { showMessage } from 'react-native-flash-message';
import { FormLabel, Spacer, TextButton } from '../../components/common';
import { strings } from '../../locales/i18n';
import { fetchAuth } from '../../utils/fetch';
-import { getErrorMessage } from '../../utils/errorHandler';
+import { getErrorDescription } from '../../utils/errorHandler';
import endpoints from '../../config/apiEndpoints';
import colors from '../../config/colors.json';
@@ -31,19 +31,20 @@ export default class LoginScreen extends React.Component {
* and displays the error if needed.
*/
onLoginPress() {
+ Keyboard.dismiss();
this.setState({ loading: true });
const body = this.getLoginRequestBody();
fetchAuth(endpoints.login, body).then(res => {
if (res.error) {
- this.setState({ loading: false });
+ this.setState({ loading: false, password: '' });
this.showErrorMessage(res.error);
} else {
this.setState({ loading: false });
console.log(res.sessionId);
}
}).catch(error => {
- this.setState({ loading: false });
+ this.setState({ loading: false, password: '' });
this.showErrorMessage(error);
});
}
@@ -75,9 +76,10 @@ export default class LoginScreen extends React.Component {
showErrorMessage(error) {
showMessage({
message: 'Authentication Error',
- description: getErrorMessage(error),
+ description: getErrorDescription(error),
type: 'danger',
- icon: 'auto'
+ icon: 'auto',
+ duration: 3000
});
}
@@ -118,6 +120,7 @@ export default class LoginScreen extends React.Component {
/>
this.setState({ password })}
placeholder={strings('userLoginScreen.password_placeholder')}
autoCorrect={false}
@@ -152,8 +155,6 @@ export default class LoginScreen extends React.Component {
disabled={this.isButtonDiabled()}
fontSize={20}
/>
-
-
);
@@ -165,10 +166,7 @@ export default class LoginScreen extends React.Component {
}
return (
-
+
{this.renderContent()}
);
diff --git a/src/screens/boarding/WelcomeScreen.js b/src/screens/onboaring/WelcomeScreen.js
similarity index 100%
rename from src/screens/boarding/WelcomeScreen.js
rename to src/screens/onboaring/WelcomeScreen.js
diff --git a/src/utils/errorHandler.js b/src/utils/errorHandler.js
index 33f468f..0cd2911 100644
--- a/src/utils/errorHandler.js
+++ b/src/utils/errorHandler.js
@@ -1,10 +1,10 @@
import errors from '../config/errors';
-export function getErrorMessage(error) {
+export function getErrorDescription(error) {
switch (error) {
case errors.AUTH_INVALID_EMAIL: return 'Invalid email format used.';
case errors.AUTH_INVALID_PHONE_NUMBER: return 'Invalid phone number format is used.';
- case errors.AUTH_USER_NOT_FOUND: return 'Identifier used does not exist.';
+ case errors.AUTH_USER_NOT_FOUND: return 'User is not recognized.';
case errors.AUTH_WRONG_PASSWORD: return 'Wrong password.';
case errors.AUTH_UID_ALREADY_EXISTS: return 'TagferID already exists.';
case errors.AUTH_EMAIL_ALREADY_EXISTS: return 'Email already exists.';
@@ -17,4 +17,4 @@ export function getErrorMessage(error) {
case errors.MISSING_BODY_ATTRIBUTES: return 'Invalid request. Please try again.';
default: return 'Oops, an error occured! Please try again.';
}
-}
\ No newline at end of file
+}