From 839d68c20fb66f35a1d4351601c688851832248d Mon Sep 17 00:00:00 2001 From: MrPiglr Date: Thu, 5 Feb 2026 15:17:56 +0000 Subject: [PATCH] new file: src/frontend/contexts/MessagingContext.jsx --- src/frontend/App.jsx | 81 +- src/frontend/Demo.css | 3 +- src/frontend/contexts/MessagingContext.jsx | 159 + src/frontend/index.css | 30 +- src/frontend/mockup/ActivityStatus.jsx | 186 + src/frontend/mockup/AppDirectory.jsx | 220 + src/frontend/mockup/AuditLog.jsx | 170 + src/frontend/mockup/AutoModSettings.jsx | 259 + src/frontend/mockup/BanList.jsx | 127 + src/frontend/mockup/CategoryEditor.jsx | 155 + src/frontend/mockup/ChannelPermissions.jsx | 182 + src/frontend/mockup/ChannelSettings.jsx | 271 + src/frontend/mockup/ChannelSidebar.jsx | 157 +- src/frontend/mockup/ChatArea.jsx | 302 +- src/frontend/mockup/ContextMenu.jsx | 133 + src/frontend/mockup/CreateChannelModal.jsx | 115 + src/frontend/mockup/DMsView.jsx | 242 + src/frontend/mockup/EmojiPicker.jsx | 68 + src/frontend/mockup/EmojiUpload.jsx | 185 + src/frontend/mockup/EventsPanel.jsx | 392 + src/frontend/mockup/FileUpload.jsx | 180 + src/frontend/mockup/ForumChannel.jsx | 293 + src/frontend/mockup/GifPicker.jsx | 107 + src/frontend/mockup/InviteManager.jsx | 185 + src/frontend/mockup/InviteModal.jsx | 146 + src/frontend/mockup/MainLayout.jsx | 204 +- src/frontend/mockup/MarkdownText.jsx | 139 + src/frontend/mockup/MemberListPanel.jsx | 155 + src/frontend/mockup/MemberSidebar.jsx | 89 +- src/frontend/mockup/MessageActions.jsx | 84 + src/frontend/mockup/ModerationModal.jsx | 273 + src/frontend/mockup/NitroPanel.jsx | 233 + src/frontend/mockup/NotificationSettings.jsx | 158 + src/frontend/mockup/NotificationsPanel.jsx | 189 + src/frontend/mockup/PinnedMessagesPanel.jsx | 92 + src/frontend/mockup/PollCreator.jsx | 234 + src/frontend/mockup/QuickSwitcher.jsx | 216 + src/frontend/mockup/ReplyPreview.jsx | 54 + src/frontend/mockup/RoleEditor.jsx | 272 + src/frontend/mockup/SearchPanel.jsx | 176 + src/frontend/mockup/ServerBanner.jsx | 133 + src/frontend/mockup/ServerDiscovery.jsx | 199 + src/frontend/mockup/ServerInsights.jsx | 256 + src/frontend/mockup/ServerList.jsx | 87 +- src/frontend/mockup/ServerSettingsModal.jsx | 159 + src/frontend/mockup/ServerTemplate.jsx | 247 + src/frontend/mockup/SlowModeSettings.jsx | 186 + src/frontend/mockup/Soundboard.jsx | 129 + src/frontend/mockup/StageChannel.jsx | 152 + src/frontend/mockup/StickerPicker.jsx | 142 + src/frontend/mockup/StreamOverlay.jsx | 238 + src/frontend/mockup/ThreadPanel.jsx | 114 + src/frontend/mockup/TypingIndicator.jsx | 28 + src/frontend/mockup/UserProfileCard.jsx | 165 + src/frontend/mockup/UserSettingsBar.jsx | 105 + src/frontend/mockup/UserSettingsModal.jsx | 375 + src/frontend/mockup/VideoCall.jsx | 206 + src/frontend/mockup/VoiceChannel.jsx | 73 + src/frontend/mockup/WelcomeScreen.jsx | 193 + src/frontend/mockup/contexts/AuthContext.jsx | 84 + src/frontend/mockup/index.jsx | 81 +- src/frontend/mockup/mockup.css | 10311 +++++++++++++++++ src/frontend/mockup/pages/AboutPage.jsx | 163 + src/frontend/mockup/pages/FeaturesPage.jsx | 159 + src/frontend/mockup/pages/LandingPage.jsx | 202 + src/frontend/mockup/pages/LoginPage.jsx | 107 + src/frontend/mockup/pages/RegisterPage.jsx | 183 + src/frontend/package-lock.json | 786 +- src/frontend/package.json | 5 + src/frontend/postcss.config.js | 6 + src/frontend/services/messaging.js | 159 + src/frontend/tailwind.config.js | 22 + 72 files changed, 22125 insertions(+), 246 deletions(-) create mode 100644 src/frontend/contexts/MessagingContext.jsx create mode 100644 src/frontend/mockup/ActivityStatus.jsx create mode 100644 src/frontend/mockup/AppDirectory.jsx create mode 100644 src/frontend/mockup/AuditLog.jsx create mode 100644 src/frontend/mockup/AutoModSettings.jsx create mode 100644 src/frontend/mockup/BanList.jsx create mode 100644 src/frontend/mockup/CategoryEditor.jsx create mode 100644 src/frontend/mockup/ChannelPermissions.jsx create mode 100644 src/frontend/mockup/ChannelSettings.jsx create mode 100644 src/frontend/mockup/ContextMenu.jsx create mode 100644 src/frontend/mockup/CreateChannelModal.jsx create mode 100644 src/frontend/mockup/DMsView.jsx create mode 100644 src/frontend/mockup/EmojiPicker.jsx create mode 100644 src/frontend/mockup/EmojiUpload.jsx create mode 100644 src/frontend/mockup/EventsPanel.jsx create mode 100644 src/frontend/mockup/FileUpload.jsx create mode 100644 src/frontend/mockup/ForumChannel.jsx create mode 100644 src/frontend/mockup/GifPicker.jsx create mode 100644 src/frontend/mockup/InviteManager.jsx create mode 100644 src/frontend/mockup/InviteModal.jsx create mode 100644 src/frontend/mockup/MarkdownText.jsx create mode 100644 src/frontend/mockup/MemberListPanel.jsx create mode 100644 src/frontend/mockup/MessageActions.jsx create mode 100644 src/frontend/mockup/ModerationModal.jsx create mode 100644 src/frontend/mockup/NitroPanel.jsx create mode 100644 src/frontend/mockup/NotificationSettings.jsx create mode 100644 src/frontend/mockup/NotificationsPanel.jsx create mode 100644 src/frontend/mockup/PinnedMessagesPanel.jsx create mode 100644 src/frontend/mockup/PollCreator.jsx create mode 100644 src/frontend/mockup/QuickSwitcher.jsx create mode 100644 src/frontend/mockup/ReplyPreview.jsx create mode 100644 src/frontend/mockup/RoleEditor.jsx create mode 100644 src/frontend/mockup/SearchPanel.jsx create mode 100644 src/frontend/mockup/ServerBanner.jsx create mode 100644 src/frontend/mockup/ServerDiscovery.jsx create mode 100644 src/frontend/mockup/ServerInsights.jsx create mode 100644 src/frontend/mockup/ServerSettingsModal.jsx create mode 100644 src/frontend/mockup/ServerTemplate.jsx create mode 100644 src/frontend/mockup/SlowModeSettings.jsx create mode 100644 src/frontend/mockup/Soundboard.jsx create mode 100644 src/frontend/mockup/StageChannel.jsx create mode 100644 src/frontend/mockup/StickerPicker.jsx create mode 100644 src/frontend/mockup/StreamOverlay.jsx create mode 100644 src/frontend/mockup/ThreadPanel.jsx create mode 100644 src/frontend/mockup/TypingIndicator.jsx create mode 100644 src/frontend/mockup/UserProfileCard.jsx create mode 100644 src/frontend/mockup/UserSettingsBar.jsx create mode 100644 src/frontend/mockup/UserSettingsModal.jsx create mode 100644 src/frontend/mockup/VideoCall.jsx create mode 100644 src/frontend/mockup/VoiceChannel.jsx create mode 100644 src/frontend/mockup/WelcomeScreen.jsx create mode 100644 src/frontend/mockup/contexts/AuthContext.jsx create mode 100644 src/frontend/mockup/mockup.css create mode 100644 src/frontend/mockup/pages/AboutPage.jsx create mode 100644 src/frontend/mockup/pages/FeaturesPage.jsx create mode 100644 src/frontend/mockup/pages/LandingPage.jsx create mode 100644 src/frontend/mockup/pages/LoginPage.jsx create mode 100644 src/frontend/mockup/pages/RegisterPage.jsx create mode 100644 src/frontend/postcss.config.js create mode 100644 src/frontend/services/messaging.js create mode 100644 src/frontend/tailwind.config.js diff --git a/src/frontend/App.jsx b/src/frontend/App.jsx index 366d5e5..91e2f0f 100644 --- a/src/frontend/App.jsx +++ b/src/frontend/App.jsx @@ -1,84 +1,33 @@ import React from 'react'; import { AuthProvider, useAuth } from './contexts/AuthContext'; -import DomainVerification from './components/DomainVerification'; -import VerifiedDomainBadge from './components/VerifiedDomainBadge'; -import './App.css'; +import { MessagingProvider } from './contexts/MessagingContext'; +import MainLayout from './mockup/MainLayout'; +import './index.css'; /** * Main application component - * Demo of domain verification feature + * AeThex Connect - Discord-style communication platform */ -function App() { +function AppContent() { const { user, loading } = useAuth(); - const [showVerification, setShowVerification] = React.useState(false); if (loading) { - return
Loading...
; + return ( +
+
+
+ ); } - return ( -
-
-

AeThex Passport

-

Domain Verification

-
- -
- {user && ( -
-
-

{user.email}

- {user.verifiedDomain && ( - - )} -
- -
- - - {showVerification && ( -
- -
- )} -
-
- )} - -
-

About Domain Verification

-

- Domain verification allows you to prove ownership of a domain by adding - a DNS TXT record or connecting a wallet that owns a .aethex blockchain domain. -

-
    -
  • โœ“ Verify traditional domains via DNS TXT records
  • -
  • โœ“ Verify .aethex domains via blockchain
  • -
  • โœ“ Display verified domain on your profile
  • -
  • โœ“ Prevent domain impersonation
  • -
-
-
- -
-

© 2026 AeThex Corporation. All rights reserved.

-
-
- ); + return ; } -export default function AppWrapper() { +export default function App() { return ( - + + + ); } diff --git a/src/frontend/Demo.css b/src/frontend/Demo.css index a573ce9..faca2d5 100644 --- a/src/frontend/Demo.css +++ b/src/frontend/Demo.css @@ -14,7 +14,8 @@ display: flex; flex-direction: column; align-items: center; - justify-cont#0a0a0f; + justify-content: center; + background: #0a0a0f; color: #e4e4e7; } diff --git a/src/frontend/contexts/MessagingContext.jsx b/src/frontend/contexts/MessagingContext.jsx new file mode 100644 index 0000000..df2ae62 --- /dev/null +++ b/src/frontend/contexts/MessagingContext.jsx @@ -0,0 +1,159 @@ +import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; +import { messagingService, socket, connectSocket, disconnectSocket } from '../services/messaging'; +import { useAuth } from './AuthContext'; + +const MessagingContext = createContext(); + +export function useMessaging() { + const context = useContext(MessagingContext); + if (!context) { + throw new Error('useMessaging must be used within a MessagingProvider'); + } + return context; +} + +export function MessagingProvider({ children }) { + const { user } = useAuth(); + const [conversations, setConversations] = useState([]); + const [currentConversation, setCurrentConversation] = useState(null); + const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + // Load conversations when user logs in + useEffect(() => { + if (user?.id) { + connectSocket(user.id); + loadConversations(); + } else { + disconnectSocket(); + setConversations([]); + setMessages([]); + } + + return () => { + disconnectSocket(); + }; + }, [user?.id]); + + // Listen for real-time messages + useEffect(() => { + const handleNewMessage = (message) => { + if (message.conversation_id === currentConversation?.id) { + setMessages(prev => [...prev, message]); + } + // Update conversation's last message + setConversations(prev => + prev.map(conv => + conv.id === message.conversation_id + ? { ...conv, lastMessage: message, updated_at: message.created_at } + : conv + ) + ); + }; + + socket.on('message:new', handleNewMessage); + return () => { + socket.off('message:new', handleNewMessage); + }; + }, [currentConversation?.id]); + + // Subscribe to current conversation's messages + useEffect(() => { + if (!currentConversation?.id) return; + + const unsubscribe = messagingService.subscribeToMessages( + currentConversation.id, + (newMessage) => { + setMessages(prev => { + // Avoid duplicates + if (prev.find(m => m.id === newMessage.id)) return prev; + return [...prev, newMessage]; + }); + } + ); + + return unsubscribe; + }, [currentConversation?.id]); + + const loadConversations = useCallback(async () => { + if (!user?.id) return; + + setLoading(true); + try { + const data = await messagingService.getConversations(user.id); + setConversations(data); + } catch (err) { + console.error('Failed to load conversations:', err); + setError(err.message); + } finally { + setLoading(false); + } + }, [user?.id]); + + const selectConversation = useCallback(async (conversation) => { + setCurrentConversation(conversation); + setLoading(true); + try { + const msgs = await messagingService.getMessages(conversation.id); + setMessages(msgs); + } catch (err) { + console.error('Failed to load messages:', err); + setError(err.message); + } finally { + setLoading(false); + } + }, []); + + const sendMessage = useCallback(async (content) => { + if (!currentConversation?.id || !user?.id || !content.trim()) return; + + try { + const message = await messagingService.sendMessage( + currentConversation.id, + user.id, + content + ); + // Optimistically add to local state + setMessages(prev => [...prev, message]); + } catch (err) { + console.error('Failed to send message:', err); + setError(err.message); + } + }, [currentConversation?.id, user?.id]); + + const createConversation = useCallback(async (type, title, participantIds) => { + if (!user?.id) return; + + try { + const conversation = await messagingService.createConversation( + type, + title, + [user.id, ...participantIds] + ); + setConversations(prev => [conversation, ...prev]); + return conversation; + } catch (err) { + console.error('Failed to create conversation:', err); + setError(err.message); + } + }, [user?.id]); + + const value = { + conversations, + currentConversation, + messages, + loading, + error, + selectConversation, + sendMessage, + createConversation, + refreshConversations: loadConversations, + }; + + return ( + + {children} + + ); +} diff --git a/src/frontend/index.css b/src/frontend/index.css index 7073852..5c9cc6c 100644 --- a/src/frontend/index.css +++ b/src/frontend/index.css @@ -1,3 +1,5 @@ +@import "tailwindcss"; + * { margin: 0; padding: 0; @@ -5,39 +7,25 @@ } body { - margin: 0; - padding: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - background: #000000; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #0a0a0a; color: #e4e4e7; line-height: 1.5; } -code { - font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace; -} - ::-webkit-scrollbar { - width: 10px; - height: 10px; + width: 8px; } ::-webkit-scrollbar-track { - background: #18181b; + background: #111; } ::-webkit-scrollbar-thumb { - background: #3f3f46; - border-radius: 5px; + background: #333; + border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: #52525b; -} - -::selection { - background: rgba(139, 92, 246, 0.3); - color: #e4e4e7; + background: #444; } diff --git a/src/frontend/mockup/ActivityStatus.jsx b/src/frontend/mockup/ActivityStatus.jsx new file mode 100644 index 0000000..4086ea5 --- /dev/null +++ b/src/frontend/mockup/ActivityStatus.jsx @@ -0,0 +1,186 @@ +import React from 'react'; + +export default function ActivityStatus({ activity, size = 'normal' }) { + if (!activity) return null; + + const activityTypes = { + playing: { icon: '๐ŸŽฎ', label: 'Playing' }, + streaming: { icon: '๐Ÿ“บ', label: 'Streaming' }, + listening: { icon: '๐ŸŽง', label: 'Listening to' }, + watching: { icon: '๐Ÿ“บ', label: 'Watching' }, + competing: { icon: '๐Ÿ†', label: 'Competing in' }, + custom: { icon: null, label: null }, + developing: { icon: '๐Ÿ’ป', label: 'Developing' }, + building: { icon: '๐Ÿ”จ', label: 'Building' }, + }; + + const typeInfo = activityTypes[activity.type] || activityTypes.custom; + + if (size === 'small') { + return ( +
+ {typeInfo.icon && {typeInfo.icon}} + + {typeInfo.label ? `${typeInfo.label} ${activity.name}` : activity.name} + +
+ ); + } + + if (size === 'mini') { + return ( + + {activity.name} + + ); + } + + return ( +
+
+ {typeInfo.icon && {typeInfo.icon}} + {typeInfo.label || 'Activity'} +
+ +
+ {activity.largeImage && ( +
+ + {activity.smallImage && ( + + )} +
+ )} + +
+
{activity.name}
+ {activity.details &&
{activity.details}
} + {activity.state &&
{activity.state}
} + {activity.startTime && ( +
+ +
+ )} +
+
+ + {activity.buttons && activity.buttons.length > 0 && ( +
+ {activity.buttons.map((btn, idx) => ( + + ))} +
+ )} +
+ ); +} + +function ActivityTimer({ startTime }) { + const [elapsed, setElapsed] = React.useState(''); + + React.useEffect(() => { + const updateElapsed = () => { + const now = Date.now(); + const diff = now - startTime; + const hours = Math.floor(diff / 3600000); + const minutes = Math.floor((diff % 3600000) / 60000); + const seconds = Math.floor((diff % 60000) / 1000); + + if (hours > 0) { + setElapsed(`${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')} elapsed`); + } else { + setElapsed(`${minutes}:${seconds.toString().padStart(2, '0')} elapsed`); + } + }; + + updateElapsed(); + const interval = setInterval(updateElapsed, 1000); + return () => clearInterval(interval); + }, [startTime]); + + return {elapsed}; +} + +// Rich Presence card for games +export function RichPresence({ game }) { + if (!game) return null; + + return ( +
+
+ ๐ŸŽฎ + Playing a game +
+ +
+
+ {game.icon ? ( + {game.name} + ) : ( + ๐ŸŽฎ + )} +
+ +
+
{game.name}
+ {game.details &&
{game.details}
} + {game.state &&
{game.state}
} +
+ +
+
+
+
+ ); +} + +// Spotify-style listening activity +export function SpotifyActivity({ track }) { + if (!track) return null; + + return ( +
+
+ ๐ŸŽง + Listening to Spotify +
+ +
+
+ {track.albumArt ? ( + {track.album} + ) : ( + ๐ŸŽต + )} +
+ +
+
{track.name}
+
by {track.artist}
+
on {track.album}
+
+
+ +
+
+
+
+
+ {formatTime(track.elapsed)} + {formatTime(track.duration)} +
+
+
+ ); +} + +function formatTime(ms) { + const minutes = Math.floor(ms / 60000); + const seconds = Math.floor((ms % 60000) / 1000); + return `${minutes}:${seconds.toString().padStart(2, '0')}`; +} diff --git a/src/frontend/mockup/AppDirectory.jsx b/src/frontend/mockup/AppDirectory.jsx new file mode 100644 index 0000000..09748fd --- /dev/null +++ b/src/frontend/mockup/AppDirectory.jsx @@ -0,0 +1,220 @@ +import React, { useState } from 'react'; + +const categories = [ + { id: 'all', label: 'All Apps', icon: '๐Ÿ”ท' }, + { id: 'moderation', label: 'Moderation', icon: '๐Ÿ›ก๏ธ' }, + { id: 'entertainment', label: 'Entertainment', icon: '๐ŸŽฎ' }, + { id: 'social', label: 'Social', icon: '๐Ÿ’ฌ' }, + { id: 'utility', label: 'Utility', icon: '๐Ÿ”ง' }, + { id: 'music', label: 'Music', icon: '๐ŸŽต' }, +]; + +const apps = [ + { + id: 1, + name: 'MEE6', + icon: '๐Ÿค–', + color: '#5865f2', + description: 'The most popular bot for moderation, leveling, and social commands.', + category: 'moderation', + verified: true, + servers: '19M+', + rating: 4.5, + features: ['Moderation', 'Leveling', 'Custom Commands', 'Music'], + }, + { + id: 2, + name: 'Dyno', + icon: 'โšก', + color: '#ffa500', + description: 'Fully customizable server moderation bot with a web dashboard.', + category: 'moderation', + verified: true, + servers: '4.7M+', + rating: 4.3, + features: ['Auto-mod', 'Dashboard', 'Logs', 'Announcements'], + }, + { + id: 3, + name: 'Rythm', + icon: '๐ŸŽต', + color: '#e91e63', + description: 'High quality music bot with support for multiple platforms.', + category: 'music', + verified: true, + servers: '6M+', + rating: 4.7, + features: ['Music', 'Playlists', 'Lyrics', 'Volume Control'], + }, + { + id: 4, + name: 'Dank Memer', + icon: '๐Ÿธ', + color: '#3ba55d', + description: 'Meme posting, currency system, and fun commands.', + category: 'entertainment', + verified: true, + servers: '8M+', + rating: 4.6, + features: ['Economy', 'Memes', 'Games', 'Gambling'], + }, + { + id: 5, + name: 'Carl-bot', + icon: '๐ŸŽญ', + color: '#9b59b6', + description: 'Reaction roles, logging, automod, and utility commands.', + category: 'utility', + verified: true, + servers: '3.5M+', + rating: 4.4, + features: ['Reaction Roles', 'Logging', 'Tags', 'Embeds'], + }, + { + id: 6, + name: 'Pokรฉtwo', + icon: '๐Ÿ”ด', + color: '#ff0000', + description: 'Catch, trade, and battle Pokรฉmon in Discord.', + category: 'entertainment', + verified: true, + servers: '1.2M+', + rating: 4.5, + features: ['Pokรฉmon', 'Trading', 'Battles', 'Events'], + }, + { + id: 7, + name: 'Tickets', + icon: '๐ŸŽซ', + color: '#5865f2', + description: 'Simple and powerful ticket system for support.', + category: 'utility', + verified: false, + servers: '500K+', + rating: 4.2, + features: ['Tickets', 'Transcripts', 'Categories', 'Staff Roles'], + }, + { + id: 8, + name: 'Arcane', + icon: '๐Ÿ”ฎ', + color: '#eb459e', + description: 'XP leveling, role rewards, and leaderboards.', + category: 'social', + verified: false, + servers: '800K+', + rating: 4.1, + features: ['Leveling', 'Role Rewards', 'Leaderboard', 'Card Design'], + }, +]; + +export default function AppDirectory({ onClose }) { + const [search, setSearch] = useState(''); + const [activeCategory, setActiveCategory] = useState('all'); + const [selectedApp, setSelectedApp] = useState(null); + + const filteredApps = apps.filter(app => { + const matchesSearch = !search || + app.name.toLowerCase().includes(search.toLowerCase()) || + app.description.toLowerCase().includes(search.toLowerCase()); + const matchesCategory = activeCategory === 'all' || app.category === activeCategory; + return matchesSearch && matchesCategory; + }); + + return ( +
+
+

๐Ÿ”ท App Directory

+ +
+ +
+ ๐Ÿ” + setSearch(e.target.value)} + /> +
+ +
+ {categories.map(cat => ( + + ))} +
+ +
+ {selectedApp ? ( +
+ +
+
+ {selectedApp.icon} +
+
+
+

{selectedApp.name}

+ {selectedApp.verified && โœ“ Verified} +
+

{selectedApp.description}

+
+ ๐Ÿ“Š {selectedApp.servers} servers + โญ {selectedApp.rating}/5 +
+
+ +
+
+

Features

+
+ {selectedApp.features.map((feature, idx) => ( + {feature} + ))} +
+
+
+ ) : ( +
+ {filteredApps.map(app => ( +
setSelectedApp(app)} + > +
+ {app.icon} +
+
+
+ {app.name} + {app.verified && โœ“} +
+

{app.description}

+
+ {app.servers} servers + โญ {app.rating} +
+
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/src/frontend/mockup/AuditLog.jsx b/src/frontend/mockup/AuditLog.jsx new file mode 100644 index 0000000..8258509 --- /dev/null +++ b/src/frontend/mockup/AuditLog.jsx @@ -0,0 +1,170 @@ +import React, { useState } from 'react'; + +const mockLogs = [ + { id: 1, action: 'member_kick', user: { name: 'ModUser', avatar: 'M', color: '#5865f2' }, target: 'SpamBot#1234', time: '2 minutes ago', reason: 'Spam' }, + { id: 2, action: 'channel_create', user: { name: 'Admin', avatar: 'A', color: '#ff0000' }, target: '#announcements', time: '15 minutes ago' }, + { id: 3, action: 'role_update', user: { name: 'Admin', avatar: 'A', color: '#ff0000' }, target: 'Moderator', time: '1 hour ago', changes: 'Added kick permission' }, + { id: 4, action: 'member_ban', user: { name: 'ModUser', avatar: 'M', color: '#5865f2' }, target: 'ToxicUser#9999', time: '2 hours ago', reason: 'Harassment' }, + { id: 5, action: 'message_delete', user: { name: 'AutoMod', avatar: '๐Ÿค–', color: '#3ba55d' }, target: '5 messages', time: '3 hours ago', reason: 'Spam filter' }, + { id: 6, action: 'invite_create', user: { name: 'Member', avatar: 'M', color: '#ffa500' }, target: 'discord.gg/abc123', time: '4 hours ago' }, + { id: 7, action: 'member_timeout', user: { name: 'ModUser', avatar: 'M', color: '#5865f2' }, target: 'RuleBreaker#0001', time: '5 hours ago', reason: 'Breaking rules', duration: '10 minutes' }, + { id: 8, action: 'server_update', user: { name: 'Owner', avatar: 'O', color: '#e91e63' }, target: 'Server name', time: '1 day ago', changes: 'Changed to AeThex Foundation' }, + { id: 9, action: 'emoji_create', user: { name: 'Admin', avatar: 'A', color: '#ff0000' }, target: ':custom_emoji:', time: '2 days ago' }, + { id: 10, action: 'webhook_create', user: { name: 'Developer', avatar: 'D', color: '#0066ff' }, target: 'GitHub Webhook', time: '3 days ago' }, +]; + +const actionTypes = [ + { id: 'all', label: 'All Actions' }, + { id: 'member', label: 'Member Actions' }, + { id: 'channel', label: 'Channel Actions' }, + { id: 'role', label: 'Role Actions' }, + { id: 'message', label: 'Message Actions' }, + { id: 'server', label: 'Server Actions' }, + { id: 'invite', label: 'Invite Actions' }, +]; + +const actionIcons = { + member_kick: '๐Ÿ‘ข', + member_ban: '๐Ÿ”จ', + member_timeout: 'โฐ', + member_join: '๐Ÿ“ฅ', + member_leave: '๐Ÿ“ค', + channel_create: 'โž•', + channel_delete: '๐Ÿ—‘๏ธ', + channel_update: '๐Ÿ“', + role_create: '๐ŸŽญ', + role_update: '๐ŸŽญ', + role_delete: '๐ŸŽญ', + message_delete: '๐Ÿ’ฌ', + message_pin: '๐Ÿ“Œ', + server_update: 'โš™๏ธ', + invite_create: '๐Ÿ”—', + invite_delete: '๐Ÿ”—', + emoji_create: '๐Ÿ˜€', + emoji_delete: '๐Ÿ˜€', + webhook_create: '๐Ÿ”Œ', + webhook_delete: '๐Ÿ”Œ', +}; + +const actionLabels = { + member_kick: 'kicked', + member_ban: 'banned', + member_timeout: 'timed out', + member_join: 'joined', + member_leave: 'left', + channel_create: 'created channel', + channel_delete: 'deleted channel', + channel_update: 'updated channel', + role_create: 'created role', + role_update: 'updated role', + role_delete: 'deleted role', + message_delete: 'deleted', + message_pin: 'pinned', + server_update: 'updated', + invite_create: 'created invite', + invite_delete: 'deleted invite', + emoji_create: 'added emoji', + emoji_delete: 'removed emoji', + webhook_create: 'created webhook', + webhook_delete: 'deleted webhook', +}; + +export default function AuditLog({ onClose }) { + const [filter, setFilter] = useState('all'); + const [searchUser, setSearchUser] = useState(''); + const [expandedLog, setExpandedLog] = useState(null); + + const filteredLogs = mockLogs.filter(log => { + if (filter !== 'all' && !log.action.startsWith(filter)) return false; + if (searchUser && !log.user.name.toLowerCase().includes(searchUser.toLowerCase())) return false; + return true; + }); + + return ( +
+
+

๐Ÿ“‹ Audit Log

+ +
+ +
+
+ ๐Ÿ” + setSearchUser(e.target.value)} + /> +
+ +
+ +
+ {filteredLogs.map(log => ( +
setExpandedLog(expandedLog === log.id ? null : log.id)} + > +
+ {actionIcons[log.action] || '๐Ÿ“'} +
+ {log.user.avatar} +
+
+ {log.user.name} + {actionLabels[log.action] || log.action} + {log.target} +
+ {log.time} +
+ + {expandedLog === log.id && ( +
+ {log.reason && ( +
+ Reason: + {log.reason} +
+ )} + {log.duration && ( +
+ Duration: + {log.duration} +
+ )} + {log.changes && ( +
+ Changes: + {log.changes} +
+ )} +
+ ID: + {log.id} +
+
+ )} +
+ ))} +
+ +
+ {filteredLogs.length} entries + +
+
+ ); +} diff --git a/src/frontend/mockup/AutoModSettings.jsx b/src/frontend/mockup/AutoModSettings.jsx new file mode 100644 index 0000000..67fff47 --- /dev/null +++ b/src/frontend/mockup/AutoModSettings.jsx @@ -0,0 +1,259 @@ +import React, { useState } from 'react'; + +const autoModRules = [ + { + id: 'profanity', + name: 'Block Profanity', + description: 'Automatically block common profane words', + icon: '๐Ÿคฌ', + severity: 'high' + }, + { + id: 'spam', + name: 'Block Spam', + description: 'Prevent repeated messages, excessive mentions, and invite links', + icon: '๐Ÿ“ง', + severity: 'medium' + }, + { + id: 'links', + name: 'Block Links', + description: 'Block messages containing links (with allowlist)', + icon: '๐Ÿ”—', + severity: 'low' + }, + { + id: 'mentions', + name: 'Limit Mentions', + description: 'Block messages with excessive @mentions', + icon: '@', + severity: 'medium' + }, + { + id: 'caps', + name: 'Block Excessive Caps', + description: 'Block messages that are mostly uppercase', + icon: '๐Ÿ” ', + severity: 'low' + }, + { + id: 'attachments', + name: 'Block Attachments', + description: 'Restrict file attachments in messages', + icon: '๐Ÿ“Ž', + severity: 'low' + }, +]; + +const defaultActions = [ + { id: 'delete', label: 'Delete Message', icon: '๐Ÿ—‘๏ธ' }, + { id: 'alert', label: 'Send Alert to Channel', icon: 'โš ๏ธ' }, + { id: 'timeout', label: 'Timeout Member', icon: 'โฐ' }, + { id: 'log', label: 'Log to Mod Channel', icon: '๐Ÿ“' }, +]; + +export default function AutoModSettings({ onSave, onClose }) { + const [rules, setRules] = useState({ + profanity: { enabled: true, action: 'delete' }, + spam: { enabled: true, action: 'delete' }, + links: { enabled: false, action: 'delete', allowlist: [] }, + mentions: { enabled: true, action: 'alert', limit: 5 }, + caps: { enabled: false, action: 'delete', threshold: 70 }, + attachments: { enabled: false, action: 'delete' }, + }); + + const [customWords, setCustomWords] = useState(['badword1', 'badword2']); + const [allowedLinks, setAllowedLinks] = useState(['youtube.com', 'twitter.com']); + const [logChannel, setLogChannel] = useState('mod-logs'); + const [activeRule, setActiveRule] = useState(null); + + const toggleRule = (ruleId) => { + setRules(prev => ({ + ...prev, + [ruleId]: { ...prev[ruleId], enabled: !prev[ruleId]?.enabled } + })); + }; + + const updateRuleAction = (ruleId, action) => { + setRules(prev => ({ + ...prev, + [ruleId]: { ...prev[ruleId], action } + })); + }; + + return ( +
+
+

๐Ÿค– AutoMod

+

Automatically moderate your server with configurable rules

+ +
+ +
+
+

Moderation Rules

+
+ {autoModRules.map(rule => ( +
+
+
+ toggleRule(rule.id)} + /> +
+
{rule.icon}
+
+

{rule.name}

+

{rule.description}

+
+
+ {rule.severity} +
+ +
+ + {activeRule === rule.id && ( +
+
+ +
+ {defaultActions.map(action => ( + + ))} +
+
+ + {rule.id === 'mentions' && ( +
+ + setRules(prev => ({ + ...prev, + mentions: { ...prev.mentions, limit: Number(e.target.value) } + }))} + className="number-input" + /> +
+ )} + + {rule.id === 'caps' && ( +
+ + setRules(prev => ({ + ...prev, + caps: { ...prev.caps, threshold: Number(e.target.value) } + }))} + /> + {rules.caps?.threshold || 70}% +
+ )} + + {rule.id === 'links' && ( +
+ +
+ {allowedLinks.map((link, idx) => ( + + {link} + + + ))} +
+ { + if (e.key === 'Enter' && e.target.value) { + setAllowedLinks(prev => [...prev, e.target.value]); + e.target.value = ''; + } + }} + /> +
+ )} +
+ )} +
+ ))} +
+
+ +
+

Custom Blocked Words

+

Add your own words to block in messages

+
+
+ {customWords.map((word, idx) => ( + + {word} + + + ))} +
+ { + if (e.key === 'Enter' && e.target.value) { + setCustomWords(prev => [...prev, e.target.value.toLowerCase()]); + e.target.value = ''; + } + }} + /> +
+
+ +
+

