diff --git a/assets/images/invite_friends_promo_banner.webp b/assets/images/invite_friends_promo_banner.webp new file mode 100644 index 0000000000..cb6d45e681 Binary files /dev/null and b/assets/images/invite_friends_promo_banner.webp differ diff --git a/src/features/inviteFriends/InviteScannerScreen.tsx b/src/features/inviteFriends/InviteScannerScreen.tsx index 6bb0e5b123..4b6a19cba6 100644 --- a/src/features/inviteFriends/InviteScannerScreen.tsx +++ b/src/features/inviteFriends/InviteScannerScreen.tsx @@ -25,7 +25,7 @@ export function InviteScannerScreen() { const insets = useSafeAreaInsets() const navigation = useNavigation() const [permission, requestPermission] = useCameraPermissions() - const [error, setError] = useState(null) + const [showError, setShowError] = useState(false) const [scannerEnabled, setScannerEnabled] = useState(true) const onClose = useCallback(() => { @@ -44,13 +44,7 @@ export function InviteScannerScreen() { ) if (!profileMatch) { setScannerEnabled(false) - setError( - l({ - message: 'Profile not found', - comment: - 'Error shown when a scanned QR code is not a valid Bluesky profile URL', - }), - ) + setShowError(true) return } @@ -58,11 +52,11 @@ export function InviteScannerScreen() { const handle = profileMatch[1] navigation.replace('Profile', {name: handle}) }, - [scannerEnabled, navigation, l], + [scannerEnabled, navigation], ) const onRetry = useCallback(() => { - setError(null) + setShowError(false) setScannerEnabled(true) }, []) @@ -165,7 +159,7 @@ export function InviteScannerScreen() { {/* Error overlay shown when scanned QR isn't a valid profile URL */} - {error && ( + {showError && ( [ a.flex_row, @@ -34,14 +35,11 @@ export function FollowersPromoBanner({ opacity: pressed ? 0.85 : 1, }, ]}> - () const initialNumToRender = useInitialNumToRender() const {currentAccount} = useSession() @@ -165,14 +165,18 @@ export function ProfileFollowers({name}: {name: string}) { [ax, followers, resolvedDid], ) - const inviteFriendsControl = useDialogControl() const [followersPromoDismissed, setFollowersPromoDismissed] = useFollowersPromoDismissed() - // Native-only: the invite-friends sheet is a no-op on web, so the banner - // would open nothing. Gate it to avoid a dead promo on web. + const findContactsEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation() + // The banner deep-links into the Find and Invite Friends settings screen, so + // mirror that screen's availability gates: native-only, allowed in the user's + // region (geolocation allowlist), and not disabled by the feature flag. This + // avoids promoting contact import where the settings entry itself is hidden. const showFollowersPromo = IS_NATIVE && isMe && + findContactsEnabled && + !ax.features.enabled(ax.features.ImportContactsSettingsDisable) && !followersPromoDismissed && followers.length < 1 && !isDidLoading && @@ -184,7 +188,7 @@ export function ProfileFollowers({name}: {name: string}) { <> {showFollowersPromo && ( inviteFriendsControl.open()} + onPress={() => navigation.navigate('FindContactsSettings')} onDismiss={() => setFollowersPromoDismissed(true)} /> )} @@ -210,9 +214,6 @@ export function ProfileFollowers({name}: {name: string}) { onPress: () => navigation.goBack(), }} /> - {showFollowersPromo && ( - - )} ) }