mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-05 06:13:11 +00:00
feat: unmoderate content
This commit is contained in:
parent
6d76962ed9
commit
c6e9997c62
@ -25,6 +25,7 @@ interface CommentCardProps {
|
||||
cellId?: string;
|
||||
canModerate: boolean;
|
||||
onModerateComment: (commentId: string) => void;
|
||||
onUnmoderateComment?: (commentId: string) => void;
|
||||
onModerateUser: (userAddress: string) => void;
|
||||
}
|
||||
|
||||
@ -48,6 +49,7 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
||||
cellId,
|
||||
canModerate,
|
||||
onModerateComment,
|
||||
onUnmoderateComment,
|
||||
onModerateUser,
|
||||
}) => {
|
||||
const { voteComment, isVoting } = useForumActions();
|
||||
@ -155,6 +157,23 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{canModerate && comment.moderated && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 px-2 text-cyber-neutral hover:text-green-500"
|
||||
onClick={() => onUnmoderateComment?.(comment.id)}
|
||||
>
|
||||
Unmoderate
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Unmoderate comment</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{cellId && canModerate && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@ -39,6 +39,7 @@ const PostDetail = () => {
|
||||
createComment,
|
||||
votePost,
|
||||
moderateComment,
|
||||
unmoderateComment,
|
||||
moderateUser,
|
||||
isCreatingComment,
|
||||
isVoting,
|
||||
@ -126,6 +127,13 @@ const PostDetail = () => {
|
||||
await moderateComment(cell.id, commentId, reason);
|
||||
};
|
||||
|
||||
const handleUnmoderateComment = async (commentId: string) => {
|
||||
const reason =
|
||||
window.prompt('Optional note for unmoderation?') || undefined;
|
||||
if (!cell) return;
|
||||
await unmoderateComment(cell.id, commentId, reason);
|
||||
};
|
||||
|
||||
const handleModerateUser = async (userAddress: string) => {
|
||||
const reason =
|
||||
window.prompt('Reason for moderating this user? (optional)') || undefined;
|
||||
@ -319,6 +327,7 @@ const PostDetail = () => {
|
||||
cellId={cell?.id}
|
||||
canModerate={canModerate(cell?.id || '')}
|
||||
onModerateComment={handleModerateComment}
|
||||
onUnmoderateComment={handleUnmoderateComment}
|
||||
onModerateUser={handleModerateUser}
|
||||
/>
|
||||
))
|
||||
|
||||
@ -45,6 +45,7 @@ const PostList = () => {
|
||||
createPost,
|
||||
votePost,
|
||||
moderatePost,
|
||||
unmoderatePost,
|
||||
moderateUser,
|
||||
refreshData,
|
||||
isCreatingPost,
|
||||
@ -143,6 +144,13 @@ const PostList = () => {
|
||||
await moderatePost(cell.id, postId, reason);
|
||||
};
|
||||
|
||||
const handleUnmoderate = async (postId: string) => {
|
||||
const reason =
|
||||
window.prompt('Optional note for unmoderation?') || undefined;
|
||||
if (!cell) return;
|
||||
await unmoderatePost(cell.id, postId, reason);
|
||||
};
|
||||
|
||||
const handleModerateUser = async (userAddress: string) => {
|
||||
const reason =
|
||||
window.prompt('Reason for moderating this user? (optional)') || undefined;
|
||||
@ -352,10 +360,22 @@ const PostList = () => {
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{post.moderated && (
|
||||
<span className="ml-2 text-xs text-red-500">
|
||||
[Moderated]
|
||||
</span>
|
||||
{canModerate(cell.id) && post.moderated && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 px-2 text-cyber-neutral hover:text-green-500"
|
||||
onClick={() => handleUnmoderate(post.id)}
|
||||
>
|
||||
Unmoderate
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Unmoderate post</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
|
||||
|
||||
|
||||
interface AuthContextType {
|
||||
currentUser: User | null;
|
||||
isAuthenticating: boolean;
|
||||
|
||||
@ -70,18 +70,36 @@ interface ForumContextType {
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => Promise<boolean>;
|
||||
unmoderatePost: (
|
||||
cellId: string,
|
||||
postId: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => Promise<boolean>;
|
||||
moderateComment: (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => Promise<boolean>;
|
||||
unmoderateComment: (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => Promise<boolean>;
|
||||
moderateUser: (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => Promise<boolean>;
|
||||
unmoderateUser: (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const ForumContext = createContext<ForumContextType | undefined>(undefined);
|
||||
@ -563,6 +581,39 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnmoderatePost = async (
|
||||
cellId: string,
|
||||
postId: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => {
|
||||
toast({
|
||||
title: 'Unmoderating Post',
|
||||
description: 'Sending unmoderation message to the network...',
|
||||
});
|
||||
|
||||
const result = await forumActions.unmoderatePost(
|
||||
{ cellId, postId, reason, currentUser, isAuthenticated, cellOwner },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Post Unmoderated',
|
||||
description: 'The post is now visible again.',
|
||||
});
|
||||
return result.data || false;
|
||||
} else {
|
||||
toast({
|
||||
title: 'Unmoderation Failed',
|
||||
description:
|
||||
result.error || 'Failed to unmoderate post. Please try again.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleModerateComment = async (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
@ -596,6 +647,39 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnmoderateComment = async (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => {
|
||||
toast({
|
||||
title: 'Unmoderating Comment',
|
||||
description: 'Sending unmoderation message to the network...',
|
||||
});
|
||||
|
||||
const result = await forumActions.unmoderateComment(
|
||||
{ cellId, commentId, reason, currentUser, isAuthenticated, cellOwner },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Comment Unmoderated',
|
||||
description: 'The comment is now visible again.',
|
||||
});
|
||||
return result.data || false;
|
||||
} else {
|
||||
toast({
|
||||
title: 'Unmoderation Failed',
|
||||
description:
|
||||
result.error || 'Failed to unmoderate comment. Please try again.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleModerateUser = async (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
@ -624,6 +708,34 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnmoderateUser = async (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => {
|
||||
const result = await forumActions.unmoderateUser(
|
||||
{ cellId, userAddress, reason, currentUser, isAuthenticated, cellOwner },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'User Unmoderated',
|
||||
description: `User ${userAddress} has been unmoderated in this cell.`,
|
||||
});
|
||||
return result.data || false;
|
||||
} else {
|
||||
toast({
|
||||
title: 'Unmoderation Failed',
|
||||
description:
|
||||
result.error || 'Failed to unmoderate user. Please try again.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ForumContext.Provider
|
||||
value={{
|
||||
@ -652,8 +764,11 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
createCell: handleCreateCell,
|
||||
refreshData: handleRefreshData,
|
||||
moderatePost: handleModeratePost,
|
||||
unmoderatePost: handleUnmoderatePost,
|
||||
moderateComment: handleModerateComment,
|
||||
unmoderateComment: handleUnmoderateComment,
|
||||
moderateUser: handleModerateUser,
|
||||
unmoderateUser: handleUnmoderateUser,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -33,6 +33,11 @@ export interface ForumActions extends ForumActionStates {
|
||||
postId: string,
|
||||
reason?: string
|
||||
) => Promise<boolean>;
|
||||
unmoderatePost: (
|
||||
cellId: string,
|
||||
postId: string,
|
||||
reason?: string
|
||||
) => Promise<boolean>;
|
||||
|
||||
// Comment actions
|
||||
createComment: (postId: string, content: string) => Promise<Comment | null>;
|
||||
@ -42,6 +47,11 @@ export interface ForumActions extends ForumActionStates {
|
||||
commentId: string,
|
||||
reason?: string
|
||||
) => Promise<boolean>;
|
||||
unmoderateComment: (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
reason?: string
|
||||
) => Promise<boolean>;
|
||||
|
||||
// User moderation
|
||||
moderateUser: (
|
||||
@ -49,6 +59,11 @@ export interface ForumActions extends ForumActionStates {
|
||||
userAddress: string,
|
||||
reason?: string
|
||||
) => Promise<boolean>;
|
||||
unmoderateUser: (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
reason?: string
|
||||
) => Promise<boolean>;
|
||||
|
||||
// Data refresh
|
||||
refreshData: () => Promise<void>;
|
||||
@ -65,8 +80,11 @@ export function useForumActions(): ForumActions {
|
||||
votePost: baseVotePost,
|
||||
voteComment: baseVoteComment,
|
||||
moderatePost: baseModeratePost,
|
||||
unmoderatePost: baseUnmoderatePost,
|
||||
moderateComment: baseModerateComment,
|
||||
unmoderateComment: baseUnmoderateComment,
|
||||
moderateUser: baseModerateUser,
|
||||
unmoderateUser: baseUnmoderateUser,
|
||||
refreshData: baseRefreshData,
|
||||
isPostingCell,
|
||||
isPostingPost,
|
||||
@ -328,6 +346,54 @@ export function useForumActions(): ForumActions {
|
||||
[permissions, currentUser, getCellById, baseModeratePost, toast]
|
||||
);
|
||||
|
||||
// Post unmoderation
|
||||
const unmoderatePost = useCallback(
|
||||
async (
|
||||
cellId: string,
|
||||
postId: string,
|
||||
reason?: string
|
||||
): Promise<boolean> => {
|
||||
const cell = getCellById(cellId);
|
||||
const canModerate =
|
||||
permissions.canModerate(cellId) &&
|
||||
cell &&
|
||||
currentUser?.address === cell.author;
|
||||
|
||||
if (!canModerate) {
|
||||
toast({
|
||||
title: 'Permission Denied',
|
||||
description: 'You must be the cell owner to unmoderate content.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await baseUnmoderatePost(
|
||||
cellId,
|
||||
postId,
|
||||
reason,
|
||||
cell.author
|
||||
);
|
||||
if (result) {
|
||||
toast({
|
||||
title: 'Post Unmoderated',
|
||||
description: 'The post is now visible again.',
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Unmoderation Failed',
|
||||
description: 'Failed to unmoderate post. Please try again.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[permissions, currentUser, getCellById, baseUnmoderatePost, toast]
|
||||
);
|
||||
|
||||
// Comment moderation
|
||||
const moderateComment = useCallback(
|
||||
async (
|
||||
@ -376,6 +442,54 @@ export function useForumActions(): ForumActions {
|
||||
[permissions, currentUser, getCellById, baseModerateComment, toast]
|
||||
);
|
||||
|
||||
// Comment unmoderation
|
||||
const unmoderateComment = useCallback(
|
||||
async (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
reason?: string
|
||||
): Promise<boolean> => {
|
||||
const cell = getCellById(cellId);
|
||||
const canModerate =
|
||||
permissions.canModerate(cellId) &&
|
||||
cell &&
|
||||
currentUser?.address === cell.author;
|
||||
|
||||
if (!canModerate) {
|
||||
toast({
|
||||
title: 'Permission Denied',
|
||||
description: 'You must be the cell owner to unmoderate content.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await baseUnmoderateComment(
|
||||
cellId,
|
||||
commentId,
|
||||
reason,
|
||||
cell.author
|
||||
);
|
||||
if (result) {
|
||||
toast({
|
||||
title: 'Comment Unmoderated',
|
||||
description: 'The comment is now visible again.',
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Unmoderation Failed',
|
||||
description: 'Failed to unmoderate comment. Please try again.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[permissions, currentUser, getCellById, baseUnmoderateComment, toast]
|
||||
);
|
||||
|
||||
// User moderation
|
||||
const moderateUser = useCallback(
|
||||
async (
|
||||
@ -433,6 +547,63 @@ export function useForumActions(): ForumActions {
|
||||
[permissions, currentUser, getCellById, baseModerateUser, toast]
|
||||
);
|
||||
|
||||
// User unmoderation
|
||||
const unmoderateUser = useCallback(
|
||||
async (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
reason?: string
|
||||
): Promise<boolean> => {
|
||||
const cell = getCellById(cellId);
|
||||
const canModerate =
|
||||
permissions.canModerate(cellId) &&
|
||||
cell &&
|
||||
currentUser?.address === cell.author;
|
||||
|
||||
if (!canModerate) {
|
||||
toast({
|
||||
title: 'Permission Denied',
|
||||
description: 'You must be the cell owner to unmoderate users.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userAddress === currentUser?.address) {
|
||||
toast({
|
||||
title: 'Invalid Action',
|
||||
description: 'You cannot unmoderate yourself.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await baseUnmoderateUser(
|
||||
cellId,
|
||||
userAddress,
|
||||
reason,
|
||||
cell.author
|
||||
);
|
||||
if (result) {
|
||||
toast({
|
||||
title: 'User Unmoderated',
|
||||
description: 'The user is now unmoderated in this cell.',
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Unmoderation Failed',
|
||||
description: 'Failed to unmoderate user. Please try again.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[permissions, currentUser, getCellById, baseUnmoderateUser, toast]
|
||||
);
|
||||
|
||||
// Data refresh
|
||||
const refreshData = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
@ -465,8 +636,11 @@ export function useForumActions(): ForumActions {
|
||||
votePost,
|
||||
voteComment,
|
||||
moderatePost,
|
||||
unmoderatePost,
|
||||
moderateComment,
|
||||
unmoderateComment,
|
||||
moderateUser,
|
||||
unmoderateUser,
|
||||
refreshData,
|
||||
};
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
CellMessage,
|
||||
CommentMessage,
|
||||
PostMessage,
|
||||
EModerationAction,
|
||||
} from '@/types/waku';
|
||||
import { Cell, Comment, Post } from '@/types/forum';
|
||||
import { EVerificationStatus, User } from '@/types/identity';
|
||||
@ -380,6 +381,7 @@ export class ForumActions {
|
||||
targetType: 'post',
|
||||
targetId: postId,
|
||||
reason,
|
||||
action: EModerationAction.MODERATE,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser!.address,
|
||||
};
|
||||
@ -453,6 +455,7 @@ export class ForumActions {
|
||||
targetType: 'comment',
|
||||
targetId: commentId,
|
||||
reason,
|
||||
action: EModerationAction.MODERATE,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser!.address,
|
||||
};
|
||||
@ -526,6 +529,7 @@ export class ForumActions {
|
||||
targetType: 'user',
|
||||
targetId: userAddress,
|
||||
reason,
|
||||
action: EModerationAction.MODERATE,
|
||||
author: currentUser!.address,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
@ -563,6 +567,222 @@ export class ForumActions {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async unmoderatePost(
|
||||
params: PostModerationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const { cellId, postId, reason, currentUser, isAuthenticated, cellOwner } =
|
||||
params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to unmoderate posts.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can unmoderate posts.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'post',
|
||||
targetId: postId,
|
||||
reason,
|
||||
action: EModerationAction.UNMODERATE,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser!.address,
|
||||
};
|
||||
|
||||
const signed = await this.delegationManager.signMessage(unsignedMod);
|
||||
if (!signed) {
|
||||
const status = await this.delegationManager.getStatus(
|
||||
currentUser!.address,
|
||||
currentUser!.walletType
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: status.isValid
|
||||
? 'Key delegation required. Please delegate a signing key from your profile menu.'
|
||||
: 'Key delegation expired. Please re-delegate your key through the profile menu.',
|
||||
};
|
||||
}
|
||||
|
||||
await localDatabase.updateCache(signed);
|
||||
localDatabase.markPending(signed.id);
|
||||
localDatabase.setSyncing(true);
|
||||
updateStateFromCache();
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
} catch (error) {
|
||||
console.error('Error unmoderating post:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to unmoderate post. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async unmoderateComment(
|
||||
params: CommentModerationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const {
|
||||
cellId,
|
||||
commentId,
|
||||
reason,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
} = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to unmoderate comments.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can unmoderate comments.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'comment',
|
||||
targetId: commentId,
|
||||
reason,
|
||||
action: EModerationAction.UNMODERATE,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser!.address,
|
||||
};
|
||||
|
||||
const signed = await this.delegationManager.signMessage(unsignedMod);
|
||||
if (!signed) {
|
||||
const status = await this.delegationManager.getStatus(
|
||||
currentUser!.address,
|
||||
currentUser!.walletType
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: status.isValid
|
||||
? 'Key delegation required. Please delegate a signing key from your profile menu.'
|
||||
: 'Key delegation expired. Please re-delegate your key through the profile menu.',
|
||||
};
|
||||
}
|
||||
|
||||
await localDatabase.updateCache(signed);
|
||||
localDatabase.markPending(signed.id);
|
||||
localDatabase.setSyncing(true);
|
||||
updateStateFromCache();
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
} catch (error) {
|
||||
console.error('Error unmoderating comment:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to unmoderate comment. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async unmoderateUser(
|
||||
params: UserModerationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const {
|
||||
cellId,
|
||||
userAddress,
|
||||
reason,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
} = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to unmoderate users.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can unmoderate users.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'user',
|
||||
targetId: userAddress,
|
||||
reason,
|
||||
action: EModerationAction.UNMODERATE,
|
||||
author: currentUser!.address,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
const signed = await this.delegationManager.signMessage(unsignedMod);
|
||||
if (!signed) {
|
||||
const status = await this.delegationManager.getStatus(
|
||||
currentUser!.address,
|
||||
currentUser!.walletType
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: status.isValid
|
||||
? 'Key delegation required. Please delegate a signing key from your profile menu.'
|
||||
: 'Key delegation expired. Please re-delegate your key through the profile menu.',
|
||||
};
|
||||
}
|
||||
|
||||
await localDatabase.updateCache(signed);
|
||||
localDatabase.markPending(signed.id);
|
||||
localDatabase.setSyncing(true);
|
||||
updateStateFromCache();
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
} catch (error) {
|
||||
console.error('Error unmoderating user:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to unmoderate user. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Base interface for all actions that require user authentication
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
CommentMessage,
|
||||
PostMessage,
|
||||
VoteMessage,
|
||||
EModerationAction,
|
||||
} from '@/types/waku';
|
||||
import messageManager from '@/lib/waku';
|
||||
import { RelevanceCalculator } from './RelevanceCalculator';
|
||||
@ -79,7 +80,10 @@ export const transformPost = async (
|
||||
);
|
||||
|
||||
const modMsg = messageManager.messageCache.moderations[postMessage.id];
|
||||
const isPostModerated = !!modMsg && modMsg.targetType === 'post';
|
||||
const isPostModerated =
|
||||
!!modMsg &&
|
||||
modMsg.targetType === 'post' &&
|
||||
modMsg.action === EModerationAction.MODERATE;
|
||||
const userModMsg = Object.values(
|
||||
messageManager.messageCache.moderations
|
||||
).find(
|
||||
@ -88,7 +92,8 @@ export const transformPost = async (
|
||||
m.cellId === postMessage.cellId &&
|
||||
m.targetId === postMessage.author
|
||||
);
|
||||
const isUserModerated = !!userModMsg;
|
||||
const isUserModerated =
|
||||
!!userModMsg && userModMsg.action === EModerationAction.MODERATE;
|
||||
|
||||
const transformedPost: Post = {
|
||||
id: postMessage.id,
|
||||
@ -194,7 +199,10 @@ export const transformComment = async (
|
||||
);
|
||||
|
||||
const modMsg = messageManager.messageCache.moderations[commentMessage.id];
|
||||
const isCommentModerated = !!modMsg && modMsg.targetType === 'comment';
|
||||
const isCommentModerated =
|
||||
!!modMsg &&
|
||||
modMsg.targetType === 'comment' &&
|
||||
modMsg.action === EModerationAction.MODERATE;
|
||||
// Find the post to get the correct cell ID
|
||||
const parentPost = Object.values(messageManager.messageCache.posts).find(
|
||||
post => post.id === commentMessage.postId
|
||||
@ -207,7 +215,8 @@ export const transformComment = async (
|
||||
m.cellId === parentPost?.cellId &&
|
||||
m.targetId === commentMessage.author
|
||||
);
|
||||
const isUserModerated = !!userModMsg;
|
||||
const isUserModerated =
|
||||
!!userModMsg && userModMsg.action === EModerationAction.MODERATE;
|
||||
|
||||
const transformedComment: Comment = {
|
||||
id: commentMessage.id,
|
||||
|
||||
@ -13,6 +13,14 @@ export enum MessageType {
|
||||
USER_PROFILE_UPDATE = 'user_profile_update',
|
||||
}
|
||||
|
||||
/**
|
||||
* Moderation action types
|
||||
*/
|
||||
export enum EModerationAction {
|
||||
MODERATE = 'moderate',
|
||||
UNMODERATE = 'unmoderate',
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for unsigned messages (before signing)
|
||||
*/
|
||||
@ -67,6 +75,7 @@ export interface UnsignedModerateMessage extends UnsignedBaseMessage {
|
||||
targetType: 'post' | 'comment' | 'user';
|
||||
targetId: string; // postId, commentId, or user address (for user moderation)
|
||||
reason?: string;
|
||||
action: EModerationAction;
|
||||
}
|
||||
|
||||
export interface UnsignedUserProfileUpdateMessage extends UnsignedBaseMessage {
|
||||
@ -110,6 +119,7 @@ export interface ModerateMessage extends BaseMessage {
|
||||
targetType: 'post' | 'comment' | 'user';
|
||||
targetId: string; // postId, commentId, or user address (for user moderation)
|
||||
reason?: string;
|
||||
action: EModerationAction;
|
||||
}
|
||||
|
||||
export interface UserProfileUpdateMessage extends BaseMessage {
|
||||
|
||||
@ -3,10 +3,7 @@ import tailwindcssAnimate from 'tailwindcss-animate';
|
||||
|
||||
export default {
|
||||
darkMode: ['class'],
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
content: ['./index.html', './src/**/*.{ts,tsx}'],
|
||||
prefix: '',
|
||||
theme: {
|
||||
container: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user