Web login/signup and shell

This commit is contained in:
Eric Bailey 2023-11-09 20:35:17 -06:00
parent 487d871cfd
commit ab878ba9a6
21 changed files with 581 additions and 374 deletions

View file

@ -6,7 +6,6 @@ import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button'
import {ErrorMessage} from '../util/error/ErrorMessage'
import * as Toast from '../util/Toast'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
@ -15,6 +14,7 @@ import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {useSession, useSessionApi} from '#/state/session'
enum Stages {
InputEmail,
@ -26,12 +26,11 @@ export const snapPoints = ['90%']
export const Component = observer(function Component({}: {}) {
const pal = usePalette('default')
const store = useStores()
const {agent, currentAccount} = useSession()
const {updateCurrentAccount} = useSessionApi()
const {_} = useLingui()
const [stage, setStage] = useState<Stages>(Stages.InputEmail)
const [email, setEmail] = useState<string>(
store.session.currentSession?.email || '',
)
const [email, setEmail] = useState<string>(currentAccount?.email || '')
const [confirmationCode, setConfirmationCode] = useState<string>('')
const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [error, setError] = useState<string>('')
@ -39,19 +38,19 @@ export const Component = observer(function Component({}: {}) {
const {openModal, closeModal} = useModalControls()
const onRequestChange = async () => {
if (email === store.session.currentSession?.email) {
if (email === currentAccount?.email) {
setError('Enter your new email above')
return
}
setError('')
setIsProcessing(true)
try {
const res = await store.agent.com.atproto.server.requestEmailUpdate()
const res = await agent.com.atproto.server.requestEmailUpdate()
if (res.data.tokenRequired) {
setStage(Stages.ConfirmCode)
} else {
await store.agent.com.atproto.server.updateEmail({email: email.trim()})
store.session.updateLocalAccountData({
await agent.com.atproto.server.updateEmail({email: email.trim()})
updateCurrentAccount({
email: email.trim(),
emailConfirmed: false,
})
@ -79,11 +78,11 @@ export const Component = observer(function Component({}: {}) {
setError('')
setIsProcessing(true)
try {
await store.agent.com.atproto.server.updateEmail({
await agent.com.atproto.server.updateEmail({
email: email.trim(),
token: confirmationCode.trim(),
})
store.session.updateLocalAccountData({
updateCurrentAccount({
email: email.trim(),
emailConfirmed: false,
})
@ -120,8 +119,8 @@ export const Component = observer(function Component({}: {}) {
) : stage === Stages.ConfirmCode ? (
<Trans>
An email has been sent to your previous address,{' '}
{store.session.currentSession?.email || ''}. It includes a
confirmation code which you can enter below.
{currentAccount?.email || ''}. It includes a confirmation code
which you can enter below.
</Trans>
) : (
<Trans>

View file

@ -6,7 +6,6 @@ import {
View,
} from 'react-native'
import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
@ -19,26 +18,94 @@ import {BottomSheetScrollView} from '@gorhom/bottom-sheet'
import {Haptics} from 'lib/haptics'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSession, useSessionApi, SessionAccount} from '#/state/session'
import {useGetProfile} from '#/data/useGetProfile'
export const snapPoints = ['40%', '90%']
function SwitchAccountCard({account}: {account: SessionAccount}) {
const pal = usePalette('default')
const {_} = useLingui()
const {track} = useAnalytics()
const {isSwitchingAccounts, currentAccount} = useSession()
const {logout} = useSessionApi()
const {isError, data: profile} = useGetProfile({did: account.did})
const isCurrentAccount = account.did === currentAccount?.did
const {onPressSwitchAccount} = useAccountSwitcher()
const onPressSignout = React.useCallback(() => {
track('Settings:SignOutButtonClicked')
logout()
}, [track, logout])
// TODO
if (isError || !currentAccount) return null
const contents = (
<View style={[pal.view, styles.linkCard]}>
<View style={styles.avi}>
<UserAvatar size={40} avatar={profile?.avatar} />
</View>
<View style={[s.flex1]}>
<Text type="md-bold" style={pal.text} numberOfLines={1}>
{profile?.displayName || currentAccount.handle}
</Text>
<Text type="sm" style={pal.textLight} numberOfLines={1}>
{currentAccount.handle}
</Text>
</View>
{isCurrentAccount ? (
<TouchableOpacity
testID="signOutBtn"
onPress={isSwitchingAccounts ? undefined : onPressSignout}
accessibilityRole="button"
accessibilityLabel={_(msg`Sign out`)}
accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}>
<Text type="lg" style={pal.link}>
<Trans>Sign out</Trans>
</Text>
</TouchableOpacity>
) : (
<AccountDropdownBtn handle={account.handle} />
)}
</View>
)
return isCurrentAccount ? (
<Link
href={makeProfileLink({
did: currentAccount.did,
handle: currentAccount.handle,
})}
title="Your profile"
noFeedback>
{contents}
</Link>
) : (
<TouchableOpacity
testID={`switchToAccountBtn-${account.handle}`}
key={account.did}
style={[isSwitchingAccounts && styles.dimmed]}
onPress={
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
}
accessibilityRole="button"
accessibilityLabel={`Switch to ${account.handle}`}
accessibilityHint="Switches the account you are logged in to">
{contents}
</TouchableOpacity>
)
}
export function Component({}: {}) {
const pal = usePalette('default')
const {track} = useAnalytics()
const {_: _lingui} = useLingui()
const store = useStores()
const [isSwitching, _, onPressSwitchAccount] = useAccountSwitcher()
const {isSwitchingAccounts, currentAccount, accounts} = useSession()
React.useEffect(() => {
Haptics.default()
})
const onPressSignout = React.useCallback(() => {
track('Settings:SignOutButtonClicked')
store.session.logout()
}, [track, store])
return (
<BottomSheetScrollView
style={[styles.container, pal.view]}
@ -46,62 +113,20 @@ export function Component({}: {}) {
<Text type="title-xl" style={[styles.title, pal.text]}>
<Trans>Switch Account</Trans>
</Text>
{isSwitching ? (
{isSwitchingAccounts || !currentAccount ? (
<View style={[pal.view, styles.linkCard]}>
<ActivityIndicator />
</View>
) : (
<Link href={makeProfileLink(store.me)} title="Your profile" noFeedback>
<View style={[pal.view, styles.linkCard]}>
<View style={styles.avi}>
<UserAvatar size={40} avatar={store.me.avatar} />
</View>
<View style={[s.flex1]}>
<Text type="md-bold" style={pal.text} numberOfLines={1}>
{store.me.displayName || store.me.handle}
</Text>
<Text type="sm" style={pal.textLight} numberOfLines={1}>
{store.me.handle}
</Text>
</View>
<TouchableOpacity
testID="signOutBtn"
onPress={isSwitching ? undefined : onPressSignout}
accessibilityRole="button"
accessibilityLabel={_lingui(msg`Sign out`)}
accessibilityHint={`Signs ${store.me.displayName} out of Bluesky`}>
<Text type="lg" style={pal.link}>
<Trans>Sign out</Trans>
</Text>
</TouchableOpacity>
</View>
</Link>
<SwitchAccountCard account={currentAccount} />
)}
{store.session.switchableAccounts.map(account => (
<TouchableOpacity
testID={`switchToAccountBtn-${account.handle}`}
key={account.did}
style={[pal.view, styles.linkCard, isSwitching && styles.dimmed]}
onPress={
isSwitching ? undefined : () => onPressSwitchAccount(account)
}
accessibilityRole="button"
accessibilityLabel={`Switch to ${account.handle}`}
accessibilityHint="Switches the account you are logged in to">
<View style={styles.avi}>
<UserAvatar size={40} avatar={account.aviUrl} />
</View>
<View style={[s.flex1]}>
<Text type="md-bold" style={pal.text}>
{account.displayName || account.handle}
</Text>
<Text type="sm" style={pal.textLight}>
{account.handle}
</Text>
</View>
<AccountDropdownBtn handle={account.handle} />
</TouchableOpacity>
))}
{accounts
.filter(a => a.did !== currentAccount?.did)
.map(account => (
<SwitchAccountCard key={account.did} account={account} />
))}
</BottomSheetScrollView>
)
}

View file

@ -14,7 +14,6 @@ import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button'
import {ErrorMessage} from '../util/error/ErrorMessage'
import * as Toast from '../util/Toast'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
@ -23,6 +22,7 @@ import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {useSession, useSessionApi} from '#/state/session'
export const snapPoints = ['90%']
@ -38,7 +38,8 @@ export const Component = observer(function Component({
showReminder?: boolean
}) {
const pal = usePalette('default')
const store = useStores()
const {agent, currentAccount} = useSession()
const {updateCurrentAccount} = useSessionApi()
const {_} = useLingui()
const [stage, setStage] = useState<Stages>(
showReminder ? Stages.Reminder : Stages.Email,
@ -53,7 +54,7 @@ export const Component = observer(function Component({
setError('')
setIsProcessing(true)
try {
await store.agent.com.atproto.server.requestEmailConfirmation()
await agent.com.atproto.server.requestEmailConfirmation()
setStage(Stages.ConfirmCode)
} catch (e) {
setError(cleanError(String(e)))
@ -66,11 +67,11 @@ export const Component = observer(function Component({
setError('')
setIsProcessing(true)
try {
await store.agent.com.atproto.server.confirmEmail({
email: (store.session.currentSession?.email || '').trim(),
await agent.com.atproto.server.confirmEmail({
email: (currentAccount?.email || '').trim(),
token: confirmationCode.trim(),
})
store.session.updateLocalAccountData({emailConfirmed: true})
updateCurrentAccount({emailConfirmed: true})
Toast.show('Email verified')
closeModal()
} catch (e) {
@ -112,9 +113,8 @@ export const Component = observer(function Component({
</Trans>
) : stage === Stages.ConfirmCode ? (
<Trans>
An email has been sent to{' '}
{store.session.currentSession?.email || ''}. It includes a
confirmation code which you can enter below.
An email has been sent to {currentAccount?.email || ''}. It
includes a confirmation code which you can enter below.
</Trans>
) : (
''
@ -130,7 +130,7 @@ export const Component = observer(function Component({
size={16}
/>
<Text type="xl-medium" style={[pal.text, s.flex1, {minWidth: 0}]}>
{store.session.currentSession?.email || ''}
{currentAccount?.email || ''}
</Text>
</View>
<Pressable