Compare commits

...

4 Commits

Author SHA1 Message Date
Alex Benzer ff175c751c Adds signup CTA tests for logged-out visitors (#8906) 2025-08-26 16:54:26 +01:00
Eric Bailey d3876f3e1f Update origin (#8889)
(cherry picked from commit c28195067f)
2025-08-23 15:33:51 -05:00
Eric Bailey 7f53c54a62 Debug (#8888)
* Swap URL

* Add debug

(cherry picked from commit 3b7b93a411)
2025-08-23 14:46:18 -05:00
Eric Bailey e5a948b1b0 [LEG-246] Geo overlay (#8881)
* Add AgeBlockedGeo

* Add MaxMind usage text

* Add geo overlay

---------

Co-authored-by: rafael <rafael@blueskyweb.xyz>
(cherry picked from commit 912ab1bd9b)
2025-08-22 14:57:45 -05:00
13 changed files with 409 additions and 29 deletions
+2 -2
View File
@@ -606,10 +606,10 @@ type IPCCRequest struct {
type IPCCResponse struct {
CC string `json:"countryCode"`
AgeRestrictedGeo bool `json:"isAgeRestrictedGeo,omitempty"`
AgeBlockedGeo bool `json:"isAgeBlockedGeo,omitempty"`
}
// IP address data is powered by IPinfo
// https://ipinfo.io
// This product includes GeoLite2 Data created by MaxMind, available from https://www.maxmind.com.
func (srv *Server) WebIpCC(c echo.Context) error {
realIP := c.RealIP()
addr, err := netip.ParseAddr(realIP)
+109
View File
@@ -0,0 +1,109 @@
import {useEffect} from 'react'
import {ScrollView, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {Full as Logo, Mark} from '#/components/icons/Logo'
import {SimpleInlineLinkText as InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
export function BlockedGeoOverlay() {
const t = useTheme()
const {_} = useLingui()
const {gtPhone} = useBreakpoints()
const insets = useSafeAreaInsets()
useEffect(() => {
// just counting overall hits here
logger.metric(`blockedGeoOverlay:shown`, {})
}, [])
const textStyles = [a.text_md, a.leading_normal]
const links = {
blog: {
to: `https://bsky.social/about/blog/08-22-2025-mississippi-hb1126`,
label: _(msg`Read our blog post`),
overridePresentation: false,
disableMismatchWarning: true,
style: textStyles,
},
}
const blocks = [
_(msg`Unfortunately, Bluesky is unavailable in Mississippi right now.`),
_(
msg`A new Mississippi law requires us to implement age verification for all users before they can access Bluesky. We think this law creates challenges that go beyond its child safety goals, and creates significant barriers that limit free speech and disproportionately harm smaller platforms and emerging technologies.`,
),
_(
msg`As a small team, we cannot justify building the expensive infrastructure this requirement demands while legal challenges to this law are pending.`,
),
_(
msg`For now, we have made the difficult decision to block access to Bluesky in the state of Mississippi.`,
),
<>
To learn more, read our{' '}
<InlineLinkText {...links.blog}>blog post</InlineLinkText>.
</>,
]
return (
<ScrollView
contentContainerStyle={[
a.px_2xl,
{
paddingTop: isWeb ? a.p_5xl.padding : insets.top + a.p_2xl.padding,
paddingBottom: 100,
},
]}>
<View
style={[
a.mx_auto,
web({
maxWidth: 440,
paddingTop: gtPhone ? '8vh' : undefined,
}),
]}>
<View style={[a.align_start]}>
<View
style={[
a.pl_md,
a.pr_lg,
a.py_sm,
a.rounded_full,
a.flex_row,
a.align_center,
a.gap_xs,
{
backgroundColor: t.palette.primary_25,
},
]}>
<Mark fill={t.palette.primary_600} width={14} />
<Text
style={[
a.font_bold,
{
color: t.palette.primary_600,
},
]}>
<Trans>Announcement</Trans>
</Text>
</View>
</View>
<View style={[a.gap_lg, {paddingTop: 32, paddingBottom: 48}]}>
{blocks.map((block, index) => (
<Text key={index} style={[textStyles]}>
{block}
</Text>
))}
</View>
<Logo width={120} textFill={t.atoms.text.color} />
</View>
</ScrollView>
)
}
+87 -1
View File
@@ -1,5 +1,5 @@
import React, {useMemo} from 'react'
import {type GestureResponderEvent} from 'react-native'
import {type GestureResponderEvent, Linking} from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {
type LinkProps as RNLinkProps,
@@ -13,6 +13,7 @@ import {type AllNavigatorParams, type RouteParams} from '#/lib/routes/types'
import {shareUrl} from '#/lib/sharing'
import {
convertBskyAppUrlIfNeeded,
createProxiedUrl,
isBskyDownloadUrl,
isExternalUrl,
linkRequiresWarning,
@@ -407,6 +408,91 @@ export function InlineLinkText({
)
}
/**
* A barebones version of `InlineLinkText`, for use outside a
* `react-navigation` context.
*/
export function SimpleInlineLinkText({
children,
to,
style,
download,
selectable,
label,
disableUnderline,
shouldProxy,
...rest
}: Omit<
InlineLinkProps,
| 'to'
| 'action'
| 'disableMismatchWarning'
| 'overridePresentation'
| 'onPress'
| 'onLongPress'
| 'shareOnLongPress'
> & {
to: string
}) {
const t = useTheme()
const {
state: hovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const flattenedStyle = flatten(style) || {}
const isExternal = isExternalUrl(to)
let href = to
if (shouldProxy) {
href = createProxiedUrl(href)
}
const onPress = () => {
Linking.openURL(href)
}
return (
<Text
selectable={selectable}
accessibilityHint=""
accessibilityLabel={label}
{...rest}
style={[
{color: t.palette.primary_500},
hovered &&
!disableUnderline && {
...web({
outline: 0,
textDecorationLine: 'underline',
textDecorationColor:
flattenedStyle.color ?? t.palette.primary_500,
}),
},
flattenedStyle,
]}
role="link"
onPress={onPress}
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
accessibilityRole="link"
href={href}
{...web({
hrefAttrs: {
target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
download,
},
dataSet: {
// default to no underline, apply this ourselves
noUnderline: '1',
},
})}>
{children}
</Text>
)
}
export function WebOnlyInlineLinkText({
children,
to,
+80
View File
@@ -0,0 +1,80 @@
import {View, type ViewStyle} from 'react-native'
import {Trans} from '@lingui/macro'
import {type Gate} from '#/lib/statsig/gates'
import {useGate} from '#/lib/statsig/statsig'
import {isWeb} from '#/platform/detection'
import {useSession} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {Text} from '#/components/Typography'
interface LoggedOutCTAProps {
style?: ViewStyle
gateName: Gate
}
export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) {
const {hasSession} = useSession()
const {requestSwitchToAccount} = useLoggedOutViewControls()
const gate = useGate()
const t = useTheme()
// Only show for logged-out users on web
if (hasSession || !isWeb) {
return null
}
// Check gate at the last possible moment to avoid counting users as exposed when they won't see the element
if (!gate(gateName)) {
return null
}
return (
<View style={[a.pb_md, style]}>
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.px_lg,
a.py_md,
a.rounded_md,
a.mb_xs,
t.atoms.bg_contrast_25,
]}>
<View style={[a.flex_row, a.align_center, a.flex_1, a.pr_md]}>
<Logo width={30} style={[a.mr_md]} />
<View style={[a.flex_1]}>
<Text style={[a.text_lg, a.font_bold, a.leading_snug]}>
<Trans>Join Bluesky</Trans>
</Text>
<Text
style={[
a.text_md,
a.font_medium,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
<Trans>The open social network.</Trans>
</Text>
</View>
</View>
<Button
onPress={() => {
requestSwitchToAccount({requestedAccount: 'new'})
}}
label="Create account"
size="small"
variant="solid"
color="primary">
<ButtonText>
<Trans>Create account</Trans>
</ButtonText>
</Button>
</View>
</View>
)
}
+37
View File
@@ -1,5 +1,42 @@
import Svg, {Path} from 'react-native-svg'
import {type Props, useCommonSVGProps} from './common'
import {createSinglePathSVG} from './TEMPLATE'
export const Mark = createSinglePathSVG({
path: 'M6.335 4.212c2.293 1.76 4.76 5.327 5.665 7.241.906-1.914 3.372-5.482 5.665-7.241C19.319 2.942 22 1.96 22 5.086c0 .624-.35 5.244-.556 5.994-.713 2.608-3.315 3.273-5.629 2.87 4.045.704 5.074 3.035 2.852 5.366-4.22 4.426-6.066-1.111-6.54-2.53-.086-.26-.126-.382-.127-.278 0-.104-.041.018-.128.278-.473 1.419-2.318 6.956-6.539 2.53-2.222-2.331-1.193-4.662 2.852-5.366-2.314.403-4.916-.262-5.63-2.87C2.35 10.33 2 5.71 2 5.086c0-3.126 2.68-2.144 4.335-.874Z',
})
export function Full(
props: Omit<Props, 'fill' | 'size' | 'height'> & {
markFill?: Props['fill']
textFill?: Props['fill']
},
) {
const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props)
const ratio = 123 / 555
return (
<Svg
fill="none"
{...rest}
viewBox="0 0 555 123"
width={size}
height={size * ratio}
style={[style]}>
{gradient}
<Path
fill={props.markFill ?? fill}
fillRule="evenodd"
clipRule="evenodd"
d="M101.821 7.673C112.575-.367 130-6.589 130 13.21c0 3.953-2.276 33.214-3.611 37.965-4.641 16.516-21.549 20.729-36.591 18.179 26.292 4.457 32.979 19.218 18.535 33.98-27.433 28.035-39.428-7.034-42.502-16.02-.563-1.647-.827-2.418-.831-1.763-.004-.655-.268.116-.831 1.763-3.074 8.986-15.07 44.055-42.502 16.02C7.223 88.571 13.91 73.81 40.202 69.353c-15.041 2.55-31.95-1.663-36.59-18.179C2.275 46.424 0 17.162 0 13.21 0-6.59 17.426-.368 28.18 7.673 43.084 18.817 59.114 41.413 65 53.54c5.886-12.125 21.917-34.722 36.821-45.866Z"
/>
<Path
fill={props.textFill ?? fill}
fillRule="evenodd"
clipRule="evenodd"
d="m454.459 63.823 24.128-25.056h32.638l4.825 15.104c3.561 11.357 6.664 22.598 9.422 33.72 2.527-9.6 5.744-20.84 9.536-33.603l4.826-15.221H555l-22.864 65.335c-2.413 6.673-5.4 11.475-9.192 14.168-3.791 2.693-9.192 3.98-16.315 3.98-2.413 0-4.481-.117-6.319-.352v-11.59h5.514c6.549 0 9.767-4.099 9.767-9.719 0-2.81-.92-6.908-2.758-12.177l-17.177-49.478-22.239 22.665L497.2 99.184h-16.545l-17.234-28.101-8.962 9.133v18.968h-14.246V15.817h14.246v48.006Zm-48.373-26.46c16.889 0 25.622 6.79 26.196 20.49h-13.673c-.344-7.377-4.595-9.954-12.523-9.954-6.894 0-10.341 2.342-10.341 7.026 0 4.215 2.987 6.089 9.881 7.377l7.469 1.17c14.361 2.694 20.566 8.08 20.566 18.384 0 12.176-9.652 18.967-26.311 18.967-17.235 0-26.311-6.908-27.116-20.842h14.132c.804 7.494 4.481 10.304 13.213 10.304 7.813 0 11.72-2.459 11.72-7.26 0-4.332-2.758-6.44-11.605-7.962l-6.778-1.17c-12.983-2.224-19.418-8.313-19.418-18.265 0-11.358 8.847-18.266 24.588-18.266ZM270.534 76.351c0 7.61 3.677 11.474 11.145 11.474 7.008 0 13.212-5.268 13.213-15.22v-33.84h14.476v60.418h-14.016v-8.782c-4.481 6.791-10.686 10.187-18.614 10.187-12.523 0-20.68-7.728-20.68-21.778V38.767h14.476v37.585Zm75.432-38.99c8.961 0 16.085 3.045 21.37 9.016s7.928 13.933 7.928 23.651v3.513h-44.35c1.034 10.42 6.664 15.572 15.396 15.572 6.663 0 11.144-2.927 13.557-8.664h13.903c-3.103 12.294-13.443 20.139-27.575 20.139-8.847 0-15.971-2.927-21.371-8.664-5.4-5.737-8.157-13.348-8.157-22.95 0-9.483 2.643-17.094 8.043-22.949 5.4-5.737 12.409-8.664 21.256-8.664ZM195.628 15.817c17.809 0 26.426 9.251 26.426 21.545 0 8.196-3.677 14.168-10.915 17.914 9.306 3.396 14.247 11.24 14.247 20.022 0 14.87-9.767 23.886-28.494 23.886h-38.26V15.817h36.996Zm51.264 83.367h-14.477V15.817h14.477v83.367ZM174.143 86.07h21.944c8.732 0 13.443-4.098 13.443-11.474 0-7.728-4.481-11.592-13.443-11.592h-21.944V86.07Zm171.708-37.233c-7.928 0-13.443 4.683-14.822 14.401h29.758c-1.264-8.781-6.549-14.401-14.936-14.401Zm-171.708 1.756h20.336c7.927 0 12.178-4.215 12.178-11.24 0-6.44-4.366-10.539-12.178-10.539h-20.336v21.779Z"
/>
</Svg>
)
}
+2
View File
@@ -1,6 +1,8 @@
export type Gate =
// Keep this alphabetic please.
| 'alt_share_icon'
| 'cta_above_post_heading'
| 'cta_above_post_replies'
| 'debug_show_feedcontext'
| 'debug_subscriptions'
| 'disable_onboarding_policy_update_notice'
+7
View File
@@ -475,4 +475,11 @@ export type MetricEvents = {
'ageAssurance:redirectDialogFail': {}
'ageAssurance:appealDialogOpen': {}
'ageAssurance:appealDialogSubmit': {}
/*
* Specifically for the `BlockedGeoOverlay`
*/
'blockedGeoOverlay:shown': {}
'geo:debug': {}
}
@@ -40,12 +40,13 @@ import {
OUTER_SPACE,
REPLY_LINE_WIDTH,
} from '#/screens/PostThread/const'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {colors} from '#/components/Admonition'
import {Button} from '#/components/Button'
import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
import {InlineLinkText, Link} from '#/components/Link'
import {LoggedOutCTA} from '#/components/LoggedOutCTA'
import {ContentHider} from '#/components/moderation/ContentHider'
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
import {PostAlerts} from '#/components/moderation/PostAlerts'
@@ -178,6 +179,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
const {_, i18n} = useLingui()
const {openComposer} = useOpenComposer()
const {currentAccount, hasSession} = useSession()
const {gtTablet} = useBreakpoints()
const feedFeedback = useFeedFeedback(postSource?.feed, hasSession)
const post = postShadow
@@ -310,6 +312,8 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
},
isRoot && [a.pt_lg],
]}>
{/* Show CTA for logged-out visitors - hide on desktop and check gate */}
{!gtTablet && <LoggedOutCTA gateName="cta_above_post_heading" />}
<View style={[a.flex_row, a.gap_md, a.pb_md]}>
<View collapsable={false}>
<PreviewableUserAvatar
+3
View File
@@ -38,6 +38,7 @@ import {
import {atoms as a, native, platform, useBreakpoints, web} from '#/alf'
import * as Layout from '#/components/Layout'
import {ListFooter} from '#/components/Lists'
import {LoggedOutCTA} from '#/components/LoggedOutCTA'
const PARENT_CHUNK_SIZE = 5
const CHILDREN_CHUNK_SIZE = 50
@@ -404,6 +405,8 @@ export function PostThread({uri}: {uri: string}) {
onPostSuccess={optimisticOnPostReply}
postSource={anchorPostSource}
/>
{/* Show CTA for logged-out visitors */}
<LoggedOutCTA style={a.px_lg} gateName="cta_above_post_replies" />
</View>
)
} else {
+50 -6
View File
@@ -5,6 +5,9 @@ import {networkRetry} from '#/lib/async/retry'
import {logger} from '#/logger'
import {type Device, device} from '#/storage'
const IPCC_URL = `https://bsky.app/ipcc`
const BAPP_CONFIG_URL = `https://ip.bsky.app/config`
const events = new EventEmitter()
const EVENT = 'geolocation-updated'
const emitGeolocationUpdate = (geolocation: Device['geolocation']) => {
@@ -25,11 +28,22 @@ const onGeolocationUpdate = (
*/
export const DEFAULT_GEOLOCATION: Device['geolocation'] = {
countryCode: undefined,
isAgeBlockedGeo: undefined,
isAgeRestrictedGeo: false,
}
async function getGeolocation(): Promise<Device['geolocation']> {
const res = await fetch(`https://bsky.app/ipcc`)
function sanitizeGeolocation(
geolocation: Device['geolocation'],
): Device['geolocation'] {
return {
countryCode: geolocation?.countryCode ?? undefined,
isAgeBlockedGeo: geolocation?.isAgeBlockedGeo ?? false,
isAgeRestrictedGeo: geolocation?.isAgeRestrictedGeo ?? false,
}
}
async function getGeolocation(url: string): Promise<Device['geolocation']> {
const res = await fetch(url)
if (!res.ok) {
throw new Error(`geolocation: lookup failed ${res.status}`)
@@ -40,13 +54,41 @@ async function getGeolocation(): Promise<Device['geolocation']> {
if (json.countryCode) {
return {
countryCode: json.countryCode,
isAgeBlockedGeo: json.isAgeBlockedGeo ?? false,
isAgeRestrictedGeo: json.isAgeRestrictedGeo ?? false,
// @ts-ignore
regionCode: json.regionCode ?? undefined,
}
} else {
return undefined
}
}
async function compareWithIPCC(bapp: Device['geolocation']) {
try {
const ipcc = await getGeolocation(IPCC_URL)
if (!ipcc || !bapp) return
logger.metric(
'geo:debug',
{
bappCountryCode: bapp.countryCode,
// @ts-ignore
bappRegionCode: bapp.regionCode,
bappIsAgeBlockedGeo: bapp.isAgeBlockedGeo,
bappIsAgeRestrictedGeo: bapp.isAgeRestrictedGeo,
ipccCountryCode: ipcc.countryCode,
ipccIsAgeBlockedGeo: ipcc.isAgeBlockedGeo,
ipccIsAgeRestrictedGeo: ipcc.isAgeRestrictedGeo,
},
{
statsig: false,
},
)
} catch {}
}
/**
* Local promise used within this file only.
*/
@@ -79,11 +121,12 @@ export function beginResolveGeolocation() {
try {
// Try once, fail fast
const geolocation = await getGeolocation()
const geolocation = await getGeolocation(BAPP_CONFIG_URL)
if (geolocation) {
device.set(['geolocation'], geolocation)
device.set(['geolocation'], sanitizeGeolocation(geolocation))
emitGeolocationUpdate(geolocation)
logger.debug(`geolocation: success`, {geolocation})
compareWithIPCC(geolocation)
} else {
// endpoint should throw on all failures, this is insurance
throw new Error(`geolocation: nothing returned from initial request`)
@@ -99,13 +142,14 @@ export function beginResolveGeolocation() {
device.set(['geolocation'], DEFAULT_GEOLOCATION)
// retry 3 times, but don't await, proceed with default
networkRetry(3, getGeolocation)
networkRetry(3, () => getGeolocation(BAPP_CONFIG_URL))
.then(geolocation => {
if (geolocation) {
device.set(['geolocation'], geolocation)
device.set(['geolocation'], sanitizeGeolocation(geolocation))
emitGeolocationUpdate(geolocation)
logger.debug(`geolocation: success`, {geolocation})
success = true
compareWithIPCC(geolocation)
} else {
// endpoint should throw on all failures, this is insurance
throw new Error(`geolocation: nothing returned from retries`)
+1
View File
@@ -10,6 +10,7 @@ export type Device = {
geolocation?: {
countryCode: string | undefined
isAgeRestrictedGeo: boolean | undefined
isAgeBlockedGeo: boolean | undefined
}
trendingBetaEnabled: boolean
devMode: boolean
+13 -5
View File
@@ -13,6 +13,7 @@ import {useNotificationsRegistration} from '#/lib/notifications/notifications'
import {isStateAtTabRoot} from '#/lib/routes/helpers'
import {isAndroid, isIOS} from '#/platform/detection'
import {useDialogStateControlContext} from '#/state/dialogs'
import {useGeolocation} from '#/state/geolocation'
import {useSession} from '#/state/session'
import {
useIsDrawerOpen,
@@ -26,6 +27,7 @@ import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, select, useTheme} from '#/alf'
import {setSystemUITheme} from '#/alf/util/systemUI'
import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
import {BlockedGeoOverlay} from '#/components/BlockedGeoOverlay'
import {EmailDialog} from '#/components/dialogs/EmailDialog'
import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent'
import {LinkWarningDialog} from '#/components/dialogs/LinkWarning'
@@ -180,9 +182,11 @@ function ShellInner() {
)
}
export const Shell: React.FC = function ShellImpl() {
const {fullyExpandedCount} = useDialogStateControlContext()
export function Shell() {
const t = useTheme()
const {geolocation} = useGeolocation()
const {fullyExpandedCount} = useDialogStateControlContext()
useIntentHandler()
useEffect(() => {
@@ -200,9 +204,13 @@ export const Shell: React.FC = function ShellImpl() {
navigationBar: t.name !== 'light' ? 'light' : 'dark',
}}
/>
<RoutesContainer>
<ShellInner />
</RoutesContainer>
{geolocation?.isAgeBlockedGeo ? (
<BlockedGeoOverlay />
) : (
<RoutesContainer>
<ShellInner />
</RoutesContainer>
)}
</View>
)
}
+13 -14
View File
@@ -5,11 +5,10 @@ import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {type NavigationProp} from '#/lib/routes/types'
import {colors} from '#/lib/styles'
import {useGeolocation} from '#/state/geolocation'
import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
import {useComposerKeyboardShortcut} from '#/state/shell/composer/useComposerKeyboardShortcut'
import {useCloseAllActiveElements} from '#/state/util'
@@ -18,6 +17,7 @@ import {ModalsContainer} from '#/view/com/modals/Modal'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, select, useTheme} from '#/alf'
import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
import {BlockedGeoOverlay} from '#/components/BlockedGeoOverlay'
import {EmailDialog} from '#/components/dialogs/EmailDialog'
import {LinkWarningDialog} from '#/components/dialogs/LinkWarning'
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
@@ -130,24 +130,23 @@ function ShellInner() {
)
}
export const Shell: React.FC = function ShellImpl() {
const pageBg = useColorSchemeStyle(styles.bgLight, styles.bgDark)
export function Shell() {
const t = useTheme()
const {geolocation} = useGeolocation()
return (
<View style={[a.util_screen_outer, pageBg]}>
<RoutesContainer>
<ShellInner />
</RoutesContainer>
<View style={[a.util_screen_outer, t.atoms.bg]}>
{geolocation?.isAgeBlockedGeo ? (
<BlockedGeoOverlay />
) : (
<RoutesContainer>
<ShellInner />
</RoutesContainer>
)}
</View>
)
}
const styles = StyleSheet.create({
bgLight: {
backgroundColor: colors.white,
},
bgDark: {
backgroundColor: colors.black, // TODO
},
drawerMask: {
...a.fixed,
width: '100%',