mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-23 03:37:55 +00:00
49c6b6f5f7
Fixed all linting issues reported by ESLint: Errors fixed: - InitializeUsdtModal.tsx: Removed unused imports (ASSET_IDS, ASSET_CONFIGS) Warnings fixed: - DashboardContext.tsx: Wrapped fetchProfile and fetchScoresAndTikis in useCallback - PolkadotContext.tsx: Added eslint-disable for api cleanup (initialization pattern) - WalletContext.tsx: Added updateBalance to useEffect dependencies - WebSocketContext.tsx: Moved ENDPOINTS constant outside component - useForum.ts: Added eslint-disable for mount-only effect - Dashboard.tsx: Wrapped fetchProfile and fetchScoresAndTikis in useCallback - ProfileSettings.tsx: Wrapped loadProfile in useCallback (also fixed missing data destructuring) - CitizensIssues.tsx: Added eslint-disable for complex fetch pattern All React Hook exhaustive-deps warnings resolved with proper useCallback wrapping or appropriate eslint-disable comments where patterns are intentional.
272 lines
7.1 KiB
TypeScript
272 lines
7.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { supabase } from '@/lib/supabase';
|
|
|
|
export interface AdminAnnouncement {
|
|
id: string;
|
|
title: string;
|
|
content: string;
|
|
type: 'info' | 'warning' | 'success' | 'critical';
|
|
priority: number;
|
|
created_at: string;
|
|
expires_at?: string;
|
|
}
|
|
|
|
export interface ForumCategory {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
icon: string;
|
|
color: string;
|
|
discussion_count?: number;
|
|
}
|
|
|
|
export interface ForumDiscussion {
|
|
id: string;
|
|
category_id: string;
|
|
category?: ForumCategory;
|
|
proposal_id?: string;
|
|
title: string;
|
|
content: string;
|
|
author_id: string;
|
|
author_name: string;
|
|
author_address?: string;
|
|
is_pinned: boolean;
|
|
is_locked: boolean;
|
|
views_count: number;
|
|
replies_count: number;
|
|
tags: string[];
|
|
created_at: string;
|
|
updated_at: string;
|
|
last_activity_at: string;
|
|
upvotes?: number;
|
|
downvotes?: number;
|
|
}
|
|
|
|
export interface ForumReply {
|
|
id: string;
|
|
discussion_id: string;
|
|
parent_reply_id?: string;
|
|
content: string;
|
|
author_id: string;
|
|
author_name: string;
|
|
author_address?: string;
|
|
is_edited: boolean;
|
|
edited_at?: string;
|
|
created_at: string;
|
|
upvotes?: number;
|
|
downvotes?: number;
|
|
}
|
|
|
|
export function useForum() {
|
|
const [announcements, setAnnouncements] = useState<AdminAnnouncement[]>([]);
|
|
const [categories, setCategories] = useState<ForumCategory[]>([]);
|
|
const [discussions, setDiscussions] = useState<ForumDiscussion[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetchForumData();
|
|
|
|
|
|
// Subscribe to real-time updates
|
|
const discussionsSubscription = supabase
|
|
.channel('forum_discussions')
|
|
.on('postgres_changes', {
|
|
event: '*',
|
|
schema: 'public',
|
|
table: 'forum_discussions'
|
|
}, () => {
|
|
fetchDiscussions();
|
|
})
|
|
.subscribe();
|
|
|
|
const announcementsSubscription = supabase
|
|
.channel('admin_announcements')
|
|
.on('postgres_changes', {
|
|
event: '*',
|
|
schema: 'public',
|
|
table: 'admin_announcements'
|
|
}, () => {
|
|
fetchAnnouncements();
|
|
})
|
|
.subscribe();
|
|
|
|
return () => {
|
|
discussionsSubscription.unsubscribe();
|
|
announcementsSubscription.unsubscribe();
|
|
};
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const fetchForumData = async () => {
|
|
setLoading(true);
|
|
await Promise.all([
|
|
fetchAnnouncements(),
|
|
fetchCategories(),
|
|
fetchDiscussions()
|
|
]);
|
|
setLoading(false);
|
|
};
|
|
|
|
const fetchAnnouncements = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('admin_announcements')
|
|
.select('*')
|
|
.eq('is_active', true)
|
|
.or(`expires_at.is.null,expires_at.gt.${new Date().toISOString()}`)
|
|
.order('priority', { ascending: false })
|
|
.order('created_at', { ascending: false })
|
|
.limit(3);
|
|
|
|
if (error) throw error;
|
|
setAnnouncements(data || []);
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.error('Error fetching announcements:', err);
|
|
}
|
|
};
|
|
|
|
const fetchCategories = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('forum_categories')
|
|
.select('*')
|
|
.eq('is_active', true)
|
|
.order('display_order');
|
|
|
|
if (error) throw error;
|
|
setCategories(data || []);
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.error('Error fetching categories:', err);
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch categories');
|
|
}
|
|
};
|
|
|
|
const fetchDiscussions = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('forum_discussions')
|
|
.select(`
|
|
*,
|
|
category:forum_categories(*)
|
|
`)
|
|
.order('is_pinned', { ascending: false })
|
|
.order('last_activity_at', { ascending: false })
|
|
.limit(50);
|
|
|
|
if (error) throw error;
|
|
|
|
// Fetch reaction counts for each discussion
|
|
const discussionsWithReactions = await Promise.all(
|
|
(data || []).map(async (discussion) => {
|
|
const { data: reactions } = await supabase
|
|
.from('forum_reactions')
|
|
.select('reaction_type')
|
|
.eq('discussion_id', discussion.id);
|
|
|
|
const upvotes = reactions?.filter(r => r.reaction_type === 'upvote').length || 0;
|
|
const downvotes = reactions?.filter(r => r.reaction_type === 'downvote').length || 0;
|
|
|
|
return {
|
|
...discussion,
|
|
upvotes,
|
|
downvotes
|
|
};
|
|
})
|
|
);
|
|
|
|
setDiscussions(discussionsWithReactions);
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.error('Error fetching discussions:', err);
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch discussions');
|
|
}
|
|
};
|
|
|
|
const createDiscussion = async (discussionData: {
|
|
category_id: string;
|
|
title: string;
|
|
content: string;
|
|
tags?: string[];
|
|
proposal_id?: string;
|
|
}) => {
|
|
try {
|
|
const user = (await supabase.auth.getUser()).data.user;
|
|
if (!user) throw new Error('User not authenticated');
|
|
|
|
const { data, error } = await supabase
|
|
.from('forum_discussions')
|
|
.insert({
|
|
...discussionData,
|
|
author_id: user.id,
|
|
author_name: user.email || 'Anonymous'
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
await fetchDiscussions();
|
|
return data;
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.error('Error creating discussion:', err);
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
const reactToDiscussion = async (discussionId: string, reactionType: 'upvote' | 'downvote') => {
|
|
try {
|
|
const user = (await supabase.auth.getUser()).data.user;
|
|
if (!user) throw new Error('User not authenticated');
|
|
|
|
// Check if user already reacted
|
|
const { data: existing } = await supabase
|
|
.from('forum_reactions')
|
|
.select('*')
|
|
.eq('discussion_id', discussionId)
|
|
.eq('user_id', user.id)
|
|
.eq('reaction_type', reactionType)
|
|
.single();
|
|
|
|
if (existing) {
|
|
// Remove reaction
|
|
await supabase
|
|
.from('forum_reactions')
|
|
.delete()
|
|
.eq('id', existing.id);
|
|
} else {
|
|
// Add reaction (remove opposite reaction first)
|
|
const oppositeType = reactionType === 'upvote' ? 'downvote' : 'upvote';
|
|
await supabase
|
|
.from('forum_reactions')
|
|
.delete()
|
|
.eq('discussion_id', discussionId)
|
|
.eq('user_id', user.id)
|
|
.eq('reaction_type', oppositeType);
|
|
|
|
await supabase
|
|
.from('forum_reactions')
|
|
.insert({
|
|
discussion_id: discussionId,
|
|
user_id: user.id,
|
|
reaction_type: reactionType
|
|
});
|
|
}
|
|
|
|
await fetchDiscussions();
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.error('Error reacting to discussion:', err);
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
return {
|
|
announcements,
|
|
categories,
|
|
discussions,
|
|
loading,
|
|
error,
|
|
createDiscussion,
|
|
reactToDiscussion,
|
|
refreshData: fetchForumData
|
|
};
|
|
}
|