diff --git a/assets/icons/raisingHand4Finger_stroke2_corner0_rounded.svg b/assets/icons/raisingHand4Finger_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..aed3d9e7ec
--- /dev/null
+++ b/assets/icons/raisingHand4Finger_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/icons/RaisingHand.tsx b/src/components/icons/RaisingHand.tsx
new file mode 100644
index 0000000000..cd023cb7ef
--- /dev/null
+++ b/src/components/icons/RaisingHand.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const RaisingHande4Finger_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M10.25 4a.75.75 0 0 0-.75.75V11a1 1 0 1 1-2 0V6.75a.75.75 0 0 0-1.5 0V14a6 6 0 0 0 12 0V9a2 2 0 0 0-2 2v1.5a1 1 0 0 1-.684.949l-.628.21A2.469 2.469 0 0 0 13 16a1 1 0 1 1-2 0 4.469 4.469 0 0 1 3-4.22V11c0-.703.181-1.364.5-1.938V5.75a.75.75 0 0 0-1.5 0V9a1 1 0 1 1-2 0V4.75a.75.75 0 0 0-.75-.75Zm2.316-.733A2.75 2.75 0 0 1 16.5 5.75v1.54c.463-.187.97-.29 1.5-.29h1a1 1 0 0 1 1 1v6a8 8 0 1 1-16 0V6.75a2.75 2.75 0 0 1 3.571-2.625 2.751 2.751 0 0 1 4.995-.858Z',
+})
diff --git a/src/screens/ProfileModerationService/ErrorState.tsx b/src/screens/ProfileModerationService/ErrorState.tsx
new file mode 100644
index 0000000000..b421d20c36
--- /dev/null
+++ b/src/screens/ProfileModerationService/ErrorState.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import {View} from 'react-native'
+import {Trans, msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {useTheme, atoms as a} from '#/alf'
+import {Text} from '#/components/Typography'
+import {Button, ButtonText} from '#/components/Button'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+import {NavigationProp} from '#/lib/routes/types'
+
+export function ErrorState({error}: {error: string}) {
+ const t = useTheme()
+ const {_} = useLingui()
+ const navigation = useNavigation()
+
+ const onPressBack = React.useCallback(() => {
+ if (navigation.canGoBack()) {
+ navigation.goBack()
+ } else {
+ navigation.navigate('Home')
+ }
+ }, [navigation])
+
+ return (
+ <>
+
+
+
+ Hmmmm, we couldn't load that moderation service.
+
+
+
+ This moderation service is unavailable. See below for more details. If
+ this issue persists, contact us.
+
+
+
+ {error}
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/screens/ProfileModerationService/Header.tsx b/src/screens/ProfileModerationService/Header.tsx
new file mode 100644
index 0000000000..2dac57b3c4
--- /dev/null
+++ b/src/screens/ProfileModerationService/Header.tsx
@@ -0,0 +1,232 @@
+import React from 'react'
+import {Pressable, StyleSheet, View} from 'react-native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {useNavigation} from '@react-navigation/native'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {Text as OldText} from '#/view/com/util/text/Text'
+import {TextLink} from '#/view/com/util/Link'
+import {CenteredView} from '#/view/com/util/Views'
+import {sanitizeHandle} from 'lib/strings/handles'
+import {makeProfileLink} from 'lib/routes/links'
+import {NavigationProp} from 'lib/routes/types'
+import {BACK_HITSLOP} from 'lib/constants'
+import {isNative} from 'platform/detection'
+import {useLingui} from '@lingui/react'
+import {Trans, msg} from '@lingui/macro'
+import {useSetDrawerOpen} from '#/state/shell'
+import {emitSoftReset} from '#/state/events'
+import {AppBskyModerationDefs} from '@atproto/api'
+import {HandIcon} from '#/lib/icons'
+import {shareUrl} from 'lib/sharing'
+import {sanitizeDisplayName} from '#/lib/strings/display-names'
+import {Button as OldButton} from '#/view/com/util/forms/Button'
+import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
+import {useSession} from '#/state/session'
+import {useModalControls} from '#/state/modals'
+
+import { useTheme, atoms as a } from '#/alf'
+import {Text} from '#/components/Typography'
+import {Button, ButtonText} from '#/components/Button'
+import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand'
+
+export function Header({
+ info,
+}: {
+ info: AppBskyModerationDefs.ModServiceViewDetailed
+}) {
+ const setDrawerOpen = useSetDrawerOpen()
+ const navigation = useNavigation()
+ const {_} = useLingui()
+ const {isMobile} = useWebMediaQueries()
+ const pal = usePalette('default')
+ const canGoBack = navigation.canGoBack()
+
+ const onPressBack = React.useCallback(() => {
+ if (navigation.canGoBack()) {
+ navigation.goBack()
+ } else {
+ navigation.navigate('Home')
+ }
+ }, [navigation])
+
+ const onPressMenu = React.useCallback(() => {
+ setDrawerOpen(true)
+ }, [setDrawerOpen])
+
+ return (
+
+ {isMobile && (
+
+
+ {canGoBack ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ Moderation service
+
+
+
+
+ {!isMobile && }
+
+
+
+ )
+}
+
+function CommonControls({
+ info,
+}: {
+ info: AppBskyModerationDefs.ModServiceViewDetailed
+}) {
+ const pal = usePalette('default')
+ const {hasSession} = useSession()
+ const {openModal} = useModalControls()
+ const {_} = useLingui()
+
+ const onPressShare = React.useCallback(() => {
+ const url = makeProfileLink(info.creator, 'modservice')
+ shareUrl(url)
+ // track('CustomFeed:Share') TODO
+ }, [info /*, track*/])
+
+ const onPressReport = React.useCallback(() => {
+ if (!info) return
+ openModal({
+ name: 'report',
+ uri: info.uri,
+ cid: info.cid,
+ })
+ }, [openModal, info])
+
+ const dropdownItems: DropdownItem[] = React.useMemo(() => {
+ return [
+ hasSession && {
+ testID: 'modHeaderDropdownReportBtn',
+ label: _(msg`Report mod service`),
+ onPress: onPressReport,
+ icon: {
+ ios: {
+ name: 'exclamationmark.triangle',
+ },
+ android: 'ic_menu_report_image',
+ web: 'circle-exclamation',
+ },
+ },
+ {
+ testID: 'modHeaderDropdownShareBtn',
+ label: _(msg`Share mod service`),
+ onPress: onPressShare,
+ icon: {
+ ios: {
+ name: 'square.and.arrow.up',
+ },
+ android: 'ic_menu_share',
+ web: 'share',
+ },
+ },
+ ].filter(Boolean) as DropdownItem[]
+ }, [hasSession, onPressReport, onPressShare, _])
+
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
+
+const styles = StyleSheet.create({
+ backBtn: {
+ width: 20,
+ height: 30,
+ },
+ backBtnWide: {
+ width: 20,
+ height: 30,
+ paddingHorizontal: 6,
+ },
+ backIcon: {
+ marginTop: 6,
+ },
+ btn: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 6,
+ paddingVertical: 7,
+ paddingHorizontal: 14,
+ borderRadius: 50,
+ },
+})
+
diff --git a/src/screens/ProfileModerationService/index.tsx b/src/screens/ProfileModerationService/index.tsx
index a07d4672a9..b8ce5f807e 100644
--- a/src/screens/ProfileModerationService/index.tsx
+++ b/src/screens/ProfileModerationService/index.tsx
@@ -36,104 +36,90 @@ import {
} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
-import {ModServiceHeader} from '#/view/com/moderation/ModServiceHeader'
import {sanitizeHandle} from '#/lib/strings/handles'
-import { useTheme, atoms as a } from '#/alf'
+import {useTheme, atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
+import {Loader} from '#/components/Loader'
import {Button, ButtonText} from '#/components/Button'
-import { CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+
+import {ErrorState} from '#/screens/ProfileModerationService/ErrorState'
+import {Header} from '#/screens/ProfileModerationService/Header'
// TODO
const MIN_HEIGHT = Dimensions.get('window').height * 1.5
+// TODO loader default color
-type Props = NativeStackScreenProps
-export function ProfileModserviceScreen(props: Props) {
+export function ProfileModserviceScreen(
+ props: NativeStackScreenProps,
+) {
const t = useTheme()
- const {name: handleOrDid} = props.route.params
-
- const pal = usePalette('default')
const {_} = useLingui()
- const navigation = useNavigation()
+ const {name: handleOrDid} = props.route.params
+ const {isLoading, error, data: resolvedDid} = useResolveDidQuery(handleOrDid)
- const {error, data: resolvedDid} = useResolveDidQuery(handleOrDid)
-
- const onPressBack = React.useCallback(() => {
- if (navigation.canGoBack()) {
- navigation.goBack()
- } else {
- navigation.navigate('Home')
- }
- }, [navigation])
-
- if (error) {
- return (
-
-
-
-
-
- Hmmmm, we couldn't load that moderation service.
-
-
- This moderation service is unavailable. See below for more details. If this issue persists, contact us.
-
-
-
- Error: {error.toString()}
-
-
-
-
-
-
-
-
- )
- }
-
- return resolvedDid ? (
-
- ) : (
+ return (
-
-
+
+ {isLoading ? (
+
+
+
+ ) : resolvedDid ? (
+
+ ) : (
+
+ )}
)
}
function ProfileModservicecreenIntermediate({modDid}: {modDid: string}) {
- const {data: preferences} = usePreferencesQuery()
- const {data: info} = useModServiceInfoQuery({did: modDid})
+ const t = useTheme()
+ const {_} = useLingui()
+ const {
+ isLoading: isPreferencesLoading,
+ error: preferencesError,
+ data: preferences,
+ } = usePreferencesQuery()
+ const {
+ isLoading: isModServiceLoading,
+ error: modServiceError,
+ data: info,
+ } = useModServiceInfoQuery({did: modDid})
- if (!preferences || !info) {
- return (
-
-
-
-
-
- )
- }
+ const isLoading = isPreferencesLoading || isModServiceLoading
+ const error = preferencesError || modServiceError
- return (
+ return isLoading ? (
+
+
+
+ ) : preferences && info ? (
+ ) : (
+
)
}
@@ -211,7 +197,7 @@ export function ProfileModserviceScreenInner({
contentContainerStyle={{
minHeight: Dimensions.get('window').height * 1.5,
}}>
-
+