mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
chore(architecture): finish refactor audit
Merge the architecture audit refactors and follow-up UX fixes: remove release notes from generated LLM context, keep author edit controls delete-only, stabilize board refresh rendering, and address review feedback for role loading, refresh holds, moderation actions, and P2P stats polling.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Comment, usePublishCommentModeration } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { approvePendingCommentModeration, rejectPendingCommentModeration } from '../lib/utils/pending-approval-moderation';
|
||||
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
|
||||
import { formatErrorForDisplay } from '../lib/utils/error-utils';
|
||||
import useChallengesStore from '../stores/use-challenges-store';
|
||||
|
||||
export type PendingModerationStatus = 'approved' | 'rejected' | 'failed' | null;
|
||||
|
||||
export interface UsePendingCommentModerationActionsOptions {
|
||||
// The comment the moderation challenge is published against. Forwarded to the
|
||||
// challenge store so the challenge UI can reference the target comment.
|
||||
comment: Comment | undefined;
|
||||
commentCid: string | undefined;
|
||||
communityAddress: string | undefined;
|
||||
// When false, the underlying publishers are disabled by withholding the
|
||||
// community address (mirrors the mobile post page gating its actions).
|
||||
enabled?: boolean;
|
||||
// Run after a successful approve/reject publish, inside the same try block as
|
||||
// the publish call so a throw here is logged exactly like the publish path.
|
||||
onApproveSuccess?: () => void | Promise<void>;
|
||||
onRejectSuccess?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface PendingCommentModerationActions {
|
||||
handleApprove: () => Promise<void>;
|
||||
handleReject: () => Promise<void>;
|
||||
isPublishing: boolean;
|
||||
// Action-derived status only ('approved'/'rejected' on success, 'failed' on
|
||||
// error). Callers that also have a baseline (e.g. an already-moderated mod
|
||||
// queue comment) combine that baseline with this value themselves.
|
||||
status: PendingModerationStatus;
|
||||
error: unknown;
|
||||
errorMessage: string | undefined;
|
||||
}
|
||||
|
||||
// Shared approve/reject state machine for pending-approval comment moderation.
|
||||
// Extracted from the duplicated copies in desktop posts, mobile posts, and the
|
||||
// mod queue so the riskiest moderation flow lives behind one tested boundary.
|
||||
export const usePendingCommentModerationActions = ({
|
||||
comment,
|
||||
commentCid,
|
||||
communityAddress,
|
||||
enabled = true,
|
||||
onApproveSuccess,
|
||||
onRejectSuccess,
|
||||
}: UsePendingCommentModerationActionsOptions): PendingCommentModerationActions => {
|
||||
const { t } = useTranslation();
|
||||
const effectiveCommunityAddress = enabled ? communityAddress : undefined;
|
||||
|
||||
const {
|
||||
publishCommentModeration: approve,
|
||||
state: approveState,
|
||||
error: approveError,
|
||||
} = usePublishCommentModeration({
|
||||
commentCid,
|
||||
communityAddress: effectiveCommunityAddress,
|
||||
commentModeration: approvePendingCommentModeration,
|
||||
onChallenge: async (...args: any) => {
|
||||
useChallengesStore.getState().addChallenge([...args, comment]);
|
||||
},
|
||||
onChallengeVerification: async (challengeVerification, challengedComment) => {
|
||||
alertChallengeVerificationFailed(challengeVerification, challengedComment);
|
||||
},
|
||||
onError: (error: Error & { details?: unknown }) => {
|
||||
console.error('Approve failed:', error, error.details);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
publishCommentModeration: reject,
|
||||
state: rejectState,
|
||||
error: rejectError,
|
||||
} = usePublishCommentModeration({
|
||||
commentCid,
|
||||
communityAddress: effectiveCommunityAddress,
|
||||
commentModeration: rejectPendingCommentModeration,
|
||||
onChallenge: async (...args: any) => {
|
||||
useChallengesStore.getState().addChallenge([...args, comment]);
|
||||
},
|
||||
onChallengeVerification: async (challengeVerification, challengedComment) => {
|
||||
alertChallengeVerificationFailed(challengeVerification, challengedComment);
|
||||
},
|
||||
onError: (error: Error & { details?: unknown }) => {
|
||||
console.error('Reject failed:', error, error.details);
|
||||
},
|
||||
});
|
||||
|
||||
const [initiatedAction, setInitiatedAction] = useState<'approve' | 'reject' | null>(null);
|
||||
const pendingActionRef = useRef<'approve' | 'reject' | null>(null);
|
||||
|
||||
const handleApprove = useCallback(async () => {
|
||||
if (pendingActionRef.current) return;
|
||||
if (!window.confirm(t('double_confirm'))) {
|
||||
return;
|
||||
}
|
||||
pendingActionRef.current = 'approve';
|
||||
setInitiatedAction('approve');
|
||||
try {
|
||||
await approve();
|
||||
await onApproveSuccess?.();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
pendingActionRef.current = null;
|
||||
}
|
||||
}, [approve, onApproveSuccess, t]);
|
||||
|
||||
const handleReject = useCallback(async () => {
|
||||
if (pendingActionRef.current) return;
|
||||
if (!window.confirm(t('double_confirm'))) {
|
||||
return;
|
||||
}
|
||||
pendingActionRef.current = 'reject';
|
||||
setInitiatedAction('reject');
|
||||
try {
|
||||
await reject();
|
||||
await onRejectSuccess?.();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
pendingActionRef.current = null;
|
||||
}
|
||||
}, [reject, onRejectSuccess, t]);
|
||||
|
||||
const isApproving = initiatedAction === 'approve' && approveState !== 'succeeded' && approveState !== 'failed';
|
||||
const isRejecting = initiatedAction === 'reject' && rejectState !== 'succeeded' && rejectState !== 'failed';
|
||||
const isPublishing = isApproving || isRejecting;
|
||||
|
||||
const approveSucceeded = initiatedAction === 'approve' && approveState === 'succeeded';
|
||||
const rejectSucceeded = initiatedAction === 'reject' && rejectState === 'succeeded';
|
||||
const approveFailed = initiatedAction === 'approve' && approveState === 'failed';
|
||||
const rejectFailed = initiatedAction === 'reject' && rejectState === 'failed';
|
||||
|
||||
const status: PendingModerationStatus = approveSucceeded ? 'approved' : rejectSucceeded ? 'rejected' : approveFailed || rejectFailed ? 'failed' : null;
|
||||
const error = approveFailed ? approveError : rejectFailed ? rejectError : undefined;
|
||||
const errorMessage = formatErrorForDisplay(error);
|
||||
|
||||
return { handleApprove, handleReject, isPublishing, status, error, errorMessage };
|
||||
};
|
||||
|
||||
export default usePendingCommentModerationActions;
|
||||
Reference in New Issue
Block a user