Log Channel

+

Where to send AutoMod logs

+ +
+
+ +
+ + +
+
+ ); +} diff --git a/src/frontend/mockup/BanList.jsx b/src/frontend/mockup/BanList.jsx new file mode 100644 index 0000000..bad16b1 --- /dev/null +++ b/src/frontend/mockup/BanList.jsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; + +const mockBans = [ + { id: 1, user: { name: 'SpamBot_123', tag: '#0000', avatar: 'S' }, reason: 'Spamming invite links', bannedBy: 'Trevor', bannedAt: '2025-12-15T14:30:00' }, + { id: 2, user: { name: 'ToxicUser', tag: '#6666', avatar: 'T' }, reason: 'Harassment and hate speech', bannedBy: 'Sarah', bannedAt: '2025-11-20T09:15:00' }, + { id: 3, user: { name: 'RaidLeader', tag: '#9999', avatar: 'R' }, reason: 'Organizing raid against server', bannedBy: 'Trevor', bannedAt: '2025-10-05T18:45:00' }, + { id: 4, user: { name: 'ScammerX', tag: '#1111', avatar: 'X' }, reason: 'Attempting to scam members', bannedBy: 'Sarah', bannedAt: '2025-09-12T11:00:00' }, + { id: 5, user: { name: 'BadActor', tag: '#4321', avatar: 'B' }, reason: 'No reason given', bannedBy: 'Trevor', bannedAt: '2025-08-01T16:20:00' }, +]; + +export default function BanList({ onClose }) { + const [bans, setBans] = useState(mockBans); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedBan, setSelectedBan] = useState(null); + const [showRevokeConfirm, setShowRevokeConfirm] = useState(false); + + const filteredBans = bans.filter(ban => + ban.user.name.toLowerCase().includes(searchQuery.toLowerCase()) || + ban.reason.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const formatDate = (dateStr) => { + const date = new Date(dateStr); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + }; + + const handleRevoke = (banId) => { + setBans(prev => prev.filter(b => b.id !== banId)); + setShowRevokeConfirm(false); + setSelectedBan(null); + }; + + return ( +
+
e.stopPropagation()}> +
+

๐Ÿšซ Server Bans

+ +
+ +
+ setSearchQuery(e.target.value)} + /> +
+ +
+ {filteredBans.length === 0 ? ( +
+ โœจ +

No bans found

+ This server has no banned users +
+ ) : ( + <> +
{filteredBans.length} banned user{filteredBans.length !== 1 ? 's' : ''}
+ {filteredBans.map(ban => ( +
setSelectedBan(selectedBan === ban.id ? null : ban.id)} + > +
+
{ban.user.avatar}
+
+
+ {ban.user.name}{ban.user.tag} +
+
{ban.reason}
+
+
+ +
+
{formatDate(ban.bannedAt)}
+
by {ban.bannedBy}
+
+ + {selectedBan === ban.id && ( +
+ +
+ )} +
+ ))} + + )} +
+ + {showRevokeConfirm && selectedBan && ( +
setShowRevokeConfirm(false)}> +
e.stopPropagation()}> +

