@@ -254,6 +262,11 @@ const PostDetail = () => {
Moderate
)}
+ {isCellAdmin && comment.authorAddress !== cell.signature && (
+
+ )}
{comment.moderated && (
)}
diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx
index 9583300..8a671cd 100644
--- a/src/components/PostList.tsx
+++ b/src/components/PostList.tsx
@@ -25,7 +25,8 @@ const PostList = () => {
votePost,
isVoting,
posts,
- moderatePost
+ moderatePost,
+ moderateUser
} = useForum();
const { isAuthenticated, currentUser, verificationStatus } = useAuth();
const [newPostTitle, setNewPostTitle] = useState('');
@@ -121,6 +122,12 @@ const PostList = () => {
await moderatePost(cell.id, postId, reason, cell.signature);
};
+ const handleModerateUser = async (userAddress: string) => {
+ const reason = window.prompt('Reason for moderating this user? (optional)') || undefined;
+ if (!cell) return;
+ await moderateUser(cell.id, userAddress, reason, cell.signature);
+ };
+
return (
@@ -259,6 +266,11 @@ const PostList = () => {
Moderate
)}
+ {isCellAdmin && post.authorAddress !== cell.signature && (
+
+ )}
{post.moderated && (
[Moderated]
)}
diff --git a/src/contexts/ForumContext.tsx b/src/contexts/ForumContext.tsx
index 5f46cac..909ef7b 100644
--- a/src/contexts/ForumContext.tsx
+++ b/src/contexts/ForumContext.tsx
@@ -8,7 +8,8 @@ import {
vote,
createCell,
moderatePost,
- moderateComment
+ moderateComment,
+ moderateUser
} from './forum/actions';
import {
setupPeriodicQueries,
@@ -53,6 +54,12 @@ interface ForumContextType {
reason: string | undefined,
cellOwner: string
) => Promise;
+ moderateUser: (
+ cellId: string,
+ userAddress: string,
+ reason: string | undefined,
+ cellOwner: string
+ ) => Promise;
}
const ForumContext = createContext(undefined);
@@ -232,6 +239,63 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
return result;
};
+ const handleModeratePost = async (
+ cellId: string,
+ postId: string,
+ reason: string | undefined,
+ cellOwner: string
+ ) => {
+ return moderatePost(
+ cellId,
+ postId,
+ reason,
+ currentUser,
+ isAuthenticated,
+ cellOwner,
+ toast,
+ updateStateFromCache,
+ messageSigning
+ );
+ };
+
+ const handleModerateComment = async (
+ cellId: string,
+ commentId: string,
+ reason: string | undefined,
+ cellOwner: string
+ ) => {
+ return moderateComment(
+ cellId,
+ commentId,
+ reason,
+ currentUser,
+ isAuthenticated,
+ cellOwner,
+ toast,
+ updateStateFromCache,
+ messageSigning
+ );
+ };
+
+ const handleModerateUser = async (
+ cellId: string,
+ userAddress: string,
+ reason: string | undefined,
+ cellOwner: string
+ ) => {
+ return moderateUser(
+ cellId,
+ userAddress,
+ reason,
+ currentUser,
+ isAuthenticated,
+ cellOwner,
+ toast,
+ updateStateFromCache,
+ messageSigning
+ );
+ };
+
return (
{children}
diff --git a/src/contexts/forum/actions.ts b/src/contexts/forum/actions.ts
index 0923b18..65ab831 100644
--- a/src/contexts/forum/actions.ts
+++ b/src/contexts/forum/actions.ts
@@ -468,4 +468,55 @@ export const moderateComment = async (
});
return false;
}
+};
+
+export const moderateUser = async (
+ cellId: string,
+ userAddress: string,
+ reason: string | undefined,
+ currentUser: User | null,
+ isAuthenticated: boolean,
+ cellOwner: string,
+ toast: ToastFunction,
+ updateStateFromCache: () => void,
+ messageSigning?: MessageSigning
+): Promise => {
+ if (!isAuthenticated || !currentUser) {
+ toast({
+ title: "Authentication Required",
+ description: "You need to verify Ordinal ownership to moderate users.",
+ variant: "destructive",
+ });
+ return false;
+ }
+ if (currentUser.address !== cellOwner) {
+ toast({
+ title: "Not Authorized",
+ description: "Only the cell admin can moderate users.",
+ variant: "destructive",
+ });
+ return false;
+ }
+ const message: ModerateMessage = {
+ type: MessageType.MODERATE,
+ cellId,
+ targetType: 'user',
+ targetId: userAddress,
+ reason,
+ author: currentUser.address,
+ timestamp: Date.now(),
+ signature: '',
+ browserPubKey: currentUser.browserPubKey,
+ };
+ const sent = await signAndSendMessage(message, currentUser, messageSigning!, toast);
+ if (sent) {
+ updateStateFromCache();
+ toast({
+ title: "User Moderated",
+ description: `User ${userAddress} has been moderated in this cell.`,
+ variant: "default",
+ });
+ return true;
+ }
+ return false;
};
\ No newline at end of file
diff --git a/src/contexts/forum/transformers.ts b/src/contexts/forum/transformers.ts
index 584a69f..dbc20d7 100644
--- a/src/contexts/forum/transformers.ts
+++ b/src/contexts/forum/transformers.ts
@@ -47,8 +47,15 @@ export const transformPost = (
const upvotes = filteredVotes.filter(vote => vote.value === 1);
const downvotes = filteredVotes.filter(vote => vote.value === -1);
+ // Check for post moderation
const modMsg = messageManager.messageCache.moderations[postMessage.id];
- const isModerated = !!modMsg && modMsg.targetType === 'post';
+ const isPostModerated = !!modMsg && modMsg.targetType === 'post';
+
+ // Check for user moderation in this cell
+ const userModMsg = Object.values(messageManager.messageCache.moderations).find(
+ m => m.targetType === 'user' && m.cellId === postMessage.cellId && m.targetId === postMessage.author
+ );
+ const isUserModerated = !!userModMsg;
return {
id: postMessage.id,
@@ -61,10 +68,10 @@ export const transformPost = (
downvotes: downvotes,
signature: postMessage.signature,
browserPubKey: postMessage.browserPubKey,
- moderated: isModerated,
- moderatedBy: isModerated ? modMsg.author : undefined,
- moderationReason: isModerated ? modMsg.reason : undefined,
- moderationTimestamp: isModerated ? modMsg.timestamp : undefined,
+ moderated: isPostModerated || isUserModerated,
+ moderatedBy: isPostModerated ? modMsg.author : isUserModerated ? userModMsg!.author : undefined,
+ moderationReason: isPostModerated ? modMsg.reason : isUserModerated ? userModMsg!.reason : undefined,
+ moderationTimestamp: isPostModerated ? modMsg.timestamp : isUserModerated ? userModMsg!.timestamp : undefined,
};
};
@@ -92,9 +99,15 @@ export const transformComment = (
const upvotes = filteredVotes.filter(vote => vote.value === 1);
const downvotes = filteredVotes.filter(vote => vote.value === -1);
- // Check for moderation
+ // Check for comment moderation
const modMsg = messageManager.messageCache.moderations[commentMessage.id];
- const isModerated = !!modMsg && modMsg.targetType === 'comment';
+ const isCommentModerated = !!modMsg && modMsg.targetType === 'comment';
+
+ // Check for user moderation in this cell
+ const userModMsg = Object.values(messageManager.messageCache.moderations).find(
+ m => m.targetType === 'user' && m.cellId === commentMessage.postId.split('-')[0] && m.targetId === commentMessage.author
+ );
+ const isUserModerated = !!userModMsg;
return {
id: commentMessage.id,
@@ -106,10 +119,10 @@ export const transformComment = (
downvotes: downvotes,
signature: commentMessage.signature,
browserPubKey: commentMessage.browserPubKey,
- moderated: isModerated,
- moderatedBy: isModerated ? modMsg.author : undefined,
- moderationReason: isModerated ? modMsg.reason : undefined,
- moderationTimestamp: isModerated ? modMsg.timestamp : undefined,
+ moderated: isCommentModerated || isUserModerated,
+ moderatedBy: isCommentModerated ? modMsg.author : isUserModerated ? userModMsg!.author : undefined,
+ moderationReason: isCommentModerated ? modMsg.reason : isUserModerated ? userModMsg!.reason : undefined,
+ moderationTimestamp: isCommentModerated ? modMsg.timestamp : isUserModerated ? userModMsg!.timestamp : undefined,
};
};
diff --git a/src/lib/waku/types.ts b/src/lib/waku/types.ts
index 1d6e49f..c5db885 100644
--- a/src/lib/waku/types.ts
+++ b/src/lib/waku/types.ts
@@ -68,8 +68,8 @@ export interface VoteMessage extends BaseMessage {
export interface ModerateMessage extends BaseMessage {
type: MessageType.MODERATE;
cellId: string;
- targetType: 'post' | 'comment';
- targetId: string;
+ targetType: 'post' | 'comment' | 'user';
+ targetId: string; // postId, commentId, or user address (for user moderation)
reason?: string;
}