import { useState } from 'react';
import { type ModerationCause, ModerationCauseType } from '@atcute/bluesky-moderation';
import { useConstant } from '#/lib/hooks/use-constant';
import type { AppModerationCause } from '#/lib/moderation/causes';
import { profileTarget } from '#/lib/routes/targets';
import { useModerationCauseDescription } from '#/state/moderation/use-moderation-cause-description';
import { useSession } from '#/state/session';
import { relativeMessageParts } from '#/locale/intl/timeAgo';
import { Trans } from '#/locale/Trans';
import * as Dialog from '#/components/Dialog';
import { AppealForm } from '#/components/moderation/AppealForm';
import { ModerationListLink } from '#/components/moderation/ModerationListLink';
import { Stack } from '#/components/Stack';
import { Text } from '#/components/Text';
import { Admonition } from '#/components/web/Admonition';
import { Button, ButtonText } from '#/components/web/Button';
import { InlineLinkText } from '#/components/web/Link';
import { m } from '#/paraglide/messages';
import * as styles from './ModerationDetailsDialog.css';
export interface ModerationDetailsDialogProps {
handle: Dialog.DialogHandle;
modcause?: AppModerationCause | ModerationCause;
}
/** shows the details behind a moderation cause. */
export function ModerationDetailsDialog({ handle, modcause }: ModerationDetailsDialogProps) {
return (
<Dialog.Root handle={handle}>
<Dialog.Popup padding="none" size="medium">
<ModerationDetailsDialogInner handle={handle} modcause={modcause} />
</Dialog.Popup>
</Dialog.Root>
);
}
function ModerationDetailsDialogInner({ handle, modcause }: ModerationDetailsDialogProps) {
const desc = useModerationCauseDescription(modcause);
const { currentAccount } = useSession();
const now = useConstant(Date.now);
const [isAppealing, setIsAppealing] = useState(false);
// appeal eligibility: a label cause on the current user's own content that they didn't self-apply.
const canAppeal =
modcause?.type === ModerationCauseType.Label &&
!!currentAccount &&
modcause.label.src !== currentAccount.did &&
(modcause.label.uri === currentAccount.did ||
modcause.label.uri.startsWith('at://' + currentAccount.did + '/'));
if (isAppealing && modcause?.type === ModerationCauseType.Label) {
return (
<div className={styles.main}>
<AppealForm handle={handle} label={modcause.label} onPressBack={() => setIsAppealing(false)} />
</div>
);
}
let name;
let description;
if (!modcause) {
name = m['common.moderation.contentWarning']();
description = m['common.moderation.generalWarning']();
} else if (modcause.type === 'replyHidden') {
const isYou = currentAccount?.did === modcause.source.did;
name = isYou ? m['common.thread.replyHiddenByYou']() : m['common.thread.replyHiddenByAuthor']();
description = isYou ? m['common.thread.youHidReply']() : m['common.thread.authorHiddenReply']();
} else if (modcause.type === ModerationCauseType.Blocking) {
if (modcause.source) {
const list = modcause.source;
name = m['common.block.byList.title']();
description = (
<Trans
message={m['common.block.byList.message']}
inputs={{ name: list.name }}
markup={{
t0: ({ children }) => (
<ModerationListLink list={list} size="sm">
{children}
</ModerationListLink>
),
}}
/>
);
} else {
name = m['common.block.byYou.title']();
description = m['common.block.byYou.message']();
}
} else if (modcause.type === ModerationCauseType.BlockedBy) {
name = m['common.block.blocksYou.title']();
description = m['common.block.blocksYou.message']();
} else if (modcause.type === ModerationCauseType.MutedPermanent) {
if (modcause.source) {
const list = modcause.source;
name = m['common.mute.byList.title']();
description = (
<Trans
message={m['common.mute.byList.message']}
inputs={{ name: list.name }}
markup={{
t0: ({ children }) => (
<ModerationListLink list={list} size="sm">
{children}
</ModerationListLink>
),
}}
/>
);
} else {
name = m['common.mute.byYou.title']();
description = m['common.mute.byYou.message']();
}
} else if (modcause.type === ModerationCauseType.MutedTemporary) {
name = m['common.mute.byYou.title']();
description = m['common.mute.byYou.message']();
} else if (modcause.type === ModerationCauseType.MutedKeyword) {
name = m['common.mutedWord.postHidden']();
description = m['common.mutedWord.hiddenTag']();
} else if (modcause.type === ModerationCauseType.Hidden) {
name = m['common.thread.postHiddenByYou']();
description = m['common.thread.youHidPost']();
} else if (modcause.type === ModerationCauseType.Label) {
name = desc.name;
description = <Text size="md">{desc.description}</Text>;
} else {
// should never happen
name = '';
description = '';
}
const sourceName = desc.source || desc.sourceDisplayName || m['common.moderation.unknownLabeler']();
return (
<>
<div className={styles.main}>
<Stack gap="xs">
<Dialog.TitleRow>
<Dialog.Title>{name}</Dialog.Title>
<Dialog.Close />
</Dialog.TitleRow>
<Text>{description}</Text>
</Stack>
{canAppeal && (
<div className={styles.appealRow}>
<Text className={styles.appealHint} color="textContrastMedium" size="md_sub">
{m['components.moderation.appeal.thisLabelHint']()}
</Text>
<Button
color="primary_subtle"
label={m['components.moderation.appeal.action']()}
onClick={() => setIsAppealing(true)}
size="small"
variant="solid"
>
<ButtonText>{m['components.moderation.appeal.action']()}</ButtonText>
</Button>
</div>
)}
{desc.isSubjectAccount && (
<Admonition className={styles.admonition} type="info">
{m['common.moderation.accountHint']()}
</Admonition>
)}
</div>
{modcause?.type === ModerationCauseType.Label && (
<div className={styles.labelBand}>
{modcause.source === null ? (
<Text size="md">{m['common.moderation.appliedByAuthor']()}</Text>
) : (
<div className={styles.sourceRow}>
<Text className={styles.sourceText} color="textContrastMedium" numberOfLines={1}>
<Trans
message={m['common.moderation.source']}
inputs={{ source: sourceName }}
markup={{
t0: ({ children }) => (
<InlineLinkText
label={sourceName}
onPress={() => handle.close()}
to={profileTarget(modcause.label.src)}
>
{children}
</InlineLinkText>
),
}}
/>
</Text>
{modcause.label.exp && (
<Text className={styles.expires} color="textContrastMedium" size="sm">
{m['common.mutedWord.expires'](relativeMessageParts(modcause.label.exp, now))}
</Text>
)}
</div>
)}
</div>
)}
</>
);
}