Files
bsky-social-app/src/screens/Deactivated.tsx
2026-08-13 22:13:45 +03:00

216 lines
7.3 KiB
TypeScript

import {useCallback, useState} from 'react'
import {View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
import {isErrorMaybeAppPasswordPermissions} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {
type SessionAccount,
usePdsClient,
useSession,
useSessionApi,
} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useTheme} from '#/alf'
import {AccountList} from '#/components/AccountList'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import {com} from '#/lexicons'
const COL_WIDTH = 400
export function Deactivated() {
const {_} = useLingui()
const t = useTheme()
const insets = useSafeAreaInsets()
const {currentAccount, accounts} = useSession()
const {onPressSwitchAccount, pendingDid} = useAccountSwitcher()
const {setShowLoggedOut} = useLoggedOutViewControls()
const hasOtherAccounts = accounts.length > 1
const {logoutCurrentAccount, refreshSession} = useSessionApi()
const pdsClient = usePdsClient()
const [pending, setPending] = useState(false)
const [error, setError] = useState<string | undefined>()
const queryClient = useQueryClient()
const onSelectAccount = useCallback(
(account: SessionAccount) => {
if (account.did !== currentAccount?.did) {
onPressSwitchAccount(account, 'SwitchAccount')
}
},
[currentAccount, onPressSwitchAccount],
)
const onPressAddAccount = useCallback(() => {
setShowLoggedOut(true)
}, [setShowLoggedOut])
const onPressLogout = useCallback(() => {
if (IS_WEB) {
// We're switching accounts, which remounts the entire app.
// On mobile, this gets us Home, but on the web we also need reset the URL.
// We can't change the URL via a navigate() call because the navigator
// itself is about to unmount, and it calls pushState() too late.
// So we change the URL ourselves. The navigator will pick it up on remount.
history.pushState(null, '', '/')
}
logoutCurrentAccount('Deactivated')
}, [logoutCurrentAccount])
const handleActivate = useCallback(async () => {
try {
setPending(true)
await pdsClient.call(com.atproto.server.activateAccount)
await queryClient.resetQueries()
await refreshSession()
} catch (e: any) {
/*
* `activateAccount` declares no lexicon errors, so the app-password case
* arrives as an undeclared code plus a message. The shared helper matches
* both that and the plain-string form the old exact `e.message` switch
* relied on.
*/
if (isErrorMaybeAppPasswordPermissions(e)) {
setError(
_(
msg`You're signed in with an App Password. Please sign in with your main password to continue deactivating your account.`,
),
)
} else {
setError(_(msg`Something went wrong, please try again`))
}
logger.error(e, {
message: 'Failed to activate account',
})
} finally {
setPending(false)
}
}, [_, pdsClient, refreshSession, setPending, setError, queryClient])
return (
<View style={[a.util_screen_outer, a.flex_1]}>
<Layout.Content
ignoreTabletLayoutOffset
contentContainerStyle={[
a.px_2xl,
{
paddingTop: IS_WEB ? 64 : insets.top + 16,
paddingBottom: IS_WEB ? 64 : insets.bottom,
},
]}>
<View
style={[a.w_full, {marginHorizontal: 'auto', maxWidth: COL_WIDTH}]}>
<View style={[a.w_full, a.justify_center, a.align_center, a.pb_5xl]}>
<Logo allowVariants={false} width={40} />
</View>
<View style={[a.gap_xs, a.pb_3xl]}>
<Text style={[a.text_xl, a.font_semi_bold, a.leading_snug]}>
<Trans>Welcome back!</Trans>
</Text>
<Text style={[a.text_sm, a.leading_snug]}>
<Trans>
You previously deactivated @{currentAccount?.handle}.
</Trans>
</Text>
<Text style={[a.text_sm, a.leading_snug, a.pb_md]}>
<Trans>
You can reactivate your account to continue logging in. Your
profile and posts will be visible to other users.
</Trans>
</Text>
<View style={[a.gap_sm]}>
<Button
label={_(msg`Reactivate your account`)}
size="large"
variant="solid"
color="primary"
onPress={handleActivate}>
<ButtonText>
<Trans>Yes, reactivate my account</Trans>
</ButtonText>
{pending && <ButtonIcon icon={Loader} position="right" />}
</Button>
<Button
label={_(msg`Cancel reactivation and sign out`)}
size="large"
variant="solid"
color="secondary"
onPress={onPressLogout}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
</View>
{error && (
<View
style={[
a.flex_row,
a.gap_sm,
a.mt_md,
a.p_md,
a.rounded_sm,
t.atoms.bg_contrast_25,
]}>
<CircleInfo size="md" fill={t.palette.negative_400} />
<Text style={[a.flex_1, a.leading_snug]}>{error}</Text>
</View>
)}
</View>
<View style={[a.pb_3xl]}>
<Divider />
</View>
{hasOtherAccounts ? (
<>
<Text
style={[t.atoms.text_contrast_medium, a.pb_md, a.leading_snug]}>
<Trans>Or, sign in to one of your other accounts.</Trans>
</Text>
<AccountList
onSelectAccount={onSelectAccount}
onSelectOther={onPressAddAccount}
otherLabel={_(msg`Add account`)}
pendingDid={pendingDid}
/>
</>
) : (
<>
<Text
style={[t.atoms.text_contrast_medium, a.pb_md, a.leading_snug]}>
<Trans>Or, continue with another account.</Trans>
</Text>
<Button
label={_(msg`Sign in or create an account`)}
size="large"
variant="solid"
color="secondary"
onPress={() => setShowLoggedOut(true)}>
<ButtonText>
<Trans>Sign in or create an account</Trans>
</ButtonText>
</Button>
</>
)}
</View>
</Layout.Content>
</View>
)
}