From f4724e378d9227820db404b2172ba1c72bdd6cae Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 31 Jul 2025 19:47:22 +0300 Subject: [PATCH 01/18] Don't animate images that will be posted as JPGs (#8760) * don't play gifs that'll be png'd * add media inset border * fix unnecessary typecast * Revert "fix unnecessary typecast" This reverts commit 3a8bcc92774d83f6ece0577f919f527cd920761b. --- src/view/com/composer/photos/Gallery.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index c44461b60d..a66d65d97a 100644 --- a/src/view/com/composer/photos/Gallery.tsx +++ b/src/view/com/composer/photos/Gallery.tsx @@ -21,6 +21,7 @@ import {type ComposerImage, cropImage} from '#/state/gallery' import {Text} from '#/view/com/util/text/Text' import {useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' +import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {type PostAction} from '../state/composer' import {EditImageDialog} from './EditImageDialog' import {ImageAltTextDialog} from './ImageAltTextDialog' @@ -227,8 +228,12 @@ const GalleryItem = ({ }} accessible={true} accessibilityIgnoresInvertColors + cachePolicy="none" + autoplay={false} /> + + Date: Thu, 31 Jul 2025 13:47:13 -0400 Subject: [PATCH 02/18] README: Use alert for the contributions note --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f86d52b530..9dfd8338c9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ The Bluesky Social application encompasses a set of schemas and APIs built in th ## Contributions +> [!NOTE] > While we do accept contributions, we prioritize high quality issues and pull requests. Adhering to the below guidelines will ensure a more timely review. **Rules:** From 91574f1f56aafe8f212eaeeeba4558da7b0f864d Mon Sep 17 00:00:00 2001 From: Jimmy Cushnie Date: Thu, 31 Jul 2025 13:52:49 -0400 Subject: [PATCH 03/18] README: improve phrasing of packages mention --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9dfd8338c9..8f1b6940e1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Get the app itself: ## Development Resources -This is a [React Native](https://reactnative.dev/) application, written in the TypeScript programming language. It builds on the `atproto` TypeScript packages (like [`@atproto/api`](https://www.npmjs.com/package/@atproto/api)), code for which is also open source, but in [a different git repository](https://github.com/bluesky-social/atproto). +This is a [React Native](https://reactnative.dev/) application, written in the TypeScript programming language. It builds on the `atproto` TypeScript packages (like [`@atproto/api`](https://www.npmjs.com/package/@atproto/api)), which are also open source, but in [a different git repository](https://github.com/bluesky-social/atproto). There is a small amount of Go language source code (in `./bskyweb/`), for a web service that returns the React Native Web application. From bf455f3e1968eaa6ead8b51fd3e2ee8f83c503c1 Mon Sep 17 00:00:00 2001 From: Jimmy Cushnie Date: Thu, 31 Jul 2025 13:55:13 -0400 Subject: [PATCH 04/18] README: fix brand capitalization ("Github" -> "GitHub") --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f1b6940e1..8d32f67ccc 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The [Build Instructions](./docs/build.md) are a good place to get started with t The Authenticated Transfer Protocol ("AT Protocol" or "atproto") is a decentralized social media protocol. You don't *need* to understand AT Protocol to work with this application, but it can help. Learn more at: - [Overview and Guides](https://atproto.com/guides/overview) -- [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions +- [GitHub Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions - [Protocol Specifications](https://atproto.com/specs/atp) - [Blogpost on self-authenticating data structures](https://bsky.social/about/blog/3-6-2022-a-self-authenticating-social-protocol) From 648dda754110aed2c452154ef023be614656dc03 Mon Sep 17 00:00:00 2001 From: Jimmy Cushnie Date: Thu, 31 Jul 2025 13:59:29 -0400 Subject: [PATCH 05/18] README: fix verb form of CC While both forms are used, CC'd is much more common and generally considered the correct form. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d32f67ccc..eed6ac4fec 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Please be sure to: ## Security disclosures -If you discover any security issues, please send an email to security@bsky.app. The email is automatically CCed to the entire team and we'll respond promptly. +If you discover any security issues, please send an email to security@bsky.app. The email is automatically CC'd to the entire team and we'll respond promptly. ## Are you a developer interested in building on atproto? From 8b344ca9fd3f348ecb4906be4cb3d41567f22217 Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Wed, 30 Jul 2025 21:39:44 -0700 Subject: [PATCH 06/18] fix(ui): adjust padding for ProfileGrid layout consistency Fixes the issue of the shadow being cutoff by the overflow, now will give proper spacing to the cards to display the shadow. --- src/components/FeedInterstitials.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index 2a3a00ba7d..0a112e0397 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -378,8 +378,8 @@ export function ProfileGrid({ style={[a.border_t, t.atoms.border_contrast_low, t.atoms.bg_contrast_25]}> {gtMobile ? ( - + {content} @@ -411,9 +411,8 @@ export function ProfileGrid({ horizontal showsHorizontalScrollIndicator={false} snapToInterval={MOBILE_CARD_WIDTH + a.gap_md.gap} - decelerationRate="fast" - style={[a.overflow_visible]}> - + decelerationRate="fast"> + {content} From 79f365b21c2551b0d4d095e6f352e1a699d81722 Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Wed, 30 Jul 2025 21:40:32 -0700 Subject: [PATCH 07/18] fix(ui): small bug where on small web views the final card was not full height --- src/components/FeedInterstitials.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index 0a112e0397..18da12b22c 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -433,6 +433,7 @@ function SeeMoreSuggestedProfilesCard() { return ( + + + + By clicking "Continue" you acknowledge that you understand and + agree to these updates. + + + + + + ) +} diff --git a/src/components/PolicyUpdateOverlay/usePolicyUpdateState.ts b/src/components/PolicyUpdateOverlay/usePolicyUpdateState.ts new file mode 100644 index 0000000000..29d8afe067 --- /dev/null +++ b/src/components/PolicyUpdateOverlay/usePolicyUpdateState.ts @@ -0,0 +1,135 @@ +import {useMemo} from 'react' + +import {useNux, useSaveNux} from '#/state/queries/nuxs' +import {ACTIVE_UPDATE_ID} from '#/components/PolicyUpdateOverlay/config' +import {logger} from '#/components/PolicyUpdateOverlay/logger' +import {IS_DEV} from '#/env' +import {device, useStorage} from '#/storage' + +export type PolicyUpdateState = { + completed: boolean + complete: () => void +} + +export function usePolicyUpdateState() { + const nux = useNux(ACTIVE_UPDATE_ID) + const {mutate: save, variables} = useSaveNux() + const deviceStorage = useStorage(device, [ACTIVE_UPDATE_ID]) + const debugOverride = + !!useStorage(device, ['policyUpdateDebugOverride'])[0] && IS_DEV + return useMemo(() => { + const nuxIsReady = nux.status === 'ready' + const nuxIsCompleted = nux.nux?.completed === true + const nuxIsOptimisticallyCompleted = !!variables?.completed + const [completedForDevice, setCompletedForDevice] = deviceStorage + + const completed = computeCompletedState({ + nuxIsReady, + nuxIsCompleted, + nuxIsOptimisticallyCompleted, + completedForDevice, + }) + + logger.debug(`state`, { + completed, + nux, + completedForDevice, + }) + + if (!debugOverride) { + syncCompletedState({ + nuxIsReady, + nuxIsCompleted, + nuxIsOptimisticallyCompleted, + completedForDevice, + save, + setCompletedForDevice, + }) + } + + return { + completed, + complete() { + logger.debug(`user completed`) + save({ + id: ACTIVE_UPDATE_ID, + completed: true, + data: undefined, + }) + setCompletedForDevice(true) + }, + } + }, [nux, save, variables, deviceStorage, debugOverride]) +} + +export function computeCompletedState({ + nuxIsReady, + nuxIsCompleted, + nuxIsOptimisticallyCompleted, + completedForDevice, +}: { + nuxIsReady: boolean + nuxIsCompleted: boolean + nuxIsOptimisticallyCompleted: boolean + completedForDevice: boolean | undefined +}): boolean { + /** + * Assume completed to prevent flash + */ + let completed = true + + /** + * Prefer server state, if available + */ + if (nuxIsReady) { + completed = nuxIsCompleted + } + + /** + * Override with optimistic state or device state + */ + if (nuxIsOptimisticallyCompleted || !!completedForDevice) { + completed = true + } + + return completed +} + +export function syncCompletedState({ + nuxIsReady, + nuxIsCompleted, + nuxIsOptimisticallyCompleted, + completedForDevice, + save, + setCompletedForDevice, +}: { + nuxIsReady: boolean + nuxIsCompleted: boolean + nuxIsOptimisticallyCompleted: boolean + completedForDevice: boolean | undefined + save: ReturnType['mutate'] + setCompletedForDevice: (value: boolean) => void +}) { + /* + * Sync device state to server state for this account + */ + if ( + nuxIsReady && + !nuxIsCompleted && + !nuxIsOptimisticallyCompleted && + !!completedForDevice + ) { + logger.debug(`syncing device state to server state`) + save({ + id: ACTIVE_UPDATE_ID, + completed: true, + data: undefined, + }) + } else if (nuxIsReady && nuxIsCompleted && !completedForDevice) { + logger.debug(`syncing server state to device state`) + /* + * Sync server state to device state + */ + setCompletedForDevice(true) + } +} diff --git a/src/components/PolicyUpdateOverlay/usePreemptivelyCompleteActivePolicyUpdate.ts b/src/components/PolicyUpdateOverlay/usePreemptivelyCompleteActivePolicyUpdate.ts new file mode 100644 index 0000000000..f41b3e6d7d --- /dev/null +++ b/src/components/PolicyUpdateOverlay/usePreemptivelyCompleteActivePolicyUpdate.ts @@ -0,0 +1,21 @@ +import {useCallback} from 'react' + +import {ACTIVE_UPDATE_ID} from '#/components/PolicyUpdateOverlay/config' +import {logger} from '#/components/PolicyUpdateOverlay/logger' +import {device, useStorage} from '#/storage' + +/* + * Marks the active policy update as completed in device storage. + * `usePolicyUpdateState` will react to this and replicate this status in the + * server NUX state for this account. + */ +export function usePreemptivelyCompleteActivePolicyUpdate() { + const [_completedForDevice, setCompletedForDevice] = useStorage(device, [ + ACTIVE_UPDATE_ID, + ]) + + return useCallback(() => { + logger.debug(`preemptively completing active policy update`) + setCompletedForDevice(true) + }, [setCompletedForDevice]) +} diff --git a/src/logger/types.ts b/src/logger/types.ts index 4743e866c4..ee3069a083 100644 --- a/src/logger/types.ts +++ b/src/logger/types.ts @@ -13,6 +13,7 @@ export enum LogContext { FeedFeedback = 'feed-feedback', PostSource = 'post-source', AgeAssurance = 'age-assurance', + PolicyUpdate = 'policy-update', /** * METRIC IS FOR INTERNAL USE ONLY, don't create any other loggers using this diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 719bbf9a29..5023982eb1 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -25,6 +25,7 @@ import {clearStorage} from '#/state/persisted' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' +import {useAgent} from '#/state/session' import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' @@ -35,6 +36,7 @@ import * as SettingsList from '#/screens/Settings/components/SettingsList' import {atoms as a, platform, tokens, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceDismissibleNotice} from '#/components/ageAssurance/AgeAssuranceDismissibleNotice' import {AvatarStackWithFetch} from '#/components/AvatarStack' +import {Button, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' @@ -58,6 +60,7 @@ import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/W import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Menu from '#/components/Menu' +import {ID as PolicyUpdate202508} from '#/components/PolicyUpdateOverlay/updates/202508/config' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {useFullVerificationState} from '#/components/verification' @@ -66,6 +69,7 @@ import { VerificationCheckButton, } from '#/components/verification/VerificationCheckButton' import {IS_INTERNAL} from '#/env' +import {device, useStorage} from '#/storage' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' type Props = NativeStackScreenProps @@ -363,6 +367,10 @@ function ProfilePreview({ function DevOptions() { const {_} = useLingui() + const agent = useAgent() + const [override, setOverride] = useStorage(device, [ + 'policyUpdateDebugOverride', + ]) const onboardingDispatch = useOnboardingDispatch() const navigation = useNavigation() const {mutate: deleteChatDeclarationRecord} = useDeleteActorDeclaration() @@ -502,6 +510,40 @@ function DevOptions() { ) : null} + + + + PolicyUpdate202508 Debug + + + + + + + + ) } diff --git a/src/screens/Signup/state.ts b/src/screens/Signup/state.ts index 48ea4ccd93..ae0b20f1c5 100644 --- a/src/screens/Signup/state.ts +++ b/src/screens/Signup/state.ts @@ -15,6 +15,7 @@ import {getAge} from '#/lib/strings/time' import {logger} from '#/logger' import {useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' +import {usePreemptivelyCompleteActivePolicyUpdate} from '#/components/PolicyUpdateOverlay/usePreemptivelyCompleteActivePolicyUpdate' export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema @@ -252,6 +253,8 @@ export function useSubmitSignup() { const {_} = useLingui() const {createAccount} = useSessionApi() const onboardingDispatch = useOnboardingDispatch() + const preemptivelyCompleteActivePolicyUpdate = + usePreemptivelyCompleteActivePolicyUpdate() return useCallback( async (state: SignupState, dispatch: (action: SignupAction) => void) => { @@ -325,6 +328,12 @@ export function useSubmitSignup() { }, ) + /** + * Marks any active policy update as completed, since user just agreed + * to TOS/privacy during sign up + */ + preemptivelyCompleteActivePolicyUpdate() + /* * Must happen last so that if the user has multiple tabs open and * createAccount fails, one tab is not stuck in onboarding — Eric @@ -363,6 +372,11 @@ export function useSubmitSignup() { dispatch({type: 'setIsLoading', value: false}) } }, - [_, onboardingDispatch, createAccount], + [ + _, + onboardingDispatch, + createAccount, + preemptivelyCompleteActivePolicyUpdate, + ], ) } diff --git a/src/state/persisted/index.ts b/src/state/persisted/index.ts index 51d757ad8b..8c043a3420 100644 --- a/src/state/persisted/index.ts +++ b/src/state/persisted/index.ts @@ -3,11 +3,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage' import {logger} from '#/logger' import { defaults, - Schema, + type Schema, tryParse, tryStringify, } from '#/state/persisted/schema' -import {PersistedApi} from './types' +import {device} from '#/storage' +import {type PersistedApi} from './types' import {normalizeData} from './util' export type {PersistedAccount, Schema} from '#/state/persisted/schema' @@ -53,6 +54,7 @@ onUpdate satisfies PersistedApi['onUpdate'] export async function clearStorage() { try { await AsyncStorage.removeItem(BSKY_STORAGE) + device.removeAll() } catch (e: any) { logger.error(`persisted store: failed to clear`, {message: e.toString()}) } diff --git a/src/state/queries/nuxs/__mocks__/index.ts b/src/state/queries/nuxs/__mocks__/index.ts new file mode 100644 index 0000000000..c718b15940 --- /dev/null +++ b/src/state/queries/nuxs/__mocks__/index.ts @@ -0,0 +1,25 @@ +import {jest} from '@jest/globals' + +export {Nux} from '#/state/queries/nuxs/definitions' + +export const useNuxs = jest.fn(() => { + return { + nuxs: undefined, + status: 'loading' as const, + } +}) + +export const useNux = jest.fn((id: string) => { + return { + nux: undefined, + status: 'loading' as const, + } +}) + +export const useSaveNux = jest.fn(() => { + return {} +}) + +export const useResetNuxs = jest.fn(() => { + return {} +}) diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts index 3d5c132f26..7577d6b205 100644 --- a/src/state/queries/nuxs/definitions.ts +++ b/src/state/queries/nuxs/definitions.ts @@ -9,6 +9,11 @@ export enum Nux { ActivitySubscriptions = 'ActivitySubscriptions', AgeAssuranceDismissibleNotice = 'AgeAssuranceDismissibleNotice', AgeAssuranceDismissibleFeedBanner = 'AgeAssuranceDismissibleFeedBanner', + + /* + * Blocking announcements. New IDs are required for each new announcement. + */ + PolicyUpdate202508 = 'PolicyUpdate202508', } export const nuxNames = new Set(Object.values(Nux)) @@ -38,6 +43,10 @@ export type AppNux = BaseNux< id: Nux.AgeAssuranceDismissibleFeedBanner data: undefined } + | { + id: Nux.PolicyUpdate202508 + data: undefined + } > export const NuxSchemas: Record | undefined> = { @@ -47,4 +56,5 @@ export const NuxSchemas: Record | undefined> = { [Nux.ActivitySubscriptions]: undefined, [Nux.AgeAssuranceDismissibleNotice]: undefined, [Nux.AgeAssuranceDismissibleFeedBanner]: undefined, + [Nux.PolicyUpdate202508]: undefined, } diff --git a/src/storage/index.ts b/src/storage/index.ts index 4d45134e1b..4c42005104 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -66,6 +66,13 @@ export class Storage { keys.forEach(key => this.remove([...scopes, key])) } + /** + * For debugging purposes + */ + removeAll() { + this.store.clearAll() + } + /** * Fires a callback when the storage associated with a given key changes * diff --git a/src/storage/schema.ts b/src/storage/schema.ts index c05a7531df..421264ac1e 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -1,3 +1,5 @@ +import {type ID as PolicyUpdate202508} from '#/components/PolicyUpdateOverlay/updates/202508/config' + /** * Device data that's specific to the device and does not vary based account */ @@ -13,6 +15,12 @@ export type Device = { devMode: boolean demoMode: boolean activitySubscriptionsNudged?: boolean + + /** + * Policy update overlays. New IDs are required for each new announcement. + */ + policyUpdateDebugOverride?: boolean + [PolicyUpdate202508]?: boolean } export type Account = { diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 1c32971d42..bb022a0134 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -40,6 +40,7 @@ import {Onboarding} from '#/screens/Onboarding' import {SignupQueued} from '#/screens/SignupQueued' import {Takendown} from '#/screens/Takendown' import {atoms as a, useLayoutBreakpoints} from '#/alf' +import {PolicyUpdateOverlay} from '#/components/PolicyUpdateOverlay' import {BottomBarWeb} from './bottom-bar/BottomBarWeb' import {DesktopLeftNav} from './desktop/LeftNav' import {DesktopRightNav} from './desktop/RightNav' @@ -167,6 +168,9 @@ function NativeStackNavigator({ {!isMobile && } )} + + {/* Only shown after logged in and onboaring etc are complete */} + {hasSession && } ) } diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 4d1a8c51b6..0d8c245665 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -31,6 +31,10 @@ import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsen import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {SigninDialog} from '#/components/dialogs/Signin' +import { + Outlet as PolicyUpdateOverlayPortalOutlet, + usePolicyUpdateStateContext, +} from '#/components/PolicyUpdateOverlay' import {Outlet as PortalOutlet} from '#/components/Portal' import {RoutesContainer, TabsNavigator} from '#/Navigation' import {BottomSheetOutlet} from '../../../modules/bottom-sheet' @@ -45,6 +49,7 @@ function ShellInner() { const setIsDrawerOpen = useSetDrawerOpen() const winDim = useWindowDimensions() const insets = useSafeAreaInsets() + const policyUpdateState = usePolicyUpdateStateContext() const renderDrawerContent = useCallback(() => , []) const onOpenDrawer = useCallback( @@ -151,6 +156,7 @@ function ShellInner() { + @@ -160,8 +166,16 @@ function ShellInner() { - - + + {/* Until policy update has been completed by the user, don't render anything that is portaled */} + {policyUpdateState.completed && ( + <> + + + + )} + + ) } diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 77c3f45f62..c1565e8ee7 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -22,6 +22,10 @@ import {EmailDialog} from '#/components/dialogs/EmailDialog' import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {SigninDialog} from '#/components/dialogs/Signin' +import { + Outlet as PolicyUpdateOverlayPortalOutlet, + usePolicyUpdateStateContext, +} from '#/components/PolicyUpdateOverlay' import {Outlet as PortalOutlet} from '#/components/Portal' import {FlatNavigator, RoutesContainer} from '#/Navigation' import {Composer} from './Composer.web' @@ -37,6 +41,7 @@ function ShellInner() { const {_} = useLingui() const showDrawer = !isDesktop && isDrawerOpen const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) + const policyUpdateState = usePolicyUpdateStateContext() useLayoutEffect(() => { if (showDrawer !== showDrawerDelayedExit) { @@ -74,7 +79,13 @@ function ShellInner() { - + + {/* Until policy update has been completed by the user, don't render anything that is portaled */} + {policyUpdateState.completed && ( + <> + + + )} {showDrawerDelayedExit && ( <> @@ -113,6 +124,8 @@ function ShellInner() { )} + + ) } From 75094f48603e120605642e20e1048d839be23788 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 6 Aug 2025 15:16:08 -0500 Subject: [PATCH 18/18] [LEG-203] Add onboarding text for policy updates (#8783) * Add onboarding text for policy updates * Add gate, add to other case * Update other UI cases to use Admonitions * Th * Update date --- src/lib/constants.ts | 7 ++++ src/lib/statsig/gates.ts | 1 + src/screens/Signup/StepInfo/Policies.tsx | 52 +++++++++++++++++++----- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 3f0d499894..ab52b87107 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -207,3 +207,10 @@ export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app' export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev' export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same + +export const webLinks = { + tos: `https://bsky.social/about/support/tos`, + privacy: `https://bsky.social/about/support/privacy-policy`, + community: `https://bsky.social/about/support/community-guidelines`, + communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`, +} diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 3b1106480d..c3bd1a7cbf 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -3,6 +3,7 @@ export type Gate = | 'alt_share_icon' | 'debug_show_feedcontext' | 'debug_subscriptions' + | 'disable_onboarding_policy_update_notice' | 'explore_show_suggested_feeds' | 'old_postonboarding' | 'onboarding_add_video_feed' diff --git a/src/screens/Signup/StepInfo/Policies.tsx b/src/screens/Signup/StepInfo/Policies.tsx index 17980172de..0bc14fa6ab 100644 --- a/src/screens/Signup/StepInfo/Policies.tsx +++ b/src/screens/Signup/StepInfo/Policies.tsx @@ -4,11 +4,42 @@ import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {webLinks} from '#/lib/constants' +import {useGate} from '#/lib/statsig/statsig' import {atoms as a, useTheme} from '#/alf' -import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {Admonition} from '#/components/Admonition' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +function CommunityGuidelinesNotice({}: {}) { + const {_} = useLingui() + const gate = useGate() + + if (gate('disable_onboarding_policy_update_notice')) return null + + return ( + + + + You also agree to{' '} + + Bluesky’s Community Guidelines + + . An{' '} + + updated version of our Community Guidelines + {' '} + will take effect on October 13th. + + + + ) +} + export const Policies = ({ serviceDescription, needsGuardian, @@ -30,14 +61,13 @@ export const Policies = ({ if (!tos && !pp) { return ( - - - - + + This service has not provided terms of service or a privacy policy. - + + ) } @@ -102,19 +132,21 @@ export const Policies = ({ ) : null} {under13 ? ( - + You must be 13 years of age or older to create an account. - + ) : needsGuardian ? ( - + If you are not yet an adult according to the laws of your country, your parent or legal guardian must read these Terms on your behalf. - + ) : undefined} + + ) }