about summary refs log tree commit diff
path: root/src/screens
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2024-11-23 16:20:24 -0800
committerGitHub <noreply@github.com>2024-11-23 16:20:24 -0800
commit32bf8122e8c8a0fbadd53b8a015cfbc9014519a2 (patch)
tree55bd24596e6fadadbf4326b26e3d14e418c5c7bb /src/screens
parent523d1f01a51c0e85e49916fb42b204f7004ffac1 (diff)
parentb4d07c4112b9a62b5380948051aa4a7fd391a2d4 (diff)
downloadvoidsky-32bf8122e8c8a0fbadd53b8a015cfbc9014519a2.tar.zst
Merge branch 'main' into main
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/Login/PasswordUpdatedForm.tsx1
-rw-r--r--src/screens/Login/SetNewPasswordForm.tsx2
-rw-r--r--src/screens/Messages/ChatList.tsx2
-rw-r--r--src/screens/Messages/Conversation.tsx26
-rw-r--r--src/screens/Messages/Settings.tsx2
-rw-r--r--src/screens/Messages/components/ChatDisabled.tsx2
-rw-r--r--src/screens/Messages/components/MessageInput.tsx21
-rw-r--r--src/screens/Messages/components/MessageInput.web.tsx4
-rw-r--r--src/screens/Messages/components/MessageInputEmbed.tsx2
-rw-r--r--src/screens/Messages/components/MessagesList.tsx36
-rw-r--r--src/screens/Onboarding/Layout.tsx3
-rw-r--r--src/screens/Onboarding/StepFinished.tsx41
-rw-r--r--src/screens/Profile/Header/DisplayName.tsx1
-rw-r--r--src/screens/Profile/Header/EditProfileDialog.tsx2
-rw-r--r--src/screens/Profile/Header/GrowableAvatar.tsx2
-rw-r--r--src/screens/Profile/Header/GrowableBanner.tsx15
-rw-r--r--src/screens/Profile/Header/Handle.tsx3
-rw-r--r--src/screens/Profile/Header/Metrics.tsx1
-rw-r--r--src/screens/Profile/Header/Shell.tsx18
-rw-r--r--src/screens/Settings/AboutSettings.tsx1
-rw-r--r--src/screens/Settings/AccessibilitySettings.tsx1
-rw-r--r--src/screens/Settings/AccountSettings.tsx1
-rw-r--r--src/screens/Settings/AppPasswords.tsx2
-rw-r--r--src/screens/Settings/ContentAndMediaSettings.tsx57
-rw-r--r--src/screens/Settings/ExternalMediaPreferences.tsx2
-rw-r--r--src/screens/Settings/FollowingFeedPreferences.tsx1
-rw-r--r--src/screens/Settings/LanguageSettings.tsx2
-rw-r--r--src/screens/Settings/NotificationSettings.tsx1
-rw-r--r--src/screens/Settings/PrivacyAndSecuritySettings.tsx1
-rw-r--r--src/screens/Settings/Settings.tsx2
-rw-r--r--src/screens/Settings/ThreadPreferences.tsx7
-rw-r--r--src/screens/Settings/components/AddAppPasswordDialog.tsx2
-rw-r--r--src/screens/Settings/components/ChangeHandleDialog.tsx3
-rw-r--r--src/screens/Settings/components/CopyButton.tsx2
-rw-r--r--src/screens/Settings/components/DisableEmail2FADialog.tsx2
-rw-r--r--src/screens/Settings/components/SettingsList.tsx1
-rw-r--r--src/screens/Signup/BackNextButtons.tsx1
-rw-r--r--src/screens/Signup/StepInfo/Policies.tsx2
-rw-r--r--src/screens/SignupQueued.tsx53
-rw-r--r--src/screens/StarterPack/Wizard/StepDetails.tsx1
-rw-r--r--src/screens/StarterPack/Wizard/StepFeeds.tsx2
-rw-r--r--src/screens/StarterPack/Wizard/StepProfiles.tsx2
42 files changed, 202 insertions, 131 deletions
diff --git a/src/screens/Login/PasswordUpdatedForm.tsx b/src/screens/Login/PasswordUpdatedForm.tsx
index 9c12a47e3..b85815018 100644
--- a/src/screens/Login/PasswordUpdatedForm.tsx
+++ b/src/screens/Login/PasswordUpdatedForm.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx
index 9efbb96ce..4d09e32a3 100644
--- a/src/screens/Login/SetNewPasswordForm.tsx
+++ b/src/screens/Login/SetNewPasswordForm.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react'
+import {useState} from 'react'
 import {ActivityIndicator, View} from 'react-native'
 import {BskyAgent} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx
index 45b3bf14f..4f2bd251f 100644
--- a/src/screens/Messages/ChatList.tsx
+++ b/src/screens/Messages/ChatList.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react'
+import {useCallback, useEffect, useMemo, useState} from 'react'
 import {View} from 'react-native'
 import {ChatBskyConvoDefs} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx
index e2e646a3d..ee09adaf0 100644
--- a/src/screens/Messages/Conversation.tsx
+++ b/src/screens/Messages/Conversation.tsx
@@ -4,10 +4,11 @@ import {useKeyboardController} from 'react-native-keyboard-controller'
 import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {useFocusEffect} from '@react-navigation/native'
