diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 48651b3d..753734ed 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -13,7 +13,7 @@ export type LogEvents = { withPassword: boolean } 'account:loggedOut': { - logContext: 'SwitchAccount' | 'Settings' | 'Deactivated' + logContext: 'SwitchAccount' | 'Settings' | 'SignupQueued' | 'Deactivated' } 'notifications:openApp': {} 'notifications:request': { diff --git a/src/screens/Deactivated.tsx b/src/screens/Deactivated.tsx index c9e9f952..faee517c 100644 --- a/src/screens/Deactivated.tsx +++ b/src/screens/Deactivated.tsx @@ -1,19 +1,22 @@ import React from 'react' import {View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {msg, plural, Trans} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' -import {logger} from '#/logger' +import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {isWeb} from '#/platform/detection' -import {isSessionDeactivated, useAgent, useSessionApi} from '#/state/session' -import {useOnboardingDispatch} from '#/state/shell' +import {type SessionAccount, useSession, useSessionApi} from '#/state/session' +import {useSetMinimalShellMode} from '#/state/shell' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {ScrollView} from '#/view/com/util/Views' import {Logo} from '#/view/icons/Logo' -import {atoms as a, useBreakpoints, useTheme} from '#/alf' -import {Button, ButtonIcon, ButtonText} from '#/components/Button' -import {Loader} from '#/components/Loader' -import {P, Text} from '#/components/Typography' +import {atoms as a, useTheme} from '#/alf' +import {AccountList} from '#/components/AccountList' +import {Button, ButtonText} from '#/components/Button' +import {Divider} from '#/components/Divider' +import {Text} from '#/components/Typography' const COL_WIDTH = 400 @@ -21,199 +24,151 @@ export function Deactivated() { const {_} = useLingui() const t = useTheme() const insets = useSafeAreaInsets() - const {gtMobile} = useBreakpoints() - const onboardingDispatch = useOnboardingDispatch() + const {currentAccount, accounts} = useSession() + const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() + const {setShowLoggedOut} = useLoggedOutViewControls() + const hasOtherAccounts = accounts.length > 1 + const setMinimalShellMode = useSetMinimalShellMode() const {logout} = useSessionApi() - const agent = useAgent() - const [isProcessing, setProcessing] = React.useState(false) - const [estimatedTime, setEstimatedTime] = React.useState( - undefined, - ) - const [placeInQueue, setPlaceInQueue] = React.useState( - undefined, + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(true) + }, [setMinimalShellMode]), ) - const checkStatus = React.useCallback(async () => { - setProcessing(true) - try { - const res = await agent.com.atproto.temp.checkSignupQueue() - if (res.data.activated) { - // ready to go, exchange the access token for a usable one and kick off onboarding - await agent.refreshSession() - if (!isSessionDeactivated(agent.session?.accessJwt)) { - onboardingDispatch({type: 'start'}) - } - } else { - // not ready, update UI - setEstimatedTime(msToString(res.data.estimatedTimeMs)) - if (typeof res.data.placeInQueue !== 'undefined') { - setPlaceInQueue(Math.max(res.data.placeInQueue, 1)) - } + const onSelectAccount = React.useCallback( + (account: SessionAccount) => { + if (account.did !== currentAccount?.did) { + onPressSwitchAccount(account, 'SwitchAccount') } - } catch (e: any) { - logger.error('Failed to check signup queue', {err: e.toString()}) - } finally { - setProcessing(false) - } - }, [ - setProcessing, - setEstimatedTime, - setPlaceInQueue, - onboardingDispatch, - agent, - ]) - - React.useEffect(() => { - checkStatus() - const interval = setInterval(checkStatus, 60e3) - return () => clearInterval(interval) - }, [checkStatus]) - - const checkBtn = ( - + }, + [currentAccount, onPressSwitchAccount], ) + const onPressAddAccount = React.useCallback(() => { + setShowLoggedOut(true) + }, [setShowLoggedOut]) + + const onPressLogout = React.useCallback(() => { + if (isWeb) { + // 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, '', '/') + } + logout('Deactivated') + }, [logout]) + return ( - + - - - - - - - - You're in line - -

- - There's been a rush of new users to Bluesky! We'll activate your - account as soon as we can. - -

- - - {typeof placeInQueue === 'number' && ( - - {placeInQueue} - - )} -

- {typeof placeInQueue === 'number' ? ( - left to go. - ) : ( - You are in line. - )}{' '} - {estimatedTime ? ( - - We estimate {estimatedTime} until your account is ready. - - ) : ( - - We will let you know when your account is ready. - - )} -

-
- - {isWeb && gtMobile && ( - - - {checkBtn} - - )} -
- - - -
- - {(!isWeb || !gtMobile) && ( - - {checkBtn} - + + + + + + + + + Welcome back! + + + + You previously deactivated @{currentAccount?.handle}. + + + + + You can reactivate your account to continue logging in. Your + profile and posts will be visible to other users. + + + + + + + + + + + + + + {hasOtherAccounts ? ( + <> + + Or, log into one of your other accounts. + + + + ) : ( + <> + + Or, continue with another account. + + + + )} + - )} + ) } - -function msToString(ms: number | undefined): string | undefined { - if (ms && ms > 0) { - const estimatedTimeMins = Math.ceil(ms / 60e3) - if (estimatedTimeMins > 59) { - const estimatedTimeHrs = Math.round(estimatedTimeMins / 60) - if (estimatedTimeHrs > 6) { - // dont even bother - return undefined - } - // hours - return `${estimatedTimeHrs} ${plural(estimatedTimeHrs, { - one: 'hour', - other: 'hours', - })}` - } - // minutes - return `${estimatedTimeMins} ${plural(estimatedTimeMins, { - one: 'minute', - other: 'minutes', - })}` - } - return undefined -} diff --git a/src/screens/Settings/components/DeactivateAccountDialog.tsx b/src/screens/Settings/components/DeactivateAccountDialog.tsx new file mode 100644 index 00000000..4330ffca --- /dev/null +++ b/src/screens/Settings/components/DeactivateAccountDialog.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useTheme} from '#/alf' +import {DialogOuterProps} from '#/components/Dialog' +import {Divider} from '#/components/Divider' +import * as Prompt from '#/components/Prompt' +import {Text} from '#/components/Typography' + +export function DeactivateAccountDialog({ + control, +}: { + control: DialogOuterProps['control'] +}) { + const t = useTheme() + const {_} = useLingui() + + return ( + + {_(msg`Deactivate account`)} + + + 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. + + + + + + + + + There is no time limit for account deactivation, come back any + time. + + + + + If you're trying to change your handle or email, do so before you + deactivate. + + + + + + + + {}} + color="negative" + /> + + + + ) +} diff --git a/src/screens/SignupQueued.tsx b/src/screens/SignupQueued.tsx new file mode 100644 index 00000000..4e4fedcf --- /dev/null +++ b/src/screens/SignupQueued.tsx @@ -0,0 +1,219 @@ +import React from 'react' +import {View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {msg, plural, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' +import {useOnboardingDispatch} from '#/state/shell' +import {ScrollView} from '#/view/com/util/Views' +import {Logo} from '#/view/icons/Logo' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {Loader} from '#/components/Loader' +import {P, Text} from '#/components/Typography' + +const COL_WIDTH = 400 + +export function SignupQueued() { + const {_} = useLingui() + const t = useTheme() + const insets = useSafeAreaInsets() + const {gtMobile} = useBreakpoints() + const onboardingDispatch = useOnboardingDispatch() + const {logout} = useSessionApi() + const agent = useAgent() + + const [isProcessing, setProcessing] = React.useState(false) + const [estimatedTime, setEstimatedTime] = React.useState( + undefined, + ) + const [placeInQueue, setPlaceInQueue] = React.useState( + undefined, + ) + + const checkStatus = React.useCallback(async () => { + setProcessing(true) + try { + const res = await agent.com.atproto.temp.checkSignupQueue() + if (res.data.activated) { + // ready to go, exchange the access token for a usable one and kick off onboarding + await agent.refreshSession() + if (!isSignupQueued(agent.session?.accessJwt)) { + onboardingDispatch({type: 'start'}) + } + } else { + // not ready, update UI + setEstimatedTime(msToString(res.data.estimatedTimeMs)) + if (typeof res.data.placeInQueue !== 'undefined') { + setPlaceInQueue(Math.max(res.data.placeInQueue, 1)) + } + } + } catch (e: any) { + logger.error('Failed to check signup queue', {err: e.toString()}) + } finally { + setProcessing(false) + } + }, [ + setProcessing, + setEstimatedTime, + setPlaceInQueue, + onboardingDispatch, + agent, + ]) + + React.useEffect(() => { + checkStatus() + const interval = setInterval(checkStatus, 60e3) + return () => clearInterval(interval) + }, [checkStatus]) + + const checkBtn = ( + + ) + + return ( + + + + + + + + + + You're in line + +

+ + There's been a rush of new users to Bluesky! We'll activate your + account as soon as we can. + +

+ + + {typeof placeInQueue === 'number' && ( + + {placeInQueue} + + )} +

+ {typeof placeInQueue === 'number' ? ( + left to go. + ) : ( + You are in line. + )}{' '} + {estimatedTime ? ( + + We estimate {estimatedTime} until your account is ready. + + ) : ( + + We will let you know when your account is ready. + + )} +

+
+ + {isWeb && gtMobile && ( + + + {checkBtn} + + )} +
+ + + +
+ + {(!isWeb || !gtMobile) && ( + + + {checkBtn} + + + + )} +
+ ) +} + +function msToString(ms: number | undefined): string | undefined { + if (ms && ms > 0) { + const estimatedTimeMins = Math.ceil(ms / 60e3) + if (estimatedTimeMins > 59) { + const estimatedTimeHrs = Math.round(estimatedTimeMins / 60) + if (estimatedTimeHrs > 6) { + // dont even bother + return undefined + } + // hours + return `${estimatedTimeHrs} ${plural(estimatedTimeHrs, { + one: 'hour', + other: 'hours', + })}` + } + // minutes + return `${estimatedTimeMins} ${plural(estimatedTimeMins, { + one: 'minute', + other: 'minutes', + })}` + } + return undefined +} diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 1860d34d..7d579d55 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -17,7 +17,10 @@ const accountSchema = z.object({ emailAuthFactor: z.boolean().optional(), refreshJwt: z.string().optional(), // optional because it can expire accessJwt: z.string().optional(), // optional because it can expire - deactivated: z.boolean().optional(), + signupQueued: z.boolean().optional(), + status: z + .enum(['active', 'takendown', 'suspended', 'deactivated']) + .optional(), pdsUrl: z.string().optional(), }) export type PersistedAccount = z.infer diff --git a/src/state/session/__tests__/session-test.ts b/src/state/session/__tests__/session-test.ts index daf8d70c..c8c1e103 100644 --- a/src/state/session/__tests__/session-test.ts +++ b/src/state/session/__tests__/session-test.ts @@ -50,7 +50,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-1", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -59,6 +58,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-1", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -87,7 +88,6 @@ describe('session', () => { "accounts": [ { "accessJwt": undefined, - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -96,6 +96,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -136,7 +138,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-1", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -145,6 +146,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-1", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -183,7 +186,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "bob-access-jwt-1", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -192,10 +194,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-1", "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "alice-access-jwt-1", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -204,6 +207,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-1", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -242,7 +247,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-2", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -251,10 +255,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "bob-access-jwt-1", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -263,6 +268,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-1", "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -299,7 +306,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "jay-access-jwt-1", - "deactivated": false, "did": "jay-did", "email": undefined, "emailAuthFactor": false, @@ -308,10 +314,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "jay-refresh-jwt-1", "service": "https://jay.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "alice-access-jwt-2", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -320,10 +327,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "bob-access-jwt-1", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -332,6 +340,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-1", "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -364,7 +374,6 @@ describe('session', () => { "accounts": [ { "accessJwt": undefined, - "deactivated": false, "did": "jay-did", "email": undefined, "emailAuthFactor": false, @@ -373,10 +382,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://jay.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": undefined, - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -385,10 +395,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": undefined, - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -397,6 +408,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -446,7 +459,6 @@ describe('session', () => { "accounts": [ { "accessJwt": undefined, - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -455,6 +467,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -490,7 +504,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-2", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -499,6 +512,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -601,7 +616,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "bob-access-jwt-1", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -610,6 +624,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-1", "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -681,7 +697,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-2", - "deactivated": false, "did": "alice-did", "email": "alice@foo.bar", "emailAuthFactor": false, @@ -690,6 +705,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -731,7 +748,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-3", - "deactivated": false, "did": "alice-did", "email": "alice@foo.baz", "emailAuthFactor": true, @@ -740,6 +756,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-3", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -781,7 +799,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-4", - "deactivated": false, "did": "alice-did", "email": "alice@foo.baz", "emailAuthFactor": false, @@ -790,6 +807,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-4", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -937,7 +956,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "bob-access-jwt-1", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -946,10 +964,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-1", "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "alice-access-jwt-2", - "deactivated": false, "did": "alice-did", "email": "alice@foo.bar", "emailAuthFactor": false, @@ -958,6 +977,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -997,7 +1018,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "bob-access-jwt-2", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -1006,10 +1026,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-2", "service": "https://bob.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "alice-access-jwt-2", - "deactivated": false, "did": "alice-did", "email": "alice@foo.bar", "emailAuthFactor": false, @@ -1018,6 +1039,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -1156,7 +1179,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "alice-access-jwt-1", - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -1165,6 +1187,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "alice-refresh-jwt-1", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -1218,7 +1242,6 @@ describe('session', () => { "accounts": [ { "accessJwt": undefined, - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -1227,6 +1250,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -1280,7 +1305,6 @@ describe('session', () => { "accounts": [ { "accessJwt": undefined, - "deactivated": false, "did": "alice-did", "email": undefined, "emailAuthFactor": false, @@ -1289,6 +1313,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": undefined, "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -1371,7 +1397,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "jay-access-jwt-1", - "deactivated": false, "did": "jay-did", "email": undefined, "emailAuthFactor": false, @@ -1380,10 +1405,11 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "jay-refresh-jwt-1", "service": "https://jay.com/", + "signupQueued": false, + "status": "active", }, { "accessJwt": "bob-access-jwt-2", - "deactivated": false, "did": "bob-did", "email": undefined, "emailAuthFactor": false, @@ -1392,6 +1418,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "bob-refresh-jwt-2", "service": "https://alice.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { @@ -1429,7 +1457,6 @@ describe('session', () => { "accounts": [ { "accessJwt": "clarence-access-jwt-2", - "deactivated": false, "did": "clarence-did", "email": undefined, "emailAuthFactor": false, @@ -1438,6 +1465,8 @@ describe('session', () => { "pdsUrl": undefined, "refreshJwt": "clarence-refresh-jwt-2", "service": "https://clarence.com/", + "signupQueued": false, + "status": "active", }, ], "currentAgentState": { diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 45013deb..cdd24cd1 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -16,7 +16,7 @@ import { configureModerationForGuest, } from './moderation' import {SessionAccount} from './types' -import {isSessionDeactivated, isSessionExpired} from './util' +import {isSessionExpired, isSignupQueued} from './util' export function createPublicAgent() { configureModerationForGuest() // Side effect but only relevant for tests @@ -51,7 +51,7 @@ export async function createAgentAndResume( await networkRetry(1, () => agent.resumeSession(prevSession)) } else { agent.session = prevSession - if (!storedAccount.deactivated) { + if (!storedAccount.signupQueued) { // Intentionally not awaited to unblock the UI: networkRetry(3, () => agent.resumeSession(prevSession)).catch( (e: any) => { @@ -135,7 +135,7 @@ export async function createAgentAndCreateAccount( const account = agentToSessionAccountOrThrow(agent) const gates = tryFetchGates(account.did, 'prefer-fresh-gates') const moderation = configureModerationForAccount(agent, account) - if (!account.deactivated) { + if (!account.signupQueued) { /*dont await*/ agent.upsertProfile(_existing => { return { displayName: '', @@ -234,7 +234,9 @@ export function agentToSessionAccount( emailAuthFactor: agent.session.emailAuthFactor || false, refreshJwt: agent.session.refreshJwt, accessJwt: agent.session.accessJwt, - deactivated: isSessionDeactivated(agent.session.accessJwt), + signupQueued: isSignupQueued(agent.session.accessJwt), + // @ts-expect-error TODO remove when backend is ready + status: agent.session.status || 'active', pdsUrl: agent.pdsUrl?.toString(), } } diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index e38dd2bb..371bd459 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -17,7 +17,7 @@ import { } from './agent' import {getInitialState, reducer} from './reducer' -export {isSessionDeactivated} from './util' +export {isSignupQueued} from './util' export type {SessionAccount} from '#/state/session/types' import {SessionApiContext, SessionStateContext} from '#/state/session/types' diff --git a/src/state/session/util.ts b/src/state/session/util.ts index 8948ecd6..3a5909e8 100644 --- a/src/state/session/util.ts +++ b/src/state/session/util.ts @@ -10,11 +10,12 @@ export function readLastActiveAccount() { return accounts.find(a => a.did === currentAccount?.did) } -export function isSessionDeactivated(accessJwt: string | undefined) { +export function isSignupQueued(accessJwt: string | undefined) { if (accessJwt) { const sessData = jwtDecode(accessJwt) return ( - hasProp(sessData, 'scope') && sessData.scope === 'com.atproto.deactivated' + hasProp(sessData, 'scope') && + sessData.scope === 'com.atproto.signupQueued' ) } return false diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx index 06f1e111..6dd248ca 100644 --- a/src/view/com/modals/DeleteAccount.tsx +++ b/src/view/com/modals/DeleteAccount.tsx @@ -18,7 +18,13 @@ 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 {isAndroid} from 'platform/detection' +import {isAndroid, isWeb} from 'platform/detection' +import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' +import {atoms as a, useTheme as useNewTheme} from '#/alf' +import {useDialogControl} from '#/components/Dialog' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {InlineLinkText} from '#/components/Link' +import {Text as NewText} from '#/components/Typography' import {resetToTab} from '../../../Navigation' import {ErrorMessage} from '../util/error/ErrorMessage' import {Text} from '../util/text/Text' @@ -30,6 +36,7 @@ export const snapPoints = isAndroid ? ['90%'] : ['55%'] export function Component({}: {}) { const pal = usePalette('default') const theme = useTheme() + const t = useNewTheme() const {currentAccount} = useSession() const agent = useAgent() const {removeAccount} = useSessionApi() @@ -41,6 +48,7 @@ export function Component({}: {}) { const [password, setPassword] = React.useState('') const [isProcessing, setIsProcessing] = React.useState(false) const [error, setError] = React.useState('') + const deactivateAccountControl = useDialogControl() const onPressSendEmail = async () => { setError('') setIsProcessing(true) @@ -168,6 +176,50 @@ export function Component({}: {}) { )} + + + + + + + + You can also temporarily deactivate your account instead, + and reactivate it at any time. + {' '} + { + e.preventDefault() + deactivateAccountControl.open() + return false + }}> + Click here for more information. + + + + + + ) : ( <> diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index a647ea90..d075cc69 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -60,6 +60,7 @@ import {Text} from 'view/com/util/text/Text' import * as Toast from 'view/com/util/Toast' import {UserAvatar} from 'view/com/util/UserAvatar' import {ScrollView} from 'view/com/util/Views' +import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' import {useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' @@ -307,6 +308,11 @@ export function SettingsScreen({}: Props) { Toast.show(_(msg`Legacy storage cleared, you need to restart the app now.`)) }, [_]) + const deactivateAccountControl = useDialogControl() + const onPressDeactivateAccount = React.useCallback(() => { + deactivateAccountControl.open() + }, [deactivateAccountControl]) + const {mutate: onPressDeleteChatDeclaration} = useDeleteActorDeclaration() return ( @@ -791,6 +797,29 @@ export function SettingsScreen({}: Props) { Export My Data + + + + + + + Deactivate my account + + + + } - if (hasSession && currentAccount?.deactivated) { - return + if (hasSession && currentAccount?.signupQueued) { + return } if (showLoggedOut) { return setShowLoggedOut(false)} /> } + if (currentAccount?.status === 'deactivated') { + return + } if (onboardingState.isActive) { return }