diff options
Diffstat (limited to 'src/view/com/modals/ChangeHandle.tsx')
-rw-r--r-- | src/view/com/modals/ChangeHandle.tsx | 210 |
1 files changed, 106 insertions, 104 deletions
diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx index c54c1c043..03516d35a 100644 --- a/src/view/com/modals/ChangeHandle.tsx +++ b/src/view/com/modals/ChangeHandle.tsx @@ -1,5 +1,6 @@ import React, {useState} from 'react' import Clipboard from '@react-native-clipboard/clipboard' +import {ComAtprotoServerDescribeServer} from '@atproto/api' import * as Toast from '../util/Toast' import { ActivityIndicator, @@ -13,8 +14,6 @@ import {Text} from '../util/text/Text' import {Button} from '../util/forms/Button' import {SelectableBtn} from '../util/forms/SelectableBtn' import {ErrorMessage} from '../util/error/ErrorMessage' -import {useStores} from 'state/index' -import {ServiceDescription} from 'state/models/session' import {s} from 'lib/styles' import {createFullHandle, makeValidHandle} from 'lib/strings/handles' import {usePalette} from 'lib/hooks/usePalette' @@ -22,75 +21,74 @@ import {useTheme} from 'lib/ThemeContext' import {useAnalytics} from 'lib/analytics/analytics' import {cleanError} from 'lib/strings/errors' import {logger} from '#/logger' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useModalControls} from '#/state/modals' +import {useServiceQuery} from '#/state/queries/service' +import {useUpdateHandleMutation, useFetchDid} from '#/state/queries/handle' +import { + useSession, + useSessionApi, + SessionAccount, + getAgent, +} from '#/state/session' export const snapPoints = ['100%'] -export function Component({onChanged}: {onChanged: () => void}) { - const store = useStores() - const [error, setError] = useState<string>('') +export type Props = {onChanged: () => void} + +export function Component(props: Props) { + const {currentAccount} = useSession() + const { + isLoading, + data: serviceInfo, + error: serviceInfoError, + } = useServiceQuery(getAgent().service.toString()) + + return isLoading || !currentAccount ? ( + <View style={{padding: 18}}> + <ActivityIndicator /> + </View> + ) : serviceInfoError || !serviceInfo ? ( + <ErrorMessage message={cleanError(serviceInfoError)} /> + ) : ( + <Inner + {...props} + currentAccount={currentAccount} + serviceInfo={serviceInfo} + /> + ) +} + +export function Inner({ + currentAccount, + serviceInfo, + onChanged, +}: Props & { + currentAccount: SessionAccount + serviceInfo: ComAtprotoServerDescribeServer.OutputSchema +}) { + const {_} = useLingui() const pal = usePalette('default') const {track} = useAnalytics() + const {updateCurrentAccount} = useSessionApi() + const {closeModal} = useModalControls() + const {mutateAsync: updateHandle, isPending: isUpdateHandlePending} = + useUpdateHandleMutation() + + const [error, setError] = useState<string>('') - const [isProcessing, setProcessing] = useState<boolean>(false) - const [retryDescribeTrigger, setRetryDescribeTrigger] = React.useState<any>( - {}, - ) - const [serviceDescription, setServiceDescription] = React.useState< - ServiceDescription | undefined - >(undefined) - const [userDomain, setUserDomain] = React.useState<string>('') const [isCustom, setCustom] = React.useState<boolean>(false) const [handle, setHandle] = React.useState<string>('') const [canSave, setCanSave] = React.useState<boolean>(false) - // init - // = - React.useEffect(() => { - let aborted = false - setError('') - setServiceDescription(undefined) - setProcessing(true) - - // load the service description so we can properly provision handles - store.session.describeService(String(store.agent.service)).then( - desc => { - if (aborted) { - return - } - setServiceDescription(desc) - setUserDomain(desc.availableUserDomains[0]) - setProcessing(false) - }, - err => { - if (aborted) { - return - } - setProcessing(false) - logger.warn( - `Failed to fetch service description for ${String( - store.agent.service, - )}`, - {error: err}, - ) - setError( - 'Unable to contact your service. Please check your Internet connection.', - ) - }, - ) - return () => { - aborted = true - } - }, [store.agent.service, store.session, retryDescribeTrigger]) + const userDomain = serviceInfo.availableUserDomains?.[0] // events // = const onPressCancel = React.useCallback(() => { - store.shell.closeModal() - }, [store]) - const onPressRetryConnect = React.useCallback( - () => setRetryDescribeTrigger({}), - [setRetryDescribeTrigger], - ) + closeModal() + }, [closeModal]) const onToggleCustom = React.useCallback(() => { // toggle between a provided domain vs a custom one setHandle('') @@ -101,32 +99,42 @@ export function Component({onChanged}: {onChanged: () => void}) { ) }, [setCustom, isCustom, track]) const onPressSave = React.useCallback(async () => { - setError('') - setProcessing(true) + if (!userDomain) { + logger.error(`ChangeHandle: userDomain is undefined`, { + service: serviceInfo, + }) + setError(`The service you've selected has no domains configured.`) + return + } + try { track('EditHandle:SetNewHandle') const newHandle = isCustom ? handle : createFullHandle(handle, userDomain) logger.debug(`Updating handle to ${newHandle}`) - await store.agent.updateHandle({ + await updateHandle({ + handle: newHandle, + }) + updateCurrentAccount({ handle: newHandle, }) - store.shell.closeModal() + closeModal() onChanged() } catch (err: any) { setError(cleanError(err)) logger.error('Failed to update handle', {handle, error: err}) } finally { - setProcessing(false) } }, [ setError, - setProcessing, handle, userDomain, - store, isCustom, onChanged, track, + closeModal, + updateCurrentAccount, + updateHandle, + serviceInfo, ]) // rendering @@ -138,7 +146,7 @@ export function Component({onChanged}: {onChanged: () => void}) { <TouchableOpacity onPress={onPressCancel} accessibilityRole="button" - accessibilityLabel="Cancel change handle" + accessibilityLabel={_(msg`Cancel change handle`)} accessibilityHint="Exits handle change process" onAccessibilityEscape={onPressCancel}> <Text type="lg" style={pal.textLight}> @@ -150,30 +158,19 @@ export function Component({onChanged}: {onChanged: () => void}) { type="2xl-bold" style={[styles.titleMiddle, pal.text]} numberOfLines={1}> - Change Handle + <Trans>Change Handle</Trans> </Text> <View style={styles.titleRight}> - {isProcessing ? ( + {isUpdateHandlePending ? ( <ActivityIndicator /> - ) : error && !serviceDescription ? ( - <TouchableOpacity - testID="retryConnectButton" - onPress={onPressRetryConnect} - accessibilityRole="button" - accessibilityLabel="Retry change handle" - accessibilityHint={`Retries handle change to ${handle}`}> - <Text type="xl-bold" style={[pal.link, s.pr5]}> - Retry - </Text> - </TouchableOpacity> ) : canSave ? ( <TouchableOpacity onPress={onPressSave} accessibilityRole="button" - accessibilityLabel="Save handle change" + accessibilityLabel={_(msg`Save handle change`)} accessibilityHint={`Saves handle change to ${handle}`}> <Text type="2xl-medium" style={pal.link}> - Save + <Trans>Save</Trans> </Text> </TouchableOpacity> ) : undefined} @@ -188,8 +185,9 @@ export function Component({onChanged}: {onChanged: () => void}) { {isCustom ? ( <CustomHandleForm + currentAccount={currentAccount} handle={handle} - isProcessing={isProcessing} + isProcessing={isUpdateHandlePending} canSave={canSave} onToggleCustom={onToggleCustom} setHandle={setHandle} @@ -200,7 +198,7 @@ export function Component({onChanged}: {onChanged: () => void}) { <ProvidedHandleForm handle={handle} userDomain={userDomain} - isProcessing={isProcessing} + isProcessing={isUpdateHandlePending} onToggleCustom={onToggleCustom} setHandle={setHandle} setCanSave={setCanSave} @@ -231,6 +229,7 @@ function ProvidedHandleForm({ }) { const pal = usePalette('default') const theme = useTheme() + const {_} = useLingui() // events // = @@ -263,12 +262,12 @@ function ProvidedHandleForm({ onChangeText={onChangeHandle} editable={!isProcessing} accessible={true} - accessibilityLabel="Handle" + accessibilityLabel={_(msg`Handle`)} accessibilityHint="Sets Bluesky username" /> </View> <Text type="md" style={[pal.textLight, s.pl10, s.pt10]}> - Your full handle will be{' '} + <Trans>Your full handle will be </Trans> <Text type="md-bold" style={pal.textLight}> @{createFullHandle(handle, userDomain)} </Text> @@ -277,9 +276,9 @@ function ProvidedHandleForm({ onPress={onToggleCustom} accessibilityRole="button" accessibilityHint="Hosting provider" - accessibilityLabel="Opens modal for using custom domain"> + accessibilityLabel={_(msg`Opens modal for using custom domain`)}> <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}> - I have my own domain + <Trans>I have my own domain</Trans> </Text> </TouchableOpacity> </> @@ -290,6 +289,7 @@ function ProvidedHandleForm({ * The form for using a custom domain */ function CustomHandleForm({ + currentAccount, handle, canSave, isProcessing, @@ -298,6 +298,7 @@ function CustomHandleForm({ onPressSave, setCanSave, }: { + currentAccount: SessionAccount handle: string canSave: boolean isProcessing: boolean @@ -306,20 +307,23 @@ function CustomHandleForm({ onPressSave: () => void setCanSave: (v: boolean) => void }) { - const store = useStores() const pal = usePalette('default') const palSecondary = usePalette('secondary') const palError = usePalette('error') const theme = useTheme() + const {_} = useLingui() const [isVerifying, setIsVerifying] = React.useState(false) const [error, setError] = React.useState<string>('') const [isDNSForm, setDNSForm] = React.useState<boolean>(true) + const fetchDid = useFetchDid() // events // = const onPressCopy = React.useCallback(() => { - Clipboard.setString(isDNSForm ? `did=${store.me.did}` : store.me.did) + Clipboard.setString( + isDNSForm ? `did=${currentAccount.did}` : currentAccount.did, + ) Toast.show('Copied to clipboard') - }, [store.me.did, isDNSForm]) + }, [currentAccount, isDNSForm]) const onChangeHandle = React.useCallback( (v: string) => { setHandle(v) @@ -334,13 +338,11 @@ function CustomHandleForm({ try { setIsVerifying(true) setError('') - const res = await store.agent.com.atproto.identity.resolveHandle({ - handle, - }) - if (res.data.did === store.me.did) { + const did = await fetchDid(handle) + if (did === currentAccount.did) { setCanSave(true) } else { - setError(`Incorrect DID returned (got ${res.data.did})`) + setError(`Incorrect DID returned (got ${did})`) } } catch (err: any) { setError(cleanError(err)) @@ -350,13 +352,13 @@ function CustomHandleForm({ } }, [ handle, - store.me.did, + currentAccount, setIsVerifying, setCanSave, setError, canSave, onPressSave, - store.agent, + fetchDid, ]) // rendering @@ -364,7 +366,7 @@ function CustomHandleForm({ return ( <> <Text type="md" style={[pal.text, s.pb5, s.pl5]} nativeID="customDomain"> - Enter the domain you want to use + <Trans>Enter the domain you want to use</Trans> </Text> <View style={[pal.btn, styles.textInputWrapper]}> <FontAwesomeIcon @@ -382,7 +384,7 @@ function CustomHandleForm({ onChangeText={onChangeHandle} editable={!isProcessing} accessibilityLabelledBy="customDomain" - accessibilityLabel="Custom domain" + accessibilityLabel={_(msg`Custom domain`)} accessibilityHint="Input your preferred hosting provider" /> </View> @@ -410,7 +412,7 @@ function CustomHandleForm({ {isDNSForm ? ( <> <Text type="md" style={[pal.text, s.pb5, s.pl5]}> - Add the following DNS record to your domain: + <Trans>Add the following DNS record to your domain:</Trans> </Text> <View style={[styles.dnsTable, pal.btn]}> <Text type="md-medium" style={[styles.dnsLabel, pal.text]}> @@ -434,7 +436,7 @@ function CustomHandleForm({ </Text> <View style={[styles.dnsValue]}> <Text type="mono" style={[styles.monoText, pal.text]}> - did={store.me.did} + did={currentAccount.did} </Text> </View> </View> @@ -448,7 +450,7 @@ function CustomHandleForm({ ) : ( <> <Text type="md" style={[pal.text, s.pb5, s.pl5]}> - Upload a text file to: + <Trans>Upload a text file to:</Trans> </Text> <View style={[styles.valueContainer, pal.btn]}> <View style={[styles.dnsValue]}> @@ -464,7 +466,7 @@ function CustomHandleForm({ <View style={[styles.valueContainer, pal.btn]}> <View style={[styles.dnsValue]}> <Text type="mono" style={[styles.monoText, pal.text]}> - {store.me.did} + {currentAccount.did} </Text> </View> </View> @@ -480,7 +482,7 @@ function CustomHandleForm({ {canSave === true && ( <View style={[styles.message, palSecondary.view]}> <Text type="md-medium" style={palSecondary.text}> - Domain verified! + <Trans>Domain verified!</Trans> </Text> </View> )} @@ -508,7 +510,7 @@ function CustomHandleForm({ <View style={styles.spacer} /> <TouchableOpacity onPress={onToggleCustom} - accessibilityLabel="Use default provider" + accessibilityLabel={_(msg`Use default provider`)} accessibilityHint="Use bsky.social as hosting provider"> <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}> Nevermind, create a handle for me |