+import {useFocusEffect, useNavigation} from '@react-navigation/native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
-import {CommonNavigatorParams} from '#/lib/routes/types'
+import {useEmail} from '#/lib/hooks/useEmail'
+import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
 import {isWeb} from '#/platform/detection'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
@@ -19,6 +20,8 @@ import {useSetMinimalShellMode} from '#/state/shell'
 import {CenteredView} from '#/view/com/util/Views'
 import {MessagesList} from '#/screens/Messages/components/MessagesList'
 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
+import {useDialogControl} from '#/components/Dialog'
+import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
 import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter'
 import {MessagesListHeader} from '#/components/dms/MessagesListHeader'
 import {Error} from '#/components/Error'
@@ -161,8 +164,12 @@ function InnerReady({
   hasScrolled: boolean
   setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
 }) {
+  const {_} = useLingui()
   const convoState = useConvo()
+  const navigation = useNavigation<NavigationProp>()
   const recipient = useProfileShadow(recipientUnshadowed)
+  const verifyEmailControl = useDialogControl()
+  const {needsEmailVerification} = useEmail()
 
   const moderation = React.useMemo(() => {
     return moderateProfile(recipient, moderationOpts)
@@ -179,6 +186,12 @@ function InnerReady({
     }
   }, [moderation])
 
+  React.useEffect(() => {
+    if (needsEmailVerification) {
+      verifyEmailControl.open()
+    }
+  }, [needsEmailVerification, verifyEmailControl])
+
   return (
     <>
       <MessagesListHeader
@@ -201,6 +214,15 @@ function InnerReady({
           }
         />
       )}
+      <VerifyEmailDialog
+        reasonText={_(
+          msg`Before you may message another user, you must first verify your email.`,
+        )}
+        control={verifyEmailControl}
+        onCloseWithoutVerifying={() => {
+          navigation.navigate('Home')
+        }}
+      />
     </>
   )
 }
diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx
index 93e3bc400..50b1c4cc9 100644
--- a/src/screens/Messages/Settings.tsx
+++ b/src/screens/Messages/Settings.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback} from 'react'
+import {useCallback} from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Messages/components/ChatDisabled.tsx b/src/screens/Messages/components/ChatDisabled.tsx
index c768d2504..5e9f57fa5 100644
--- a/src/screens/Messages/components/ChatDisabled.tsx
+++ b/src/screens/Messages/components/ChatDisabled.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useState} from 'react'
+import {useCallback, useState} from 'react'
 import {View} from 'react-native'
 import {ComAtprotoModerationDefs} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Messages/components/MessageInput.tsx b/src/screens/Messages/components/MessageInput.tsx
index 21d6e574e..85509211b 100644
--- a/src/screens/Messages/components/MessageInput.tsx
+++ b/src/screens/Messages/components/MessageInput.tsx
@@ -18,6 +18,7 @@ import Graphemer from 'graphemer'
 
 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
 import {useHaptics} from '#/lib/haptics'
