[NEW] Delete my account (#4219)
* create new delete account button Co-Authored-By: Danish Ahmed Mirza <danishmirza30602@gmail.com> * change modal to action sheet * better naming * remove ? from translation * update translations * change to new figma layout * fix export * remove unused state * add new text input to base input * clean up * update bottom sheet and create a mock * remove unecessary bracket and fix type * fix header * migrate buttons to action sheet * fix imports * update yarn.lock * add separator to styles * add ternary verification * minor tweaks: keyboard for landscape android tablet, interface IactionSheetProvider and remove navigation options to get ismasterdetail from redux, fix jest setup * fix colors * disconnect from sdk when delete the account * update snapshot Co-authored-by: Danish Ahmed Mirza <danishmirza30602@gmail.com> Co-authored-by: Reinaldo Neto <reinaldonetof@hotmail.com>
This commit is contained in:
parent
cd7e9e22f8
commit
5f248ebeb5
|
@ -54,6 +54,7 @@ export const SERVER = createRequestTypes('SERVER', [
|
|||
]);
|
||||
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||
export const DELETE_ACCOUNT = 'DELETE_ACCOUNT';
|
||||
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
|
||||
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
||||
|
|
|
@ -121,3 +121,9 @@ export function setLocalAuthenticated(isLocalAuthenticated: boolean): ISetLocalA
|
|||
isLocalAuthenticated
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteAccount(): Action {
|
||||
return {
|
||||
type: types.DELETE_ACCOUNT
|
||||
};
|
||||
}
|
||||
|
|
|
@ -116,6 +116,10 @@ const ActionSheet = React.memo(
|
|||
|
||||
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
|
||||
|
||||
// Must need this prop to avoid keyboard dismiss
|
||||
// when is android tablet and the input text is focused
|
||||
const androidTablet: any = isTablet && isLandscape && !isIOS ? { android_keyboardInputMode: 'adjustResize' } : {};
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
|
@ -130,7 +134,8 @@ const ActionSheet = React.memo(
|
|||
enablePanDownToClose
|
||||
style={{ ...styles.container, ...bottomSheet }}
|
||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||
onChange={index => index === -1 && toggleVisible()}>
|
||||
onChange={index => index === -1 && toggleVisible()}
|
||||
{...androidTablet}>
|
||||
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
|
||||
</BottomSheet>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import Button from '../Button';
|
||||
import { useTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
|
||||
const FooterButtons = ({
|
||||
cancelAction = () => {},
|
||||
confirmAction = () => {},
|
||||
cancelTitle = '',
|
||||
confirmTitle = '',
|
||||
disabled = false,
|
||||
cancelBackgroundColor = '',
|
||||
confirmBackgroundColor = ''
|
||||
}): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View style={styles.footerButtonsContainer}>
|
||||
<Button
|
||||
style={[styles.buttonSeparator, { flex: 1, backgroundColor: cancelBackgroundColor || colors.cancelButton }]}
|
||||
color={colors.backdropColor}
|
||||
title={cancelTitle}
|
||||
onPress={cancelAction}
|
||||
/>
|
||||
<Button
|
||||
style={{ flex: 1, backgroundColor: confirmBackgroundColor || colors.dangerColor }}
|
||||
title={confirmTitle}
|
||||
onPress={confirmAction}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default FooterButtons;
|
|
@ -21,7 +21,7 @@ export type TActionSheetOptions = {
|
|||
children?: React.ReactElement | null;
|
||||
snaps?: string[] | number[];
|
||||
};
|
||||
interface IActionSheetProvider {
|
||||
export interface IActionSheetProvider {
|
||||
showActionSheet: (item: TActionSheetOptions) => void;
|
||||
hideActionSheet: () => void;
|
||||
}
|
||||
|
|
|
@ -63,5 +63,12 @@ export default StyleSheet.create({
|
|||
},
|
||||
rightContainer: {
|
||||
paddingLeft: 12
|
||||
},
|
||||
footerButtonsContainer: {
|
||||
flexDirection: 'row',
|
||||
paddingTop: 16
|
||||
},
|
||||
buttonSeparator: {
|
||||
marginRight: 8
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { BottomSheetTextInput } from '@gorhom/bottom-sheet';
|
||||
import React from 'react';
|
||||
import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { StyleProp, StyleSheet, Text, TextInput as RNTextInput, TextInputProps, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import TextInput from './index';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||
import ActivityIndicator from '../ActivityIndicator';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import ActivityIndicator from '../ActivityIndicator';
|
||||
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||
import TextInput from './index';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
error: {
|
||||
|
@ -63,6 +64,7 @@ export interface IRCTextInputProps extends TextInputProps {
|
|||
iconRight?: TIconsName;
|
||||
left?: JSX.Element;
|
||||
theme: TSupportedThemes;
|
||||
bottomSheet?: boolean;
|
||||
onClearInput?: () => void;
|
||||
}
|
||||
|
||||
|
@ -153,16 +155,18 @@ export default class FormTextInput extends React.PureComponent<IRCTextInputProps
|
|||
testID,
|
||||
placeholder,
|
||||
theme,
|
||||
bottomSheet,
|
||||
...inputProps
|
||||
} = this.props;
|
||||
const { dangerColor } = themes[theme];
|
||||
const Input = bottomSheet ? BottomSheetTextInput : TextInput;
|
||||
return (
|
||||
<View style={[styles.inputContainer, containerStyle]}>
|
||||
{label ? (
|
||||
<Text style={[styles.label, { color: themes[theme].titleText }, error?.error && { color: dangerColor }]}>{label}</Text>
|
||||
) : null}
|
||||
<View style={styles.wrap}>
|
||||
<TextInput
|
||||
<Input
|
||||
style={[
|
||||
styles.input,
|
||||
iconLeft && styles.inputIconLeft,
|
||||
|
@ -178,7 +182,8 @@ export default class FormTextInput extends React.PureComponent<IRCTextInputProps
|
|||
},
|
||||
inputStyle
|
||||
]}
|
||||
ref={inputRef}
|
||||
// @ts-ignore
|
||||
ref={inputRef} // bottomSheetRef overlap default ref
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
underlineColorAndroid='transparent'
|
||||
|
|
|
@ -61,4 +61,7 @@ export type UsersEndpoints = {
|
|||
success: boolean;
|
||||
};
|
||||
};
|
||||
'users.deleteOwnAccount': {
|
||||
POST: (params: { password: string; confirmRelinquish: boolean }) => { success: boolean };
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
{ "__count__empty_rooms_will_be_removed_automatically": "{{count}} empty rooms will be deleted.",
|
||||
"__count__empty_room_will_be_removed_automatically": "{{count}} empty room will be deleted.",
|
||||
"1_person_reacted": "1 person reacted",
|
||||
"1_user": "1 user",
|
||||
"error-action-not-allowed": "{{action}} is not allowed",
|
||||
|
@ -81,6 +82,8 @@
|
|||
"error-user-registration-secret": "User registration is only allowed via Secret URL",
|
||||
"error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.",
|
||||
"error-status-not-allowed": "Invisible status is disabled",
|
||||
"A_new_owner_will_be_assigned_automatically_to__count__rooms": "A new owner will be assigned automatically to {{count}} rooms.",
|
||||
"A_new_owner_will_be_assigned_automatically_to__count__room": "A new owner will be assigned automatically to {{count}} room.",
|
||||
"Actions": "Actions",
|
||||
"Activity": "Activity",
|
||||
"Add_Reaction": "Add Reaction",
|
||||
|
@ -107,6 +110,8 @@
|
|||
"archive": "archive",
|
||||
"are_typing": "are typing",
|
||||
"Are_you_sure_question_mark": "Are you sure?",
|
||||
"Are_you_sure_you_want_to_delete_your_account":"Are you sure you want to delete your account?",
|
||||
"Deleting_a_user_will_delete_all_messages":"Deleting a user will delete all messages, rooms and teams from that user as well. This cannot be undone.",
|
||||
"Are_you_sure_you_want_to_leave_the_room": "Are you sure you want to leave the room {{room}}?",
|
||||
"Audio": "Audio",
|
||||
"Authenticating": "Authenticating",
|
||||
|
@ -185,6 +190,8 @@
|
|||
"delete": "delete",
|
||||
"Delete": "Delete",
|
||||
"DELETE": "DELETE",
|
||||
"Delete_Account": "Delete account",
|
||||
"Delete_Account_confirm": "Yes, Delete",
|
||||
"move": "move",
|
||||
"deleting_room": "deleting room",
|
||||
"description": "description",
|
||||
|
@ -741,6 +748,8 @@
|
|||
"Last_owner_team_room": "You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.",
|
||||
"last-owner-can-not-be-removed": "Last owner cannot be removed",
|
||||
"Remove_User_Teams": "Select channels you want the user to be removed from.",
|
||||
"Deleting_account": "Deleting account",
|
||||
"Delete_my_account": "Delete my account",
|
||||
"Delete_Team": "Delete Team",
|
||||
"Select_channels_to_delete": "This can't be undone. Once you delete a team, all chat content and configuration will be deleted. \n\nSelect the channels you would like to delete. The ones you decide to keep will be available on your workspace. Notice that public channels will still be public and visible to everyone.",
|
||||
"You_are_deleting_the_team": "You are deleting this team.",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
{"__count__empty_rooms_will_be_removed_automatically": "{{count}} salas vazias serão excluídas.",
|
||||
"__count__empty_room_will_be_removed_automatically": "{{count}} sala vazia será excluída.",
|
||||
"1_person_reacted": "1 pessoa reagiu",
|
||||
"1_user": "1 usuário",
|
||||
"error-action-not-allowed": "{{action}} não é permitido",
|
||||
|
@ -78,6 +79,8 @@
|
|||
"error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta",
|
||||
"error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.",
|
||||
"error-status-not-allowed": "O status invisível está desativado",
|
||||
"A_new_owner_will_be_assigned_automatically_to__count__rooms": "Um novo proprietário será atribuído automaticamente a {{count}} salas.",
|
||||
"A_new_owner_will_be_assigned_automatically_to__count__room": "Um novo proprietário será atribuído automaticamente a {{count}} sala.",
|
||||
"Actions": "Ações",
|
||||
"Activity": "Atividade",
|
||||
"Add_Reaction": "Reagir",
|
||||
|
@ -102,6 +105,8 @@
|
|||
"archive": "arquivar",
|
||||
"are_typing": "estão digitando",
|
||||
"Are_you_sure_question_mark": "Você tem certeza?",
|
||||
"Are_you_sure_you_want_to_delete_your_account":"Tem certeza de que deseja excluir sua conta?",
|
||||
"Deleting_a_user_will_delete_all_messages":"A exclusão de um usuário também excluirá todas as mensagens, salas e equipes desse usuário. Isto não pode ser desfeito.",
|
||||
"Are_you_sure_you_want_to_leave_the_room": "Tem certeza de que deseja sair da sala {{room}}?",
|
||||
"Audio": "Áudio",
|
||||
"Authenticating": "Autenticando",
|
||||
|
@ -174,6 +179,8 @@
|
|||
"delete": "excluir",
|
||||
"Delete": "Excluir",
|
||||
"DELETE": "EXCLUIR",
|
||||
"Delete_Account": "Apagar conta",
|
||||
"Delete_Account_confirm": "Sim, apagar conta",
|
||||
"move": "mover",
|
||||
"deleting_room": "excluindo sala",
|
||||
"description": "descrição",
|
||||
|
@ -684,6 +691,8 @@
|
|||
"last-owner-can-not-be-removed": "O último dono não pode ser removido",
|
||||
"Remove_User_Teams": "Selecione os canais dos quais você deseja que o usuário seja removido.",
|
||||
"Delete_Team": "Excluir Time",
|
||||
"Deleting_account": "Apagando conta",
|
||||
"Delete_my_account": "Apagar minha conta",
|
||||
"Select_channels_to_delete": "Isto não pode ser desfeito. Assim que você apagar um time, todo o conteúdo e configuração do chat serão apagados.\n\nSelecione os canais que você gostaria de excluir. Os que você decidir manter estarão disponíveis no seu espaço de trabalho. Note que os canais públicos continuarão a ser públicos e visíveis para todos.",
|
||||
"You_are_deleting_the_team": "Você está apagando este time.",
|
||||
"Removing_user_from_this_team": "Você está removendo {{user}} deste time",
|
||||
|
|
|
@ -69,6 +69,7 @@ export const colors = {
|
|||
attachmentLoadingOpacity: 0.7,
|
||||
collapsibleQuoteBorder: '#CBCED1',
|
||||
collapsibleChevron: '#6C727A',
|
||||
cancelButton: '#E4E7EA',
|
||||
...mentions
|
||||
},
|
||||
dark: {
|
||||
|
@ -120,6 +121,7 @@ export const colors = {
|
|||
attachmentLoadingOpacity: 0.3,
|
||||
collapsibleQuoteBorder: '#CBCED1',
|
||||
collapsibleChevron: '#6C727A',
|
||||
cancelButton: '#E4E7EA',
|
||||
...mentions
|
||||
},
|
||||
black: {
|
||||
|
@ -171,6 +173,7 @@ export const colors = {
|
|||
attachmentLoadingOpacity: 0.3,
|
||||
collapsibleQuoteBorder: '#CBCED1',
|
||||
collapsibleChevron: '#6C727A',
|
||||
cancelButton: '#E4E7EA',
|
||||
...mentions
|
||||
}
|
||||
};
|
||||
|
|
|
@ -211,5 +211,8 @@ export const defaultSettings = {
|
|||
},
|
||||
Accounts_AvatarExternalProviderUrl: {
|
||||
type: 'valueAsString'
|
||||
},
|
||||
Accounts_AllowDeleteOwnAccount: {
|
||||
type: 'valueAsBoolean'
|
||||
}
|
||||
} as const;
|
||||
|
|
|
@ -349,5 +349,9 @@ export default {
|
|||
TC_DELETE_ROOM: 'tc_delete_room',
|
||||
TC_DELETE_ROOM_F: 'tc_delete_room_f',
|
||||
TC_TOGGLE_AUTOJOIN: 'tc_toggle_autojoin',
|
||||
TC_TOGGLE_AUTOJOIN_F: 'tc_toggle_autojoin_f'
|
||||
TC_TOGGLE_AUTOJOIN_F: 'tc_toggle_autojoin_f',
|
||||
|
||||
// DELETE OWN ACCOUNT ACCOUNT
|
||||
DELETE_OWN_ACCOUNT: 'delete_own_account',
|
||||
DELETE_OWN_ACCOUNT_F: 'delete_own_account_f'
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@ async function removeSharedCredentials({ server }: { server: string }) {
|
|||
}
|
||||
}
|
||||
|
||||
async function removeServerData({ server }: { server: string }) {
|
||||
export async function removeServerData({ server }: { server: string }): Promise<void> {
|
||||
try {
|
||||
const batch: Model[] = [];
|
||||
const serversDB = database.servers;
|
||||
|
@ -66,7 +66,7 @@ function removeCurrentServer() {
|
|||
UserPreferences.removeItem(CURRENT_SERVER);
|
||||
}
|
||||
|
||||
async function removeServerDatabase({ server }: { server: string }) {
|
||||
export async function removeServerDatabase({ server }: { server: string }): Promise<void> {
|
||||
try {
|
||||
const db = getDatabase(server);
|
||||
await db.write(() => db.unsafeResetDatabase());
|
||||
|
|
|
@ -917,3 +917,7 @@ export function getUserInfo(userId: string) {
|
|||
}
|
||||
|
||||
export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite });
|
||||
|
||||
export const deleteOwnAccount = (password: string, confirmRelinquish = false): any =>
|
||||
// RC 0.67.0
|
||||
sdk.post('users.deleteOwnAccount', { password, confirmRelinquish });
|
||||
|
|
|
@ -113,6 +113,11 @@ export default function login(state = initialState, action: TActionsLogin): ILog
|
|||
...state,
|
||||
isLocalAuthenticated: action.isLocalAuthenticated
|
||||
};
|
||||
case types.DELETE_ACCOUNT:
|
||||
return {
|
||||
...state,
|
||||
isLocalAuthenticated: false
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ import {
|
|||
getUserPresence,
|
||||
isOmnichannelModuleAvailable,
|
||||
logout,
|
||||
removeServerData,
|
||||
removeServerDatabase,
|
||||
subscribeSettings,
|
||||
subscribeUsersPresence
|
||||
} from '../lib/methods';
|
||||
|
@ -247,10 +249,46 @@ const handleSetUser = function* handleSetUser({ user }) {
|
|||
}
|
||||
};
|
||||
|
||||
const handleDeleteAccount = function* handleDeleteAccount() {
|
||||
yield put(encryptionStop());
|
||||
yield put(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Deleting_account') }));
|
||||
const server = yield select(getServer);
|
||||
if (server) {
|
||||
try {
|
||||
yield call(removeServerData, { server });
|
||||
yield call(removeServerDatabase, { server });
|
||||
const serversDB = database.servers;
|
||||
// all servers
|
||||
const serversCollection = serversDB.get('servers');
|
||||
const servers = yield serversCollection.query().fetch();
|
||||
|
||||
// see if there're other logged in servers and selects first one
|
||||
if (servers.length > 0) {
|
||||
for (let i = 0; i < servers.length; i += 1) {
|
||||
const newServer = servers[i].id;
|
||||
const token = UserPreferences.getString(`${TOKEN_KEY}-${newServer}`);
|
||||
if (token) {
|
||||
yield put(selectServerRequest(newServer));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if there's no servers, go outside
|
||||
sdk.disconnect();
|
||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
} catch (e) {
|
||||
sdk.disconnect();
|
||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
||||
yield takeLatest(types.LOGOUT, handleLogout);
|
||||
yield takeLatest(types.USER.SET, handleSetUser);
|
||||
yield takeLatest(types.DELETE_ACCOUNT, handleDeleteAccount);
|
||||
|
||||
while (true) {
|
||||
const params = yield take(types.LOGIN.SUCCESS);
|
||||
|
|
|
@ -191,11 +191,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
options={ScreenLockConfigView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen name='StatusView' component={StatusView} />
|
||||
<ModalStack.Screen
|
||||
name='ProfileView'
|
||||
component={ProfileView}
|
||||
options={props => ProfileView.navigationOptions!({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen name='ProfileView' component={ProfileView} />
|
||||
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
|
||||
<ModalStack.Screen name='AdminPanelView' component={AdminPanelView} />
|
||||
<ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import i18n from '../../../../i18n';
|
||||
|
||||
export const getTranslations = ({
|
||||
shouldChangeOwner,
|
||||
shouldBeRemoved
|
||||
}: {
|
||||
shouldChangeOwner: string[];
|
||||
shouldBeRemoved: string[];
|
||||
}): { changeOwnerRooms: string; removedRooms: string } => {
|
||||
let changeOwnerRooms = '';
|
||||
if (shouldChangeOwner.length) {
|
||||
if (shouldChangeOwner.length === 1) {
|
||||
changeOwnerRooms = i18n.t('A_new_owner_will_be_assigned_automatically_to__count__room', {
|
||||
count: shouldChangeOwner.length
|
||||
});
|
||||
} else {
|
||||
changeOwnerRooms = i18n.t('A_new_owner_will_be_assigned_automatically_to__count__rooms', {
|
||||
count: shouldChangeOwner.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let removedRooms = '';
|
||||
if (shouldBeRemoved.length) {
|
||||
if (shouldBeRemoved.length === 1) {
|
||||
removedRooms = i18n.t('__count__empty_room_will_be_removed_automatically', {
|
||||
count: shouldBeRemoved.length
|
||||
});
|
||||
} else {
|
||||
removedRooms = i18n.t('__count__empty_rooms_will_be_removed_automatically', {
|
||||
count: shouldBeRemoved.length
|
||||
});
|
||||
}
|
||||
}
|
||||
return { changeOwnerRooms, removedRooms };
|
||||
};
|
|
@ -0,0 +1,128 @@
|
|||
import { sha256 } from 'js-sha256';
|
||||
import React, { useState } from 'react';
|
||||
import { Keyboard, Text, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { deleteAccount } from '../../../../actions/login';
|
||||
import { useActionSheet } from '../../../../containers/ActionSheet';
|
||||
import FooterButtons from '../../../../containers/ActionSheet/FooterButtons';
|
||||
import { CustomIcon } from '../../../../containers/CustomIcon';
|
||||
import FormTextInput from '../../../../containers/TextInput/FormTextInput';
|
||||
import i18n from '../../../../i18n';
|
||||
import { showErrorAlert } from '../../../../lib/methods/helpers';
|
||||
import { events, logEvent } from '../../../../lib/methods/helpers/log';
|
||||
import { deleteOwnAccount } from '../../../../lib/services/restApi';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { getTranslations } from './getTranslations';
|
||||
import styles from './styles';
|
||||
|
||||
const AlertHeader = ({ title = '', subTitle = '' }) => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<>
|
||||
<View style={styles.titleContainer}>
|
||||
<CustomIcon name='warning' size={32} color={colors.dangerColor} />
|
||||
<Text style={[styles.titleContainerText, { color: colors.passcodePrimary }]}>{title}</Text>
|
||||
</View>
|
||||
<Text style={[styles.subTitleContainerText, { color: colors.passcodePrimary }]}>{subTitle}</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export function DeleteAccountActionSheetContent(): React.ReactElement {
|
||||
const [password, setPassword] = useState('');
|
||||
const { theme } = useTheme();
|
||||
const { hideActionSheet, showActionSheet } = useActionSheet();
|
||||
const dispatch = useDispatch();
|
||||
const insets = useSafeAreaInsets();
|
||||
const handleDeleteAccount = async () => {
|
||||
Keyboard.dismiss();
|
||||
try {
|
||||
await deleteOwnAccount(sha256(password));
|
||||
hideActionSheet();
|
||||
} catch (error: any) {
|
||||
hideActionSheet();
|
||||
if (error.data.errorType === 'user-last-owner') {
|
||||
const { shouldChangeOwner, shouldBeRemoved } = error.data.details;
|
||||
const { changeOwnerRooms, removedRooms } = getTranslations({ shouldChangeOwner, shouldBeRemoved });
|
||||
|
||||
setTimeout(() => {
|
||||
showActionSheet({
|
||||
children: (
|
||||
<ConfirmDeleteAccountActionSheetContent
|
||||
changeOwnerRooms={changeOwnerRooms}
|
||||
removedRooms={removedRooms}
|
||||
password={sha256(password)}
|
||||
/>
|
||||
),
|
||||
headerHeight: 225 + insets.bottom
|
||||
});
|
||||
}, 250); // timeout for hide effect
|
||||
} else if (error.data.errorType === 'error-invalid-password') {
|
||||
logEvent(events.DELETE_OWN_ACCOUNT_F);
|
||||
showErrorAlert(i18n.t('error-invalid-password'));
|
||||
} else {
|
||||
logEvent(events.DELETE_OWN_ACCOUNT_F);
|
||||
showErrorAlert(i18n.t(error.data.details));
|
||||
}
|
||||
return;
|
||||
}
|
||||
dispatch(deleteAccount());
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<AlertHeader
|
||||
title={i18n.t('Are_you_sure_you_want_to_delete_your_account')}
|
||||
subTitle={i18n.t('For_your_security_you_must_enter_your_current_password_to_continue')}
|
||||
/>
|
||||
<FormTextInput
|
||||
value={password}
|
||||
placeholder={i18n.t('Password')}
|
||||
onChangeText={value => setPassword(value)}
|
||||
onSubmitEditing={handleDeleteAccount}
|
||||
theme={theme}
|
||||
testID='room-info-edit-view-name'
|
||||
secureTextEntry
|
||||
inputStyle={{ borderWidth: 2 }}
|
||||
bottomSheet
|
||||
/>
|
||||
<FooterButtons
|
||||
cancelTitle={i18n.t('Cancel')}
|
||||
cancelAction={hideActionSheet}
|
||||
confirmTitle={i18n.t('Delete_Account')}
|
||||
confirmAction={handleDeleteAccount}
|
||||
disabled={!password}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfirmDeleteAccountActionSheetContent({ changeOwnerRooms = '', removedRooms = '', password = '' }) {
|
||||
const { colors } = useTheme();
|
||||
const { hideActionSheet } = useActionSheet();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleDeleteAccount = async () => {
|
||||
hideActionSheet();
|
||||
await deleteOwnAccount(password, true);
|
||||
dispatch(deleteAccount());
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<AlertHeader title={i18n.t('Are_you_sure_question_mark')} subTitle={i18n.t('Deleting_a_user_will_delete_all_messages')} />
|
||||
{!!changeOwnerRooms && (
|
||||
<Text style={{ ...styles.subTitleContainerText, color: colors.dangerColor }}>{changeOwnerRooms}</Text>
|
||||
)}
|
||||
{!!removedRooms && <Text style={{ ...styles.subTitleContainerText, color: colors.dangerColor }}>{removedRooms}</Text>}
|
||||
<FooterButtons
|
||||
cancelTitle={i18n.t('Cancel')}
|
||||
cancelAction={hideActionSheet}
|
||||
confirmTitle={i18n.t('Delete_Account_confirm')}
|
||||
confirmAction={handleDeleteAccount}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../../../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
paddingTop: 16,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
paddingBottom: 32
|
||||
},
|
||||
titleContainer: {
|
||||
paddingRight: 80,
|
||||
marginBottom: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
titleContainerText: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textSemibold,
|
||||
paddingLeft: 16
|
||||
},
|
||||
subTitleContainerText: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular,
|
||||
marginBottom: 10
|
||||
}
|
||||
});
|
|
@ -41,8 +41,10 @@ import {
|
|||
IProfileParams,
|
||||
IUser
|
||||
} from '../../definitions';
|
||||
import { withActionSheet, IActionSheetProvider } from '../../containers/ActionSheet';
|
||||
import { DeleteAccountActionSheetContent } from './components/DeleteAccountActionSheetContent';
|
||||
|
||||
interface IProfileViewProps extends IBaseScreen<ProfileStackParamList, 'ProfileView'> {
|
||||
interface IProfileViewProps extends IActionSheetProvider, IBaseScreen<ProfileStackParamList, 'ProfileView'> {
|
||||
user: IUser;
|
||||
baseUrl: string;
|
||||
Accounts_AllowEmailChange: boolean;
|
||||
|
@ -52,6 +54,8 @@ interface IProfileViewProps extends IBaseScreen<ProfileStackParamList, 'ProfileV
|
|||
Accounts_AllowUsernameChange: boolean;
|
||||
Accounts_CustomFields: string;
|
||||
theme: TSupportedThemes;
|
||||
Accounts_AllowDeleteOwnAccount: boolean;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
interface IProfileViewState {
|
||||
|
@ -76,7 +80,8 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
private avatarUrl?: TextInput;
|
||||
private newPassword?: TextInput;
|
||||
|
||||
static navigationOptions = ({ navigation, isMasterDetail }: IProfileViewProps) => {
|
||||
setHeader = () => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
const options: StackNavigationOptions = {
|
||||
title: I18n.t('Profile')
|
||||
};
|
||||
|
@ -86,9 +91,15 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
options.headerRight = () => (
|
||||
<HeaderButton.Preferences onPress={() => navigation?.navigate('UserPreferencesView')} testID='preferences-view-open' />
|
||||
);
|
||||
return options;
|
||||
|
||||
navigation.setOptions(options);
|
||||
};
|
||||
|
||||
constructor(props: IProfileViewProps) {
|
||||
super(props);
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
state: IProfileViewState = {
|
||||
saving: false,
|
||||
name: '',
|
||||
|
@ -475,6 +486,14 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
});
|
||||
};
|
||||
|
||||
deleteOwnAccount = () => {
|
||||
logEvent(events.DELETE_OWN_ACCOUNT);
|
||||
this.props.showActionSheet({
|
||||
children: <DeleteAccountActionSheetContent />,
|
||||
headerHeight: 225
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, username, email, newPassword, avatarUrl, customFields, avatar, saving } = this.state;
|
||||
const {
|
||||
|
@ -485,7 +504,8 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
Accounts_AllowRealNameChange,
|
||||
Accounts_AllowUserAvatarChange,
|
||||
Accounts_AllowUsernameChange,
|
||||
Accounts_CustomFields
|
||||
Accounts_CustomFields,
|
||||
Accounts_AllowDeleteOwnAccount
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -605,6 +625,15 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
onPress={this.logoutOtherLocations}
|
||||
testID='profile-view-logout-other-locations'
|
||||
/>
|
||||
{Accounts_AllowDeleteOwnAccount ? (
|
||||
<Button
|
||||
title={I18n.t('Delete_my_account')}
|
||||
type='primary'
|
||||
backgroundColor={themes[theme].dangerColor}
|
||||
onPress={this.deleteOwnAccount}
|
||||
testID='profile-view-delete-my-account'
|
||||
/>
|
||||
) : null}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
|
@ -620,7 +649,9 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
Accounts_AllowUserAvatarChange: state.settings.Accounts_AllowUserAvatarChange as boolean,
|
||||
Accounts_AllowUsernameChange: state.settings.Accounts_AllowUsernameChange as boolean,
|
||||
Accounts_CustomFields: state.settings.Accounts_CustomFields as string,
|
||||
baseUrl: state.server.server
|
||||
baseUrl: state.server.server,
|
||||
Accounts_AllowDeleteOwnAccount: state.settings.Accounts_AllowDeleteOwnAccount as boolean,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(ProfileView));
|
||||
export default connect(mapStateToProps)(withTheme(withActionSheet(ProfileView)));
|
||||
|
|
|
@ -59,3 +59,12 @@ jest.mock('react-native-notifications', () => ({
|
|||
})
|
||||
}
|
||||
}));
|
||||
|
||||
jest.mock('@gorhom/bottom-sheet', () => {
|
||||
const react = require('react-native');
|
||||
return {
|
||||
__esModule: true,
|
||||
default: react.View,
|
||||
BottomSheetScrollView: react.ScrollView
|
||||
};
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"dependencies": {
|
||||
"@bugsnag/react-native": "^7.10.5",
|
||||
"@codler/react-native-keyboard-aware-scroll-view": "^1.0.1",
|
||||
"@gorhom/bottom-sheet": "^4",
|
||||
"@gorhom/bottom-sheet": "^4.3.1",
|
||||
"@nozbe/watermelondb": "0.23.0",
|
||||
"@react-native-clipboard/clipboard": "^1.8.5",
|
||||
"@react-native-community/art": "^1.2.0",
|
||||
|
|
File diff suppressed because one or more lines are too long
29
yarn.lock
29
yarn.lock
|
@ -3018,22 +3018,22 @@
|
|||
resolved "https://registry.yarnpkg.com/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c"
|
||||
integrity sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==
|
||||
|
||||
"@gorhom/bottom-sheet@^4":
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.1.5.tgz#35341d45799de28082c380db6639537b04fa0b26"
|
||||
integrity sha512-3F5P8jK3NXwT2lGwkAdkdLwDVHaRvMZalUTXjK6Ogf0Tki6idffJ3TNlQZlg8k6+OnXAx0+i80f4XaI+J4GFrA==
|
||||
"@gorhom/bottom-sheet@^4.3.1":
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.3.2.tgz#83de68387f54f6e3cc9419329b8e4f925a3426c8"
|
||||
integrity sha512-kOnxKz3TuxbwagxhHvAyo2b1fgUCzw/Xs3OQD2lTE7vjGEdibsYnRaBxdpKKLV25VOQA6zVFmZq9apICj9foPw==
|
||||
dependencies:
|
||||
"@gorhom/portal" "^1.0.11"
|
||||
"@gorhom/portal" "1.0.13"
|
||||
invariant "^2.2.4"
|
||||
nanoid "^3.1.20"
|
||||
nanoid "^3.3.3"
|
||||
react-native-redash "^16.1.1"
|
||||
|
||||
"@gorhom/portal@^1.0.11":
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@gorhom/portal/-/portal-1.0.12.tgz#1c0deabb3f9057c736352a88bae9ca891a100346"
|
||||
integrity sha512-JOYe85RUwiksgdMbhLWDCLpH3kgFFz+LCu1lnxOMMBQSfAKtL5kkTKVrhtmQ3Lq3lJM2paGnLc4wJrlVuaC5Jw==
|
||||
"@gorhom/portal@1.0.13":
|
||||
version "1.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@gorhom/portal/-/portal-1.0.13.tgz#da3af4d427e1fa68d264107de4b3072a4adf35ce"
|
||||
integrity sha512-ViClKPkyGnj8HVMW45OGQSnGbWBVh8i3tgMOkGqpm6Cv0WVcDfUL7SER6zyGQy8Wdoj3GUDpAJFMqVOxpmRpzw==
|
||||
dependencies:
|
||||
nanoid "^3.1.23"
|
||||
nanoid "^3.3.1"
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.2.1"
|
||||
|
@ -13485,11 +13485,16 @@ nanoid@3.1.23:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
|
||||
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
|
||||
|
||||
nanoid@^3.1.20, nanoid@^3.1.23:
|
||||
nanoid@^3.1.23:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
|
||||
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
|
||||
|
||||
nanoid@^3.3.1, nanoid@^3.3.3:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
|
|
Loading…
Reference in New Issue