diff --git a/package.json b/package.json index f38093caab..5009428eec 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.13.30", + "@atproto/api": "^0.13.31", "@bitdrift/react-native": "^0.6.2", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 50e741ea7b..26cea59686 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -79,8 +79,10 @@ export function useLink({ onPress: outerOnPress, onLongPress: outerOnLongPress, shareOnLongPress, + overridePresentation, }: BaseLinkProps & { displayText: string + overridePresentation?: boolean }) { const navigation = useNavigationDeduped() const {href} = useLinkProps({ @@ -116,7 +118,7 @@ export function useLink({ }) } else { if (isExternal) { - openLink(href) + openLink(href, overridePresentation) } else { const shouldOpenInNewTab = shouldClickOpenNewTab(e) @@ -158,6 +160,7 @@ export function useLink({ closeModal, action, navigation, + overridePresentation, ], ) @@ -254,12 +257,13 @@ export function Link({ export type InlineLinkProps = React.PropsWithChildren< BaseLinkProps & TextStyleProp & - Pick -> & - Pick & { - disableUnderline?: boolean - title?: TextProps['title'] - } + Pick & + Pick & { + disableUnderline?: boolean + title?: TextProps['title'] + overridePresentation?: boolean + } +> export function InlineLinkText({ children, @@ -274,6 +278,7 @@ export function InlineLinkText({ label, shareOnLongPress, disableUnderline, + overridePresentation, ...rest }: InlineLinkProps) { const t = useTheme() @@ -286,6 +291,7 @@ export function InlineLinkText({ onPress: outerOnPress, onLongPress: outerOnLongPress, shareOnLongPress, + overridePresentation, }) const { state: hovered, diff --git a/src/components/dms/ReportDialog.tsx b/src/components/dms/ReportDialog.tsx index a67ac47f2c..4f9bc23ca4 100644 --- a/src/components/dms/ReportDialog.tsx +++ b/src/components/dms/ReportDialog.tsx @@ -224,11 +224,10 @@ function SubmitStep({ multiline defaultValue={details} onChangeText={setDetails} - label="Text field" + label={_(msg`Text field`)} style={{paddingRight: 60}} numberOfLines={5} /> - ) + const logoutBtn = ( + + ) + + const webLayout = isWeb && gtMobile + return ( - + You're in line