+import {useEmail} from '#/lib/hooks/useEmail'
 import {isIOS} from '#/platform/detection'
 import {
   useMessageDraft,
@@ -61,10 +62,15 @@ export function MessageInput({
   const [message, setMessage] = React.useState(getDraft)
   const inputRef = useAnimatedRef<TextInput>()
 
+  const {needsEmailVerification} = useEmail()
+
   useSaveMessageDraft(message)
   useExtractEmbedFromFacets(message, setEmbed)
 
   const onSubmit = React.useCallback(() => {
+    if (needsEmailVerification) {
+      return
+    }
     if (!hasEmbed && message.trim() === '') {
       return
     }
@@ -84,6 +90,7 @@ export function MessageInput({
       inputRef.current?.focus()
     }, 100)
   }, [
+    needsEmailVerification,
     hasEmbed,
     message,
     clearDraft,
@@ -101,22 +108,22 @@ export function MessageInput({
         const measurement = measure(inputRef)
         if (!measurement) return
 
-        const max = windowHeight - -keyboardHeight.value - topInset - 150
+        const max = windowHeight - -keyboardHeight.get() - topInset - 150
         const availableSpace = max - measurement.height
 
-        maxHeight.value = max
-        isInputScrollable.value = availableSpace < 30
+        maxHeight.set(max)
+        isInputScrollable.set(availableSpace < 30)
       },
     },
     [windowHeight, topInset],
   )
 
   const animatedStyle = useAnimatedStyle(() => ({
-    maxHeight: maxHeight.value,
+    maxHeight: maxHeight.get(),
   }))
 
   const animatedProps = useAnimatedProps(() => ({
-    scrollEnabled: isInputScrollable.value,
+    scrollEnabled: isInputScrollable.get(),
   }))
 
   return (
@@ -159,6 +166,7 @@ export function MessageInput({
           ref={inputRef}
           hitSlop={HITSLOP_10}
           animatedProps={animatedProps}
+          editable={!needsEmailVerification}
         />
         <Pressable
           accessibilityRole="button"
@@ -171,7 +179,8 @@ export function MessageInput({
             a.justify_center,
             {height: 30, width: 30, backgroundColor: t.palette.primary_500},
           ]}
-          onPress={onSubmit}>
+          onPress={onSubmit}
+          disabled={needsEmailVerification}>
           <PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} />
         </Pressable>
       </View>
diff --git a/src/screens/Messages/components/MessageInput.web.tsx b/src/screens/Messages/components/MessageInput.web.tsx
index b15cd2492..72e0382a9 100644
--- a/src/screens/Messages/components/MessageInput.web.tsx
+++ b/src/screens/Messages/components/MessageInput.web.tsx
@@ -38,7 +38,7 @@ export function MessageInput({
   children?: React.ReactNode
   openEmojiPicker?: (pos: EmojiPickerPosition) => void
 }) {
-  const {isTabletOrDesktop} = useWebMediaQueries()
+  const {isMobile} = useWebMediaQueries()
   const {_} = useLingui()
   const t = useTheme()
   const {getDraft, clearDraft} = useMessageDraft()
@@ -212,7 +212,7 @@ export function MessageInput({
           onChange={onChange}
           // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
           // in these cases.
-          onKeyDown={isTouchDevice && isTabletOrDesktop ? undefined : onKeyDown}
+          onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown}
         />
         <Pressable
           accessibilityRole="button"
diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx
index 2d1551019..6df0ef2fc 100644
--- a/src/screens/Messages/components/MessageInputEmbed.tsx
+++ b/src/screens/Messages/components/MessageInputEmbed.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react'
+import {useCallback, useEffect, useMemo, useState} from 'react'
 import {LayoutAnimation, View} from 'react-native'
 import {
   AppBskyFeedPost,
diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx
index 9db4f07b6..9f67929a3 100644
--- a/src/screens/Messages/components/MessagesList.tsx
+++ b/src/screens/Messages/components/MessagesList.tsx
@@ -145,7 +145,7 @@ export function MessagesList({
     (_: number, height: number) => {
       // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
       // previous off whenever we add new content to the previous offset whenever we add new content to the list.
-      if (isWeb && isAtTop.value && hasScrolled) {
+      if (isWeb && isAtTop.get() && hasScrolled) {
         flatListRef.current?.scrollToOffset({
           offset: height - prevContentHeight.current,
           animated: false,
@@ -153,7 +153,7 @@ export function MessagesList({
       }
 
       // This number _must_ be the height of the MaybeLoader component
-      if (height > 50 && isAtBottom.value) {
+      if (height > 50 && isAtBottom.get()) {
         // If the size of the content is changing by more than the height of the screen, then we don't
         // want to scroll further than the start of all the new content. Since we are storing the previous offset,
         // we can just scroll the user to that offset and add a little bit of padding. We'll also show the pill
@@ -161,7 +161,7 @@ export function MessagesList({
         if (
           didBackground.current &&
           hasScrolled &&
-          height - prevContentHeight.current > layoutHeight.value - 50 &&
+          height - prevContentHeight.current > layoutHeight.get() - 50 &&
           convoState.items.length - prevItemCount.current > 1
         ) {
           flatListRef.current?.scrollToOffset({
@@ -209,7 +209,7 @@ export function MessagesList({
   )
 
   const onStartReached = useCallback(() => {
-    if (hasScrolled && prevContentHeight.current > layoutHeight.value) {
+    if (hasScrolled && prevContentHeight.current > layoutHeight.get()) {
       convoState.fetchMessageHistory()
     }
   }, [convoState, hasScrolled, layoutHeight])
@@ -217,18 +217,18 @@ export function MessagesList({
   const onScroll = React.useCallback(
     (e: ReanimatedScrollEvent) => {
       'worklet'
-      layoutHeight.value = e.layoutMeasurement.height
+      layoutHeight.set(e.layoutMeasurement.height)
       const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height
 
       // Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom
       // when a new message is added, hence the 100 pixel offset
-      isAtBottom.value = e.contentSize.height - 100 < bottomOffset
-      isAtTop.value = e.contentOffset.y <= 1
+      isAtBottom.set(e.contentSize.height - 100 < bottomOffset)
+      isAtTop.set(e.contentOffset.y <= 1)
 
       if (
         newMessagesPill.show &&
         (e.contentOffset.y > newMessagesPill.startContentOffset + 200 ||
-          isAtBottom.value)
+          isAtBottom.get())
       ) {
         runOnJS(setNewMessagesPill)({
           show: false,
@@ -256,28 +256,28 @@ export function MessagesList({
       // Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should
       // just update the height here instead of having the `onMove` event do it (that event will not fire!)
       if (e.duration === 0) {
-        layoutScrollWithoutAnimation.value = true
-        keyboardHeight.value = e.height
+        layoutScrollWithoutAnimation.set(true)
+        keyboardHeight.set(e.height)
       } else {
-        keyboardIsOpening.value = true
+        keyboardIsOpening.set(true)
       }
     },
     onMove: e => {
       'worklet'
-      keyboardHeight.value = e.height
+      keyboardHeight.set(e.height)
       if (e.height > bottomOffset) {
         scrollTo(flatListRef, 0, 1e7, false)
       }
     },
     onEnd: () => {
       'worklet'
-      keyboardIsOpening.value = false
+      keyboardIsOpening.set(false)
     },
   })
 
   const animatedListStyle = useAnimatedStyle(() => ({
     marginBottom:
-      keyboardHeight.value > bottomOffset ? keyboardHeight.value : bottomOffset,
+      keyboardHeight.get() > bottomOffset ? keyboardHeight.get() : bottomOffset,
   }))
 
   // -- Message sending
@@ -363,13 +363,13 @@ export function MessagesList({
   // -- List layout changes (opening emoji keyboard, etc.)
   const onListLayout = React.useCallback(
     (e: LayoutChangeEvent) => {
-      layoutHeight.value = e.nativeEvent.layout.height
+      layoutHeight.set(e.nativeEvent.layout.height)
 
-      if (isWeb || !keyboardIsOpening.value) {
+      if (isWeb || !keyboardIsOpening.get()) {
         flatListRef.current?.scrollToEnd({
-          animated: !layoutScrollWithoutAnimation.value,
+          animated: !layoutScrollWithoutAnimation.get(),
         })
-        layoutScrollWithoutAnimation.value = false
+        layoutScrollWithoutAnimation.set(false)
       }
     },
     [
diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx
index 02b207303..4a07ebd83 100644
--- a/src/screens/Onboarding/Layout.tsx
+++ b/src/screens/Onboarding/Layout.tsx
@@ -17,10 +17,11 @@ import {
   useTheme,
   web,
 } from '#/alf'
+import {leading} from '#/alf/typography'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
 import {createPortalGroup} from '#/components/Portal'
-import {leading, P, Text} from '#/components/Typography'
+import {P, Text} from '#/components/Typography'
 import {IS_DEV} from '#/env'
 
 const COL_WIDTH = 420
diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx
index fdc0a3eb7..0d8971b6f 100644
--- a/src/screens/Onboarding/StepFinished.tsx
+++ b/src/screens/Onboarding/StepFinished.tsx
@@ -127,31 +127,36 @@ export function StepFinished() {
         })(),
         (async () => {
           const {imageUri, imageMime} = profileStepResults
-          if (imageUri && imageMime) {
-            const blobPromise = uploadBlob(agent, imageUri, imageMime)
-            await agent.upsertProfile(async existing => {
-              existing = existing ?? {}
+          const blobPromise =
+            imageUri && imageMime
+              ? uploadBlob(agent, imageUri, imageMime)
+              : undefined
+
+          await agent.upsertProfile(async existing => {
+            existing = existing ?? {}
+
+            if (blobPromise) {
               const res = await blobPromise
               if (res.data.blob) {
                 existing.avatar = res.data.blob
               }
+            }
 
-              if (starterPack) {
-                existing.joinedViaStarterPack = {
-                  uri: starterPack.uri,
-                  cid: starterPack.cid,
-                }
+            if (starterPack) {
+              existing.joinedViaStarterPack = {
+                uri: starterPack.uri,
+                cid: starterPack.cid,
               }
+            }
 
-              existing.displayName = ''
-              // HACKFIX
-              // creating a bunch of identical profile objects is breaking the relay
-              // tossing this unspecced field onto it to reduce the size of the problem
-              // -prf
-              existing.createdAt = new Date().toISOString()
-              return existing
-            })
-          }
+            existing.displayName = ''
+            // HACKFIX
+            // creating a bunch of identical profile objects is breaking the relay
+            // tossing this unspecced field onto it to reduce the size of the problem
+            // -prf
+            existing.createdAt = new Date().toISOString()
+            return existing
+          })
 
           logEvent('onboarding:finished:avatarResult', {
             avatarResult: profileStepResults.isCreatedAvatar
diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx
index 6d4eea2eb..b74c063d4 100644
--- a/src/screens/Profile/Header/DisplayName.tsx
+++ b/src/screens/Profile/Header/DisplayName.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
 
diff --git a/src/screens/Profile/Header/EditProfileDialog.tsx b/src/screens/Profile/Header/EditProfileDialog.tsx
index af4b5498a..952184816 100644
--- a/src/screens/Profile/Header/EditProfileDialog.tsx
+++ b/src/screens/Profile/Header/EditProfileDialog.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useState} from 'react'
+import {useCallback, useEffect, useState} from 'react'
 import {Dimensions, View} from 'react-native'
 import {Image as RNImage} from 'react-native-image-crop-picker'
 import {AppBskyActorDefs} from '@atproto/api'
diff --git a/src/screens/Profile/Header/GrowableAvatar.tsx b/src/screens/Profile/Header/GrowableAvatar.tsx
index 20ac14892..dab69f955 100644
--- a/src/screens/Profile/Header/GrowableAvatar.tsx
+++ b/src/screens/Profile/Header/GrowableAvatar.tsx
@@ -45,7 +45,7 @@ function GrowableAvatarInner({
   const animatedStyle = useAnimatedStyle(() => ({
     transform: [
       {
-        scale: interpolate(scrollY.value, [-150, 0], [1.2, 1], {
+        scale: interpolate(scrollY.get(), [-150, 0], [1.2, 1], {
           extrapolateRight: Extrapolation.CLAMP,
         }),
       },
diff --git a/src/screens/Profile/Header/GrowableBanner.tsx b/src/screens/Profile/Header/GrowableBanner.tsx
index 144b7cd2d..7f5a3cd6e 100644
--- a/src/screens/Profile/Header/GrowableBanner.tsx
+++ b/src/screens/Profile/Header/GrowableBanner.tsx
@@ -66,7 +66,7 @@ function GrowableBannerInner({
   const animatedStyle = useAnimatedStyle(() => ({
     transform: [
       {
-        scale: interpolate(scrollY.value, [-150, 0], [2, 1], {
+        scale: interpolate(scrollY.get(), [-150, 0], [2, 1], {
           extrapolateRight: Extrapolation.CLAMP,
         }),
       },
@@ -76,7 +76,7 @@ function GrowableBannerInner({
   const animatedBlurViewProps = useAnimatedProps(() => {
     return {
       intensity: interpolate(
-        scrollY.value,
+        scrollY.get(),
         [-300, -65, -15],
         [50, 40, 0],
         Extrapolation.CLAMP,
@@ -85,16 +85,17 @@ function GrowableBannerInner({
   })
 
   const animatedSpinnerStyle = useAnimatedStyle(() => {
+    const scrollYValue = scrollY.get()
     return {
-      display: scrollY.value < 0 ? 'flex' : 'none',
+      display: scrollYValue < 0 ? 'flex' : 'none',
       opacity: interpolate(
-        scrollY.value,
+        scrollYValue,
         [-60, -15],
         [1, 0],
         Extrapolation.CLAMP,
       ),
       transform: [
-        {translateY: interpolate(scrollY.value, [-150, 0], [-75, 0])},
+        {translateY: interpolate(scrollYValue, [-150, 0], [-75, 0])},
         {rotate: '90deg'},
       ],
     }
@@ -103,7 +104,7 @@ function GrowableBannerInner({
   const animatedBackButtonStyle = useAnimatedStyle(() => ({
     transform: [
       {
-        translateY: interpolate(scrollY.value, [-150, 60], [-150, 60], {
+        translateY: interpolate(scrollY.get(), [-150, 60], [-150, 60], {
           extrapolateRight: Extrapolation.CLAMP,
         }),
       },
@@ -168,7 +169,7 @@ function useShouldAnimateSpinner({
   const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10)
 
   useAnimatedReaction(
-    () => scrollY.value < -5,
+    () => scrollY.get() < -5,
     (value, prevValue) => {
       if (value !== prevValue) {
         runOnJS(setIsOverscrolled)(value)
diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx
index acfba3fb2..27b73da70 100644
--- a/src/screens/Profile/Header/Handle.tsx
+++ b/src/screens/Profile/Header/Handle.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {AppBskyActorDefs} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
@@ -24,7 +23,7 @@ export function ProfileHeaderHandle({
   const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy
   return (
     <View
-      style={[a.flex_row, a.gap_xs, a.align_center]}
+      style={[a.flex_row, a.gap_xs, a.align_center, {maxWidth: '100%'}]}
       pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}>
       <NewskieDialog profile={profile} disabled={disableTaps} />
       {profile.viewer?.followedBy && !blockHide ? (
diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx
index 30686ef99..bd4c4521c 100644
--- a/src/screens/Profile/Header/Metrics.tsx
+++ b/src/screens/Profile/Header/Metrics.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {AppBskyActorDefs} from '@atproto/api'
 import {msg, plural} from '@lingui/macro'
diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx
index 1a1e7d4a2..573d38145 100644
--- a/src/screens/Profile/Header/Shell.tsx
+++ b/src/screens/Profile/Header/Shell.tsx
@@ -1,12 +1,6 @@
 import React, {memo} from 'react'
 import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
-import Animated, {
-  measure,
-  MeasuredDimensions,
-  runOnJS,
-  runOnUI,
-  useAnimatedRef,
-} from 'react-native-reanimated'
+import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
 import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg} from '@lingui/macro'
@@ -14,6 +8,7 @@ import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
 
 import {BACK_HITSLOP} from '#/lib/constants'
+import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {NavigationProp} from '#/lib/routes/types'
 import {isIOS} from '#/platform/detection'
@@ -49,7 +44,7 @@ let ProfileHeaderShell = ({
   const {openLightbox} = useLightboxControls()
   const navigation = useNavigation<NavigationProp>()
   const {isDesktop} = useWebMediaQueries()
-  const aviRef = useAnimatedRef()
+  const aviRef = useHandleRef()
 
   const onPressBack = React.useCallback(() => {
     if (navigation.canGoBack()) {
@@ -86,9 +81,10 @@ let ProfileHeaderShell = ({
     const modui = moderation.ui('avatar')
     const avatar = profile.avatar
     if (avatar && !(modui.blur && modui.noOverride)) {
+      const aviHandle = aviRef.current
       runOnUI(() => {
         'worklet'
-        const rect = measure(aviRef)
+        const rect = measureHandle(aviHandle)
         runOnJS(_openLightbox)(avatar, rect)
       })()
     }
@@ -170,14 +166,14 @@ let ProfileHeaderShell = ({
               styles.avi,
               profile.associated?.labeler && styles.aviLabeler,
             ]}>
-            <Animated.View ref={aviRef} collapsable={false}>
+            <View ref={aviRef} collapsable={false}>
               <UserAvatar
                 type={profile.associated?.labeler ? 'labeler' : 'user'}
                 size={90}
                 avatar={profile.avatar}
                 moderation={moderation.ui('avatar')}
               />
-            </Animated.View>
+            </View>
           </View>
         </TouchableWithoutFeedback>
       </GrowableAvatar>
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
index 3c445b966..8019a20f9 100644
--- a/src/screens/Settings/AboutSettings.tsx
+++ b/src/screens/Settings/AboutSettings.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {Platform} from 'react-native'
 import {setStringAsync} from 'expo-clipboard'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Settings/AccessibilitySettings.tsx b/src/screens/Settings/AccessibilitySettings.tsx
index dfe2c14a5..6ab0131d9 100644
--- a/src/screens/Settings/AccessibilitySettings.tsx
+++ b/src/screens/Settings/AccessibilitySettings.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
diff --git a/src/screens/Settings/AccountSettings.tsx b/src/screens/Settings/AccountSettings.tsx
index 35c5f3aa0..2495a0f2f 100644
--- a/src/screens/Settings/AccountSettings.tsx
+++ b/src/screens/Settings/AccountSettings.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
diff --git a/src/screens/Settings/AppPasswords.tsx b/src/screens/Settings/AppPasswords.tsx
index 8cebf97ce..1ea0bd1b3 100644
--- a/src/screens/Settings/AppPasswords.tsx
+++ b/src/screens/Settings/AppPasswords.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback} from 'react'
+import {useCallback} from 'react'
 import {View} from 'react-native'
 import Animated, {
   FadeIn,
diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx
index 6d293fbea..27448ba9a 100644
--- a/src/screens/Settings/ContentAndMediaSettings.tsx
+++ b/src/screens/Settings/ContentAndMediaSettings.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
@@ -10,9 +9,16 @@ import {
   useInAppBrowser,
   useSetInAppBrowser,
 } from '#/state/preferences/in-app-browser'
+import {
+  useOptOutOfUtm,
+  useSetOptOutOfUtm,
+} from '#/state/preferences/opt-out-of-utm'
 import * as SettingsList from '#/screens/Settings/components/SettingsList'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
 import * as Toggle from '#/components/forms/Toggle'
 import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble'
+import {ChainLink3_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
 import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons/Hashtag'
 import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home'
 import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh'
@@ -30,6 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) {
   const setAutoplayDisabledPref = useSetAutoplayDisabled()
   const inAppBrowserPref = useInAppBrowser()
   const setUseInAppBrowser = useSetInAppBrowser()
+  const optOutOfUtm = useOptOutOfUtm()
+  const setOptOutOfUtm = useSetOptOutOfUtm()
 
   return (
     <Layout.Screen>
@@ -69,6 +77,19 @@ export function ContentAndMediaSettingsScreen({}: Props) {
             </SettingsList.ItemText>
           </SettingsList.LinkItem>
           <SettingsList.Divider />
+          <Toggle.Item
+            name="disable_autoplay"
+            label={_(msg`Autoplay videos and GIFs`)}
+            value={!autoplayDisabledPref}
+            onChange={value => setAutoplayDisabledPref(!value)}>
+            <SettingsList.Item>
+              <SettingsList.ItemIcon icon={PlayIcon} />
+              <SettingsList.ItemText>
+                <Trans>Autoplay videos and GIFs</Trans>
+              </SettingsList.ItemText>
+              <Toggle.Platform />
+            </SettingsList.Item>
+          </Toggle.Item>
           {isNative && (
             <Toggle.Item
               name="use_in_app_browser"
@@ -84,19 +105,31 @@ export function ContentAndMediaSettingsScreen({}: Props) {
               </SettingsList.Item>
             </Toggle.Item>
           )}
-          <Toggle.Item
-            name="disable_autoplay"
-            label={_(msg`Autoplay videos and GIFs`)}
-            value={!autoplayDisabledPref}
-            onChange={value => setAutoplayDisabledPref(!value)}>
+          {isNative && <SettingsList.Divider />}
+          {isNative && (
+            <Toggle.Item
+              name="allow_utm"
+              label={_(msg`Specify Bluesky as a referer`)}
+              value={!(optOutOfUtm ?? false)}
+              onChange={value => setOptOutOfUtm(!value)}>
+              <SettingsList.Item>
+                <SettingsList.ItemIcon icon={ChainLinkIcon} />
+                <SettingsList.ItemText>
+                  <Trans>Send Bluesky referrer</Trans>
+                </SettingsList.ItemText>
+                <Toggle.Platform />
+              </SettingsList.Item>
+            </Toggle.Item>
+          )}
+          {isNative && (
             <SettingsList.Item>
-              <SettingsList.ItemIcon icon={PlayIcon} />
-              <SettingsList.ItemText>
-                <Trans>Autoplay videos and GIFs</Trans>
-              </SettingsList.ItemText>
-              <Toggle.Platform />
+              <Admonition type="info" style={[a.flex_1]}>
+                <Trans>
+                  Helps external sites estimate traffic from Bluesky.
+                </Trans>
+              </Admonition>
             </SettingsList.Item>
-          </Toggle.Item>
+          )}
         </SettingsList.Container>
       </Layout.Content>
     </Layout.Screen>
diff --git a/src/screens/Settings/ExternalMediaPreferences.tsx b/src/screens/Settings/ExternalMediaPreferences.tsx
index 91c7ea7fc..f7e081429 100644
--- a/src/screens/Settings/ExternalMediaPreferences.tsx
+++ b/src/screens/Settings/ExternalMediaPreferences.tsx
@@ -1,4 +1,4 @@
-import React, {Fragment} from 'react'
+import {Fragment} from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Settings/FollowingFeedPreferences.tsx b/src/screens/Settings/FollowingFeedPreferences.tsx
index 12de2a31a..089491dd0 100644
--- a/src/screens/Settings/FollowingFeedPreferences.tsx
+++ b/src/screens/Settings/FollowingFeedPreferences.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
diff --git a/src/screens/Settings/LanguageSettings.tsx b/src/screens/Settings/LanguageSettings.tsx
index c6cd8bb5a..acad0520c 100644
--- a/src/screens/Settings/LanguageSettings.tsx
+++ b/src/screens/Settings/LanguageSettings.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useMemo} from 'react'
+import {useCallback, useMemo} from 'react'
 import {View} from 'react-native'
 import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Settings/NotificationSettings.tsx b/src/screens/Settings/NotificationSettings.tsx
index 8e1033742..c5f7078c4 100644
--- a/src/screens/Settings/NotificationSettings.tsx
+++ b/src/screens/Settings/NotificationSettings.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {Text} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Settings/PrivacyAndSecuritySettings.tsx b/src/screens/Settings/PrivacyAndSecuritySettings.tsx
index 12e28e0d2..d695f830d 100644
--- a/src/screens/Settings/PrivacyAndSecuritySettings.tsx
+++ b/src/screens/Settings/PrivacyAndSecuritySettings.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx
index adea42e5e..126a1bc88 100644
--- a/src/screens/Settings/Settings.tsx
+++ b/src/screens/Settings/Settings.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react'
+import {useState} from 'react'
 import {LayoutAnimation, Pressable, View} from 'react-native'
 import {Linking} from 'react-native'
 import {useReducedMotion} from 'react-native-reanimated'
diff --git a/src/screens/Settings/ThreadPreferences.tsx b/src/screens/Settings/ThreadPreferences.tsx
index 24dd91bf6..d29daa58b 100644
--- a/src/screens/Settings/ThreadPreferences.tsx
+++ b/src/screens/Settings/ThreadPreferences.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -57,6 +56,12 @@ export function ThreadPreferencesScreen({}: Props) {
                 values={sortReplies ? [sortReplies] : []}
                 onChange={values => setThreadViewPrefs({sort: values[0]})}>
                 <View style={[a.gap_sm, a.flex_1]}>
+                  <Toggle.Item name="hotness" label={_(msg`Hot replies first`)}>
+                    <Toggle.Radio />
+                    <Toggle.LabelText>
+                      <Trans>Hot replies first</Trans>
+                    </Toggle.LabelText>
+                  </Toggle.Item>
                   <Toggle.Item
                     name="oldest"
                     label={_(msg`Oldest replies first`)}>
diff --git a/src/screens/Settings/components/AddAppPasswordDialog.tsx b/src/screens/Settings/components/AddAppPasswordDialog.tsx
index dcb212879..927875778 100644
--- a/src/screens/Settings/components/AddAppPasswordDialog.tsx
+++ b/src/screens/Settings/components/AddAppPasswordDialog.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect, useMemo, useState} from 'react'
+import {useEffect, useMemo, useState} from 'react'
 import {useWindowDimensions, View} from 'react-native'
 import Animated, {
   FadeIn,
diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx
index e76d6257f..9b2c4f7c3 100644
--- a/src/screens/Settings/components/ChangeHandleDialog.tsx
+++ b/src/screens/Settings/components/ChangeHandleDialog.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useMemo, useState} from 'react'
+import {useCallback, useMemo, useState} from 'react'
 import {useWindowDimensions, View} from 'react-native'
 import Animated, {
   FadeIn,
@@ -86,7 +86,6 @@ function ChangeHandleDialogInner() {
   return (
     <Dialog.ScrollableInner
       label={_(msg`Change Handle`)}
-      style={[a.overflow_hidden]}
       header={
         <Dialog.Header renderLeft={cancelButton}>
           <Dialog.HeaderText>
diff --git a/src/screens/Settings/components/CopyButton.tsx b/src/screens/Settings/components/CopyButton.tsx
index 8c6cdfa8a..82c11f58d 100644
--- a/src/screens/Settings/components/CopyButton.tsx
+++ b/src/screens/Settings/components/CopyButton.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useState} from 'react'
+import {useCallback, useEffect, useState} from 'react'
 import {GestureResponderEvent, View} from 'react-native'
 import Animated, {
   FadeOutUp,
diff --git a/src/screens/Settings/components/DisableEmail2FADialog.tsx b/src/screens/Settings/components/DisableEmail2FADialog.tsx
index 1378759b0..8ee49c989 100644
--- a/src/screens/Settings/components/DisableEmail2FADialog.tsx
+++ b/src/screens/Settings/components/DisableEmail2FADialog.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react'
+import {useState} from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx
index 07117aef3..520df4118 100644
--- a/src/screens/Settings/components/SettingsList.tsx
+++ b/src/screens/Settings/components/SettingsList.tsx
@@ -213,7 +213,6 @@ export function ItemIcon({
 }
 
 export function ItemText({
-  // eslint-disable-next-line react/prop-types
   style,
   ...props
 }: React.ComponentProps<typeof Button.ButtonText>) {
diff --git a/src/screens/Signup/BackNextButtons.tsx b/src/screens/Signup/BackNextButtons.tsx
index e2401bb11..888b9071e 100644
--- a/src/screens/Signup/BackNextButtons.tsx
+++ b/src/screens/Signup/BackNextButtons.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Signup/StepInfo/Policies.tsx b/src/screens/Signup/StepInfo/Policies.tsx
index ba0cfc2b4..3cde95be7 100644
--- a/src/screens/Signup/StepInfo/Policies.tsx
+++ b/src/screens/Signup/StepInfo/Policies.tsx
@@ -1,4 +1,4 @@
-import React, {ReactElement} from 'react'
+import {ReactElement} from 'react'
 import {View} from 'react-native'
 import {ComAtprotoServerDescribeServer} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/SignupQueued.tsx b/src/screens/SignupQueued.tsx
index e7336569c..ed261f29e 100644
--- a/src/screens/SignupQueued.tsx
+++ b/src/screens/SignupQueued.tsx
@@ -1,16 +1,17 @@
 import React from 'react'
-import {View} from 'react-native'
+import {Modal, View} from 'react-native'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {StatusBar} from 'expo-status-bar'
 import {msg, plural, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {logger} from '#/logger'
-import {isWeb} from '#/platform/detection'
+import {isIOS, 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 {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {Loader} from '#/components/Loader'
 import {P, Text} from '#/components/Typography'
@@ -86,19 +87,22 @@ export function SignupQueued() {
   )
 
   return (
-    <View
-      aria-modal
-      role="dialog"
-      aria-role="dialog"
-      aria-label={_(msg`You're in line`)}
-      accessibilityLabel={_(msg`You're in line`)}
-      accessibilityHint=""
-      style={[a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}>
+    <Modal
+      visible
+      animationType={native('slide')}
+      presentationStyle="formSheet"
+      style={[web(a.util_screen_outer)]}>
+      {isIOS && <StatusBar style="light" />}
       <ScrollView
-        style={[a.h_full, a.w_full]}
-        contentContainerStyle={{borderWidth: 0}}>
+        style={[a.flex_1, t.atoms.bg]}
+        contentContainerStyle={{borderWidth: 0}}
+        bounces={false}>
         <View
-          style={[a.flex_row, a.justify_center, gtMobile ? a.pt_4xl : a.px_xl]}>
+          style={[
+            a.flex_row,
+            a.justify_center,
+            gtMobile ? a.pt_4xl : [a.px_xl, a.pt_xl],
+          ]}>
           <View style={[a.flex_1, {maxWidth: COL_WIDTH}]}>
             <View
               style={[a.w_full, a.justify_center, a.align_center, a.my_4xl]}>
@@ -121,11 +125,14 @@ export function SignupQueued() {
                 a.px_2xl,
                 a.py_4xl,
                 a.mt_2xl,
-                t.atoms.bg_contrast_50,
+                a.mb_md,
+                a.border,
+                t.atoms.bg_contrast_25,
+                t.atoms.border_contrast_medium,
               ]}>
               {typeof placeInQueue === 'number' && (
                 <Text
-                  style={[a.text_5xl, a.text_center, a.font_bold, a.mb_2xl]}>
+                  style={[a.text_5xl, a.text_center, a.font_heavy, a.mb_2xl]}>
                   {placeInQueue}
                 </Text>
               )}
@@ -148,7 +155,14 @@ export function SignupQueued() {
             </View>
 
             {isWeb && gtMobile && (
-              <View style={[a.w_full, a.flex_row, a.justify_between, a.pt_5xl]}>
+              <View
+                style={[
+                  a.w_full,
+                  a.flex_row,
+                  a.justify_between,
+                  a.pt_5xl,
+                  {paddingBottom: 200},
+                ]}>
                 <Button
                   variant="ghost"
                   size="large"
@@ -162,8 +176,6 @@ export function SignupQueued() {
               </View>
             )}
           </View>
-
-          <View style={{height: 200}} />
         </View>
       </ScrollView>
 
@@ -171,6 +183,7 @@ export function SignupQueued() {
         <View
           style={[
             a.align_center,
+            t.atoms.bg,
             gtMobile ? a.px_5xl : a.px_xl,
             {
               paddingBottom: Math.max(insets.bottom, a.pb_5xl.paddingBottom),
@@ -190,7 +203,7 @@ export function SignupQueued() {
           </View>
         </View>
       )}
-    </View>
+    </Modal>
   )
 }
 
diff --git a/src/screens/StarterPack/Wizard/StepDetails.tsx b/src/screens/StarterPack/Wizard/StepDetails.tsx
index 4ee2cada9..49de979ee 100644
--- a/src/screens/StarterPack/Wizard/StepDetails.tsx
+++ b/src/screens/StarterPack/Wizard/StepDetails.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx
index 0cf6ab231..b776e9551 100644
--- a/src/screens/StarterPack/Wizard/StepFeeds.tsx
+++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react'
+import {useState} from 'react'
 import {ListRenderItemInfo, View} from 'react-native'
 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
 import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api'
diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx
index 054a6a63e..68f0edb90 100644
--- a/src/screens/StarterPack/Wizard/StepProfiles.tsx
+++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react'
+import {useState} from 'react'
 import {ListRenderItemInfo, View} from 'react-native'
 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
 import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'