From 4da86e5864e10b14880900b78cb94d33c199b7da Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 18 Jun 2025 16:17:54 +0300 Subject: Modernise link warning dialog (#8243) * add link warning dialog * add copy for if sharing * delete old modal * get web working --- src/components/Link.tsx | 28 +++-- src/components/dialogs/Context.tsx | 12 +++ src/components/dialogs/LinkWarning.tsx | 161 +++++++++++++++++++++++++++++ src/state/modals/index.tsx | 10 -- src/view/com/modals/LinkWarning.tsx | 180 --------------------------------- src/view/com/modals/Modal.tsx | 4 - src/view/com/modals/Modal.web.tsx | 3 - src/view/com/util/Link.tsx | 11 +- src/view/shell/index.tsx | 2 + src/view/shell/index.web.tsx | 2 + 10 files changed, 200 insertions(+), 213 deletions(-) create mode 100644 src/components/dialogs/LinkWarning.tsx delete mode 100644 src/view/com/modals/LinkWarning.tsx (limited to 'src') diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 49c9c5235..d0f8678ff 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -24,6 +24,7 @@ import {Button, type ButtonProps} from '#/components/Button' import {useInteractionState} from '#/components/hooks/useInteractionState' import {Text, type TextProps} from '#/components/Typography' import {router} from '#/routes' +import {useGlobalDialogsControlContext} from './dialogs/Context' /** * Only available within a `Link`, since that inherits from `Button`. @@ -111,7 +112,8 @@ export function useLink({ } const isExternal = isExternalUrl(href) - const {openModal, closeModal} = useModalControls() + const {closeModal} = useModalControls() + const {linkWarningDialogControl} = useGlobalDialogsControlContext() const openLink = useOpenLink() const onPress = React.useCallback( @@ -132,10 +134,9 @@ export function useLink({ } if (requiresWarning) { - openModal({ - name: 'link-warning', - text: displayText, - href: href, + linkWarningDialogControl.open({ + displayText, + href, }) } else { if (isExternal) { @@ -176,13 +177,13 @@ export function useLink({ displayText, isExternal, href, - openModal, openLink, closeModal, action, navigation, overridePresentation, shouldProxy, + linkWarningDialogControl, ], ) @@ -195,16 +196,21 @@ export function useLink({ ) if (requiresWarning) { - openModal({ - name: 'link-warning', - text: displayText, - href: href, + linkWarningDialogControl.open({ + displayText, + href, share: true, }) } else { shareUrl(href) } - }, [disableMismatchWarning, displayText, href, isExternal, openModal]) + }, [ + disableMismatchWarning, + displayText, + href, + isExternal, + linkWarningDialogControl, + ]) const onLongPress = React.useCallback( (e: GestureResponderEvent) => { diff --git a/src/components/dialogs/Context.tsx b/src/components/dialogs/Context.tsx index 728044325..1ee4d2739 100644 --- a/src/components/dialogs/Context.tsx +++ b/src/components/dialogs/Context.tsx @@ -17,6 +17,11 @@ type ControlsContext = { signinDialogControl: Control inAppBrowserConsentControl: StatefulControl emailDialogControl: StatefulControl + linkWarningDialogControl: StatefulControl<{ + href: string + displayText: string + share?: boolean + }> } const ControlsContext = createContext(null) @@ -36,6 +41,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const signinDialogControl = Dialog.useDialogControl() const inAppBrowserConsentControl = useStatefulDialogControl() const emailDialogControl = useStatefulDialogControl() + const linkWarningDialogControl = useStatefulDialogControl<{ + href: string + displayText: string + share?: boolean + }>() const ctx = useMemo( () => ({ @@ -43,12 +53,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) { signinDialogControl, inAppBrowserConsentControl, emailDialogControl, + linkWarningDialogControl, }), [ mutedWordsDialogControl, signinDialogControl, inAppBrowserConsentControl, emailDialogControl, + linkWarningDialogControl, ], ) diff --git a/src/components/dialogs/LinkWarning.tsx b/src/components/dialogs/LinkWarning.tsx new file mode 100644 index 000000000..9ae871812 --- /dev/null +++ b/src/components/dialogs/LinkWarning.tsx @@ -0,0 +1,161 @@ +import {useCallback, useMemo} from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useOpenLink} from '#/lib/hooks/useOpenLink' +import {shareUrl} from '#/lib/sharing' +import {isPossiblyAUrl, splitApexDomain} from '#/lib/strings/url-helpers' +import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {Text} from '#/components/Typography' +import {useGlobalDialogsControlContext} from './Context' + +export function LinkWarningDialog() { + const {linkWarningDialogControl} = useGlobalDialogsControlContext() + + return ( + + + + + ) +} + +function InAppBrowserConsentInner({ + link, +}: { + link?: {href: string; displayText: string; share?: boolean} +}) { + const control = Dialog.useDialogContext() + const {_} = useLingui() + const t = useTheme() + const openLink = useOpenLink() + const {gtMobile} = useBreakpoints() + + const potentiallyMisleading = useMemo( + () => link && isPossiblyAUrl(link.displayText), + [link], + ) + + const onPressVisit = useCallback(() => { + control.close(() => { + if (!link) return + if (link.share) { + shareUrl(link.href) + } else { + openLink(link.href, undefined, true) + } + }) + }, [control, link, openLink]) + + const onCancel = useCallback(() => { + control.close() + }, [control]) + + return ( + + + + + {potentiallyMisleading ? ( + Potentially misleading link + ) : ( + Leaving Bluesky + )} + + + This link is taking you to the following website: + + {link && } + {potentiallyMisleading && ( + + Make sure this is where you intend to go! + + )} + + + + + + + + + ) +} + +function LinkBox({href}: {href: string}) { + const t = useTheme() + const [scheme, hostname, rest] = useMemo(() => { + try { + const urlp = new URL(href) + const [subdomain, apexdomain] = splitApexDomain(urlp.hostname) + return [ + urlp.protocol + '//' + subdomain, + apexdomain, + urlp.pathname.replace(/\/$/, '') + urlp.search + urlp.hash, + ] + } catch { + return ['', href, ''] + } + }, [href]) + return ( + + + {scheme} + + {hostname} + + {rest} + + + ) +} diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 7ebcec4c7..a2cc63745 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -43,13 +43,6 @@ export interface ChangePasswordModal { name: 'change-password' } -export interface LinkWarningModal { - name: 'link-warning' - text: string - href: string - share?: boolean -} - export type Modal = // Account | DeleteAccountModal @@ -67,9 +60,6 @@ export type Modal = | WaitlistModal | InviteCodesModal - // Generic - | LinkWarningModal - const ModalContext = React.createContext<{ isModalActive: boolean activeModals: Modal[] diff --git a/src/view/com/modals/LinkWarning.tsx b/src/view/com/modals/LinkWarning.tsx deleted file mode 100644 index b0bf76ede..000000000 --- a/src/view/com/modals/LinkWarning.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react' -import {SafeAreaView, StyleSheet, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {useOpenLink} from '#/lib/hooks/useOpenLink' -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {shareUrl} from '#/lib/sharing' -import {isPossiblyAUrl, splitApexDomain} from '#/lib/strings/url-helpers' -import {colors, s} from '#/lib/styles' -import {isWeb} from '#/platform/detection' -import {useModalControls} from '#/state/modals' -import {Button} from '#/view/com/util/forms/Button' -import {Text} from '#/view/com/util/text/Text' -import {ScrollView} from './util' - -export const snapPoints = ['50%'] - -export function Component({ - text, - href, - share, -}: { - text: string - href: string - share?: boolean -}) { - const pal = usePalette('default') - const {closeModal} = useModalControls() - const {isMobile} = useWebMediaQueries() - const {_} = useLingui() - const potentiallyMisleading = isPossiblyAUrl(text) - const openLink = useOpenLink() - - const onPressVisit = () => { - closeModal() - if (share) { - shareUrl(href) - } else { - openLink(href, false, true) - } - } - - return ( - - - - {potentiallyMisleading ? ( - <> - - - Potentially Misleading Link - - - ) : ( - - Leaving Bluesky - - )} - - - - - This link is taking you to the following website: - - - - - {potentiallyMisleading && ( - - Make sure this is where you intend to go! - - )} - - - -