Revoke Ban

+

+ Are you sure you want to unban{' '} + {bans.find(b => b.id === selectedBan)?.user.name}? +

+

They will be able to rejoin the server with an invite.

+
+ + +
+
+
+ )} +
+
+ ); +} diff --git a/src/frontend/mockup/CategoryEditor.jsx b/src/frontend/mockup/CategoryEditor.jsx new file mode 100644 index 0000000..af81e6f --- /dev/null +++ b/src/frontend/mockup/CategoryEditor.jsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react'; + +export default function CategoryEditor({ category, onClose, onSave, onDelete }) { + const categoryData = category || { + id: 'new', + name: '', + channels: [], + }; + + const [name, setName] = useState(categoryData.name); + const [isPrivate, setIsPrivate] = useState(false); + const [syncPermissions, setSyncPermissions] = useState(true); + + const availableRoles = [ + { id: 'founder', name: 'Founder', color: '#ff0000' }, + { id: 'foundation', name: 'Foundation', color: '#ff0000' }, + { id: 'corporation', name: 'Corporation', color: '#0066ff' }, + { id: 'labs', name: 'Labs', color: '#ffa500' }, + { id: 'everyone', name: '@everyone', color: '#99aab5' }, + ]; + + const [allowedRoles, setAllowedRoles] = useState(['everyone']); + + const toggleRole = (roleId) => { + setAllowedRoles(prev => + prev.includes(roleId) + ? prev.filter(id => id !== roleId) + : [...prev, roleId] + ); + }; + + const handleSave = () => { + onSave?.({ + id: categoryData.id, + name: name.toUpperCase(), + isPrivate, + allowedRoles, + syncPermissions, + }); + }; + + const isNew = !category?.name; + + return ( +
+
e.stopPropagation()}> +
+

