From 15ccb73f3737e63f4ea6cc44bb692176d40fdc6d Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:42:44 -0300 Subject: [PATCH] [NEW] Flow to resend email confirmation from mobile (#3439) * [NEW] Flow to resend email confirmation from mobile * Hooks * theme * minor tweak * refactor: improving withTheme ts * minor tweaks Co-authored-by: AlexAlexandre --- app/containers/TextInput.tsx | 32 ++++----- app/i18n/locales/en.json | 2 + app/i18n/locales/pt-BR.json | 4 +- app/lib/rocketchat.js | 11 ++- app/stacks/OutsideStack.js | 6 ++ app/theme.tsx | 6 +- app/utils/log/events.js | 3 + app/views/LoginView.js | 12 +++- app/views/SendEmailConfirmationView.tsx | 94 +++++++++++++++++++++++++ 9 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 app/views/SendEmailConfirmationView.tsx diff --git a/app/containers/TextInput.tsx b/app/containers/TextInput.tsx index f9c2236a8..fbaa6bb15 100644 --- a/app/containers/TextInput.tsx +++ b/app/containers/TextInput.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import { StyleProp, StyleSheet, Text, TextInputProps, TextStyle, View, ViewStyle } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import sharedStyles from '../views/Styles'; @@ -50,23 +50,21 @@ const styles = StyleSheet.create({ } }); -interface IRCTextInputProps { - label: string; - error: { +interface IRCTextInputProps extends TextInputProps { + label?: string; + error?: { error: any; reason: any; }; - loading: boolean; - secureTextEntry: boolean; - containerStyle: any; - inputStyle: object; - inputRef: any; - testID: string; - iconLeft: string; - iconRight: string; - placeholder: string; - left: JSX.Element; - onIconRightPress(): void; + loading?: boolean; + containerStyle?: StyleProp; + inputStyle?: TextStyle; + inputRef?: React.Ref; + testID?: string; + iconLeft?: string; + iconRight?: string; + left?: JSX.Element; + onIconRightPress?(): void; theme: string; } @@ -152,7 +150,7 @@ export default class RCTextInput extends React.PureComponent + style={[styles.label, { color: themes[theme].titleText }, error?.error && { color: dangerColor }]}> {label} ) : null} @@ -168,7 +166,7 @@ export default class RCTextInput extends React.PureComponent { try { @@ -1060,8 +1065,12 @@ const RocketChat = { }, methodCallWrapper(method, ...params) { const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; + const { user } = reduxStore.getState().login; if (API_Use_REST_For_DDP_Calls) { - return this.post(`method.call/${method}`, { message: EJSON.stringify({ method, params }) }); + const url = isEmpty(user) ? 'method.callAnon' : 'method.call'; + return this.post(`${url}/${method}`, { + message: EJSON.stringify({ method, params }) + }); } const parsedParams = params.map(param => { if (param instanceof Date) { diff --git a/app/stacks/OutsideStack.js b/app/stacks/OutsideStack.js index f23e65c3d..392850c3e 100644 --- a/app/stacks/OutsideStack.js +++ b/app/stacks/OutsideStack.js @@ -10,6 +10,7 @@ import NewServerView from '../views/NewServerView'; import WorkspaceView from '../views/WorkspaceView'; import LoginView from '../views/LoginView'; import ForgotPasswordView from '../views/ForgotPasswordView'; +import SendEmailConfirmationView from '../views/SendEmailConfirmationView'; import RegisterView from '../views/RegisterView'; import LegalView from '../views/LegalView'; import AuthenticationWebView from '../views/AuthenticationWebView'; @@ -25,6 +26,11 @@ const _OutsideStack = () => { + diff --git a/app/theme.tsx b/app/theme.tsx index 450bc2801..4accff2cd 100644 --- a/app/theme.tsx +++ b/app/theme.tsx @@ -3,14 +3,14 @@ import hoistNonReactStatics from 'hoist-non-react-statics'; interface IThemeContextProps { theme: string; - themePreferences: { + themePreferences?: { currentTheme: 'automatic' | 'light'; darkLevel: string; }; - setTheme: (newTheme?: {}) => void; + setTheme?: (newTheme?: {}) => void; } -export const ThemeContext = React.createContext>({ theme: 'light' }); +export const ThemeContext = React.createContext({ theme: 'light' }); export function withTheme(Component: any) { const ThemedComponent = (props: any) => ( diff --git a/app/utils/log/events.js b/app/utils/log/events.js index 4d2564b0c..505a42e4c 100644 --- a/app/utils/log/events.js +++ b/app/utils/log/events.js @@ -11,6 +11,9 @@ export default { FP_FORGOT_PASSWORD: 'fp_forgot_password', FP_FORGOT_PASSWORD_F: 'fp_forgot_password_f', + // SEND EMAIL CONFIRMATION VIEW + SEC_SEND_EMAIL_CONFIRMATION: 'sec_send_email_confirmation', + // REGISTER VIEW REGISTER_DEFAULT_SIGN_UP: 'register_default_sign_up', REGISTER_DEFAULT_SIGN_UP_F: 'register_default_sign_up_f', diff --git a/app/views/LoginView.js b/app/views/LoginView.js index c240ab8f4..6925f4ca7 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -81,7 +81,11 @@ class LoginView extends React.Component { UNSAFE_componentWillReceiveProps(nextProps) { const { error } = this.props; if (nextProps.failure && !dequal(error, nextProps.error)) { - Alert.alert(I18n.t('Oops'), I18n.t('Login_error')); + if (nextProps.error.error === 'error-invalid-email') { + this.resendEmailConfirmation(); + } else { + Alert.alert(I18n.t('Oops'), I18n.t('Login_error')); + } } } @@ -105,6 +109,12 @@ class LoginView extends React.Component { navigation.navigate('ForgotPasswordView', { title: Site_Name }); }; + resendEmailConfirmation = () => { + const { user } = this.state; + const { navigation } = this.props; + navigation.navigate('SendEmailConfirmationView', { user }); + }; + valid = () => { const { user, password } = this.state; return user.trim() && password.trim(); diff --git a/app/views/SendEmailConfirmationView.tsx b/app/views/SendEmailConfirmationView.tsx new file mode 100644 index 000000000..892673acc --- /dev/null +++ b/app/views/SendEmailConfirmationView.tsx @@ -0,0 +1,94 @@ +import React, { useEffect, useState } from 'react'; +import { StackNavigationProp } from '@react-navigation/stack'; + +import TextInput from '../containers/TextInput'; +import Button from '../containers/Button'; +import { showErrorAlert } from '../utils/info'; +import isValidEmail from '../utils/isValidEmail'; +import I18n from '../i18n'; +import RocketChat from '../lib/rocketchat'; +import { useTheme } from '../theme'; +import FormContainer, { FormContainerInner } from '../containers/FormContainer'; +import log, { events, logEvent } from '../utils/log'; +import sharedStyles from './Styles'; + +interface ISendEmailConfirmationView { + navigation: StackNavigationProp; + route: { + params: { + user?: string; + }; + }; +} + +const SendEmailConfirmationView = ({ navigation, route }: ISendEmailConfirmationView): JSX.Element => { + const [email, setEmail] = useState(''); + const [invalidEmail, setInvalidEmail] = useState(true); + const [isFetching, setIsFetching] = useState(false); + + const { theme } = useTheme(); + + const validate = (val: string) => { + const isInvalidEmail = !isValidEmail(val); + setEmail(val); + setInvalidEmail(isInvalidEmail); + }; + + const resendConfirmationEmail = async () => { + logEvent(events.SEC_SEND_EMAIL_CONFIRMATION); + if (invalidEmail || !email) { + return; + } + try { + setIsFetching(true); + const result = await RocketChat.sendConfirmationEmail(email); + if (result.success) { + navigation.pop(); + showErrorAlert(I18n.t('Verify_email_desc')); + } + } catch (e: any) { + log(e); + const msg = e?.data?.error || I18n.t('There_was_an_error_while_action', { action: I18n.t('sending_email_confirmation') }); + showErrorAlert(msg, I18n.t('Alert')); + } + setIsFetching(false); + }; + + useEffect(() => { + navigation.setOptions({ + title: 'Rocket.Chat' + }); + if (route.params?.user) { + validate(route.params.user); + } + }, []); + + return ( + + + validate(email)} + onSubmitEditing={resendConfirmationEmail} + testID='send-email-confirmation-view-email' + containerStyle={sharedStyles.inputLastChild} + theme={theme} + value={email} + /> +