mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
refactor(app): remove the report modal path selection step (#2548)
This commit is contained in:
1 parent
a2d6477b42
commit
798e64b224
38 files changed
+3893
-5055
No files matched your search
@@ -7,7 +7,6 @@ import type {
|
||||
} from '@fluxer/schema/src/domains/report/ReportSchemas';
|
||||
|
||||
export type IARReportContextType = 'message' | 'user' | 'guild';
|
||||
export type IARPrimaryPath = 'platform' | 'community' | 'preference';
|
||||
export type IARRuleReasonId =
|
||||
| 'harassment'
|
||||
| 'hate'
|
||||
|
||||
@@ -22,7 +22,6 @@ import {BanMemberModal} from '@app/features/moderation/components/modals/BanMemb
|
||||
import {IARActionCards} from '@app/features/moderation/components/report_modal/IARActionCards';
|
||||
import {
|
||||
getReportCategoryForReason,
|
||||
type IARPrimaryPath,
|
||||
type IARRuleCategoryId,
|
||||
type IARRuleReasonId,
|
||||
} from '@app/features/moderation/components/report_modal/IARFlowUtils';
|
||||
@@ -33,8 +32,6 @@ import {
|
||||
getIARCategoryTitle,
|
||||
getIARChildSafetyRoutingNote,
|
||||
getIARModalDescription,
|
||||
getIARPathTitle,
|
||||
getIARPrimaryOptions,
|
||||
getIARReasonTitle,
|
||||
getIARReportEligibilityCopy,
|
||||
getIARRuleCategoryOptions,
|
||||
@@ -104,18 +101,16 @@ interface IARModalProps {
|
||||
}
|
||||
|
||||
const logger = new Logger('IARModal');
|
||||
const STEP_ORDER: ReadonlyArray<IARStep> = ['path', 'category', 'reason', 'guidance', 'success'];
|
||||
const STEP_ORDER: ReadonlyArray<IARStep> = ['category', 'reason', 'success'];
|
||||
export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
const {i18n} = useLingui();
|
||||
const leaveGuild = useLeaveGuild();
|
||||
const [step, setStep] = useState<IARStep>('path');
|
||||
const [selectedPath, setSelectedPath] = useState<IARPrimaryPath | null>(null);
|
||||
const [step, setStep] = useState<IARStep>('category');
|
||||
const [selectedCategory, setSelectedCategory] = useState<IARRuleCategoryId | null>(null);
|
||||
const [selectedReason, setSelectedReason] = useState<IARRuleReasonId | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const canSubmitPlatformReport = canSubmitReport();
|
||||
const resolvedContext = resolveIARModalContext(i18n, context);
|
||||
const primaryOptions = getIARPrimaryOptions(i18n, context, resolvedContext);
|
||||
const ruleCategoryOptions = getIARRuleCategoryOptions(i18n);
|
||||
const ruleReasonOptions = getIARRuleReasonOptions(i18n, context.type, selectedCategory);
|
||||
const childSafetyRoutingNote = getIARChildSafetyRoutingNote(i18n, context.type, selectedReason);
|
||||
@@ -241,67 +236,38 @@ export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
onDeleteMessage: handleDeleteMessage,
|
||||
onBanUser: handleBanUser,
|
||||
};
|
||||
const guidanceActionCards = getIARActionCards(i18n, context, resolvedContext, actionHandlers, {
|
||||
includeModerationActions: false,
|
||||
});
|
||||
const successActionCards = getIARActionCards(i18n, context, resolvedContext, actionHandlers, {
|
||||
isMessageDeleted,
|
||||
isUserBanned: isUserKnownBanned,
|
||||
includeModerationActions: true,
|
||||
});
|
||||
const handleBack = useCallback(() => {
|
||||
switch (step) {
|
||||
case 'path':
|
||||
closeModal();
|
||||
return;
|
||||
case 'category':
|
||||
case 'guidance':
|
||||
setStep('path');
|
||||
case 'success':
|
||||
closeModal();
|
||||
return;
|
||||
case 'reason':
|
||||
setSelectedReason(null);
|
||||
setStep('category');
|
||||
return;
|
||||
case 'success':
|
||||
closeModal();
|
||||
return;
|
||||
}
|
||||
}, [closeModal, step]);
|
||||
const handleContinue = useCallback(() => {
|
||||
if (step === 'path') {
|
||||
if (selectedPath === null) {
|
||||
showModerationErrorModal(
|
||||
i18n,
|
||||
() => i18n._(PICK_AN_OPTION_TO_CONTINUE_DESCRIPTOR),
|
||||
'moderation.iar-modal.path-required-error-modal',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (selectedPath === 'platform') {
|
||||
if (!canSubmitPlatformReport) {
|
||||
showReportRestrictionDialog();
|
||||
return;
|
||||
}
|
||||
setStep('category');
|
||||
return;
|
||||
}
|
||||
setStep('guidance');
|
||||
if (selectedCategory === null) {
|
||||
showModerationErrorModal(
|
||||
i18n,
|
||||
() => i18n._(PICK_AN_OPTION_TO_CONTINUE_DESCRIPTOR),
|
||||
'moderation.iar-modal.category-required-error-modal',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (step === 'category') {
|
||||
if (selectedCategory === null) {
|
||||
showModerationErrorModal(
|
||||
i18n,
|
||||
() => i18n._(PICK_AN_OPTION_TO_CONTINUE_DESCRIPTOR),
|
||||
'moderation.iar-modal.category-required-error-modal',
|
||||
);
|
||||
return;
|
||||
}
|
||||
setSelectedReason(null);
|
||||
setStep('reason');
|
||||
if (!canSubmitPlatformReport) {
|
||||
showReportRestrictionDialog();
|
||||
return;
|
||||
}
|
||||
}, [canSubmitPlatformReport, i18n, selectedCategory, selectedPath, step]);
|
||||
setSelectedReason(null);
|
||||
setStep('reason');
|
||||
}, [canSubmitPlatformReport, i18n, selectedCategory]);
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (!canSubmitPlatformReport) {
|
||||
showReportRestrictionDialog();
|
||||
@@ -336,49 +302,6 @@ export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [canSubmitPlatformReport, context, i18n, reportCategory, selectedReason]);
|
||||
const renderPathStep = () => (
|
||||
<div className={styles.step} data-flx="moderation.iar-modal.render-path-step.step">
|
||||
<IARModalPreview
|
||||
context={context}
|
||||
currentChannel={resolvedContext.currentChannel}
|
||||
data-flx="moderation.iar-modal.render-path-step.iar-modal-preview"
|
||||
/>
|
||||
{!canSubmitPlatformReport && (
|
||||
<div className={styles.notice} data-flx="moderation.iar-modal.render-path-step.notice">
|
||||
<div
|
||||
className={styles.noticeTitle}
|
||||
data-flx="moderation.report-modal.iar-modal.render-path-step.notice-title"
|
||||
>
|
||||
{reportEligibilityCopy.title}
|
||||
</div>
|
||||
<div className={styles.noticeText} data-flx="moderation.report-modal.iar-modal.render-path-step.notice-text">
|
||||
{reportEligibilityCopy.body}
|
||||
</div>
|
||||
<div
|
||||
className={styles.noticeActions}
|
||||
data-flx="moderation.report-modal.iar-modal.render-path-step.notice-actions"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
small
|
||||
fitContent
|
||||
onClick={handleOpenReportEligibility}
|
||||
data-flx="moderation.iar-modal.render-path-step.button.open-report-eligibility"
|
||||
>
|
||||
{i18n._(FINISH_ACCOUNT_SETUP_DESCRIPTOR)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<RadioGroup<IARPrimaryPath>
|
||||
options={primaryOptions}
|
||||
value={selectedPath}
|
||||
onChange={setSelectedPath}
|
||||
aria-label={getIARPathTitle(i18n)}
|
||||
data-flx="moderation.iar-modal.render-path-step.radio-group.set-selected-path"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const renderCategoryStep = () => (
|
||||
<div className={styles.step} data-flx="moderation.iar-modal.render-category-step.step">
|
||||
<h2 className={styles.stepTitle} data-flx="moderation.iar-modal.render-category-step.step-title">
|
||||
@@ -389,6 +312,36 @@ export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
currentChannel={resolvedContext.currentChannel}
|
||||
data-flx="moderation.iar-modal.render-category-step.iar-modal-preview"
|
||||
/>
|
||||
{!canSubmitPlatformReport && (
|
||||
<div className={styles.notice} data-flx="moderation.iar-modal.render-category-step.notice">
|
||||
<div
|
||||
className={styles.noticeTitle}
|
||||
data-flx="moderation.report-modal.iar-modal.render-category-step.notice-title"
|
||||
>
|
||||
{reportEligibilityCopy.title}
|
||||
</div>
|
||||
<div
|
||||
className={styles.noticeText}
|
||||
data-flx="moderation.report-modal.iar-modal.render-category-step.notice-text"
|
||||
>
|
||||
{reportEligibilityCopy.body}
|
||||
</div>
|
||||
<div
|
||||
className={styles.noticeActions}
|
||||
data-flx="moderation.report-modal.iar-modal.render-category-step.notice-actions"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
small
|
||||
fitContent
|
||||
onClick={handleOpenReportEligibility}
|
||||
data-flx="moderation.iar-modal.render-category-step.button.open-report-eligibility"
|
||||
>
|
||||
{i18n._(FINISH_ACCOUNT_SETUP_DESCRIPTOR)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<RadioGroup<IARRuleCategoryId>
|
||||
options={ruleCategoryOptions}
|
||||
value={selectedCategory}
|
||||
@@ -427,17 +380,6 @@ export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const renderGuidanceStep = () => {
|
||||
if (selectedPath === null || selectedPath === 'platform') return null;
|
||||
return (
|
||||
<div className={styles.step} data-flx="moderation.iar-modal.render-guidance-step.step">
|
||||
<IARActionCards
|
||||
cards={guidanceActionCards}
|
||||
data-flx="moderation.iar-modal.render-guidance-step.iar-action-cards"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const renderSuccessStep = () => {
|
||||
const successCopy = getIARSuccessCopy(i18n);
|
||||
return (
|
||||
@@ -457,40 +399,26 @@ export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
};
|
||||
const renderStep = (): React.ReactNode => {
|
||||
switch (step) {
|
||||
case 'path':
|
||||
return renderPathStep();
|
||||
case 'category':
|
||||
return renderCategoryStep();
|
||||
case 'reason':
|
||||
return renderReasonStep();
|
||||
case 'guidance':
|
||||
return renderGuidanceStep();
|
||||
case 'success':
|
||||
return renderSuccessStep();
|
||||
}
|
||||
};
|
||||
const getSecondaryFooterLabel = (): string => {
|
||||
return step === 'path' ? i18n._(CANCEL_DESCRIPTOR) : i18n._(BACK_DESCRIPTOR);
|
||||
return step === 'category' ? i18n._(CANCEL_DESCRIPTOR) : i18n._(BACK_DESCRIPTOR);
|
||||
};
|
||||
const showSecondaryFooterButton = step !== 'success';
|
||||
const renderPrimaryFooterButton = () => {
|
||||
switch (step) {
|
||||
case 'path':
|
||||
return (
|
||||
<Button
|
||||
onClick={handleContinue}
|
||||
disabled={selectedPath === null || submitting}
|
||||
data-flx="moderation.iar-modal.render-primary-footer-button.button.continue"
|
||||
>
|
||||
{i18n._(CONTINUE_DESCRIPTOR)}
|
||||
</Button>
|
||||
);
|
||||
case 'category':
|
||||
return (
|
||||
<Button
|
||||
onClick={handleContinue}
|
||||
disabled={selectedCategory === null || submitting}
|
||||
data-flx="moderation.iar-modal.render-primary-footer-button.button.continue-category"
|
||||
data-flx="moderation.iar-modal.render-primary-footer-button.button.continue"
|
||||
>
|
||||
{i18n._(CONTINUE_DESCRIPTOR)}
|
||||
</Button>
|
||||
@@ -506,7 +434,6 @@ export const IARModal: React.FC<IARModalProps> = observer(({context}) => {
|
||||
{i18n._(SEND_REPORT_DESCRIPTOR)}
|
||||
</Button>
|
||||
);
|
||||
case 'guidance':
|
||||
case 'success':
|
||||
return (
|
||||
<Button
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
|
||||
import {CLOSE_DM_DESCRIPTOR} from '@app/features/channel/utils/ChannelMessageDescriptors';
|
||||
import {
|
||||
type IARPrimaryPath,
|
||||
type IARReportContextType,
|
||||
type IARRuleCategoryId,
|
||||
type IARRuleReasonId,
|
||||
@@ -21,23 +20,6 @@ import {BLOCK_DESCRIPTOR} from '@app/features/moderation/utils/ModerationMessage
|
||||
import type {I18n} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
|
||||
const I_DON_T_LIKE_THIS_CONTENT_DESCRIPTOR = msg({
|
||||
message: "I don't like this content",
|
||||
comment: 'IAR modal: preference path option label for a message report.',
|
||||
});
|
||||
const I_DON_T_LIKE_THIS_USER_DESCRIPTOR = msg({
|
||||
message: "I don't like this user",
|
||||
comment: 'IAR modal: preference path option label for a user report.',
|
||||
});
|
||||
const I_DON_T_LIKE_THIS_COMMUNITY_DESCRIPTOR = msg({
|
||||
message: "I don't like this community",
|
||||
comment: 'IAR modal: preference path option label for a community report.',
|
||||
});
|
||||
const WHAT_DO_YOU_NEED_DESCRIPTOR = msg({
|
||||
message: 'What do you need?',
|
||||
comment:
|
||||
'IAR modal: accessible label (aria-label only — not shown visually) for the primary-path radio group on step 1.',
|
||||
});
|
||||
const FINISH_ACCOUNT_SETUP_FIRST_DESCRIPTOR = msg({
|
||||
message: 'Finish account setup first',
|
||||
comment: 'IAR modal: title of the account-not-ready notice.',
|
||||
@@ -107,14 +89,6 @@ const USE_CHILD_SAFETY_INSTEAD_DESCRIPTOR = msg({
|
||||
message: 'If a minor is involved, use "{childSafetyReasonName}" instead.',
|
||||
comment: 'IAR modal: inline routing hint shown under reasons that overlap with child safety.',
|
||||
});
|
||||
const REPORT_A_PLATFORM_RULE_VIOLATION_DESCRIPTOR = msg({
|
||||
message: 'Report a platform rule violation',
|
||||
comment: 'IAR modal: primary path label.',
|
||||
});
|
||||
const REPORT_TO_COMMUNITY_MODS_PATH_DESCRIPTOR = msg({
|
||||
message: 'Report to the moderators of this community',
|
||||
comment: 'IAR modal: community-mod path label.',
|
||||
});
|
||||
const REPORT_SENT_TITLE_DESCRIPTOR = msg({
|
||||
message: 'Report sent',
|
||||
comment: 'IAR modal: title on the success step shown after a platform report is submitted.',
|
||||
@@ -473,25 +447,10 @@ function shouldShowContactSettings(context: IARContext): boolean {
|
||||
return context.type !== 'guild';
|
||||
}
|
||||
|
||||
function getPreferenceOptionName(i18n: I18n, context: IARContext): string {
|
||||
switch (context.type) {
|
||||
case 'message':
|
||||
return i18n._(I_DON_T_LIKE_THIS_CONTENT_DESCRIPTOR);
|
||||
case 'user':
|
||||
return i18n._(I_DON_T_LIKE_THIS_USER_DESCRIPTOR);
|
||||
case 'guild':
|
||||
return i18n._(I_DON_T_LIKE_THIS_COMMUNITY_DESCRIPTOR);
|
||||
}
|
||||
}
|
||||
|
||||
export function getIARModalDescription(i18n: I18n): string {
|
||||
return i18n._(MODAL_DESCRIPTION_DESCRIPTOR);
|
||||
}
|
||||
|
||||
export function getIARPathTitle(i18n: I18n): string {
|
||||
return i18n._(WHAT_DO_YOU_NEED_DESCRIPTOR);
|
||||
}
|
||||
|
||||
export function getIARReportEligibilityCopy(i18n: I18n): IARCopyBlock {
|
||||
return {
|
||||
title: i18n._(FINISH_ACCOUNT_SETUP_FIRST_DESCRIPTOR),
|
||||
@@ -579,30 +538,6 @@ export function getIARChildSafetyRoutingNote(
|
||||
}
|
||||
}
|
||||
|
||||
export function getIARPrimaryOptions(
|
||||
i18n: I18n,
|
||||
context: IARContext,
|
||||
resolvedContext: IARResolvedContext,
|
||||
): Array<IARRadioOption<IARPrimaryPath>> {
|
||||
const options: Array<IARRadioOption<IARPrimaryPath>> = [
|
||||
{
|
||||
value: 'platform',
|
||||
name: i18n._(REPORT_A_PLATFORM_RULE_VIOLATION_DESCRIPTOR),
|
||||
},
|
||||
];
|
||||
if (resolvedContext.hasCommunityContext) {
|
||||
options.push({
|
||||
value: 'community',
|
||||
name: i18n._(REPORT_TO_COMMUNITY_MODS_PATH_DESCRIPTOR),
|
||||
});
|
||||
}
|
||||
options.push({
|
||||
value: 'preference',
|
||||
name: getPreferenceOptionName(i18n, context),
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
function getMessageRuleReasonOptions(i18n: I18n): Array<IARRadioOption<IARRuleReasonId>> {
|
||||
return [
|
||||
{
|
||||
@@ -781,7 +716,6 @@ export function getIARSpecialSafetyNote(i18n: I18n, selectedReason: IARRuleReaso
|
||||
export interface IARActionCardsOptions {
|
||||
isMessageDeleted?: boolean;
|
||||
isUserBanned?: boolean;
|
||||
includeModerationActions?: boolean;
|
||||
}
|
||||
|
||||
export function getIARActionCards(
|
||||
@@ -792,7 +726,7 @@ export function getIARActionCards(
|
||||
options: IARActionCardsOptions = {},
|
||||
): Array<IARActionCardConfig> {
|
||||
const cards: Array<IARActionCardConfig> = [];
|
||||
const {isMessageDeleted = false, isUserBanned = false, includeModerationActions = true} = options;
|
||||
const {isMessageDeleted = false, isUserBanned = false} = options;
|
||||
if (!resolvedContext.isReportedUserBlocked && resolvedContext.reportedUser !== null) {
|
||||
cards.push({
|
||||
id: 'block-user',
|
||||
@@ -833,7 +767,7 @@ export function getIARActionCards(
|
||||
onClick: handlers.onLeaveCommunity,
|
||||
});
|
||||
}
|
||||
if (includeModerationActions && resolvedContext.canDeleteReportedMessage) {
|
||||
if (resolvedContext.canDeleteReportedMessage) {
|
||||
const deleteDisabled = isMessageDeleted;
|
||||
cards.push({
|
||||
id: 'delete-message',
|
||||
@@ -846,7 +780,7 @@ export function getIARActionCards(
|
||||
disabledTooltip: deleteDisabled ? i18n._(ALREADY_DELETED_TOOLTIP_DESCRIPTOR) : undefined,
|
||||
});
|
||||
}
|
||||
if (includeModerationActions && resolvedContext.canBanReportedUser) {
|
||||
if (resolvedContext.canBanReportedUser) {
|
||||
const banDisabled = isUserBanned;
|
||||
cards.push({
|
||||
id: 'ban-user',
|
||||
|
||||
@@ -37,7 +37,7 @@ export type IARRadioOption<T> = RadioOption<T> & {
|
||||
name: string;
|
||||
desc?: string;
|
||||
};
|
||||
export type IARStep = 'path' | 'category' | 'reason' | 'guidance' | 'success';
|
||||
export type IARStep = 'category' | 'reason' | 'success';
|
||||
export type IARContext =
|
||||
| {
|
||||
type: 'message';
|
||||
|
||||
Reference in new issue
Block a user