about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2024-11-08 22:42:18 +0000
committerGitHub <noreply@github.com>2024-11-08 22:42:18 +0000
commit5da3f29498fda9ab1181df19a718e37099cb2cf6 (patch)
tree4989300fe333edc3ad1c12c7b0d03e6ff5112ee3 /src/view/com
parente8a03058bb993bda993e78d24877884d39666d63 (diff)
downloadvoidsky-5da3f29498fda9ab1181df19a718e37099cb2cf6.tar.zst
[Settings] Ungate, and remove old settings (#6144)
* move export car dialog

* move disableemail2fadialog

* delete old settings screens

* fix type error

* Update Navigation.tsx

* Delete AccountDropdownBtn.tsx

* remove old change handle modal

* delete add app paswords

* forgot to actually delete the change handle modal
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/modals/AddAppPasswords.tsx307
-rw-r--r--src/view/com/modals/ChangeHandle.tsx614
-rw-r--r--src/view/com/modals/Modal.tsx8
-rw-r--r--src/view/com/modals/Modal.web.tsx6
-rw-r--r--src/view/com/util/AccountDropdownBtn.tsx66
-rw-r--r--src/view/com/util/List.web.tsx4
6 files changed, 3 insertions, 1002 deletions
diff --git a/src/view/com/modals/AddAppPasswords.tsx b/src/view/com/modals/AddAppPasswords.tsx
deleted file mode 100644
index f7991f59b..000000000
--- a/src/view/com/modals/AddAppPasswords.tsx
+++ /dev/null
@@ -1,307 +0,0 @@
-import React, {useState} from 'react'
-import {StyleSheet, TextInput, TouchableOpacity, View} from 'react-native'
-import {setStringAsync} from 'expo-clipboard'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {usePalette} from '#/lib/hooks/usePalette'
-import {s} from '#/lib/styles'
-import {logger} from '#/logger'
-import {isNative} from '#/platform/detection'
-import {useModalControls} from '#/state/modals'
-import {
-  useAppPasswordCreateMutation,
-  useAppPasswordsQuery,
-} from '#/state/queries/app-passwords'
-import {Button} from '#/view/com/util/forms/Button'
-import {Text} from '#/view/com/util/text/Text'
-import * as Toast from '#/view/com/util/Toast'
-import {atoms as a} from '#/alf'
-import * as Toggle from '#/components/forms/Toggle'
-
-export const snapPoints = ['90%']
-
-const shadesOfBlue: string[] = [
-  'AliceBlue',
-  'Aqua',
-  'Aquamarine',
-  'Azure',
-  'BabyBlue',
-  'Blue',
-  'BlueViolet',
-  'CadetBlue',
-  'CornflowerBlue',
-  'Cyan',
-  'DarkBlue',
-  'DarkCyan',
-  'DarkSlateBlue',
-  'DeepSkyBlue',
-  'DodgerBlue',
-  'ElectricBlue',
-  'LightBlue',
-  'LightCyan',
-  'LightSkyBlue',
-  'LightSteelBlue',
-  'MediumAquaMarine',
-  'MediumBlue',
-  'MediumSlateBlue',
-  'MidnightBlue',
-  'Navy',
-  'PowderBlue',
-  'RoyalBlue',
-  'SkyBlue',
-  'SlateBlue',
-  'SteelBlue',
-  'Teal',
-  'Turquoise',
-]
-
-export function Component({}: {}) {
-  const pal = usePalette('default')
-  const {_} = useLingui()
-  const {closeModal} = useModalControls()
-  const {data: passwords} = useAppPasswordsQuery()
-  const {mutateAsync: mutateAppPassword, isPending} =
-    useAppPasswordCreateMutation()
-  const [name, setName] = useState(
-    shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)],
-  )
-  const [appPassword, setAppPassword] = useState<string>()
-  const [wasCopied, setWasCopied] = useState(false)
-  const [privileged, setPrivileged] = useState(false)
-
-  const onCopy = React.useCallback(() => {
-    if (appPassword) {
-      setStringAsync(appPassword)
-      Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
-      setWasCopied(true)
-    }
-  }, [appPassword, _])
-
-  const onDone = React.useCallback(() => {
-    closeModal()
-  }, [closeModal])
-
-  const createAppPassword = async () => {
-    // if name is all whitespace, we don't allow it
-    if (!name || !name.trim()) {
-      Toast.show(
-        _(
-          msg`Please enter a name for your app password. All spaces is not allowed.`,
-        ),
-        'xmark',
-      )
-      return
-    }
-    // if name is too short (under 4 chars), we don't allow it
-    if (name.length < 4) {
-      Toast.show(
-        _(msg`App Password names must be at least 4 characters long.`),
-        'xmark',
-      )
-      return
-    }
-
-    if (passwords?.find(p => p.name === name)) {
-      Toast.show(_(msg`This name is already in use`), 'xmark')
-      return
-    }
-
-    try {
-      const newPassword = await mutateAppPassword({name, privileged})
-      if (newPassword) {
-        setAppPassword(newPassword.password)
-      } else {
-        Toast.show(_(msg`Failed to create app password.`), 'xmark')
-        // TODO: better error handling (?)
-      }
-    } catch (e) {
-      Toast.show(_(msg`Failed to create app password.`), 'xmark')
-      logger.error('Failed to create app password', {message: e})
-    }
-  }
-
-  const _onChangeText = (text: string) => {
-    // sanitize input
-    // we only all alphanumeric characters, spaces, dashes, and underscores
-    // if the user enters anything else, we ignore it and shake the input container
-    // also, it cannot start with a space
-    if (text.match(/^[a-zA-Z0-9-_ ]*$/)) {
-      setName(text)
-    } else {
-      Toast.show(
-        _(
-          msg`App Password names can only contain letters, numbers, spaces, dashes, and underscores.`,
-        ),
-        'xmark',
-      )
-    }
-  }
-
-  return (
-    <View style={[styles.container, pal.view]} testID="addAppPasswordsModal">
-      {!appPassword ? (
-        <>
-          <View>
-            <Text type="lg" style={[pal.text]}>
-              <Trans>
-                Please enter a unique name for this App Password or use our
-                randomly generated one.
-              </Trans>
-            </Text>
-            <View style={[pal.btn, styles.textInputWrapper]}>
-              <TextInput
-                style={[styles.input, pal.text]}
-                onChangeText={_onChangeText}
-                value={name}
-                placeholder={_(msg`Enter a name for this App Password`)}
-                placeholderTextColor={pal.colors.textLight}
-                autoCorrect={false}
-                autoComplete="off"
-                autoCapitalize="none"
-                autoFocus={true}
-                maxLength={32}
-                selectTextOnFocus={true}
-                blurOnSubmit={true}
-                editable={!isPending}
-                returnKeyType="done"
-                onSubmitEditing={createAppPassword}
-                accessible={true}
-                accessibilityLabel={_(msg`Name`)}
-                accessibilityHint={_(msg`Input name for app password`)}
-              />
-            </View>
-          </View>
-          <Text type="xs" style={[pal.textLight, s.mb10, s.mt2]}>
-            <Trans>
-              Can only contain letters, numbers, spaces, dashes, and
-              underscores. Must be at least 4 characters long, but no more than
-              32 characters long.
-            </Trans>
-          </Text>
-          <Toggle.Item
-            type="checkbox"
-            label={_(msg`Allow access to your direct messages`)}
-            value={privileged}
-            onChange={val => setPrivileged(val)}
-            name="privileged"
-            style={a.my_md}>
-            <Toggle.Checkbox />
-            <Toggle.LabelText>
-              <Trans>Allow access to your direct messages</Trans>
-            </Toggle.LabelText>
-          </Toggle.Item>
-        </>
-      ) : (
-        <>
-          <View>
-            <Text type="lg" style={[pal.text]}>
-              <Text type="lg-bold" style={[pal.text, s.mr5]}>
-                <Trans>Here is your app password.</Trans>
-              </Text>
-              <Trans>
-                Use this to sign into the other app along with your handle.
-              </Trans>
-            </Text>
-            <TouchableOpacity
-              style={[pal.border, styles.passwordContainer, pal.btn]}
-              onPress={onCopy}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Copy`)}
-              accessibilityHint={_(msg`Copies app password`)}>
-              <Text type="2xl-bold" style={[pal.text]}>
-                {appPassword}
-              </Text>
-              {wasCopied ? (
-                <Text style={[pal.textLight]}>
-                  <Trans>Copied</Trans>
-                </Text>
-              ) : (
-                <FontAwesomeIcon
-                  icon={['far', 'clone']}
-                  style={pal.text as FontAwesomeIconStyle}
-                  size={18}
-                />
-              )}
-            </TouchableOpacity>
-          </View>
-          <Text type="lg" style={[pal.textLight, s.mb10]}>
-            <Trans>
-              For security reasons, you won't be able to view this again. If you
-              lose this password, you'll need to generate a new one.
-            </Trans>
-          </Text>
-        </>
-      )}
-      <View style={styles.btnContainer}>
-        <Button
-          type="primary"
-          label={!appPassword ? _(msg`Create App Password`) : _(msg`Done`)}
-          style={styles.btn}
-          labelStyle={styles.btnLabel}
-          onPress={!appPassword ? createAppPassword : onDone}
-        />
-      </View>
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    paddingBottom: isNative ? 50 : 0,
-    paddingHorizontal: 16,
-  },
-  textInputWrapper: {
-    borderRadius: 8,
-    flexDirection: 'row',
-    alignItems: 'center',
-    marginTop: 16,
-    marginBottom: 8,
-  },
-  input: {
-    flex: 1,
-    width: '100%',
-    paddingVertical: 10,
-    paddingHorizontal: 8,
-    fontSize: 17,
-    letterSpacing: 0.25,
-    fontWeight: '400',
-    borderRadius: 10,
-  },
-  passwordContainer: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    paddingVertical: 8,
-    paddingHorizontal: 16,
-    alignItems: 'center',
-    borderRadius: 10,
-    marginTop: 16,
-    marginBottom: 12,
-  },
-  btnContainer: {
-    flexDirection: 'row',
-    justifyContent: 'center',
-    marginTop: 12,
-  },
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    borderRadius: 32,
-    paddingHorizontal: 60,
-    paddingVertical: 14,
-  },
-  btnLabel: {
-    fontSize: 18,
-  },
-  groupContent: {
-    borderTopWidth: 1,
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-})
diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx
deleted file mode 100644
index 2181a94aa..000000000
--- a/src/view/com/modals/ChangeHandle.tsx
+++ /dev/null
@@ -1,614 +0,0 @@
-import React, {useState} from 'react'
-import {
-  ActivityIndicator,
-  StyleSheet,
-  TouchableOpacity,
-  View,
-} from 'react-native'
-import {setStringAsync} from 'expo-clipboard'
-import {ComAtprotoServerDescribeServer} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {usePalette} from '#/lib/hooks/usePalette'
-import {cleanError} from '#/lib/strings/errors'
-import {createFullHandle, makeValidHandle} from '#/lib/strings/handles'
-import {s} from '#/lib/styles'
-import {useTheme} from '#/lib/ThemeContext'
-import {logger} from '#/logger'
-import {useModalControls} from '#/state/modals'
-import {useFetchDid, useUpdateHandleMutation} from '#/state/queries/handle'
-import {useServiceQuery} from '#/state/queries/service'
-import {SessionAccount, useAgent, useSession} from '#/state/session'
-import {ErrorMessage} from '../util/error/ErrorMessage'
-import {Button} from '../util/forms/Button'
-import {SelectableBtn} from '../util/forms/SelectableBtn'
-import {Text} from '../util/text/Text'
-import * as Toast from '../util/Toast'
-import {ScrollView, TextInput} from './util'
-
-export const snapPoints = ['100%']
-
-export type Props = {onChanged: () => void}
-
-export function Component(props: Props) {
-  const {currentAccount} = useSession()
-  const agent = useAgent()
-  const {
-    isLoading,
-    data: serviceInfo,
-    error: serviceInfoError,
-  } = useServiceQuery(agent.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 {closeModal} = useModalControls()
-  const {mutateAsync: updateHandle, isPending: isUpdateHandlePending} =
-    useUpdateHandleMutation()
-  const agent = useAgent()
-
-  const [error, setError] = useState<string>('')
-
-  const [isCustom, setCustom] = React.useState<boolean>(false)
-  const [handle, setHandle] = React.useState<string>('')
-  const [canSave, setCanSave] = React.useState<boolean>(false)
-
-  const userDomain = serviceInfo.availableUserDomains?.[0]
-
-  // events
-  // =
-  const onPressCancel = React.useCallback(() => {
-    closeModal()
-  }, [closeModal])
-  const onToggleCustom = React.useCallback(() => {
-    // toggle between a provided domain vs a custom one
-    setHandle('')
-    setCanSave(false)
-    setCustom(!isCustom)
-  }, [setCustom, isCustom])
-  const onPressSave = React.useCallback(async () => {
-    if (!userDomain) {
-      logger.error(`ChangeHandle: userDomain is undefined`, {
-        service: serviceInfo,
-      })
-      setError(`The service you've selected has no domains configured.`)
-      return
-    }
-
-    try {
-      const newHandle = isCustom ? handle : createFullHandle(handle, userDomain)
-      logger.debug(`Updating handle to ${newHandle}`)
-      await updateHandle({
-        handle: newHandle,
-      })
-      await agent.resumeSession(agent.session!)
-      closeModal()
-      onChanged()
-    } catch (err: any) {
-      setError(cleanError(err))
-      logger.error('Failed to update handle', {handle, message: err})
-    } finally {
-    }
-  }, [
-    setError,
-    handle,
-    userDomain,
-    isCustom,
-    onChanged,
-    closeModal,
-    updateHandle,
-    serviceInfo,
-    agent,
-  ])
-
-  // rendering
-  // =
-  return (
-    <View style={[s.flex1, pal.view]}>
-      <View style={[styles.title, pal.border]}>
-        <View style={styles.titleLeft}>
-          <TouchableOpacity
-            onPress={onPressCancel}
-            accessibilityRole="button"
-            accessibilityLabel={_(msg`Cancel change handle`)}
-            accessibilityHint={_(msg`Exits handle change process`)}
-            onAccessibilityEscape={onPressCancel}>
-            <Text type="lg" style={pal.textLight}>
-              <Trans>Cancel</Trans>
-            </Text>
-          </TouchableOpacity>
-        </View>
-        <Text
-          type="2xl-bold"
-          style={[styles.titleMiddle, pal.text]}
-          numberOfLines={1}>
-          <Trans>Change Handle</Trans>
-        </Text>
-        <View style={styles.titleRight}>
-          {isUpdateHandlePending ? (
-            <ActivityIndicator />
-          ) : canSave ? (
-            <TouchableOpacity
-              onPress={onPressSave}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Save handle change`)}
-              accessibilityHint={_(msg`Saves handle change to ${handle}`)}>
-              <Text type="2xl-medium" style={pal.link}>
-                <Trans>Save</Trans>
-              </Text>
-            </TouchableOpacity>
-          ) : undefined}
-        </View>
-      </View>
-      <ScrollView style={styles.inner}>
-        {error !== '' && (
-          <View style={styles.errorContainer}>
-            <ErrorMessage message={error} />
-          </View>
-        )}
-
-        {isCustom ? (
-          <CustomHandleForm
-            currentAccount={currentAccount}
-            handle={handle}
-            isProcessing={isUpdateHandlePending}
-            canSave={canSave}
-            onToggleCustom={onToggleCustom}
-            setHandle={setHandle}
-            setCanSave={setCanSave}
-            onPressSave={onPressSave}
-          />
-        ) : (
-          <ProvidedHandleForm
-            handle={handle}
-            userDomain={userDomain}
-            isProcessing={isUpdateHandlePending}
-            onToggleCustom={onToggleCustom}
-            setHandle={setHandle}
-            setCanSave={setCanSave}
-          />
-        )}
-      </ScrollView>
-    </View>
-  )
-}
-
-/**
- * The form for using a domain allocated by the PDS
- */
-function ProvidedHandleForm({
-  userDomain,
-  handle,
-  isProcessing,
-  setHandle,
-  onToggleCustom,
-  setCanSave,
-}: {
-  userDomain: string
-  handle: string
-  isProcessing: boolean
-  setHandle: (v: string) => void
-  onToggleCustom: () => void
-  setCanSave: (v: boolean) => void
-}) {
-  const pal = usePalette('default')
-  const theme = useTheme()
-  const {_} = useLingui()
-
-  // events
-  // =
-  const onChangeHandle = React.useCallback(
-    (v: string) => {
-      const newHandle = makeValidHandle(v)
-      setHandle(newHandle)
-      setCanSave(newHandle.length > 0)
-    },
-    [setHandle, setCanSave],
-  )
-
-  // rendering
-  // =
-  return (
-    <>
-      <View style={[pal.btn, styles.textInputWrapper]}>
-        <FontAwesomeIcon
-          icon="at"
-          style={[pal.textLight, styles.textInputIcon]}
-        />
-        <TextInput
-          testID="setHandleInput"
-          style={[pal.text, styles.textInput]}
-          placeholder={_(msg`e.g. alice`)}
-          placeholderTextColor={pal.colors.textLight}
-          autoCapitalize="none"
-          keyboardAppearance={theme.colorScheme}
-          value={handle}
-          onChangeText={onChangeHandle}
-          editable={!isProcessing}
-          accessible={true}
-          accessibilityLabel={_(msg`Handle`)}
-          accessibilityHint={_(msg`Sets Bluesky username`)}
-        />
-      </View>
-      <Text type="md" style={[pal.textLight, s.pl10, s.pt10]}>
-        <Trans>
-          Your full handle will be{' '}
-          <Text type="md-bold" style={pal.textLight}>
-            @{createFullHandle(handle, userDomain)}
-          </Text>
-        </Trans>
-      </Text>
-      <TouchableOpacity
-        onPress={onToggleCustom}
-        accessibilityRole="button"
-        accessibilityLabel={_(msg`Hosting provider`)}
-        accessibilityHint={_(msg`Opens modal for using custom domain`)}>
-        <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
-          <Trans>I have my own domain</Trans>
-        </Text>
-      </TouchableOpacity>
-    </>
-  )
-}
-
-/**
- * The form for using a custom domain
- */
-function CustomHandleForm({
-  currentAccount,
-  handle,
-  canSave,
-  isProcessing,
-  setHandle,
-  onToggleCustom,
-  onPressSave,
-  setCanSave,
-}: {
-  currentAccount: SessionAccount
-  handle: string
-  canSave: boolean
-  isProcessing: boolean
-  setHandle: (v: string) => void
-  onToggleCustom: () => void
-  onPressSave: () => void
-  setCanSave: (v: boolean) => void
-}) {
-  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(() => {
-    setStringAsync(isDNSForm ? `did=${currentAccount.did}` : currentAccount.did)
-    Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
-  }, [currentAccount, isDNSForm, _])
-  const onChangeHandle = React.useCallback(
-    (v: string) => {
-      setHandle(v)
-      setCanSave(false)
-    },
-    [setHandle, setCanSave],
-  )
-  const onPressVerify = React.useCallback(async () => {
-    if (canSave) {
-      onPressSave()
-    }
-    try {
-      setIsVerifying(true)
-      setError('')
-      const did = await fetchDid(handle)
-      if (did === currentAccount.did) {
-        setCanSave(true)
-      } else {
-        setError(`Incorrect DID returned (got ${did})`)
-      }
-    } catch (err: any) {
-      setError(cleanError(err))
-      logger.error('Failed to verify domain', {handle, error: err})
-    } finally {
-      setIsVerifying(false)
-    }
-  }, [
-    handle,
-    currentAccount,
-    setIsVerifying,
-    setCanSave,
-    setError,
-    canSave,
-    onPressSave,
-    fetchDid,
-  ])
-
-  // rendering
-  // =
-  return (
-    <>
-      <Text type="md" style={[pal.text, s.pb5, s.pl5]} nativeID="customDomain">
-        <Trans>Enter the domain you want to use</Trans>
-      </Text>
-      <View style={[pal.btn, styles.textInputWrapper]}>
-        <FontAwesomeIcon
-          icon="at"
-          style={[pal.textLight, styles.textInputIcon]}
-        />
-        <TextInput
-          testID="setHandleInput"
-          style={[pal.text, styles.textInput]}
-          placeholder={_(msg`e.g. alice.com`)}
-          placeholderTextColor={pal.colors.textLight}
-          autoCapitalize="none"
-          keyboardAppearance={theme.colorScheme}
-          value={handle}
-          onChangeText={onChangeHandle}
-          editable={!isProcessing}
-          accessibilityLabelledBy="customDomain"
-          accessibilityLabel={_(msg`Custom domain`)}
-          accessibilityHint={_(msg`Input your preferred hosting provider`)}
-        />
-      </View>
-      <View style={styles.spacer} />
-
-      <View style={[styles.selectableBtns]}>
-        <SelectableBtn
-          selected={isDNSForm}
-          label={_(msg`DNS Panel`)}
-          left
-          onSelect={() => setDNSForm(true)}
-          accessibilityHint={_(msg`Use the DNS panel`)}
-          style={s.flex1}
-        />
-        <SelectableBtn
-          selected={!isDNSForm}
-          label={_(msg`No DNS Panel`)}
-          right
-          onSelect={() => setDNSForm(false)}
-          accessibilityHint={_(msg`Use a file on your server`)}
-          style={s.flex1}
-        />
-      </View>
-      <View style={styles.spacer} />
-      {isDNSForm ? (
-        <>
-          <Text type="md" style={[pal.text, s.pb5, s.pl5]}>
-            <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]}>
-              <Trans>Host:</Trans>
-            </Text>
-            <View style={[styles.dnsValue]}>
-              <Text type="mono" style={[styles.monoText, pal.text]}>
-                _atproto
-              </Text>
-            </View>
-            <Text type="md-medium" style={[styles.dnsLabel, pal.text]}>
-              <Trans>Type:</Trans>
-            </Text>
-            <View style={[styles.dnsValue]}>
-              <Text type="mono" style={[styles.monoText, pal.text]}>
-                TXT
-              </Text>
-            </View>
-            <Text type="md-medium" style={[styles.dnsLabel, pal.text]}>
-              <Trans>Value:</Trans>
-            </Text>
-            <View style={[styles.dnsValue]}>
-              <Text type="mono" style={[styles.monoText, pal.text]}>
-                did={currentAccount.did}
-              </Text>
-            </View>
-          </View>
-          <Text type="md" style={[pal.text, s.pt20, s.pl5]}>
-            <Trans>This should create a domain record at:</Trans>
-          </Text>
-          <Text type="mono" style={[styles.monoText, pal.text, s.pt5, s.pl5]}>
-            _atproto.{handle}
-          </Text>
-        </>
-      ) : (
-        <>
-          <Text type="md" style={[pal.text, s.pb5, s.pl5]}>
-            <Trans>Upload a text file to:</Trans>
-          </Text>
-          <View style={[styles.valueContainer, pal.btn]}>
-            <View style={[styles.dnsValue]}>
-              <Text type="mono" style={[styles.monoText, pal.text]}>
-                https://{handle}/.well-known/atproto-did
-              </Text>
-            </View>
-          </View>
-          <View style={styles.spacer} />
-          <Text type="md" style={[pal.text, s.pb5, s.pl5]}>
-            <Trans>That contains the following:</Trans>
-          </Text>
-          <View style={[styles.valueContainer, pal.btn]}>
-            <View style={[styles.dnsValue]}>
-              <Text type="mono" style={[styles.monoText, pal.text]}>
-                {currentAccount.did}
-              </Text>
-            </View>
-          </View>
-        </>
-      )}
-
-      <View style={styles.spacer} />
-      <Button type="default" style={[s.p20, s.mb10]} onPress={onPressCopy}>
-        <Text type="xl" style={[pal.link, s.textCenter]}>
-          <Trans>
-            Copy {isDNSForm ? _(msg`Domain Value`) : _(msg`File Contents`)}
-          </Trans>
-        </Text>
-      </Button>
-      {canSave === true && (
-        <View style={[styles.message, palSecondary.view]}>
-          <Text type="md-medium" style={palSecondary.text}>
-            <Trans>Domain verified!</Trans>
-          </Text>
-        </View>
-      )}
-      {error ? (
-        <View style={[styles.message, palError.view]}>
-          <Text type="md-medium" style={palError.text}>
-            {error}
-          </Text>
-        </View>
-      ) : null}
-      <Button
-        type="primary"
-        style={[s.p20, isVerifying && styles.dimmed]}
-        onPress={onPressVerify}>
-        {isVerifying ? (
-          <ActivityIndicator color="white" />
-        ) : (
-          <Text type="xl-medium" style={[s.white, s.textCenter]}>
-            {canSave
-              ? _(msg`Update to ${handle}`)
-              : isDNSForm
-              ? _(msg`Verify DNS Record`)
-              : _(msg`Verify Text File`)}
-          </Text>
-        )}
-      </Button>
-      <View style={styles.spacer} />
-      <TouchableOpacity
-        onPress={onToggleCustom}
-        accessibilityLabel={_(msg`Use default provider`)}
-        accessibilityHint={_(msg`Use bsky.social as hosting provider`)}>
-        <Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
-          <Trans>Nevermind, create a handle for me</Trans>
-        </Text>
-      </TouchableOpacity>
-    </>
-  )
-}
-
-const styles = StyleSheet.create({
-  inner: {
-    padding: 14,
-  },
-  footer: {
-    padding: 14,
-  },
-  spacer: {
-    height: 20,
-  },
-  dimmed: {
-    opacity: 0.7,
-  },
-
-  selectableBtns: {
-    flexDirection: 'row',
-  },
-
-  title: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingTop: 25,
-    paddingHorizontal: 20,
-    paddingBottom: 15,
-    borderBottomWidth: 1,
-  },
-  titleLeft: {
-    width: 80,
-  },
-  titleRight: {
-    width: 80,
-    flexDirection: 'row',
-    justifyContent: 'flex-end',
-  },
-  titleMiddle: {
-    flex: 1,
-    textAlign: 'center',
-    fontSize: 21,
-  },
-
-  textInputWrapper: {
-    borderRadius: 8,
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  textInputIcon: {
-    marginLeft: 12,
-  },
-  textInput: {
-    flex: 1,
-    width: '100%',
-    paddingVertical: 10,
-    paddingHorizontal: 8,
-    fontSize: 17,
-    letterSpacing: 0.25,
-    fontWeight: '400',
-    borderRadius: 10,
-  },
-
-  valueContainer: {
-    borderRadius: 4,
-    paddingVertical: 16,
-  },
-
-  dnsTable: {
-    borderRadius: 4,
-    paddingTop: 2,
-    paddingBottom: 16,
-  },
-  dnsLabel: {
-    paddingHorizontal: 14,
-    paddingTop: 10,
-  },
-  dnsValue: {
-    paddingHorizontal: 14,
-    borderRadius: 4,
-  },
-  monoText: {
-    fontSize: 18,
-    lineHeight: 20,
-  },
-
-  message: {
-    paddingHorizontal: 12,
-    paddingVertical: 10,
-    borderRadius: 8,
-    marginBottom: 10,
-  },
-
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    width: '100%',
-    borderRadius: 32,
-    padding: 10,
-    marginBottom: 10,
-  },
-  errorContainer: {marginBottom: 10},
-})
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index becb39ff3..78f4a0117 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -7,9 +7,7 @@ import {usePalette} from '#/lib/hooks/usePalette'
 import {useModalControls, useModals} from '#/state/modals'
 import {FullWindowOverlay} from '#/components/FullWindowOverlay'
 import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
-import * as AddAppPassword from './AddAppPasswords'
 import * as ChangeEmailModal from './ChangeEmail'
-import * as ChangeHandleModal from './ChangeHandle'
 import * as ChangePasswordModal from './ChangePassword'
 import * as CreateOrEditListModal from './CreateOrEditList'
 import * as DeleteAccountModal from './DeleteAccount'
@@ -69,15 +67,9 @@ export function ModalsContainer() {
   } else if (activeModal?.name === 'delete-account') {
     snapPoints = DeleteAccountModal.snapPoints
     element = <DeleteAccountModal.Component />
-  } else if (activeModal?.name === 'change-handle') {
-    snapPoints = ChangeHandleModal.snapPoints
-    element = <ChangeHandleModal.Component {...activeModal} />
   } else if (activeModal?.name === 'invite-codes') {
     snapPoints = InviteCodesModal.snapPoints
     element = <InviteCodesModal.Component />
-  } else if (activeModal?.name === 'add-app-password') {
-    snapPoints = AddAppPassword.snapPoints
-    element = <AddAppPassword.Component />
   } else if (activeModal?.name === 'content-languages-settings') {
     snapPoints = ContentLanguagesSettingsModal.snapPoints
     element = <ContentLanguagesSettingsModal.Component />
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index 46ced58d9..e9d9c01dd 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -7,9 +7,7 @@ import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import type {Modal as ModalIface} from '#/state/modals'
 import {useModalControls, useModals} from '#/state/modals'
-import * as AddAppPassword from './AddAppPasswords'
 import * as ChangeEmailModal from './ChangeEmail'
-import * as ChangeHandleModal from './ChangeHandle'
 import * as ChangePasswordModal from './ChangePassword'
 import * as CreateOrEditListModal from './CreateOrEditList'
 import * as CropImageModal from './CropImage.web'
@@ -74,12 +72,8 @@ function Modal({modal}: {modal: ModalIface}) {
     element = <CropImageModal.Component {...modal} />
   } else if (modal.name === 'delete-account') {
     element = <DeleteAccountModal.Component />
-  } else if (modal.name === 'change-handle') {
-    element = <ChangeHandleModal.Component {...modal} />
   } else if (modal.name === 'invite-codes') {
     element = <InviteCodesModal.Component />
-  } else if (modal.name === 'add-app-password') {
-    element = <AddAppPassword.Component />
   } else if (modal.name === 'content-languages-settings') {
     element = <ContentLanguagesSettingsModal.Component />
   } else if (modal.name === 'post-languages-settings') {
diff --git a/src/view/com/util/AccountDropdownBtn.tsx b/src/view/com/util/AccountDropdownBtn.tsx
deleted file mode 100644
index e7985bccf..000000000
--- a/src/view/com/util/AccountDropdownBtn.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from 'react'
-import {Pressable} from 'react-native'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {usePalette} from '#/lib/hooks/usePalette'
-import {s} from '#/lib/styles'
-import {SessionAccount, useSessionApi} from '#/state/session'
-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}) {
-  const pal = usePalette('default')
-  const {removeAccount} = useSessionApi()
-  const removePromptControl = useDialogControl()
-  const {_} = useLingui()
-
-  const items: DropdownItem[] = [
-    {
-      label: _(msg`Remove account`),
-      onPress: removePromptControl.open,
-      icon: {
-        ios: {
-          name: 'trash',
-        },
-        android: 'ic_delete',
-        web: ['far', 'trash-can'],
-      },
-    },
-  ]
-  return (
-    <>
-      <Pressable accessibilityRole="button" style={s.pl10}>
-        <NativeDropdown
-          testID="accountSettingsDropdownBtn"
-          items={items}
-          accessibilityLabel={_(msg`Account options`)}
-          accessibilityHint="">
-          <FontAwesomeIcon
-            icon="ellipsis-h"
-            style={pal.textLight as FontAwesomeIconStyle}
-          />
-        </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"
-      />
-    </>
-  )
-}
diff --git a/src/view/com/util/List.web.tsx b/src/view/com/util/List.web.tsx
index 59a79b531..5ddc4ea8a 100644
--- a/src/view/com/util/List.web.tsx
+++ b/src/view/com/util/List.web.tsx
@@ -448,7 +448,9 @@ let Row = function RowImpl<ItemT>({
   onItemSeen: ((item: any) => void) | undefined
 }): React.ReactNode {
   const rowRef = React.useRef(null)
-  const intersectionTimeout = React.useRef<NodeJS.Timer | undefined>(undefined)
+  const intersectionTimeout = React.useRef<
+    ReturnType<typeof setTimeout> | undefined
+  >(undefined)
 
   const handleIntersection = useNonReactiveCallback(
     (entries: IntersectionObserverEntry[]) => {