diff --git a/assets/icons/squareBehindSquare4_stroke2_corner0_rounded.svg b/assets/icons/squareBehindSquare4_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..3fa7e5d390 --- /dev/null +++ b/assets/icons/squareBehindSquare4_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 25f3497014..0cd68d014c 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -171,11 +171,18 @@ export type LinkProps = Omit & * Intended to behave as a web anchor tag. For more complex routing, use a * `Button`. */ -export function Link({children, to, action = 'push', ...rest}: LinkProps) { +export function Link({ + children, + to, + action = 'push', + onPress: outerOnPress, + ...rest +}: LinkProps) { const {href, isExternal, onPress} = useLink({ to, displayText: typeof children === 'string' ? children : '', action, + onPress: outerOnPress, }) return ( @@ -218,6 +225,7 @@ export function InlineLink({ action = 'push', warnOnMismatchingTextChild, style, + onPress: outerOnPress, ...rest }: InlineLinkProps) { const t = useTheme() @@ -227,6 +235,7 @@ export function InlineLink({ displayText: stringChildren ? children : '', action, warnOnMismatchingTextChild, + onPress: outerOnPress, }) const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() const { diff --git a/src/components/ModerationServiceCard/Card.tsx b/src/components/ModerationServiceCard/Card.tsx index 2dfff4c280..c86b4cdc7b 100644 --- a/src/components/ModerationServiceCard/Card.tsx +++ b/src/components/ModerationServiceCard/Card.tsx @@ -7,6 +7,7 @@ import {Text} from '#/components/Typography' import {RichText} from '#/components/RichText' import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand' import {UserAvatar} from '#/view/com/util/UserAvatar' +import {sanitizeHandle} from '#/lib/strings/handles' export function Outer({ children, @@ -43,6 +44,22 @@ export function Avatar({avatar}: {avatar?: string}) { return } +export function Title({value}: {value: string}) { + return {value} +} + +export function Description({value, handle}: {value?: string; handle: string}) { + return value ? ( + + ) : ( + + + Moderation service managed by @{sanitizeHandle(handle, '@')} + + + ) +} + export function Content({ title, description, @@ -63,7 +80,9 @@ export function Content({ ) : ( - Moderation service managed by @{handle} + + Moderation service managed by @{sanitizeHandle(handle, '@')} + )} diff --git a/src/components/icons/SquareBehindSquare4.tsx b/src/components/icons/SquareBehindSquare4.tsx new file mode 100644 index 0000000000..425599cbce --- /dev/null +++ b/src/components/icons/SquareBehindSquare4.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const SquareBehindSquare4_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M8 8V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-5v5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h5Zm1 8a1 1 0 0 1-1-1v-5H4v10h10v-4H9Z', +}) diff --git a/src/screens/Moderation/SettingsDialog.tsx b/src/screens/Moderation/SettingsDialog.tsx index 11c7596cea..b84882286c 100644 --- a/src/screens/Moderation/SettingsDialog.tsx +++ b/src/screens/Moderation/SettingsDialog.tsx @@ -13,6 +13,10 @@ import * as ModerationServiceCard from '#/components/ModerationServiceCard' import {getModerationServiceTitle} from '#/lib/moderation' import {UsePreferencesQueryResponse} from '#/state/queries/preferences' import {useModServiceLabelGroupEnableMutation} from '#/state/queries/modservice' +import {Divider} from '#/components/Divider' +import {InlineLink} from '#/components/Link' +import {useDialogStateControlContext} from '#/state/dialogs' +import {logger} from '#/logger' function LabelerToggle({ labelGroup, @@ -24,14 +28,24 @@ function LabelerToggle({ preferences: UsePreferencesQueryResponse }) { const t = useTheme() - const {mutateAsync, variables} = useModServiceLabelGroupEnableMutation() + const { + mutateAsync: toggleGroupEnabled, + variables: optimisticToggleGroupEnabled, + } = useModServiceLabelGroupEnableMutation() + const {closeAllDialogs} = useDialogStateControlContext() - const modservicePreferences = preferences.moderationOpts.mods.find( - ({did}) => did === labeler.creator.did, + const labelerPrefs = React.useMemo( + () => + preferences.moderationOpts.mods.find( + ({did}) => did === labeler.creator.did, + ), + [preferences.moderationOpts.mods, labeler.creator.did], ) - const enabled = - variables?.enabled ?? - !modservicePreferences?.disabledLabelGroups?.includes(labelGroup) + const isLabelerEnabled = !!labelerPrefs?.enabled + const isEnabled = isLabelerEnabled + ? optimisticToggleGroupEnabled?.enabled ?? + !labelerPrefs?.disabledLabelGroups?.includes(labelGroup) + : false const title = getModerationServiceTitle({ displayName: labeler.creator.displayName, handle: labeler.creator.handle, @@ -39,42 +53,66 @@ function LabelerToggle({ const onToggleEnabled = React.useCallback(async () => { try { - await mutateAsync({ - // @ts-ignore TODO - did: modservicePreferences?.did, + if (!labelerPrefs) throw new Error(`labelerPrefs not found`) + + await toggleGroupEnabled({ + did: labelerPrefs.did, group: labelGroup, - enabled: !enabled, + enabled: !isEnabled, }) } catch (e: any) { - // TODO - console.error(e) + logger.error(`Failed to toggle label group enabled`, { + message: e.message, + labelGroup, + }) } - }, [mutateAsync, enabled, modservicePreferences, labelGroup]) + }, [toggleGroupEnabled, isEnabled, labelerPrefs, labelGroup]) + return ( - - {ctx => ( - + + + - - - )} - + + + + + + + + {isEnabled ? 'Enabled' : 'Disabled'} + + + + + + + + + Configure more settings for this labeler{' '} + + here. + + + + ) } @@ -115,11 +153,10 @@ export function SettingsDialog({ a.p_md, a.rounded_md, a.mb_xl, - a.border, - t.atoms.border_contrast_low, + t.atoms.bg_contrast_25, ]}> - {groupInfoStrings.name} + {groupInfoStrings.name} {groupInfoStrings.description} diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 620133f1ad..8ed51dd5cb 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -26,11 +26,12 @@ import { } from '#/state/queries/preferences' import {useModServicesDetailedInfoQuery} from '#/state/queries/modservice' -import {useTheme, atoms as a, useBreakpoints} from '#/alf' +import {useTheme, atoms as a, useBreakpoints, native} from '#/alf' import {Divider} from '#/components/Divider' import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' +import {SquareBehindSquare4_Stroke2_Corner0_Rounded as SquareBehindSquare} from '#/components/icons/SquareBehindSquare4' import {Text} from '#/components/Typography' import * as Toggle from '#/components/forms/Toggle' import * as ToggleButton from '#/components/forms/ToggleButton' @@ -38,7 +39,7 @@ import {InlineLink, Link} from '#/components/Link' import {Loader} from '#/components/Loader' import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings' import * as Dialog from '#/components/Dialog' -import {Button} from '#/components/Button' +import {Button, ButtonText, ButtonIcon} from '#/components/Button' import { getModerationServiceTitle, useConfigurableLabelGroups, @@ -365,79 +366,90 @@ function LabelGroup({ {!!mods.length && ( - + + + {mods.map(mod => { + const modservicePreferences = + preferences.moderationOpts.mods.find( + ({did}) => did === mod.creator.did, + ) + const labelGroupEnabled = + !modservicePreferences?.disabledLabelGroups?.includes( + labelGroup, + ) + const isLabelerEnabled = modservicePreferences?.enabled + const enabled = labelGroupEnabled && isLabelerEnabled + return ( + + + + {getModerationServiceTitle({ + displayName: mod.creator.displayName, + handle: mod.creator.handle, + })} + + + ) + })} + + )} diff --git a/src/screens/ProfileModerationService/Header.tsx b/src/screens/ProfileModerationService/Header.tsx index f7ba831f02..5ff4dbe014 100644 --- a/src/screens/ProfileModerationService/Header.tsx +++ b/src/screens/ProfileModerationService/Header.tsx @@ -19,11 +19,9 @@ import { } from '#/view/com/util/forms/NativeDropdown' import {useSession} from '#/state/session' import {useModalControls} from '#/state/modals' -import { - useModServiceSubscriptionMutation, - useModServiceEnableMutation, -} from '#/state/queries/modservice' +import {useModServiceSubscriptionMutation} from '#/state/queries/modservice' import {UsePreferencesQueryResponse} from '#/state/queries/preferences' +import {logger} from '#/logger' import {useTheme, atoms as a, tokens, web, native, useBreakpoints} from '#/alf' import {Text} from '#/components/Typography' @@ -54,16 +52,7 @@ export function Header({ preferences.moderationOpts.mods.find( mod => mod.did === modservice.creator.did, ) - const {mutateAsync: toggleEnabled, variables: enabledVariables} = - useModServiceEnableMutation() - const isEnabled = - enabledVariables?.enabled ?? - preferences.moderationOpts.mods.find( - mod => mod.did === modservice.creator.did && mod.enabled, - ) ?? - variables?.subscribe const [subscriptionError, setSubscriptionError] = React.useState('') - const [enablementError, setEnablementError] = React.useState('') const onPressBack = React.useCallback(() => { if (navigation.canGoBack()) { @@ -85,20 +74,10 @@ export function Header({ }) } catch (e: any) { setSubscriptionError(e.message) + logger.error(`Failed to subscribe to labeler`, {message: e.message}) } }, [toggleSubscription, isSubscribed, modservice.creator.did]) - const onPressEnable = React.useCallback(async () => { - try { - await toggleEnabled({ - did: modservice.creator.did, - enabled: !isEnabled, - }) - } catch (e: any) { - setEnablementError(e.message) - } - }, [toggleEnabled, isEnabled, modservice.creator.did]) - return ( {!gtMobile && ( @@ -179,23 +158,6 @@ export function Header({ {gtMobile && ( - {isSubscribed && ( - - )} -