{isNew ? 'Create Category' : 'Edit Category'}

+ +
+ +
+
+ + setName(e.target.value)} + placeholder="New Category" + maxLength={100} + /> +
+ +
+
+ +

Only selected roles can view this category

+
+ +
+ + {isPrivate && ( +
+ +
+ {availableRoles.map(role => ( + + ))} +
+
+ )} + + {!isNew && categoryData.channels?.length > 0 && ( +
+
+ +

+ Sync permissions with {categoryData.channels.length} channels in this category +

+
+ +
+ )} + + {!isNew && ( +
+ +
+ {(categoryData.channels?.length > 0 ? categoryData.channels : [ + { id: 'general', name: 'general', type: 'text' }, + { id: 'api-discussion', name: 'api-discussion', type: 'text' }, + ]).map(ch => ( +
+ {ch.type === 'voice' ? '๐Ÿ”Š' : '#'} + {ch.name} +
+ ))} +
+
+ )} +
+ +
+ {!isNew && ( + + )} +
+ + +
+
+
+
+ ); +} diff --git a/src/frontend/mockup/ChannelPermissions.jsx b/src/frontend/mockup/ChannelPermissions.jsx new file mode 100644 index 0000000..86eb7dd --- /dev/null +++ b/src/frontend/mockup/ChannelPermissions.jsx @@ -0,0 +1,182 @@ +import React, { useState } from 'react'; + +const permissionCategories = [ + { + name: 'General Channel Permissions', + permissions: [ + { id: 'view_channel', label: 'View Channel', description: 'Allows members to view this channel' }, + { id: 'manage_channel', label: 'Manage Channel', description: 'Allows members to change name, description, and settings' }, + { id: 'manage_permissions', label: 'Manage Permissions', description: 'Allows members to change permission overrides' }, + { id: 'manage_webhooks', label: 'Manage Webhooks', description: 'Allows members to create, edit and delete webhooks' }, + ] + }, + { + name: 'Text Channel Permissions', + permissions: [ + { id: 'send_messages', label: 'Send Messages', description: 'Allows members to send messages in this channel' }, + { id: 'send_messages_threads', label: 'Send Messages in Threads', description: 'Allows members to send messages in threads' }, + { id: 'create_public_threads', label: 'Create Public Threads', description: 'Allows members to create public threads' }, + { id: 'create_private_threads', label: 'Create Private Threads', description: 'Allows members to create private threads' }, + { id: 'embed_links', label: 'Embed Links', description: 'Links will show preview embeds' }, + { id: 'attach_files', label: 'Attach Files', description: 'Allows members to upload files and images' }, + { id: 'add_reactions', label: 'Add Reactions', description: 'Allows members to add reactions to messages' }, + { id: 'use_external_emoji', label: 'Use External Emoji', description: 'Allows members to use emoji from other servers' }, + { id: 'use_external_stickers', label: 'Use External Stickers', description: 'Allows members to use stickers from other servers' }, + { id: 'mention_everyone', label: 'Mention @everyone', description: 'Allows members to mention @everyone and @here' }, + { id: 'manage_messages', label: 'Manage Messages', description: 'Allows members to delete and pin messages' }, + { id: 'manage_threads', label: 'Manage Threads', description: 'Allows members to rename, delete, and archive threads' }, + { id: 'read_message_history', label: 'Read Message History', description: 'Allows reading of message history' }, + { id: 'send_tts', label: 'Send TTS Messages', description: 'Allows sending text-to-speech messages' }, + { id: 'use_application_commands', label: 'Use Application Commands', description: 'Allows using slash commands' }, + ] + }, + { + name: 'Voice Channel Permissions', + permissions: [ + { id: 'connect', label: 'Connect', description: 'Allows members to join this voice channel' }, + { id: 'speak', label: 'Speak', description: 'Allows members to speak in this voice channel' }, + { id: 'video', label: 'Video', description: 'Allows members to share video or stream' }, + { id: 'mute_members', label: 'Mute Members', description: 'Allows muting other members' }, + { id: 'deafen_members', label: 'Deafen Members', description: 'Allows deafening other members' }, + { id: 'move_members', label: 'Move Members', description: 'Allows moving members between voice channels' }, + { id: 'use_vad', label: 'Use Voice Activity', description: 'Allows using voice activity detection instead of push-to-talk' }, + { id: 'priority_speaker', label: 'Priority Speaker', description: 'Allows using priority speaker mode' }, + { id: 'request_to_speak', label: 'Request to Speak', description: 'Allows requesting to speak in stage channels' }, + ] + } +]; + +export default function ChannelPermissions({ target, channelName, onClose, onSave }) { + const targetData = target || { + id: 'mod', + name: 'Moderator', + type: 'role', + color: '#5865f2', + }; + + const [permissions, setPermissions] = useState({}); + const [searchQuery, setSearchQuery] = useState(''); + + const getPermState = (permId) => permissions[permId] || 'neutral'; + + const cyclePermission = (permId) => { + setPermissions(prev => { + const current = prev[permId] || 'neutral'; + const next = current === 'neutral' ? 'allow' : current === 'allow' ? 'deny' : 'neutral'; + return { ...prev, [permId]: next }; + }); + }; + + const setAllPermissions = (state) => { + const newPerms = {}; + permissionCategories.forEach(cat => { + cat.permissions.forEach(perm => { + newPerms[perm.id] = state; + }); + }); + setPermissions(newPerms); + }; + + const handleSave = () => { + onSave?.(permissions); + }; + + const filteredCategories = permissionCategories.map(cat => ({ + ...cat, + permissions: cat.permissions.filter(p => + p.label.toLowerCase().includes(searchQuery.toLowerCase()) || + p.description.toLowerCase().includes(searchQuery.toLowerCase()) + ) + })).filter(cat => cat.permissions.length > 0); + + return ( +
+
e.stopPropagation()}> +
+
+

Edit Permissions

+ + {targetData.type === 'role' ? '@' : ''} + + {targetData.name} + + in #{channelName || 'general'} + +
+ +
+ +
+ setSearchQuery(e.target.value)} + /> +
+ + + +
+
+ +
+
+ โœ“ + Allow +
+
+ / + Inherit +
+
+ โœ• + Deny +
+
+ +
+ {filteredCategories.map(category => ( +
+

{category.name}

+ {category.permissions.map(perm => ( +
+
+ {perm.label} + {perm.description} +
+
+ + + +
+
+ ))} +
+ ))} +
+ +
+ + +
+
+
+ ); +} diff --git a/src/frontend/mockup/ChannelSettings.jsx b/src/frontend/mockup/ChannelSettings.jsx new file mode 100644 index 0000000..9e2550f --- /dev/null +++ b/src/frontend/mockup/ChannelSettings.jsx @@ -0,0 +1,271 @@ +import React, { useState } from 'react'; + +export default function ChannelSettings({ channel, onClose, onSave }) { + const [activeTab, setActiveTab] = useState('overview'); + + const channelData = channel || { + id: 'general', + name: 'general', + type: 'text', + topic: 'Welcome to the general chat!', + slowMode: 0, + nsfw: false, + category: 'Development', + }; + + const [name, setName] = useState(channelData.name); + const [topic, setTopic] = useState(channelData.topic); + const [slowMode, setSlowMode] = useState(channelData.slowMode); + const [nsfw, setNsfw] = useState(channelData.nsfw); + + const tabs = [ + { id: 'overview', label: 'Overview', icon: '๐Ÿ“‹' }, + { id: 'permissions', label: 'Permissions', icon: '๐Ÿ”’' }, + { id: 'invites', label: 'Invites', icon: 'โœ‰๏ธ' }, + { id: 'integrations', label: 'Integrations', icon: '๐Ÿ”—' }, + ]; + + const slowModeOptions = [ + { value: 0, label: 'Off' }, + { value: 5, label: '5s' }, + { value: 10, label: '10s' }, + { value: 15, label: '15s' }, + { value: 30, label: '30s' }, + { value: 60, label: '1m' }, + { value: 300, label: '5m' }, + { value: 600, label: '10m' }, + { value: 900, label: '15m' }, + { value: 1800, label: '30m' }, + { value: 3600, label: '1h' }, + { value: 7200, label: '2h' }, + { value: 21600, label: '6h' }, + ]; + + const permissionOverrides = [ + { id: 'founder', name: 'Founder', color: '#ff0000', type: 'role', allow: ['all'], deny: [] }, + { id: 'mod', name: 'Moderator', color: '#5865f2', type: 'role', allow: ['manage_messages'], deny: [] }, + { id: 'user123', name: 'SpecificUser', color: null, type: 'member', allow: [], deny: ['send_messages'] }, + ]; + + const handleSave = () => { + onSave?.({ name, topic, slowMode, nsfw }); + onClose?.(); + }; + + return ( +
+
e.stopPropagation()}> +
+
+ # + {name} +
+ +
+ {tabs.map(tab => ( +
setActiveTab(tab.id)} + > + {tab.icon} + {tab.label} +
+ ))} + +
+ +
+ ๐Ÿ—‘๏ธ + Delete Channel +
+
+
+ +
+
+

{tabs.find(t => t.id === activeTab)?.label}

+ +
+ + {activeTab === 'overview' && ( +
+
+ +
+ # + setName(e.target.value.toLowerCase().replace(/\s+/g, '-'))} + /> +
+
+ +
+ +