Move global "Sign out" out of the current account row (#4941)
* Rename logout to logoutEveryAccount * Add logoutCurrentAccount() * Make all "Log out" buttons refer to current account Each of these usages is completely contextual and refers to a specific account. * Add Sign out of all accounts to Settings * Move single account Sign Out below as well * Prompt on account removal * Add Other Accounts header to reduce ambiguity * Spacing fix --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>zio/stable
parent
f3b57dd456
commit
b6e515c664
|
@ -14,6 +14,7 @@ export type LogEvents = {
|
||||||
}
|
}
|
||||||
'account:loggedOut': {
|
'account:loggedOut': {
|
||||||
logContext: 'SwitchAccount' | 'Settings' | 'SignupQueued' | 'Deactivated'
|
logContext: 'SwitchAccount' | 'Settings' | 'SignupQueued' | 'Deactivated'
|
||||||
|
scope: 'current' | 'every'
|
||||||
}
|
}
|
||||||
'notifications:openApp': {}
|
'notifications:openApp': {}
|
||||||
'notifications:request': {
|
'notifications:request': {
|
||||||
|
|
|
@ -38,7 +38,7 @@ export function Deactivated() {
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
const hasOtherAccounts = accounts.length > 1
|
const hasOtherAccounts = accounts.length > 1
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {logout} = useSessionApi()
|
const {logoutCurrentAccount} = useSessionApi()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const [pending, setPending] = React.useState(false)
|
const [pending, setPending] = React.useState(false)
|
||||||
const [error, setError] = React.useState<string | undefined>()
|
const [error, setError] = React.useState<string | undefined>()
|
||||||
|
@ -72,8 +72,8 @@ export function Deactivated() {
|
||||||
// So we change the URL ourselves. The navigator will pick it up on remount.
|
// So we change the URL ourselves. The navigator will pick it up on remount.
|
||||||
history.pushState(null, '', '/')
|
history.pushState(null, '', '/')
|
||||||
}
|
}
|
||||||
logout('Deactivated')
|
logoutCurrentAccount('Deactivated')
|
||||||
}, [logout])
|
}, [logoutCurrentAccount])
|
||||||
|
|
||||||
const handleActivate = React.useCallback(async () => {
|
const handleActivate = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -35,7 +35,7 @@ function DeactivateAccountDialogInner({
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const {logout} = useSessionApi()
|
const {logoutCurrentAccount} = useSessionApi()
|
||||||
const [pending, setPending] = React.useState(false)
|
const [pending, setPending] = React.useState(false)
|
||||||
const [error, setError] = React.useState<string | undefined>()
|
const [error, setError] = React.useState<string | undefined>()
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ function DeactivateAccountDialogInner({
|
||||||
setPending(true)
|
setPending(true)
|
||||||
await agent.com.atproto.server.deactivateAccount({})
|
await agent.com.atproto.server.deactivateAccount({})
|
||||||
control.close(() => {
|
control.close(() => {
|
||||||
logout('Deactivated')
|
logoutCurrentAccount('Deactivated')
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
switch (e.message) {
|
switch (e.message) {
|
||||||
|
@ -66,7 +66,7 @@ function DeactivateAccountDialogInner({
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false)
|
setPending(false)
|
||||||
}
|
}
|
||||||
}, [agent, control, logout, _, setPending])
|
}, [agent, control, logoutCurrentAccount, _, setPending])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function SignupQueued() {
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
const {logout} = useSessionApi()
|
const {logoutCurrentAccount} = useSessionApi()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
|
||||||
const [isProcessing, setProcessing] = React.useState(false)
|
const [isProcessing, setProcessing] = React.useState(false)
|
||||||
|
@ -153,7 +153,7 @@ export function SignupQueued() {
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
label={_(msg`Log out`)}
|
label={_(msg`Log out`)}
|
||||||
onPress={() => logout('SignupQueued')}>
|
onPress={() => logoutCurrentAccount('SignupQueued')}>
|
||||||
<ButtonText style={[{color: t.palette.primary_500}]}>
|
<ButtonText style={[{color: t.palette.primary_500}]}>
|
||||||
<Trans>Log out</Trans>
|
<Trans>Log out</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
@ -182,7 +182,7 @@ export function SignupQueued() {
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
label={_(msg`Log out`)}
|
label={_(msg`Log out`)}
|
||||||
onPress={() => logout('SignupQueued')}>
|
onPress={() => logoutCurrentAccount('SignupQueued')}>
|
||||||
<ButtonText style={[{color: t.palette.primary_500}]}>
|
<ButtonText style={[{color: t.palette.primary_500}]}>
|
||||||
<Trans>Log out</Trans>
|
<Trans>Log out</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
|
|
@ -76,7 +76,7 @@ describe('session', () => {
|
||||||
|
|
||||||
state = run(state, [
|
state = run(state, [
|
||||||
{
|
{
|
||||||
type: 'logged-out',
|
type: 'logged-out-every-account',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
// Should keep the account but clear out the tokens.
|
// Should keep the account but clear out the tokens.
|
||||||
|
@ -372,7 +372,7 @@ describe('session', () => {
|
||||||
state = run(state, [
|
state = run(state, [
|
||||||
{
|
{
|
||||||
// Log everyone out.
|
// Log everyone out.
|
||||||
type: 'logged-out',
|
type: 'logged-out-every-account',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
expect(state.accounts.length).toBe(3)
|
expect(state.accounts.length).toBe(3)
|
||||||
|
@ -466,7 +466,7 @@ describe('session', () => {
|
||||||
|
|
||||||
state = run(state, [
|
state = run(state, [
|
||||||
{
|
{
|
||||||
type: 'logged-out',
|
type: 'logged-out-every-account',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
expect(state.accounts.length).toBe(1)
|
expect(state.accounts.length).toBe(1)
|
||||||
|
@ -674,6 +674,103 @@ describe('session', () => {
|
||||||
expect(state.currentAgentState.did).toBe(undefined)
|
expect(state.currentAgentState.did).toBe(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can log out of the current account', () => {
|
||||||
|
let state = getInitialState([])
|
||||||
|
|
||||||
|
const agent1 = new BskyAgent({service: 'https://alice.com'})
|
||||||
|
agent1.sessionManager.session = {
|
||||||
|
active: true,
|
||||||
|
did: 'alice-did',
|
||||||
|
handle: 'alice.test',
|
||||||
|
accessJwt: 'alice-access-jwt-1',
|
||||||
|
refreshJwt: 'alice-refresh-jwt-1',
|
||||||
|
}
|
||||||
|
state = run(state, [
|
||||||
|
{
|
||||||
|
type: 'switched-to-account',
|
||||||
|
newAgent: agent1,
|
||||||
|
newAccount: agentToSessionAccountOrThrow(agent1),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(state.accounts.length).toBe(1)
|
||||||
|
expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1')
|
||||||
|
expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-1')
|
||||||
|
expect(state.currentAgentState.did).toBe('alice-did')
|
||||||
|
|
||||||
|
const agent2 = new BskyAgent({service: 'https://bob.com'})
|
||||||
|
agent2.sessionManager.session = {
|
||||||
|
active: true,
|
||||||
|
did: 'bob-did',
|
||||||
|
handle: 'bob.test',
|
||||||
|
accessJwt: 'bob-access-jwt-1',
|
||||||
|
refreshJwt: 'bob-refresh-jwt-1',
|
||||||
|
}
|
||||||
|
state = run(state, [
|
||||||
|
{
|
||||||
|
type: 'switched-to-account',
|
||||||
|
newAgent: agent2,
|
||||||
|
newAccount: agentToSessionAccountOrThrow(agent2),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(state.accounts.length).toBe(2)
|
||||||
|
expect(state.accounts[0].accessJwt).toBe('bob-access-jwt-1')
|
||||||
|
expect(state.accounts[0].refreshJwt).toBe('bob-refresh-jwt-1')
|
||||||
|
expect(state.currentAgentState.did).toBe('bob-did')
|
||||||
|
|
||||||
|
state = run(state, [
|
||||||
|
{
|
||||||
|
type: 'logged-out-current-account',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(state.accounts.length).toBe(2)
|
||||||
|
expect(state.accounts[0].accessJwt).toBe(undefined)
|
||||||
|
expect(state.accounts[0].refreshJwt).toBe(undefined)
|
||||||
|
expect(state.accounts[1].accessJwt).toBe('alice-access-jwt-1')
|
||||||
|
expect(state.accounts[1].refreshJwt).toBe('alice-refresh-jwt-1')
|
||||||
|
expect(state.currentAgentState.did).toBe(undefined)
|
||||||
|
expect(printState(state)).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"accessJwt": undefined,
|
||||||
|
"active": true,
|
||||||
|
"did": "bob-did",
|
||||||
|
"email": undefined,
|
||||||
|
"emailAuthFactor": false,
|
||||||
|
"emailConfirmed": false,
|
||||||
|
"handle": "bob.test",
|
||||||
|
"pdsUrl": undefined,
|
||||||
|
"refreshJwt": undefined,
|
||||||
|
"service": "https://bob.com/",
|
||||||
|
"signupQueued": false,
|
||||||
|
"status": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessJwt": "alice-access-jwt-1",
|
||||||
|
"active": true,
|
||||||
|
"did": "alice-did",
|
||||||
|
"email": undefined,
|
||||||
|
"emailAuthFactor": false,
|
||||||
|
"emailConfirmed": false,
|
||||||
|
"handle": "alice.test",
|
||||||
|
"pdsUrl": undefined,
|
||||||
|
"refreshJwt": "alice-refresh-jwt-1",
|
||||||
|
"service": "https://alice.com/",
|
||||||
|
"signupQueued": false,
|
||||||
|
"status": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"currentAgentState": {
|
||||||
|
"agent": {
|
||||||
|
"service": "https://public.api.bsky.app/",
|
||||||
|
},
|
||||||
|
"did": undefined,
|
||||||
|
},
|
||||||
|
"needsPersist": true,
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
it('updates stored account with refreshed tokens', () => {
|
it('updates stored account with refreshed tokens', () => {
|
||||||
let state = getInitialState([])
|
let state = getInitialState([])
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ const AgentContext = React.createContext<BskyAgent | null>(null)
|
||||||
const ApiContext = React.createContext<SessionApiContext>({
|
const ApiContext = React.createContext<SessionApiContext>({
|
||||||
createAccount: async () => {},
|
createAccount: async () => {},
|
||||||
login: async () => {},
|
login: async () => {},
|
||||||
logout: async () => {},
|
logoutCurrentAccount: async () => {},
|
||||||
|
logoutEveryAccount: async () => {},
|
||||||
resumeSession: async () => {},
|
resumeSession: async () => {},
|
||||||
removeAccount: () => {},
|
removeAccount: () => {},
|
||||||
})
|
})
|
||||||
|
@ -115,14 +116,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
[onAgentSessionChange, cancelPendingTask],
|
[onAgentSessionChange, cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const logout = React.useCallback<SessionApiContext['logout']>(
|
const logoutCurrentAccount = React.useCallback<
|
||||||
|
SessionApiContext['logoutEveryAccount']
|
||||||
|
>(
|
||||||
logContext => {
|
logContext => {
|
||||||
addSessionDebugLog({type: 'method:start', method: 'logout'})
|
addSessionDebugLog({type: 'method:start', method: 'logout'})
|
||||||
cancelPendingTask()
|
cancelPendingTask()
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'logged-out',
|
type: 'logged-out-current-account',
|
||||||
})
|
})
|
||||||
logEvent('account:loggedOut', {logContext})
|
logEvent('account:loggedOut', {logContext, scope: 'current'})
|
||||||
|
addSessionDebugLog({type: 'method:end', method: 'logout'})
|
||||||
|
},
|
||||||
|
[cancelPendingTask],
|
||||||
|
)
|
||||||
|
|
||||||
|
const logoutEveryAccount = React.useCallback<
|
||||||
|
SessionApiContext['logoutEveryAccount']
|
||||||
|
>(
|
||||||
|
logContext => {
|
||||||
|
addSessionDebugLog({type: 'method:start', method: 'logout'})
|
||||||
|
cancelPendingTask()
|
||||||
|
dispatch({
|
||||||
|
type: 'logged-out-every-account',
|
||||||
|
})
|
||||||
|
logEvent('account:loggedOut', {logContext, scope: 'every'})
|
||||||
addSessionDebugLog({type: 'method:end', method: 'logout'})
|
addSessionDebugLog({type: 'method:end', method: 'logout'})
|
||||||
},
|
},
|
||||||
[cancelPendingTask],
|
[cancelPendingTask],
|
||||||
|
@ -230,11 +248,19 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
() => ({
|
() => ({
|
||||||
createAccount,
|
createAccount,
|
||||||
login,
|
login,
|
||||||
logout,
|
logoutCurrentAccount,
|
||||||
|
logoutEveryAccount,
|
||||||
resumeSession,
|
resumeSession,
|
||||||
removeAccount,
|
removeAccount,
|
||||||
}),
|
}),
|
||||||
[createAccount, login, logout, resumeSession, removeAccount],
|
[
|
||||||
|
createAccount,
|
||||||
|
login,
|
||||||
|
logoutCurrentAccount,
|
||||||
|
logoutEveryAccount,
|
||||||
|
resumeSession,
|
||||||
|
removeAccount,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -42,7 +42,10 @@ export type Action =
|
||||||
accountDid: string
|
accountDid: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'logged-out'
|
type: 'logged-out-current-account'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'logged-out-every-account'
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'synced-accounts'
|
type: 'synced-accounts'
|
||||||
|
@ -138,7 +141,23 @@ let reducer = (state: State, action: Action): State => {
|
||||||
needsPersist: true,
|
needsPersist: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'logged-out': {
|
case 'logged-out-current-account': {
|
||||||
|
const {currentAgentState} = state
|
||||||
|
return {
|
||||||
|
accounts: state.accounts.map(a =>
|
||||||
|
a.did === currentAgentState.did
|
||||||
|
? {
|
||||||
|
...a,
|
||||||
|
refreshJwt: undefined,
|
||||||
|
accessJwt: undefined,
|
||||||
|
}
|
||||||
|
: a,
|
||||||
|
),
|
||||||
|
currentAgentState: createPublicAgentState(),
|
||||||
|
needsPersist: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'logged-out-every-account': {
|
||||||
return {
|
return {
|
||||||
accounts: state.accounts.map(a => ({
|
accounts: state.accounts.map(a => ({
|
||||||
...a,
|
...a,
|
||||||
|
|
|
@ -29,12 +29,12 @@ export type SessionApiContext = {
|
||||||
},
|
},
|
||||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
logContext: LogEvents['account:loggedIn']['logContext'],
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
/**
|
logoutCurrentAccount: (
|
||||||
* A full logout. Clears the `currentAccount` from session, AND removes
|
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||||
* access tokens from all accounts, so that returning as any user will
|
) => void
|
||||||
* require a full login.
|
logoutEveryAccount: (
|
||||||
*/
|
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||||
logout: (logContext: LogEvents['account:loggedOut']['logContext']) => void
|
) => void
|
||||||
resumeSession: (account: SessionAccount) => Promise<void>
|
resumeSession: (account: SessionAccount) => Promise<void>
|
||||||
removeAccount: (account: SessionAccount) => void
|
removeAccount: (account: SessionAccount) => void
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ const BTN = {height: 1, width: 1, backgroundColor: 'red'}
|
||||||
|
|
||||||
export function TestCtrls() {
|
export function TestCtrls() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {logout, login} = useSessionApi()
|
const {logoutEveryAccount, login} = useSessionApi()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
@ -60,7 +60,7 @@ export function TestCtrls() {
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="e2eSignOut"
|
testID="e2eSignOut"
|
||||||
onPress={() => logout('Settings')}
|
onPress={() => logoutEveryAccount('Settings')}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={BTN}
|
style={BTN}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,26 +4,27 @@ import {
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
FontAwesomeIconStyle,
|
FontAwesomeIconStyle,
|
||||||
} from '@fortawesome/react-native-fontawesome'
|
} from '@fortawesome/react-native-fontawesome'
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {DropdownItem, NativeDropdown} from './forms/NativeDropdown'
|
|
||||||
import * as Toast from '../../com/util/Toast'
|
|
||||||
import {useSessionApi, SessionAccount} from '#/state/session'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {SessionAccount, useSessionApi} from '#/state/session'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
import * as Toast from '../../com/util/Toast'
|
||||||
|
import {DropdownItem, NativeDropdown} from './forms/NativeDropdown'
|
||||||
|
|
||||||
export function AccountDropdownBtn({account}: {account: SessionAccount}) {
|
export function AccountDropdownBtn({account}: {account: SessionAccount}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {removeAccount} = useSessionApi()
|
const {removeAccount} = useSessionApi()
|
||||||
|
const removePromptControl = useDialogControl()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const items: DropdownItem[] = [
|
const items: DropdownItem[] = [
|
||||||
{
|
{
|
||||||
label: _(msg`Remove account`),
|
label: _(msg`Remove account`),
|
||||||
onPress: () => {
|
onPress: removePromptControl.open,
|
||||||
removeAccount(account)
|
|
||||||
Toast.show(_(msg`Account removed from quick access`))
|
|
||||||
},
|
|
||||||
icon: {
|
icon: {
|
||||||
ios: {
|
ios: {
|
||||||
name: 'trash',
|
name: 'trash',
|
||||||
|
@ -34,17 +35,32 @@ export function AccountDropdownBtn({account}: {account: SessionAccount}) {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
<Pressable accessibilityRole="button" style={s.pl10}>
|
<>
|
||||||
<NativeDropdown
|
<Pressable accessibilityRole="button" style={s.pl10}>
|
||||||
testID="accountSettingsDropdownBtn"
|
<NativeDropdown
|
||||||
items={items}
|
testID="accountSettingsDropdownBtn"
|
||||||
accessibilityLabel={_(msg`Account options`)}
|
items={items}
|
||||||
accessibilityHint="">
|
accessibilityLabel={_(msg`Account options`)}
|
||||||
<FontAwesomeIcon
|
accessibilityHint="">
|
||||||
icon="ellipsis-h"
|
<FontAwesomeIcon
|
||||||
style={pal.textLight as FontAwesomeIconStyle}
|
icon="ellipsis-h"
|
||||||
/>
|
style={pal.textLight as FontAwesomeIconStyle}
|
||||||
</NativeDropdown>
|
/>
|
||||||
</Pressable>
|
</NativeDropdown>
|
||||||
|
</Pressable>
|
||||||
|
<Prompt.Basic
|
||||||
|
control={removePromptControl}
|
||||||
|
title={_(msg`Remove from quick access?`)}
|
||||||
|
description={_(
|
||||||
|
msg`This will remove @${account.handle} from the quick access list.`,
|
||||||
|
)}
|
||||||
|
onConfirm={() => {
|
||||||
|
removeAccount(account)
|
||||||
|
Toast.show(_(msg`Account removed from quick access`))
|
||||||
|
}}
|
||||||
|
confirmButtonCta={_(msg`Remove`)}
|
||||||
|
confirmButtonColor="negative"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,6 @@ import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateA
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||||
import {navigate, resetToTab} from '#/Navigation'
|
|
||||||
import {Email2FAToggle} from './Email2FAToggle'
|
import {Email2FAToggle} from './Email2FAToggle'
|
||||||
import {ExportCarDialog} from './ExportCarDialog'
|
import {ExportCarDialog} from './ExportCarDialog'
|
||||||
|
|
||||||
|
@ -77,7 +76,6 @@ function SettingsAccountCard({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {logout} = useSessionApi()
|
|
||||||
const {data: profile} = useProfileQuery({did: account.did})
|
const {data: profile} = useProfileQuery({did: account.did})
|
||||||
const isCurrentAccount = account.did === currentAccount?.did
|
const isCurrentAccount = account.did === currentAccount?.did
|
||||||
|
|
||||||
|
@ -103,31 +101,7 @@ function SettingsAccountCard({
|
||||||
{account.handle}
|
{account.handle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<AccountDropdownBtn account={account} />
|
||||||
{isCurrentAccount ? (
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="signOutBtn"
|
|
||||||
onPress={() => {
|
|
||||||
if (isNative) {
|
|
||||||
logout('Settings')
|
|
||||||
resetToTab('HomeTab')
|
|
||||||
} else {
|
|
||||||
navigate('Home').then(() => {
|
|
||||||
logout('Settings')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Sign out`)}
|
|
||||||
accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}
|
|
||||||
activeOpacity={0.8}>
|
|
||||||
<Text type="lg" style={pal.link}>
|
|
||||||
<Trans>Sign out</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
) : (
|
|
||||||
<AccountDropdownBtn account={account} />
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -173,6 +147,7 @@ export function SettingsScreen({}: Props) {
|
||||||
const {accounts, currentAccount} = useSession()
|
const {accounts, currentAccount} = useSession()
|
||||||
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
const {logoutEveryAccount} = useSessionApi()
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
const exportCarControl = useDialogControl()
|
const exportCarControl = useDialogControl()
|
||||||
const birthdayControl = useDialogControl()
|
const birthdayControl = useDialogControl()
|
||||||
|
@ -237,6 +212,10 @@ export function SettingsScreen({}: Props) {
|
||||||
openModal({name: 'delete-account'})
|
openModal({name: 'delete-account'})
|
||||||
}, [openModal])
|
}, [openModal])
|
||||||
|
|
||||||
|
const onPressLogoutEveryAccount = React.useCallback(() => {
|
||||||
|
logoutEveryAccount('Settings')
|
||||||
|
}, [logoutEveryAccount])
|
||||||
|
|
||||||
const onPressResetPreferences = React.useCallback(async () => {
|
const onPressResetPreferences = React.useCallback(async () => {
|
||||||
clearPreferences()
|
clearPreferences()
|
||||||
}, [clearPreferences])
|
}, [clearPreferences])
|
||||||
|
@ -394,6 +373,15 @@ export function SettingsScreen({}: Props) {
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<View pointerEvents={pendingDid ? 'none' : 'auto'}>
|
<View pointerEvents={pendingDid ? 'none' : 'auto'}>
|
||||||
|
{accounts.length > 1 && (
|
||||||
|
<View style={[s.flexRow, styles.heading, a.mt_sm]}>
|
||||||
|
<Text type="xl-bold" style={pal.text} numberOfLines={1}>
|
||||||
|
<Trans>Other accounts</Trans>
|
||||||
|
</Text>
|
||||||
|
<View style={s.flex1} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{accounts
|
{accounts
|
||||||
.filter(a => a.did !== currentAccount?.did)
|
.filter(a => a.did !== currentAccount?.did)
|
||||||
.map(account => (
|
.map(account => (
|
||||||
|
@ -422,6 +410,29 @@ export function SettingsScreen({}: Props) {
|
||||||
<Trans>Add account</Trans>
|
<Trans>Add account</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.linkCard, pal.view]}
|
||||||
|
onPress={
|
||||||
|
isSwitchingAccounts ? undefined : onPressLogoutEveryAccount
|
||||||
|
}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={_(msg`Sign out of all accounts`)}
|
||||||
|
accessibilityHint={undefined}>
|
||||||
|
<View style={[styles.iconContainer, pal.btn]}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="arrow-right-from-bracket"
|
||||||
|
style={pal.text as FontAwesomeIconStyle}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text type="lg" style={pal.text}>
|
||||||
|
{accounts.length > 1 ? (
|
||||||
|
<Trans>Sign out of all accounts</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Sign out</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.spacer20} />
|
<View style={styles.spacer20} />
|
||||||
|
|
Loading…
Reference in New Issue