From 5e019da18dd812a8ee122681b779342c5e6f06e5 Mon Sep 17 00:00:00 2001 From: Olivia Fernandez Date: Sat, 18 Jul 2020 18:03:10 -0300 Subject: [PATCH] Reset password (#23) * initial screen for reset password. * add verify code screen. * add new password screen. * refactor. * finish reset password. * refactor. * remove comment. --- package.json | 1 + src/app/components/OkModal/styles.js | 3 +- src/app/index.js | 12 ++- src/app/screens/HomeScreen/index.js | 7 +- src/app/screens/LoginScreen/index.js | 19 +++- src/app/screens/LoginScreen/styles.js | 7 ++ .../ForgotPasswordScreen/index.js | 77 +++++++++++++++ .../ForgotPasswordScreen/styles.js | 49 ++++++++++ .../ResetPassword/NewPasswordScreen/index.js | 97 +++++++++++++++++++ .../ResetPassword/NewPasswordScreen/styles.js | 49 ++++++++++ .../components/CodeInput/index.js | 46 +++++++++ .../components/CodeInput/styles.js | 27 ++++++ .../ResetPassword/VerifyCodeScreen/index.js | 69 +++++++++++++ .../ResetPassword/VerifyCodeScreen/styles.js | 40 ++++++++ src/constants/routes.js | 5 +- src/services/AuthService.js | 8 ++ yarn.lock | 5 + 17 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 src/app/screens/ResetPassword/ForgotPasswordScreen/index.js create mode 100644 src/app/screens/ResetPassword/ForgotPasswordScreen/styles.js create mode 100644 src/app/screens/ResetPassword/NewPasswordScreen/index.js create mode 100644 src/app/screens/ResetPassword/NewPasswordScreen/styles.js create mode 100644 src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/index.js create mode 100644 src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/styles.js create mode 100644 src/app/screens/ResetPassword/VerifyCodeScreen/index.js create mode 100644 src/app/screens/ResetPassword/VerifyCodeScreen/styles.js diff --git a/package.json b/package.json index cedc040..2b3da6e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "react": "16.11.0", "react-dom": "16.11.0", "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz", + "react-native-confirmation-code-field": "^6.5.0", "react-native-gesture-handler": "~1.6.0", "react-native-gifted-chat": "^0.16.3", "react-native-safe-area-context": "~3.0.7", diff --git a/src/app/components/OkModal/styles.js b/src/app/components/OkModal/styles.js index 875b0f2..a081f07 100644 --- a/src/app/components/OkModal/styles.js +++ b/src/app/components/OkModal/styles.js @@ -9,7 +9,8 @@ const styles = StyleSheet.create({ title: { fontSize: 20, fontWeight: 'bold', - marginBottom: 30 + marginBottom: 30, + textAlign: 'center' }, gif: { width: 150, diff --git a/src/app/index.js b/src/app/index.js index 9058cda..1c79166 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -1,7 +1,6 @@ import 'react-native-gesture-handler'; -import React, { useEffect } from 'react'; +import React from 'react'; import { Image } from 'react-native'; -import { useSelector } from 'react-redux'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; @@ -22,6 +21,9 @@ import UploadVideoScreen from '@screens/UploadVideoScreen'; import EditVideoScreen from '@screens/EditVideoScreen'; import TabBarIcon from '@components/TabBarIcon'; import HeaderButtons from '@components/HeaderButtons'; +import VerifyCodeScreen from '@screens/ResetPassword/VerifyCodeScreen'; +import NewPasswordScreen from '@screens/ResetPassword/NewPasswordScreen'; +import ForgotPasswordScreen from '@screens/ResetPassword/ForgotPasswordScreen'; Notifications.setNotificationHandler({ handleNotification: async () => ({ @@ -120,6 +122,12 @@ export default function App() { + + + diff --git a/src/app/screens/HomeScreen/index.js b/src/app/screens/HomeScreen/index.js index cc8cfce..37b032e 100644 --- a/src/app/screens/HomeScreen/index.js +++ b/src/app/screens/HomeScreen/index.js @@ -3,7 +3,6 @@ import { useDispatch, useSelector } from 'react-redux'; import { SafeAreaView } from 'react-native'; import * as Notifications from 'expo-notifications'; -import { ROUTES } from '@constants/routes'; import { registerForPushNotifications } from '@services/NotificationService'; import VideosList from '@components/VideosList'; import actionCreators from '@redux/videos/actions'; @@ -25,10 +24,8 @@ function HomeScreen({ navigation }) { console.warn(notification); }); Notifications.addNotificationResponseReceivedListener((response) => { - const {type, ...data} = response.notification.request.content.data; - const { redirect, payload } = notificationHanlder( - response.notification.request.content.data - )[type]; + const { type, ...data } = response.notification.request.content.data; + const { redirect, payload } = notificationHanlder(data)[type]; navigation.navigate(redirect, payload); }); return () => Notifications.removeAllNotificationListeners(); diff --git a/src/app/screens/LoginScreen/index.js b/src/app/screens/LoginScreen/index.js index ef5e416..ebe9cb0 100644 --- a/src/app/screens/LoginScreen/index.js +++ b/src/app/screens/LoginScreen/index.js @@ -1,5 +1,12 @@ import React, { useCallback, useState, useEffect } from 'react'; -import { View, SafeAreaView, TextInput, Text, Image } from 'react-native'; +import { + View, + SafeAreaView, + TextInput, + Text, + Image, + TouchableOpacity +} from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import logo from '@assets/tutubo-03.png'; @@ -50,6 +57,11 @@ function LoginScreen({ navigation }) { navigation.navigate(ROUTES.SignUp); }, [navigation, cleanLogin]); + const onNavigateToResetPassword = useCallback(() => { + cleanLogin(); + navigation.navigate(ROUTES.ForgotPassword); + }, [navigation, cleanLogin]); + return ( @@ -86,8 +98,11 @@ function LoginScreen({ navigation }) { onPress={onNavigateToRegister} disable={authLoading} /> + + Olvidaste tu contraseña? + - {error && {error}} + {error && {error}} ); } diff --git a/src/app/screens/LoginScreen/styles.js b/src/app/screens/LoginScreen/styles.js index 4639654..e4efa05 100644 --- a/src/app/screens/LoginScreen/styles.js +++ b/src/app/screens/LoginScreen/styles.js @@ -40,6 +40,13 @@ const styles = StyleSheet.create({ }, textDisable: { color: COLORS.white + }, + forgotPassword: { + color: COLORS.main, + textAlign: 'center' + }, + error: { + marginTop: 5 } }); diff --git a/src/app/screens/ResetPassword/ForgotPasswordScreen/index.js b/src/app/screens/ResetPassword/ForgotPasswordScreen/index.js new file mode 100644 index 0000000..d9648ab --- /dev/null +++ b/src/app/screens/ResetPassword/ForgotPasswordScreen/index.js @@ -0,0 +1,77 @@ +import React, { useCallback, useState } from 'react'; +import { View, SafeAreaView, TextInput, Text } from 'react-native'; + +import CustomButton from '@components/CustomButton'; +import { ROUTES } from '@constants/routes'; +import { COLORS } from '@constants/colors'; +import { resetPassword } from '@services/AuthService'; + +import { validateEmail } from '@utils/email'; + +import styles from './styles'; + +function ForgotPasswordScreen({ navigation }) { + const [email, setEmail] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + const emailValid = validateEmail(email); + const disable = !emailValid; + + const onEmailSubmit = useCallback(async () => { + setLoading(true); + const response = await resetPassword(email); + if (response.ok) { + navigation.navigate(ROUTES.VerifyCode, { email }); + } else { + setError(response.data.reason); + } + setLoading(false); + }, [email, navigation]); + + const onNavigateToLogin = useCallback(() => { + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.Login }] + }); + }, [navigation]); + + return ( + + Olvidaste tu contraseña? + + Para reiniciar tu contraseña ingresa tu email registrado y te enviaremos + un código de verificación. + + + + + + + {!!error && {error}} + + ); +} + +export default ForgotPasswordScreen; diff --git a/src/app/screens/ResetPassword/ForgotPasswordScreen/styles.js b/src/app/screens/ResetPassword/ForgotPasswordScreen/styles.js new file mode 100644 index 0000000..6cb1c31 --- /dev/null +++ b/src/app/screens/ResetPassword/ForgotPasswordScreen/styles.js @@ -0,0 +1,49 @@ +import { StyleSheet } from 'react-native'; +import { COLORS } from '@constants/colors'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: COLORS.white + }, + title: { + fontSize: 28, + color: COLORS.main + }, + explanation: { + margin: 30, + marginTop: 10, + textAlign: 'center', + color: COLORS.gray + }, + input: { + backgroundColor: COLORS.white, + borderColor: COLORS.gray, + borderWidth: 1, + borderRadius: 5, + marginBottom: 20, + padding: 5, + width: 200 + }, + loginButton: { + backgroundColor: COLORS.main, + borderStyle: 'solid', + borderWidth: 1, + borderColor: COLORS.main, + marginBottom: 10 + }, + buttonDisable: { + borderColor: COLORS.gray, + backgroundColor: COLORS.gray + }, + loginButtonText: { + color: COLORS.white + }, + textDisable: { + color: COLORS.white + } +}); + +export default styles; diff --git a/src/app/screens/ResetPassword/NewPasswordScreen/index.js b/src/app/screens/ResetPassword/NewPasswordScreen/index.js new file mode 100644 index 0000000..d10f9da --- /dev/null +++ b/src/app/screens/ResetPassword/NewPasswordScreen/index.js @@ -0,0 +1,97 @@ +import React, { useCallback, useState } from 'react'; +import { View, SafeAreaView, TextInput, Text } from 'react-native'; + +import CustomButton from '@components/CustomButton'; +import OkModal from '@components/OkModal'; +import { ROUTES } from '@constants/routes'; +import { COLORS } from '@constants/colors'; +import { newPassword } from '@services/AuthService'; + +import styles from './styles'; + +function NewPasswordScreen({ navigation, route }) { + const [password, setPassword] = useState(''); + const [confirmPw, setConfirmPw] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + const [openModal, setOpenModal] = useState(false); + + const email = route?.params?.email; + const code = route?.params?.code; + + const disable = !password.length > 0 || password !== confirmPw; + + const onNewPasswordSubmit = useCallback(async () => { + setLoading(true); + const response = await newPassword(email, code, password); + if (response.ok) { + setOpenModal(true); + } else { + setError(response.data.reason); + } + setLoading(false); + }, [email, code, password]); + + const onCloseModal = useCallback(() => { + setOpenModal(false); + onNavigateToLogin(); + }, [onNavigateToLogin]); + + const onNavigateToLogin = useCallback(() => { + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.Login }] + }); + }, [navigation]); + + return ( + + + Nueva Contraseña + Ingresa la nueva contraseña + + + + + + + {!!error && {error}} + + ); +} + +export default NewPasswordScreen; diff --git a/src/app/screens/ResetPassword/NewPasswordScreen/styles.js b/src/app/screens/ResetPassword/NewPasswordScreen/styles.js new file mode 100644 index 0000000..6cb1c31 --- /dev/null +++ b/src/app/screens/ResetPassword/NewPasswordScreen/styles.js @@ -0,0 +1,49 @@ +import { StyleSheet } from 'react-native'; +import { COLORS } from '@constants/colors'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: COLORS.white + }, + title: { + fontSize: 28, + color: COLORS.main + }, + explanation: { + margin: 30, + marginTop: 10, + textAlign: 'center', + color: COLORS.gray + }, + input: { + backgroundColor: COLORS.white, + borderColor: COLORS.gray, + borderWidth: 1, + borderRadius: 5, + marginBottom: 20, + padding: 5, + width: 200 + }, + loginButton: { + backgroundColor: COLORS.main, + borderStyle: 'solid', + borderWidth: 1, + borderColor: COLORS.main, + marginBottom: 10 + }, + buttonDisable: { + borderColor: COLORS.gray, + backgroundColor: COLORS.gray + }, + loginButtonText: { + color: COLORS.white + }, + textDisable: { + color: COLORS.white + } +}); + +export default styles; diff --git a/src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/index.js b/src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/index.js new file mode 100644 index 0000000..a96cf18 --- /dev/null +++ b/src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/index.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +import { + CodeField, + Cursor, + useBlurOnFulfill, + useClearByFocusCell +} from 'react-native-confirmation-code-field'; + +import styles from './styles'; + +const CELL_COUNT = 4; + +function CodeInput({ value, setValue }) { + const ref = useBlurOnFulfill({ value, cellCount: CELL_COUNT }); + const [props, getCellOnLayoutHandler] = useClearByFocusCell({ + value, + setValue + }); + + return ( + ( + + + {symbol || (isFocused ? : null)} + + + )} + /> + ); +} + +export default CodeInput; diff --git a/src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/styles.js b/src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/styles.js new file mode 100644 index 0000000..9b46be9 --- /dev/null +++ b/src/app/screens/ResetPassword/VerifyCodeScreen/components/CodeInput/styles.js @@ -0,0 +1,27 @@ +import { StyleSheet } from 'react-native'; +import { COLORS } from '@constants/colors'; + +export default StyleSheet.create({ + codeFieldRoot: { + marginVertical: 20, + width: 250, + marginLeft: 'auto', + marginRight: 'auto' + }, + cellRoot: { + width: 50, + height: 50, + justifyContent: 'center', + alignItems: 'center', + borderBottomColor: COLORS.gray, + borderBottomWidth: 1 + }, + cellText: { + fontSize: 36, + textAlign: 'center' + }, + focusCell: { + borderBottomColor: COLORS.main, + borderBottomWidth: 2 + } +}); diff --git a/src/app/screens/ResetPassword/VerifyCodeScreen/index.js b/src/app/screens/ResetPassword/VerifyCodeScreen/index.js new file mode 100644 index 0000000..447bf28 --- /dev/null +++ b/src/app/screens/ResetPassword/VerifyCodeScreen/index.js @@ -0,0 +1,69 @@ +import React, { useCallback, useState } from 'react'; +import { View, SafeAreaView, Text } from 'react-native'; + +import CustomButton from '@components/CustomButton'; +import { ROUTES } from '@constants/routes'; +import { COLORS } from '@constants/colors'; +import { verifyCode } from '@services/AuthService'; + +import styles from './styles'; +import CodeInput from './components/CodeInput'; + +function VerifyCodeScreen({ navigation, route }) { + const [email] = useState(route?.params?.email); + const [code, setCode] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + const disable = !code || code?.length < 4; + + const onCodeSubmit = useCallback(async () => { + setLoading(true); + const response = await verifyCode(email, code); + if (response.ok) { + navigation.navigate(ROUTES.NewPassword, { email, code }); + } else { + setError(response.data.reason); + } + setLoading(false); + }, [email, code, navigation]); + + const onNavigateToLogin = useCallback(() => { + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.Login }] + }); + }, [navigation]); + + return ( + + Verificar Código + + Chequea tu casilla de mails e ingresa el código de verificación que te + enviamos + + + + + + + {!!error && {error}} + + ); +} + +export default VerifyCodeScreen; diff --git a/src/app/screens/ResetPassword/VerifyCodeScreen/styles.js b/src/app/screens/ResetPassword/VerifyCodeScreen/styles.js new file mode 100644 index 0000000..60b6c81 --- /dev/null +++ b/src/app/screens/ResetPassword/VerifyCodeScreen/styles.js @@ -0,0 +1,40 @@ +import { StyleSheet } from 'react-native'; +import { COLORS } from '@constants/colors'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: COLORS.white + }, + title: { + fontSize: 28, + color: COLORS.main + }, + explanation: { + margin: 30, + marginTop: 10, + textAlign: 'center', + color: COLORS.gray + }, + loginButton: { + backgroundColor: COLORS.main, + borderStyle: 'solid', + borderWidth: 1, + borderColor: COLORS.main, + marginBottom: 10 + }, + buttonDisable: { + borderColor: COLORS.gray, + backgroundColor: COLORS.gray + }, + loginButtonText: { + color: COLORS.white + }, + textDisable: { + color: COLORS.white + } +}); + +export default styles; diff --git a/src/constants/routes.js b/src/constants/routes.js index 21c1001..59e2eee 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -10,5 +10,8 @@ export const ROUTES = { UploadVideo: 'Subir Video', Chat: 'Chat', ChatList: 'Chats', - EditVideo: 'Editar Video' + EditVideo: 'Editar Video', + ForgotPassword: 'Olvidaste tu Contraseña?', + VerifyCode: 'Verificar Código', + NewPassword: 'Nueva Contraseña' }; diff --git a/src/services/AuthService.js b/src/services/AuthService.js index 6e7a9cc..c3e0208 100644 --- a/src/services/AuthService.js +++ b/src/services/AuthService.js @@ -7,6 +7,14 @@ export const login = (username, password) => export const register = (info) => api.post('/register', info); +export const resetPassword = (email) => api.post(`/reset_password`, { email }); + +export const verifyCode = (email, code) => + api.get(`/password`, { email, code }); + +export const newPassword = (email, code, password) => + api.post(`/password`, { password }, { params: { email, code } }); + export const getSession = async () => { const token = await AsyncStorage.getItem('access-token'); const id = await AsyncStorage.getItem('userid'); diff --git a/yarn.lock b/yarn.lock index 252b16f..0578224 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7802,6 +7802,11 @@ react-native-communications@^2.2.1: resolved "https://registry.yarnpkg.com/react-native-communications/-/react-native-communications-2.2.1.tgz#7883b56b20a002eeb790c113f8616ea8692ca795" integrity sha1-eIO1ayCgAu63kMET+GFuqGksp5U= +react-native-confirmation-code-field@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/react-native-confirmation-code-field/-/react-native-confirmation-code-field-6.5.0.tgz#f5579ee66b3f902724f5596a7ea3c87308c4d6d9" + integrity sha512-thOctQtpkx5bcXIj6+S+gIKw7Z/jMBFf9FtqkiRQaZI1+IoMoA+n5CK4aYoosPQW4oAPTivL1mxOyA8T1kqmtA== + react-native-dotenv@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/react-native-dotenv/-/react-native-dotenv-0.2.0.tgz#311551cb6a35a3dcfede648bded55c0e3ece579d"