import React, { useState, useEffect } from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; import axios from 'axios'; import { Button, Typography, Box, IconButton, Switch, Select, MenuItem, FormControl, FormControlLabel, Radio, RadioGroup, TextField, Accordion, AccordionSummary, AccordionDetails, Snackbar, Alert } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; // UserSettings moved to NavBar import ConfirmDialog from './ConfirmDialog'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import DeleteIcon from '@mui/icons-material/Delete'; const ServerSettings = () => { const { guildId } = useParams(); const navigate = useNavigate(); const location = useLocation(); // settings state removed (not used) to avoid lint warnings const [isBotInServer, setIsBotInServer] = useState(false); const [clientId, setClientId] = useState(null); const [server, setServer] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [channels, setChannels] = useState([]); const [roles, setRoles] = useState([]); const [autoroleSettings, setAutoroleSettings] = useState({ enabled: false, roleId: '', }); const [commandsList, setCommandsList] = useState([]); const [invites, setInvites] = useState([]); const [deleting, setDeleting] = useState({}); const [confirmOpen, setConfirmOpen] = useState(false); const [pendingDeleteInvite, setPendingDeleteInvite] = useState(null); const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(''); const [inviteForm, setInviteForm] = useState({ channelId: '', maxAge: 0, maxUses: 0, temporary: false }); const [commandsExpanded, setCommandsExpanded] = useState(false); const [welcomeLeaveSettings, setWelcomeLeaveSettings] = useState({ welcome: { enabled: false, channel: '', message: 'Welcome to the server, {user}!', customMessage: '', }, leave: { enabled: false, channel: '', message: '{user} has left the server.', customMessage: '', }, }); const defaultWelcomeMessages = ["Welcome to the server, {user}!", "Hey {user}, welcome!", "{user} has joined the party!"]; const defaultLeaveMessages = ["{user} has left the server.", "Goodbye, {user}.", "We'll miss you, {user}."]; useEffect(() => { if (location.state && location.state.guild) { setServer(location.state.guild); } else { // Fallback if guild data is not passed in state const storedGuilds = localStorage.getItem('guilds'); if (storedGuilds) { const guild = JSON.parse(storedGuilds).find(g => g.id === guildId); setServer(guild); } } const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3002'; // Fetch settings (not used directly in this component) axios.get(`${API_BASE}/api/servers/${guildId}/settings`).catch(() => {}); // Check if bot is in server axios.get(`${API_BASE}/api/servers/${guildId}/bot-status`) .then(response => { setIsBotInServer(response.data.isBotInServer); }); // Fetch client ID axios.get(`${API_BASE}/api/client-id`) .then(response => { setClientId(response.data.clientId); }); // Fetch channels axios.get(`${API_BASE}/api/servers/${guildId}/channels`) .then(response => { setChannels(response.data); }); // Fetch welcome/leave settings axios.get(`${API_BASE}/api/servers/${guildId}/welcome-leave-settings`) .then(response => { if (response.data) { setWelcomeLeaveSettings(response.data); } }); // Fetch roles axios.get(`${API_BASE}/api/servers/${guildId}/roles`) .then(response => { setRoles(response.data); }); // Fetch autorole settings axios.get(`${API_BASE}/api/servers/${guildId}/autorole-settings`) .then(response => { if (response.data) { setAutoroleSettings(response.data); } }); // Fetch commands/help list axios.get(`${API_BASE}/api/servers/${guildId}/commands`) .then(response => { setCommandsList(response.data || []); }) .catch(() => setCommandsList([])); // Fetch invites axios.get(`${API_BASE}/api/servers/${guildId}/invites`) .then(resp => setInvites(resp.data || [])) .catch(() => setInvites([])); // Open commands accordion if navigated from Help back button if (location.state && location.state.openCommands) { setCommandsExpanded(true); } }, [guildId, location.state]); const handleAutoroleSettingUpdate = (newSettings) => { axios.post(`${process.env.REACT_APP_API_BASE || 'http://localhost:3002'}/api/servers/${guildId}/autorole-settings`, newSettings) .then(response => { if (response.data.success) { setAutoroleSettings(newSettings); } }); }; const handleAutoroleToggleChange = (event) => { const newSettings = { ...autoroleSettings, enabled: event.target.checked }; handleAutoroleSettingUpdate(newSettings); }; const handleAutoroleRoleChange = (event) => { const newSettings = { ...autoroleSettings, roleId: event.target.value }; handleAutoroleSettingUpdate(newSettings); }; const handleSettingUpdate = (newSettings) => { axios.post(`${process.env.REACT_APP_API_BASE || 'http://localhost:3002'}/api/servers/${guildId}/welcome-leave-settings`, newSettings) .then(response => { if (response.data.success) { setWelcomeLeaveSettings(newSettings); } }); } const handleToggleChange = (type) => (event) => { const newSettings = { ...welcomeLeaveSettings }; newSettings[type].enabled = event.target.checked; handleSettingUpdate(newSettings); }; const handleChannelChange = (type) => (event) => { const newSettings = { ...welcomeLeaveSettings }; newSettings[type].channel = event.target.value; handleSettingUpdate(newSettings); }; const handleMessageOptionChange = (type) => (event) => { const newSettings = { ...welcomeLeaveSettings }; if (event.target.value !== 'custom') { newSettings[type].message = event.target.value; handleSettingUpdate(newSettings); } else { const tempSettings = { ...welcomeLeaveSettings }; // Set message to custom message to get the radio button to select custom tempSettings[type].message = tempSettings[type].customMessage; setWelcomeLeaveSettings(tempSettings); } }; const handleCustomMessageChange = (type) => (event) => { const newSettings = { ...welcomeLeaveSettings }; newSettings[type].customMessage = event.target.value; setWelcomeLeaveSettings(newSettings); }; const handleApplyCustomMessage = (type) => () => { const newSettings = { ...welcomeLeaveSettings }; newSettings[type].message = newSettings[type].customMessage; handleSettingUpdate(newSettings); }; const getMessageValue = (type) => { const currentMessage = welcomeLeaveSettings[type].message; const defaultMessages = type === 'welcome' ? defaultWelcomeMessages : defaultLeaveMessages; if (defaultMessages.includes(currentMessage)) { return currentMessage; } return 'custom'; } const handleInviteBot = () => { if (!clientId) return; const permissions = 8; // Administrator const url = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&permissions=${permissions}&scope=bot%20applications.commands&guild_id=${guildId}&disable_guild_select=true`; window.open(url, '_blank'); }; const handleLeaveBot = () => { setDialogOpen(true); }; const handleConfirmLeave = async () => { try { await axios.post(`${process.env.REACT_APP_API_BASE || 'http://localhost:3002'}/api/servers/${guildId}/leave`); setIsBotInServer(false); } catch (error) { console.error('Error leaving server:', error); } setDialogOpen(false); }; const handleBack = () => { navigate('/dashboard'); } return (
{server ? `Server Settings for ${server.name}` : 'Loading...'} {isBotInServer ? ( ) : ( )} {/* UserSettings moved to NavBar */} setCommandsExpanded(prev => !prev)}> }> Commands {!isBotInServer && Invite the bot to enable commands.} {/** Render protected commands first in a fixed order **/} {(() => { const protectedOrder = ['help', 'manage-commands']; const protectedCmds = protectedOrder.map(name => commandsList.find(c => c.name === name)).filter(Boolean); const otherCmds = (commandsList || []).filter(c => !protectedOrder.includes(c.name)).sort((a,b) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'})); return ( <> {protectedCmds.map(cmd => ( {cmd.name} {cmd.description} } label="Locked" /> ))} {otherCmds.map(cmd => ( {cmd.name} {cmd.description} { const newVal = e.target.checked; // optimistic update setCommandsList(prev => prev.map(c => c.name === cmd.name ? { ...c, enabled: newVal } : c)); try { await axios.post(`${process.env.REACT_APP_API_BASE || 'http://localhost:3002'}/api/servers/${guildId}/commands/${cmd.name}/toggle`, { enabled: newVal }); } catch (err) { // revert on error setCommandsList(prev => prev.map(c => c.name === cmd.name ? { ...c, enabled: cmd.enabled } : c)); } }} disabled={!isBotInServer} label={cmd.enabled ? 'Enabled' : 'Disabled'} />} /> ))} ); })()} {/* Invite creation and list */} }> Invites {!isBotInServer && Invite features require the bot to be in the server.} Channel (optional) Expiry Max Uses Temporary setInviteForm(f => ({ ...f, temporary: e.target.checked }))} />} label="" /> {invites.length === 0 && No invites created by the bot.} {invites.map(inv => ( {inv.url} Created: {new Date(inv.createdAt).toLocaleString()} • Uses: {inv.uses || 0} • MaxUses: {inv.maxUses || 0} • MaxAge(s): {inv.maxAge || 0} • Temporary: {inv.temporary ? 'Yes' : 'No'} ))} {/* Help moved to dedicated Help page */} }> Welcome/Leave {!isBotInServer && Invite the bot to enable this feature.} Welcome Messages } label="Enable Welcome Messages" /> {defaultWelcomeMessages.map(msg => ( } label={msg} /> ))} } label="Custom" /> Leave Messages } label="Enable Leave Messages" /> {defaultLeaveMessages.map(msg => ( } label={msg} /> ))} } label="Custom" /> }> Autorole {!isBotInServer && Invite the bot to enable this feature.} } label="Enable Autorole" /> }> Admin Commands Coming soon... setDialogOpen(false)} onConfirm={handleConfirmLeave} title="Confirm Leave" message={`Are you sure you want the bot to leave ${server?.name}?`} /> {/* Confirm dialog for invite deletion */} { setConfirmOpen(false); setPendingDeleteInvite(null); }} onConfirm={async () => { // perform deletion for pendingDeleteInvite if (!pendingDeleteInvite) { setConfirmOpen(false); return; } const code = pendingDeleteInvite.code; setConfirmOpen(false); setDeleting(prev => ({ ...prev, [code]: true })); try { const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3002'; // fetch token (one retry) let token = null; try { const tokenResp = await axios.get(`${API_BASE}/api/servers/${guildId}/invite-token`); token = tokenResp && tokenResp.data && tokenResp.data.token; } catch (tErr) { try { const tokenResp2 = await axios.get(`${API_BASE}/api/servers/${guildId}/invite-token`); token = tokenResp2 && tokenResp2.data && tokenResp2.data.token; } catch (tErr2) { throw new Error('Failed to obtain delete token from server'); } } if (!token) throw new Error('No delete token received from server'); await axios.delete(`${API_BASE}/api/servers/${guildId}/invites/${code}`, { headers: { 'x-invite-token': token } }); setInvites(prev => prev.filter(i => i.code !== code)); setSnackbarMessage('Invite deleted'); setSnackbarOpen(true); } catch (err) { console.error('Error deleting invite:', err); const msg = (err && err.message) || (err && err.response && err.response.data && err.response.data.message) || 'Failed to delete invite'; setSnackbarMessage(msg); setSnackbarOpen(true); } finally { setDeleting(prev => { const copy = { ...prev }; delete copy[pendingDeleteInvite?.code]; return copy; }); setPendingDeleteInvite(null); } }} title="Delete Invite" message={`Are you sure you want to delete invite ${pendingDeleteInvite ? pendingDeleteInvite.url : ''}?`} /> setSnackbarOpen(false)}> setSnackbarOpen(false)} severity="info" sx={{ width: '100%' }}> {snackbarMessage}
); }; export default ServerSettings;