Refactor delete account dialog using ALF (#9863)

This commit is contained in:
DS Boyce
2026-02-16 12:00:46 -08:00
committed by GitHub
parent cc2255cc49
commit 49253dbd88
7 changed files with 381 additions and 359 deletions
@@ -32,8 +32,11 @@ export function TokenField({
<TextField.Root>
<TextField.Icon icon={Shield} />
<TextField.Input
autoComplete="off"
autoCorrect={false}
isInvalid={isInvalid}
label={_(msg`Confirmation code`)}
maxLength={11}
placeholder="XXXXX-XXXXX"
value={value}
onChangeText={handleOnChangeText}
+7 -3
View File
@@ -3,7 +3,6 @@ import {useLingui} from '@lingui/react'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {type CommonNavigatorParams} from '#/lib/routes/types'
import {useModalControls} from '#/state/modals'
import {useSession} from '#/state/session'
import * as SettingsList from '#/screens/Settings/components/SettingsList'
import {atoms as a, useTheme} from '#/alf'
@@ -27,6 +26,7 @@ import * as Layout from '#/components/Layout'
import {ChangeHandleDialog} from './components/ChangeHandleDialog'
import {ChangePasswordDialog} from './components/ChangePasswordDialog'
import {DeactivateAccountDialog} from './components/DeactivateAccountDialog'
import {DeleteAccountDialog} from './components/DeleteAccountDialog'
import {ExportCarDialog} from './components/ExportCarDialog'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AccountSettings'>
@@ -34,13 +34,13 @@ export function AccountSettingsScreen({}: Props) {
const t = useTheme()
const {_} = useLingui()
const {currentAccount} = useSession()
const {openModal} = useModalControls()
const emailDialogControl = useEmailDialogControl()
const birthdayControl = useDialogControl()
const changeHandleControl = useDialogControl()
const changePasswordControl = useDialogControl()
const exportCarControl = useDialogControl()
const deactivateAccountControl = useDialogControl()
const deleteAccountControl = useDialogControl()
return (
<Layout.Screen>
@@ -169,7 +169,7 @@ export function AccountSettingsScreen({}: Props) {
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={_(msg`Delete account`)}
onPress={() => openModal({name: 'delete-account'})}
onPress={() => deleteAccountControl.open()}
destructive>
<SettingsList.ItemIcon icon={Trash_Stroke2_Corner2_Rounded} />
<SettingsList.ItemText>
@@ -185,6 +185,10 @@ export function AccountSettingsScreen({}: Props) {
<ChangePasswordDialog control={changePasswordControl} />
<ExportCarDialog control={exportCarControl} />
<DeactivateAccountDialog control={deactivateAccountControl} />
<DeleteAccountDialog
control={deleteAccountControl}
deactivateDialogControl={deactivateAccountControl}
/>
</Layout.Screen>
)
}
@@ -0,0 +1,371 @@
import {useCallback, useRef, useState} from 'react'
import {type TextInput, View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {useCleanError} from '#/lib/hooks/useCleanError'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {useAgent, useSession, useSessionApi} from '#/state/session'
import {atoms as a, useTheme, web} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {type DialogOuterProps} from '#/components/Dialog'
import {
isValidCode,
TokenField,
} from '#/components/dialogs/EmailDialog/components/TokenField'
import * as TextField from '#/components/forms/TextField'
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
import {createStaticClick, InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import * as toast from '#/components/Toast'
import {Span, Text} from '#/components/Typography'
import {resetToTab} from '#/Navigation'
const WHITESPACE_RE = /\s/gu
const PASSWORD_MIN_LENGTH = 8
enum Step {
SEND_CODE,
VERIFY_CODE,
CONFIRM_DELETION,
}
enum EmailState {
DEFAULT,
PENDING,
}
function isPasswordValid(password: string) {
return password.length >= PASSWORD_MIN_LENGTH
}
export function DeleteAccountDialog({
control,
deactivateDialogControl,
}: {
control: DialogOuterProps['control']
deactivateDialogControl: DialogOuterProps['control']
}) {
return (
<Prompt.Outer control={control}>
<DeleteAccountDialogInner
control={control}
deactivateDialogControl={deactivateDialogControl}
/>
</Prompt.Outer>
)
}
function DeleteAccountDialogInner({
control,
deactivateDialogControl,
}: {
control: DialogOuterProps['control']
deactivateDialogControl: DialogOuterProps['control']
}) {
const passwordRef = useRef<TextInput | null>(null)
const t = useTheme()
const {_} = useLingui()
const cleanError = useCleanError()
const agent = useAgent()
const {currentAccount} = useSession()
const {removeAccount} = useSessionApi()
const [emailState, setEmailState] = useState(EmailState.DEFAULT)
const [emailSentCount, setEmailSentCount] = useState(0)
const [step, setStep] = useState(Step.SEND_CODE)
const [confirmCode, setConfirmCode] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const sendEmail = useCallback(async () => {
if (emailState === EmailState.PENDING) {
return
}
try {
setEmailState(EmailState.PENDING)
await agent.com.atproto.server.requestAccountDelete()
setError('')
setEmailSentCount(prevCount => prevCount + 1)
setStep(Step.VERIFY_CODE)
} catch (e: any) {
const {clean, raw} = cleanError(e)
const error = clean || raw || e
setError(error)
logger.error(raw || e, {
message: 'Failed to send account deletion verification email',
})
} finally {
setEmailState(EmailState.DEFAULT)
}
}, [agent, cleanError, emailState, setEmailState])
const confirmDeletion = useCallback(async () => {
try {
setError('')
if (!currentAccount?.did) {
throw new Error('Invalid did')
}
const token = confirmCode.replace(WHITESPACE_RE, '')
// Inform chat service of intent to delete account.
const {success} = await agent.api.chat.bsky.actor.deleteAccount(
undefined,
{
headers: DM_SERVICE_HEADERS,
},
)
if (!success) {
throw new Error('Failed to inform chat service of account deletion')
}
await agent.com.atproto.server.deleteAccount({
did: currentAccount.did,
password,
token,
})
control.close(() => {
toast.show(_(msg`Your account has been deleted, see ya! ✌️`))
resetToTab('HomeTab')
removeAccount(currentAccount)
})
} catch (e: any) {
const {clean, raw} = cleanError(e)
const error = clean || raw || e
setError(error)
logger.error(raw || e, {
message: 'Failed to delete account',
})
setConfirmCode('')
setPassword('')
setStep(Step.VERIFY_CODE)
}
}, [
_,
agent,
cleanError,
confirmCode,
control,
currentAccount,
password,
removeAccount,
])
const handleDeactivate = useCallback(() => {
control.close(() => deactivateDialogControl.open())
}, [control, deactivateDialogControl])
const handleSendEmail = useCallback(() => {
void sendEmail()
}, [sendEmail])
const handleSubmitConfirmCode = useCallback(() => {
passwordRef.current?.focus()
}, [])
const handleDeleteAccount = useCallback(() => {
setStep(Step.CONFIRM_DELETION)
}, [setStep])
const handleConfirmDeletion = useCallback(() => {
void confirmDeletion()
}, [confirmDeletion])
const currentHandle = sanitizeHandle(currentAccount?.handle ?? '', '@')
const currentEmail = currentAccount?.email ?? '(no email)'
switch (step) {
case Step.SEND_CODE:
return (
<>
<Prompt.Content>
<Prompt.TitleText>
{_(msg`Delete account “${currentHandle}`)}
</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>
For security reasons, well need to send a confirmation code to
your email address{' '}
<Span style={[a.font_semi_bold, t.atoms.text]}>
{currentEmail}
</Span>
.
</Trans>
</Prompt.DescriptionText>
</Prompt.Content>
<Prompt.Actions>
<Button
color="primary"
label={_(msg`Send email`)}
size="large"
onPress={handleSendEmail}>
<ButtonText>{_(msg`Send email`)}</ButtonText>
<ButtonIcon
icon={emailState === EmailState.PENDING ? Loader : Envelope}
/>
</Button>
<Prompt.Cancel />
</Prompt.Actions>
{error && (
<Admonition style={[a.mt_lg]} type="error">
<Text style={[a.flex_1, a.leading_snug]}>{error}</Text>
</Admonition>
)}
<Admonition style={[a.mt_lg]} type="tip">
<Trans>
You can also{' '}
<Span
style={[{color: t.palette.primary_500}, web(a.underline)]}
onPress={handleDeactivate}>
temporarily deactivate
</Span>{' '}
your account instead. Your profile, posts, feeds, and lists will
no longer be visible to other Bluesky users. You can reactivate
your account at any time by logging in.
</Trans>
</Admonition>
</>
)
case Step.VERIFY_CODE:
return (
<>
<Prompt.Content>
<Prompt.TitleText>
{_(msg`Delete account “${currentHandle}`)}
</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>
Check{' '}
<Span style={[a.font_semi_bold, t.atoms.text]}>
{currentEmail}
</Span>{' '}
for an email with the confirmation code to enter below:
</Trans>
</Prompt.DescriptionText>
</Prompt.Content>
<View style={[a.mb_xs]}>
<TextField.LabelText>
<Trans>Confirmation code</Trans>
</TextField.LabelText>
<TokenField
value={confirmCode}
onChangeText={setConfirmCode}
onSubmitEditing={handleSubmitConfirmCode}
/>
</View>
<Text
style={[
a.text_sm,
a.leading_snug,
a.mb_lg,
t.atoms.text_contrast_medium,
]}>
{emailSentCount > 1 ? (
<Trans>
Email sent!{' '}
<InlineLinkText
label={_(msg`Resend`)}
{...createStaticClick(() => {
void handleSendEmail()
})}>
Click here to resend.
</InlineLinkText>
</Trans>
) : (
<Trans>
Dont see a code?{' '}
<InlineLinkText
label={_(msg`Resend`)}
{...createStaticClick(() => {
void handleSendEmail()
})}>
Click here to resend.
</InlineLinkText>
</Trans>
)}{' '}
<Span style={{top: 1}}>
{emailState === EmailState.PENDING ? <Loader size="xs" /> : null}
</Span>
</Text>
<View style={[a.mb_xl]}>
<TextField.LabelText>
<Trans>Password</Trans>
</TextField.LabelText>
<TextField.Root>
<TextField.Icon icon={Lock} />
<TextField.Input
inputRef={passwordRef}
testID="newPasswordInput"
label={_(msg`Enter your password`)}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="done"
secureTextEntry={true}
autoComplete="off"
clearButtonMode="while-editing"
passwordRules={`minlength: ${PASSWORD_MIN_LENGTH}};`}
value={password}
onChangeText={setPassword}
onSubmitEditing={handleDeleteAccount}
/>
</TextField.Root>
</View>
<Prompt.Actions>
<Button
color="negative"
disabled={!isValidCode(confirmCode) || !isPasswordValid(password)}
size="large"
label={_(msg`Delete My Account`)}
onPress={handleDeleteAccount}>
<ButtonText>{_(msg`Delete My Account`)}</ButtonText>
</Button>
<Prompt.Cancel />
</Prompt.Actions>
{error && (
<Admonition style={[a.mt_lg]} type="error">
<Text style={[a.flex_1, a.leading_snug]}>{error}</Text>
</Admonition>
)}
</>
)
case Step.CONFIRM_DELETION:
return (
<>
<Prompt.Content>
<Prompt.TitleText>
{_(msg`Are you really, really sure?`)}
</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>
This will irreversably delete your Bluesky account{' '}
<Span style={[a.font_semi_bold, t.atoms.text]}>
{currentHandle}
</Span>{' '}
and all associated data. Note that this will affect any other{' '}
<InlineLinkText
label={_(msg`Learn more about the AT Protocol.`)}
style={[a.text_md]}
to="https://bsky.social/about/faq">
AT Protocol
</InlineLinkText>{' '}
services you use with this account.
</Trans>
</Prompt.DescriptionText>
</Prompt.Content>
<Prompt.Actions>
<Button
color="negative"
size="large"
label={_(msg`Yes, delete my account`)}
onPress={handleConfirmDeletion}>
<ButtonText>{_(msg`Yes, delete my account`)}</ButtonText>
</Button>
<Prompt.Cancel />
</Prompt.Actions>
</>
)
}
}
-7
View File
@@ -11,10 +11,6 @@ export interface UserAddRemoveListsModal {
onRemove?: (listUri: string) => void
}
export interface DeleteAccountModal {
name: 'delete-account'
}
export interface ContentLanguagesSettingsModal {
name: 'content-languages-settings'
}
@@ -23,9 +19,6 @@ export interface ContentLanguagesSettingsModal {
* @deprecated DO NOT ADD NEW MODALS
*/
export type Modal =
// Account
| DeleteAccountModal
// Curation
| ContentLanguagesSettingsModal
-342
View File
@@ -1,342 +0,0 @@
import React from 'react'
import {
ActivityIndicator,
SafeAreaView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import {LinearGradient} from 'expo-linear-gradient'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {cleanError} from '#/lib/strings/errors'
import {colors, gradients, s} from '#/lib/styles'
import {useTheme} from '#/lib/ThemeContext'
import {useModalControls} from '#/state/modals'
import {useAgent, useSession, useSessionApi} from '#/state/session'
import {atoms as a, useTheme as useNewTheme} from '#/alf'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Text as NewText} from '#/components/Typography'
import {IS_ANDROID, IS_WEB} from '#/env'
import {resetToTab} from '../../../Navigation'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast'
import {ScrollView, TextInput} from './util'
export const snapPoints = IS_ANDROID ? ['90%'] : ['55%']
export function Component({}: {}) {
const pal = usePalette('default')
const theme = useTheme()
const t = useNewTheme()
const {currentAccount} = useSession()
const agent = useAgent()
const {removeAccount} = useSessionApi()
const {_} = useLingui()
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
const [confirmCode, setConfirmCode] = React.useState<string>('')
const [password, setPassword] = React.useState<string>('')
const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
const [error, setError] = React.useState<string>('')
const onPressSendEmail = async () => {
setError('')
setIsProcessing(true)
try {
await agent.com.atproto.server.requestAccountDelete()
setIsEmailSent(true)
} catch (e: any) {
setError(cleanError(e))
}
setIsProcessing(false)
}
const onPressConfirmDelete = async () => {
if (!currentAccount?.did) {
throw new Error(`DeleteAccount modal: currentAccount.did is undefined`)
}
setError('')
setIsProcessing(true)
const token = confirmCode.replace(/\s/g, '')
try {
// inform chat service of intent to delete account
const {success} = await agent.api.chat.bsky.actor.deleteAccount(
undefined,
{
headers: DM_SERVICE_HEADERS,
},
)
if (!success) {
throw new Error('Failed to inform chat service of account deletion')
}
await agent.com.atproto.server.deleteAccount({
did: currentAccount.did,
password,
token,
})
Toast.show(_(msg`Your account has been deleted`))
resetToTab('HomeTab')
removeAccount(currentAccount)
closeModal()
} catch (e: any) {
setError(cleanError(e))
}
setIsProcessing(false)
}
const onCancel = () => {
closeModal()
}
return (
<SafeAreaView style={[s.flex1]}>
<ScrollView style={[pal.view]} keyboardShouldPersistTaps="handled">
<View style={[styles.titleContainer, pal.view]}>
<Text type="title-xl" style={[s.textCenter, pal.text]}>
<Trans>
Delete Account{' '}
<Text type="title-xl" style={[pal.text, s.bold]}>
"
</Text>
<Text
type="title-xl"
numberOfLines={1}
style={[
isMobile ? styles.titleMobile : styles.titleDesktop,
pal.text,
s.bold,
]}>
{currentAccount?.handle}
</Text>
<Text type="title-xl" style={[pal.text, s.bold]}>
"
</Text>
</Trans>
</Text>
</View>
{!isEmailSent ? (
<>
<Text type="lg" style={[styles.description, pal.text]}>
<Trans>
For security reasons, we'll need to send a confirmation code to
your email address.
</Trans>
</Text>
{error ? (
<View style={s.mt10}>
<ErrorMessage message={error} />
</View>
) : undefined}
{isProcessing ? (
<View style={[styles.btn, s.mt10]}>
<ActivityIndicator />
</View>
) : (
<>
<TouchableOpacity
style={styles.mt20}
onPress={onPressSendEmail}
accessibilityRole="button"
accessibilityLabel={_(msg`Send email`)}
accessibilityHint={_(
msg`Sends email with confirmation code for account deletion`,
)}>
<LinearGradient
colors={[
gradients.blueLight.start,
gradients.blueLight.end,
]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="button-lg" style={[s.white, s.bold]}>
<Trans context="action">Send Email</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
<TouchableOpacity
style={[styles.btn, s.mt10]}
onPress={onCancel}
accessibilityRole="button"
accessibilityLabel={_(msg`Cancel account deletion`)}
accessibilityHint=""
onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}>
<Trans context="action">Cancel</Trans>
</Text>
</TouchableOpacity>
</>
)}
<View style={[!IS_WEB && a.px_xl]}>
<View
style={[
a.w_full,
a.flex_row,
a.gap_sm,
a.mt_lg,
a.p_lg,
a.rounded_sm,
t.atoms.bg_contrast_25,
]}>
<CircleInfo
size="md"
style={[
a.relative,
{
top: -1,
},
]}
/>
<NewText style={[a.leading_snug, a.flex_1]}>
<Trans>
You can also temporarily deactivate your account instead,
and reactivate it at any time.
</Trans>
</NewText>
</View>
</View>
</>
) : (
<>
{/* TODO: Update this label to be more concise */}
<Text
type="lg"
style={[pal.text, styles.description]}
nativeID="confirmationCode">
<Trans>
Check your inbox for an email with the confirmation code to
enter below:
</Trans>
</Text>
<TextInput
style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]}
placeholder={_(msg`Confirmation code`)}
placeholderTextColor={pal.textLight.color}
keyboardAppearance={theme.colorScheme}
value={confirmCode}
onChangeText={setConfirmCode}
accessibilityLabelledBy="confirmationCode"
accessibilityLabel={_(msg`Confirmation code`)}
accessibilityHint={_(
msg`Input confirmation code for account deletion`,
)}
/>
<Text
type="lg"
style={[pal.text, styles.description]}
nativeID="password">
<Trans>Please enter your password as well:</Trans>
</Text>
<TextInput
style={[styles.textInput, pal.borderDark, pal.text]}
placeholder={_(msg`Password`)}
placeholderTextColor={pal.textLight.color}
keyboardAppearance={theme.colorScheme}
secureTextEntry
value={password}
onChangeText={setPassword}
accessibilityLabelledBy="password"
accessibilityLabel={_(msg`Password`)}
accessibilityHint={_(msg`Input password for account deletion`)}
/>
{error ? (
<View style={styles.mt20}>
<ErrorMessage message={error} />
</View>
) : undefined}
{isProcessing ? (
<View style={[styles.btn, s.mt10]}>
<ActivityIndicator />
</View>
) : (
<>
<TouchableOpacity
style={[styles.btn, styles.evilBtn, styles.mt20]}
onPress={onPressConfirmDelete}
accessibilityRole="button"
accessibilityLabel={_(msg`Confirm delete account`)}
accessibilityHint="">
<Text type="button-lg" style={[s.white, s.bold]}>
<Trans>Delete my account</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.btn, s.mt10]}
onPress={onCancel}
accessibilityRole="button"
accessibilityLabel={_(msg`Cancel account deletion`)}
accessibilityHint={_(msg`Exits account deletion process`)}
onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}>
<Trans context="action">Cancel</Trans>
</Text>
</TouchableOpacity>
</>
)}
</>
)}
</ScrollView>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
titleContainer: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
flexWrap: 'wrap',
marginTop: 12,
marginBottom: 12,
marginLeft: 20,
marginRight: 20,
},
titleMobile: {
textAlign: 'center',
},
titleDesktop: {
textAlign: 'center',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
// @ts-ignore only rendered on web
maxWidth: '400px',
},
description: {
textAlign: 'center',
paddingHorizontal: 22,
marginBottom: 10,
},
mt20: {
marginTop: 20,
},
mb20: {
marginBottom: 20,
},
textInput: {
borderWidth: 1,
borderRadius: 6,
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 20,
marginHorizontal: 20,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 32,
padding: 14,
marginHorizontal: 20,
},
evilBtn: {
backgroundColor: colors.red4,
},
})
-4
View File
@@ -7,7 +7,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
import {useModalControls, useModals} from '#/state/modals'
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import * as DeleteAccountModal from './DeleteAccount'
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
@@ -45,9 +44,6 @@ export function ModalsContainer() {
if (activeModal?.name === 'user-add-remove-lists') {
snapPoints = UserAddRemoveListsModal.snapPoints
element = <UserAddRemoveListsModal.Component {...activeModal} />
} else if (activeModal?.name === 'delete-account') {
snapPoints = DeleteAccountModal.snapPoints
element = <DeleteAccountModal.Component />
} else if (activeModal?.name === 'content-languages-settings') {
snapPoints = ContentLanguagesSettingsModal.snapPoints
element = <ContentLanguagesSettingsModal.Component />
-3
View File
@@ -6,7 +6,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {type Modal as ModalIface} from '#/state/modals'
import {useModalControls, useModals} from '#/state/modals'
import * as DeleteAccountModal from './DeleteAccount'
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
import * as UserAddRemoveLists from './UserAddRemoveLists'
@@ -48,8 +47,6 @@ function Modal({modal}: {modal: ModalIface}) {
let element
if (modal.name === 'user-add-remove-lists') {
element = <UserAddRemoveLists.Component {...modal} />
} else if (modal.name === 'delete-account') {
element = <DeleteAccountModal.Component />
} else if (modal.name === 'content-languages-settings') {
element = <ContentLanguagesSettingsModal.Component />
} else {