From ad5445cd08c460fd84237bb18f57195d8a8c4f97 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 22 Nov 2024 22:46:48 +0000 Subject: [PATCH] Add an opt out for sending utm --- src/lib/hooks/useOpenLink.ts | 8 ++- .../Settings/ContentAndMediaSettings.tsx | 56 +++++++++++++++---- src/state/persisted/schema.ts | 2 + src/state/preferences/index.tsx | 5 +- src/state/preferences/opt-out-of-utm.tsx | 42 ++++++++++++++ 5 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 src/state/preferences/opt-out-of-utm.tsx diff --git a/src/lib/hooks/useOpenLink.ts b/src/lib/hooks/useOpenLink.ts index 468992ac5e..035212f114 100644 --- a/src/lib/hooks/useOpenLink.ts +++ b/src/lib/hooks/useOpenLink.ts @@ -10,6 +10,7 @@ import { import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {useInAppBrowser} from '#/state/preferences/in-app-browser' +import {useOptOutOfUtm} from '#/state/preferences/opt-out-of-utm' import {useTheme} from '#/alf' import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' @@ -18,6 +19,7 @@ export function useOpenLink() { const enabled = useInAppBrowser() const t = useTheme() const sheetWrapper = useSheetWrapper() + const optOutOfUtm = useOptOutOfUtm() const openLink = useCallback( async (url: string, override?: boolean) => { @@ -26,6 +28,9 @@ export function useOpenLink() { } if (isNative && !url.startsWith('mailto:')) { + if (!optOutOfUtm) { + url = addUtmSource(url) + } if (override === undefined && enabled === undefined) { openModal({ name: 'in-app-browser-consent', @@ -33,7 +38,6 @@ export function useOpenLink() { }) return } else if (override ?? enabled) { - url = addUtmSource(url) await sheetWrapper( WebBrowser.openBrowserAsync(url, { presentationStyle: @@ -48,7 +52,7 @@ export function useOpenLink() { } Linking.openURL(url) }, - [enabled, openModal, t, sheetWrapper], + [enabled, openModal, t, sheetWrapper, optOutOfUtm], ) return openLink diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index b3fb8c174f..27448ba9af 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -9,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' @@ -29,6 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) { const setAutoplayDisabledPref = useSetAutoplayDisabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() + const optOutOfUtm = useOptOutOfUtm() + const setOptOutOfUtm = useSetOptOutOfUtm() return ( @@ -68,6 +77,19 @@ export function ContentAndMediaSettingsScreen({}: Props) { + setAutoplayDisabledPref(!value)}> + + + + Autoplay videos and GIFs + + + + {isNative && ( )} - setAutoplayDisabledPref(!value)}> + {isNative && } + {isNative && ( + setOptOutOfUtm(!value)}> + + + + Send Bluesky referrer + + + + + )} + {isNative && ( - - - Autoplay videos and GIFs - - + + + Helps external sites estimate traffic from Bluesky. + + - + )} diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 8040179496..85a6bf8e20 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -124,6 +124,7 @@ const schema = z.object({ subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), + optOutOfUtm: z.boolean().optional(), }) export type Schema = z.infer @@ -169,6 +170,7 @@ export const defaults: Schema = { kawaii: false, hasCheckedForStarterPack: false, subtitlesEnabled: true, + optOutOfUtm: false, } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index c7eaf27261..43a08926e8 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -9,6 +9,7 @@ import {Provider as InAppBrowserProvider} from './in-app-browser' import {Provider as KawaiiProvider} from './kawaii' import {Provider as LanguagesProvider} from './languages' import {Provider as LargeAltBadgeProvider} from './large-alt-badge' +import {Provider as OutOutOfUtmProvider} from './opt-out-of-utm' import {Provider as SubtitlesProvider} from './subtitles' import {Provider as UsedStarterPacksProvider} from './used-starter-packs' @@ -39,7 +40,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/state/preferences/opt-out-of-utm.tsx b/src/state/preferences/opt-out-of-utm.tsx new file mode 100644 index 0000000000..40144c8db5 --- /dev/null +++ b/src/state/preferences/opt-out-of-utm.tsx @@ -0,0 +1,42 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = boolean +type SetContext = (v: boolean) => void + +const stateContext = React.createContext( + Boolean(persisted.defaults.optOutOfUtm), +) +const setContext = React.createContext((_: boolean) => {}) + +export function Provider({children}: {children: React.ReactNode}) { + const [state, setState] = React.useState( + Boolean(persisted.get('optOutOfUtm')), + ) + + const setStateWrapped = React.useCallback( + (optOutOfUtm: persisted.Schema['optOutOfUtm']) => { + setState(Boolean(optOutOfUtm)) + persisted.write('optOutOfUtm', optOutOfUtm) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate('optOutOfUtm', nextOptOutOfUtm => { + setState(Boolean(nextOptOutOfUtm)) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export const useOptOutOfUtm = () => React.useContext(stateContext) +export const useSetOptOutOfUtm = () => React.useContext(setContext)