[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 <alexalexandrejr@gmail.com>
This commit is contained in:
parent
42c69f0c06
commit
15ccb73f37
|
@ -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<ViewStyle>;
|
||||
inputStyle?: TextStyle;
|
||||
inputRef?: React.Ref<unknown>;
|
||||
testID?: string;
|
||||
iconLeft?: string;
|
||||
iconRight?: string;
|
||||
left?: JSX.Element;
|
||||
onIconRightPress?(): void;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
|
@ -152,7 +150,7 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
contentDescription={null}
|
||||
// @ts-ignore
|
||||
accessibilityLabel={null}
|
||||
style={[styles.label, { color: themes[theme].titleText }, error.error && { color: dangerColor }]}>
|
||||
style={[styles.label, { color: themes[theme].titleText }, error?.error && { color: dangerColor }]}>
|
||||
{label}
|
||||
</Text>
|
||||
) : null}
|
||||
|
@ -168,7 +166,7 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
borderColor: themes[theme].separatorColor,
|
||||
color: themes[theme].titleText
|
||||
},
|
||||
error.error && {
|
||||
error?.error && {
|
||||
color: dangerColor,
|
||||
borderColor: dangerColor
|
||||
},
|
||||
|
|
|
@ -780,5 +780,7 @@
|
|||
"Content": "Content",
|
||||
"Sharing": "Sharing",
|
||||
"No_canned_responses": "No canned responses",
|
||||
"Send_email_confirmation": "Send email confirmation",
|
||||
"sending_email_confirmation": "sending email confirmation",
|
||||
"Enable_Message_Parser": "Enable Message Parser"
|
||||
}
|
||||
|
|
|
@ -679,5 +679,7 @@
|
|||
"Use": "Use",
|
||||
"Shortcut": "Atalho",
|
||||
"Content": "Conteúdo",
|
||||
"No_canned_responses": "Não há respostas predefinidas"
|
||||
"No_canned_responses": "Não há respostas predefinidas",
|
||||
"Send_email_confirmation": "Enviar email de confirmação",
|
||||
"sending_email_confirmation": "enviando email de confirmação"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Q } from '@nozbe/watermelondb';
|
|||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import defaultSettings from '../constants/settings';
|
||||
import log from '../utils/log';
|
||||
|
@ -530,6 +531,10 @@ const RocketChat = {
|
|||
return this.post('users.forgotPassword', { email }, false);
|
||||
},
|
||||
|
||||
sendConfirmationEmail(email) {
|
||||
return this.methodCallWrapper('sendConfirmationEmail', email);
|
||||
},
|
||||
|
||||
loginTOTP(params, loginEmailPassword, isFromWebView = false) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
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) {
|
||||
|
|
|
@ -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 = () => {
|
|||
<Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} />
|
||||
<Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} />
|
||||
<Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} />
|
||||
<Outside.Screen
|
||||
name='SendEmailConfirmationView'
|
||||
component={SendEmailConfirmationView}
|
||||
options={SendEmailConfirmationView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen name='RegisterView' component={RegisterView} options={RegisterView.navigationOptions} />
|
||||
<Outside.Screen name='LegalView' component={LegalView} options={LegalView.navigationOptions} />
|
||||
</Outside.Navigator>
|
||||
|
|
|
@ -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<Partial<IThemeContextProps>>({ theme: 'light' });
|
||||
export const ThemeContext = React.createContext<IThemeContextProps>({ theme: 'light' });
|
||||
|
||||
export function withTheme(Component: any) {
|
||||
const ThemedComponent = (props: any) => (
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -81,9 +81,13 @@ class LoginView extends React.Component {
|
|||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { error } = this.props;
|
||||
if (nextProps.failure && !dequal(error, nextProps.error)) {
|
||||
if (nextProps.error.error === 'error-invalid-email') {
|
||||
this.resendEmailConfirmation();
|
||||
} else {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('Login_error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get showRegistrationButton() {
|
||||
const { Accounts_RegistrationForm, inviteLinkToken } = this.props;
|
||||
|
@ -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();
|
||||
|
|
|
@ -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<any, 'SendEmailConfirmationView'>;
|
||||
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 (
|
||||
<FormContainer theme={theme} testID='send-email-confirmation-view'>
|
||||
<FormContainerInner>
|
||||
<TextInput
|
||||
autoFocus
|
||||
placeholder={I18n.t('Email')}
|
||||
keyboardType='email-address'
|
||||
returnKeyType='send'
|
||||
onChangeText={(email: string) => validate(email)}
|
||||
onSubmitEditing={resendConfirmationEmail}
|
||||
testID='send-email-confirmation-view-email'
|
||||
containerStyle={sharedStyles.inputLastChild}
|
||||
theme={theme}
|
||||
value={email}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Send_email_confirmation')}
|
||||
type='primary'
|
||||
onPress={resendConfirmationEmail}
|
||||
testID='send-email-confirmation-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={invalidEmail}
|
||||
theme={theme}
|
||||
/>
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
);
|
||||
};
|
||||
export default SendEmailConfirmationView;
|
Loading…
Reference in New Issue