mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-05-01 09:17:55 +00:00
feat(mobile): add Forum screen with categories and thread listing
- Created ForumScreen.tsx with category browsing and thread views - Support for pinned posts and locked threads - Mock data structure ready for Supabase integration - View counts, reply counts, and time-ago formatting - Pull-to-refresh functionality - FAB for creating new threads - Added Forum tab to navigation (9 tabs total)
This commit is contained in:
@@ -9,6 +9,7 @@ import WalletScreen from '../screens/WalletScreen';
|
|||||||
import SwapScreen from '../screens/SwapScreen';
|
import SwapScreen from '../screens/SwapScreen';
|
||||||
import P2PScreen from '../screens/P2PScreen';
|
import P2PScreen from '../screens/P2PScreen';
|
||||||
import EducationScreen from '../screens/EducationScreen';
|
import EducationScreen from '../screens/EducationScreen';
|
||||||
|
import ForumScreen from '../screens/ForumScreen';
|
||||||
import BeCitizenScreen from '../screens/BeCitizenScreen';
|
import BeCitizenScreen from '../screens/BeCitizenScreen';
|
||||||
import ReferralScreen from '../screens/ReferralScreen';
|
import ReferralScreen from '../screens/ReferralScreen';
|
||||||
import ProfileScreen from '../screens/ProfileScreen';
|
import ProfileScreen from '../screens/ProfileScreen';
|
||||||
@@ -19,6 +20,7 @@ export type BottomTabParamList = {
|
|||||||
Swap: undefined;
|
Swap: undefined;
|
||||||
P2P: undefined;
|
P2P: undefined;
|
||||||
Education: undefined;
|
Education: undefined;
|
||||||
|
Forum: undefined;
|
||||||
BeCitizen: undefined;
|
BeCitizen: undefined;
|
||||||
Referral: undefined;
|
Referral: undefined;
|
||||||
Profile: undefined;
|
Profile: undefined;
|
||||||
@@ -112,6 +114,18 @@ const BottomTabNavigator: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Tab.Screen
|
||||||
|
name="Forum"
|
||||||
|
component={ForumScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ color, focused }) => (
|
||||||
|
<Text style={[styles.icon, { color }]}>
|
||||||
|
{focused ? '💬' : '📝'}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="BeCitizen"
|
name="BeCitizen"
|
||||||
component={BeCitizenScreen}
|
component={BeCitizenScreen}
|
||||||
|
|||||||
@@ -0,0 +1,512 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
SafeAreaView,
|
||||||
|
FlatList,
|
||||||
|
TouchableOpacity,
|
||||||
|
ActivityIndicator,
|
||||||
|
RefreshControl,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Card, Badge } from '../components';
|
||||||
|
import { KurdistanColors, AppColors } from '../theme/colors';
|
||||||
|
|
||||||
|
interface ForumThread {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
author: string;
|
||||||
|
category: string;
|
||||||
|
replies_count: number;
|
||||||
|
views_count: number;
|
||||||
|
created_at: string;
|
||||||
|
last_activity: string;
|
||||||
|
is_pinned: boolean;
|
||||||
|
is_locked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForumCategory {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
threads_count: number;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CATEGORIES: ForumCategory[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'General Discussion',
|
||||||
|
description: 'General topics about PezkuwiChain',
|
||||||
|
threads_count: 42,
|
||||||
|
icon: '💬',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Governance',
|
||||||
|
description: 'Discuss proposals and governance',
|
||||||
|
threads_count: 28,
|
||||||
|
icon: '🏛️',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'Technical',
|
||||||
|
description: 'Development and technical discussions',
|
||||||
|
threads_count: 35,
|
||||||
|
icon: '⚙️',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: 'Trading',
|
||||||
|
description: 'Market discussions and trading',
|
||||||
|
threads_count: 18,
|
||||||
|
icon: '📈',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock data - will be replaced with Supabase
|
||||||
|
const MOCK_THREADS: ForumThread[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Welcome to PezkuwiChain Forum!',
|
||||||
|
content: 'Introduce yourself and join the community...',
|
||||||
|
author: '5GrwV...xQjz',
|
||||||
|
category: 'General Discussion',
|
||||||
|
replies_count: 24,
|
||||||
|
views_count: 156,
|
||||||
|
created_at: '2024-01-15T10:00:00Z',
|
||||||
|
last_activity: '2024-01-20T14:30:00Z',
|
||||||
|
is_pinned: true,
|
||||||
|
is_locked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'New Governance Proposal: Treasury Allocation',
|
||||||
|
content: 'Discussion about treasury fund allocation...',
|
||||||
|
author: '5HpG8...kLm2',
|
||||||
|
category: 'Governance',
|
||||||
|
replies_count: 45,
|
||||||
|
views_count: 289,
|
||||||
|
created_at: '2024-01-18T09:15:00Z',
|
||||||
|
last_activity: '2024-01-20T16:45:00Z',
|
||||||
|
is_pinned: false,
|
||||||
|
is_locked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: 'How to stake PEZ tokens?',
|
||||||
|
content: 'Guide for staking PEZ tokens...',
|
||||||
|
author: '5FHne...pQr8',
|
||||||
|
category: 'General Discussion',
|
||||||
|
replies_count: 12,
|
||||||
|
views_count: 98,
|
||||||
|
created_at: '2024-01-19T11:20:00Z',
|
||||||
|
last_activity: '2024-01-20T13:10:00Z',
|
||||||
|
is_pinned: false,
|
||||||
|
is_locked: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type ViewType = 'categories' | 'threads';
|
||||||
|
|
||||||
|
const ForumScreen: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [viewType, setViewType] = useState<ViewType>('categories');
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
|
const [threads, setThreads] = useState<ForumThread[]>(MOCK_THREADS);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
|
const fetchThreads = async (categoryId?: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// TODO: Fetch from Supabase
|
||||||
|
// const { data } = await supabase
|
||||||
|
// .from('forum_threads')
|
||||||
|
// .select('*')
|
||||||
|
// .eq('category_id', categoryId)
|
||||||
|
// .order('is_pinned', { ascending: false })
|
||||||
|
// .order('last_activity', { ascending: false });
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
setThreads(MOCK_THREADS);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch threads:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
setRefreshing(true);
|
||||||
|
fetchThreads(selectedCategory || undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategoryPress = (categoryId: string, categoryName: string) => {
|
||||||
|
setSelectedCategory(categoryId);
|
||||||
|
setViewType('threads');
|
||||||
|
fetchThreads(categoryId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTimeAgo = (dateString: string) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const now = new Date();
|
||||||
|
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||||
|
|
||||||
|
if (seconds < 60) return 'Just now';
|
||||||
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
||||||
|
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
||||||
|
return `${Math.floor(seconds / 86400)}d ago`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCategoryCard = ({ item }: { item: ForumCategory }) => (
|
||||||
|
<TouchableOpacity onPress={() => handleCategoryPress(item.id, item.name)}>
|
||||||
|
<Card style={styles.categoryCard}>
|
||||||
|
<View style={styles.categoryHeader}>
|
||||||
|
<View style={styles.categoryIcon}>
|
||||||
|
<Text style={styles.categoryIconText}>{item.icon}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.categoryInfo}>
|
||||||
|
<Text style={styles.categoryName}>{item.name}</Text>
|
||||||
|
<Text style={styles.categoryDescription} numberOfLines={2}>
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.categoryFooter}>
|
||||||
|
<Text style={styles.categoryStats}>
|
||||||
|
{item.threads_count} threads
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.categoryArrow}>→</Text>
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderThreadCard = ({ item }: { item: ForumThread }) => (
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Card style={styles.threadCard}>
|
||||||
|
{/* Thread Header */}
|
||||||
|
<View style={styles.threadHeader}>
|
||||||
|
{item.is_pinned && (
|
||||||
|
<View style={styles.pinnedBadge}>
|
||||||
|
<Text style={styles.pinnedIcon}>📌</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<Text style={styles.threadTitle} numberOfLines={2}>
|
||||||
|
{item.title}
|
||||||
|
</Text>
|
||||||
|
{item.is_locked && (
|
||||||
|
<Text style={styles.lockedIcon}>🔒</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Thread Meta */}
|
||||||
|
<View style={styles.threadMeta}>
|
||||||
|
<Text style={styles.threadAuthor}>by {item.author}</Text>
|
||||||
|
<Badge text={item.category} variant="outline" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Thread Stats */}
|
||||||
|
<View style={styles.threadStats}>
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Text style={styles.statIcon}>💬</Text>
|
||||||
|
<Text style={styles.statText}>{item.replies_count}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Text style={styles.statIcon}>👁️</Text>
|
||||||
|
<Text style={styles.statText}>{item.views_count}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Text style={styles.statIcon}>🕐</Text>
|
||||||
|
<Text style={styles.statText}>
|
||||||
|
{formatTimeAgo(item.last_activity)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderEmptyState = () => (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Text style={styles.emptyIcon}>💬</Text>
|
||||||
|
<Text style={styles.emptyTitle}>No Threads Yet</Text>
|
||||||
|
<Text style={styles.emptyText}>
|
||||||
|
Be the first to start a discussion in this category
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.title}>
|
||||||
|
{viewType === 'categories' ? 'Forum' : 'Threads'}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.subtitle}>
|
||||||
|
{viewType === 'categories'
|
||||||
|
? 'Join the community discussion'
|
||||||
|
: selectedCategory || 'All threads'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{viewType === 'threads' && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.backButton}
|
||||||
|
onPress={() => setViewType('categories')}
|
||||||
|
>
|
||||||
|
<Text style={styles.backButtonText}>← Back</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
{loading && !refreshing ? (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color={KurdistanColors.kesk} />
|
||||||
|
<Text style={styles.loadingText}>Loading...</Text>
|
||||||
|
</View>
|
||||||
|
) : viewType === 'categories' ? (
|
||||||
|
<FlatList
|
||||||
|
data={CATEGORIES}
|
||||||
|
renderItem={renderCategoryCard}
|
||||||
|
keyExtractor={(item) => item.id}
|
||||||
|
contentContainerStyle={styles.listContent}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={refreshing}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
tintColor={KurdistanColors.kesk}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FlatList
|
||||||
|
data={threads}
|
||||||
|
renderItem={renderThreadCard}
|
||||||
|
keyExtractor={(item) => item.id}
|
||||||
|
contentContainerStyle={styles.listContent}
|
||||||
|
ListEmptyComponent={renderEmptyState}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={refreshing}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
tintColor={KurdistanColors.kesk}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Create Thread FAB */}
|
||||||
|
{viewType === 'threads' && (
|
||||||
|
<TouchableOpacity style={styles.fab}>
|
||||||
|
<Text style={styles.fabIcon}>✏️</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
padding: 16,
|
||||||
|
paddingBottom: 12,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
backButtonText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: KurdistanColors.kesk,
|
||||||
|
},
|
||||||
|
listContent: {
|
||||||
|
padding: 16,
|
||||||
|
paddingTop: 8,
|
||||||
|
},
|
||||||
|
categoryCard: {
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
categoryHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
categoryIcon: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: '#F0F9F4',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
categoryIconText: {
|
||||||
|
fontSize: 24,
|
||||||
|
},
|
||||||
|
categoryInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
categoryName: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
categoryDescription: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#666',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
categoryFooter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 12,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
categoryStats: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
categoryArrow: {
|
||||||
|
fontSize: 20,
|
||||||
|
color: KurdistanColors.kesk,
|
||||||
|
},
|
||||||
|
threadCard: {
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
threadHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
pinnedBadge: {
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
pinnedIcon: {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
threadTitle: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
lineHeight: 22,
|
||||||
|
},
|
||||||
|
lockedIcon: {
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
threadMeta: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
threadAuthor: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
threadStats: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 16,
|
||||||
|
paddingTop: 12,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#F0F0F0',
|
||||||
|
},
|
||||||
|
statItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
},
|
||||||
|
statIcon: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
statText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
marginTop: 12,
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: 60,
|
||||||
|
},
|
||||||
|
emptyIcon: {
|
||||||
|
fontSize: 64,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#666',
|
||||||
|
textAlign: 'center',
|
||||||
|
paddingHorizontal: 32,
|
||||||
|
},
|
||||||
|
fab: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 20,
|
||||||
|
bottom: 20,
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 28,
|
||||||
|
backgroundColor: KurdistanColors.kesk,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.3,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
fabIcon: {
|
||||||
|
fontSize: 24,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ForumScreen;
|
||||||
Reference in New Issue
Block a user