@@ -153,7 +168,7 @@ export function SignupQueued() {

- {isWeb && gtMobile && ( + {webLayout && ( - + {logoutBtn} {checkBtn} )} @@ -178,27 +185,17 @@ export function SignupQueued() { - {(!isWeb || !gtMobile) && ( + {!webLayout && ( {checkBtn} - + {logoutBtn} )} diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx new file mode 100644 index 0000000000..5eb787e802 --- /dev/null +++ b/src/screens/Takendown.tsx @@ -0,0 +1,263 @@ +import {useMemo, useState} from 'react' +import {Modal, View} from 'react-native' +import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {StatusBar} from 'expo-status-bar' +import {ComAtprotoAdminDefs, ComAtprotoModerationDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMutation} from '@tanstack/react-query' +import Graphemer from 'graphemer' + +import {MAX_REPORT_REASON_GRAPHEME_LENGTH} from '#/lib/constants' +import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' +import {cleanError} from '#/lib/strings/errors' +import {isIOS, isWeb} from '#/platform/detection' +import {useAgent, useSession, useSessionApi} from '#/state/session' +import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' +import {Logo} from '#/view/icons/Logo' +import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as TextField from '#/components/forms/TextField' +import {InlineLinkText} from '#/components/Link' +import {Loader} from '#/components/Loader' +import {P, Text} from '#/components/Typography' + +const COL_WIDTH = 400 + +export function Takendown() { + const {_} = useLingui() + const t = useTheme() + const insets = useSafeAreaInsets() + const {gtMobile} = useBreakpoints() + const {currentAccount} = useSession() + const {logoutCurrentAccount} = useSessionApi() + const agent = useAgent() + const [isAppealling, setIsAppealling] = useState(false) + const [reason, setReason] = useState('') + const graphemer = useMemo(() => new Graphemer(), []) + + const reasonGraphemeLength = useMemo(() => { + return graphemer.countGraphemes(reason) + }, [graphemer, reason]) + + const { + mutate: submitAppeal, + isPending, + isSuccess, + error, + } = useMutation({ + mutationFn: async (appealText: string) => { + if (!currentAccount) throw new Error('No session') + await agent.com.atproto.moderation.createReport({ + reasonType: ComAtprotoModerationDefs.REASONAPPEAL, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: currentAccount.did, + } satisfies ComAtprotoAdminDefs.RepoRef, + reason: appealText, + }) + }, + onSuccess: () => setReason(''), + }) + + const primaryBtn = + isAppealling && !isSuccess ? ( + + ) : ( + + ) + + const secondaryBtn = isAppealling ? ( + !isSuccess && ( + + ) + ) : ( + + ) + + const webLayout = isWeb && gtMobile + + useEnableKeyboardController(true) + + return ( + + {isIOS && } + + + + + + + + + {isAppealling ? ( + Appeal suspension + ) : ( + Your account has been suspended + )} + + + {isAppealling ? ( + + {isSuccess ? ( +

+ + Your appeal has been submitted. If your appeal succeeds, + you will receive an email. + +

+ ) : ( + <> + + Reason for appeal + + + MAX_REPORT_REASON_GRAPHEME_LENGTH || !!error + }> + + + + + + + )} + {error && ( + + {cleanError(error)} + + )} +
+ ) : ( +

+ + Your account was found to be in violation of the{' '} + + Bluesky Social Terms of Service + + . You have been sent an email outlining the specific violation + and suspension period, if applicable. You can appeal this + decision if you believe it was made in error. + +

+ )} + + {webLayout && ( + + {secondaryBtn} + {primaryBtn} + + )} +
+
+
+ + {!webLayout && ( + + + {primaryBtn} + {secondaryBtn} + + + )} +
+ ) +} diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 84c816d44c..ba0c14c1a1 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -83,7 +83,12 @@ export async function createAgentAndLogin( ) => void, ) { const agent = new BskyAppAgent({service}) - await agent.login({identifier, password, authFactorToken}) + await agent.login({ + identifier, + password, + authFactorToken, + allowTakendown: true, + }) const account = agentToSessionAccountOrThrow(agent) const gates = tryFetchGates(account.did, 'prefer-fresh-gates') diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 48b2588630..03a8a936ab 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -258,7 +258,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ], ) - // @ts-ignore + // @ts-expect-error window type is not declared, debug only if (__DEV__ && isWeb) window.agent = state.currentAgentState.agent const agent = state.currentAgentState.agent as BskyAppAgent diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 9bcb91b7aa..35a46b4273 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -34,6 +34,7 @@ import {LoggedOut} from '#/view/com/auth/LoggedOut' import {Deactivated} from '#/screens/Deactivated' import {Onboarding} from '#/screens/Onboarding' import {SignupQueued} from '#/screens/SignupQueued' +import {Takendown} from '#/screens/Takendown' import {atoms as a} from '#/alf' import {BottomBarWeb} from './bottom-bar/BottomBarWeb' import {DesktopLeftNav} from './desktop/LeftNav' @@ -107,6 +108,9 @@ function NativeStackNavigator({ if (hasSession && currentAccount?.signupQueued) { return } + if (hasSession && currentAccount?.status === 'takendown') { + return + } if (showLoggedOut) { return setShowLoggedOut(false)} /> } diff --git a/yarn.lock b/yarn.lock index 187ad5b0cc..0c5a08c02e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -72,10 +72,10 @@ tlds "^1.234.0" zod "^3.23.8" -"@atproto/api@^0.13.30": - version "0.13.30" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.30.tgz#073165003303995d0b6b7dfc24dafb8a58a1db6f" - integrity sha512-U+3XUACcCuoEvszh48vnzZITr1D7xZ8yz3EqjadYtV+zb3KjBmGroa50eaSRqHyeaDUZF38knumHPyUe9tTuqg== +"@atproto/api@^0.13.31": + version "0.13.31" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.31.tgz#23ca2c9118eefddf6e0206f759e56b6726b68483" + integrity sha512-i2cUQuwe+3j8rgPJj4YWRjSQeJunGqJ3IzesnvbODjjZh3IS9jB80BZ/pTe/AvNg6JCBbqeWJjWDVKeFHaiZAw== dependencies: "@atproto/common-web" "^0.3.2" "@atproto/lexicon" "^0.4.5"