601 lines
28 KiB
JavaScript
601 lines
28 KiB
JavaScript
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 (
|
|
<div style={{ padding: '20px' }}>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<IconButton onClick={handleBack} sx={{ borderRadius: '50%', boxShadow: '0 8px 16px 0 rgba(0,0,0,0.2)' }}>
|
|
<ArrowBackIcon />
|
|
</IconButton>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
<Typography variant="h4" component="h1" sx={{ margin: 0 }}>
|
|
{server ? `Server Settings for ${server.name}` : 'Loading...'}
|
|
</Typography>
|
|
{isBotInServer ? (
|
|
<Button variant="contained" size="small" color="error" onClick={handleLeaveBot}>
|
|
Leave Server
|
|
</Button>
|
|
) : (
|
|
<Button variant="contained" size="small" onClick={handleInviteBot} disabled={!clientId}>
|
|
Invite Bot
|
|
</Button>
|
|
)}
|
|
</Box>
|
|
{/* UserSettings moved to NavBar */}
|
|
</Box>
|
|
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }} expanded={commandsExpanded} onChange={() => setCommandsExpanded(prev => !prev)}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography variant="h6">Commands</Typography>
|
|
</AccordionSummary>
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', padding: 1 }}>
|
|
<Button variant="text" onClick={() => navigate(`/server/${guildId}/help`)} disabled={!isBotInServer}>Commands List</Button>
|
|
</Box>
|
|
<AccordionDetails>
|
|
{!isBotInServer && <Typography>Invite the bot to enable commands.</Typography>}
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, marginTop: '10px' }}>
|
|
{/** 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 => (
|
|
<Box key={cmd.name} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: 1, border: '1px solid #eee', borderRadius: 1 }}>
|
|
<Box>
|
|
<Typography sx={{ fontWeight: 'bold' }}>{cmd.name}</Typography>
|
|
<Typography variant="body2">{cmd.description}</Typography>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<FormControlLabel control={<Switch checked={true} disabled />} label="Locked" />
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
|
|
{otherCmds.map(cmd => (
|
|
<Box key={cmd.name} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: 1, border: '1px solid #eee', borderRadius: 1 }}>
|
|
<Box>
|
|
<Typography sx={{ fontWeight: 'bold' }}>{cmd.name}</Typography>
|
|
<Typography variant="body2">{cmd.description}</Typography>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<FormControlLabel
|
|
control={<Switch checked={cmd.enabled} onChange={async (e) => {
|
|
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'} />}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</>
|
|
);
|
|
})()}
|
|
</Box>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
{/* Invite creation and list */}
|
|
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography variant="h6">Invites</Typography>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
{!isBotInServer && <Typography>Invite features require the bot to be in the server.</Typography>}
|
|
<Box sx={{ display: 'flex', gap: 2, flexDirection: { xs: 'column', sm: 'row' }, marginTop: 1 }}>
|
|
<Box sx={{ width: { xs: '100%', sm: '40%' } }}>
|
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Channel (optional)</Typography>
|
|
<FormControl fullWidth>
|
|
<Select value={inviteForm.channelId} onChange={(e) => setInviteForm(f => ({ ...f, channelId: e.target.value }))} displayEmpty>
|
|
<MenuItem value="">(Any channel)</MenuItem>
|
|
{channels.map(ch => (<MenuItem key={ch.id} value={ch.id}>{ch.name}</MenuItem>))}
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
<Box sx={{ width: { xs: '100%', sm: '20%' } }}>
|
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Expiry</Typography>
|
|
<FormControl fullWidth>
|
|
<Select value={inviteForm.maxAge} onChange={(e) => setInviteForm(f => ({ ...f, maxAge: Number(e.target.value) }))}>
|
|
<MenuItem value={0}>Never expire</MenuItem>
|
|
<MenuItem value={3600}>1 hour</MenuItem>
|
|
<MenuItem value={86400}>1 day</MenuItem>
|
|
<MenuItem value={604800}>7 days</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
<Box sx={{ width: { xs: '100%', sm: '20%' } }}>
|
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Max Uses</Typography>
|
|
<FormControl fullWidth>
|
|
<Select value={inviteForm.maxUses} onChange={(e) => setInviteForm(f => ({ ...f, maxUses: Number(e.target.value) }))}>
|
|
<MenuItem value={0}>Unlimited</MenuItem>
|
|
<MenuItem value={1}>1</MenuItem>
|
|
<MenuItem value={5}>5</MenuItem>
|
|
<MenuItem value={10}>10</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: { xs: '100%', sm: '20%' } }}>
|
|
<Box sx={{ width: '100%' }}>
|
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Temporary</Typography>
|
|
<FormControlLabel control={<Switch checked={inviteForm.temporary} onChange={(e) => setInviteForm(f => ({ ...f, temporary: e.target.checked }))} />} label="" />
|
|
</Box>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
|
|
<Button variant="contained" onClick={async () => {
|
|
try {
|
|
const resp = await axios.post(`${process.env.REACT_APP_API_BASE || 'http://localhost:3002'}/api/servers/${guildId}/invites`, inviteForm);
|
|
if (resp.data && resp.data.success) {
|
|
setInvites(prev => [...prev, resp.data.invite]);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error creating invite:', err);
|
|
}
|
|
}} disabled={!isBotInServer}>Create Invite</Button>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Box sx={{ marginTop: 2 }}>
|
|
{invites.length === 0 && <Typography>No invites created by the bot.</Typography>}
|
|
{invites.map(inv => (
|
|
<Box key={inv.code} sx={{ border: '1px solid #eee', borderRadius: 1, padding: 1, marginTop: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Box>
|
|
<Typography>{inv.url}</Typography>
|
|
<Typography variant="caption">Created: {new Date(inv.createdAt).toLocaleString()} • Uses: {inv.uses || 0} • MaxUses: {inv.maxUses || 0} • MaxAge(s): {inv.maxAge || 0} • Temporary: {inv.temporary ? 'Yes' : 'No'}</Typography>
|
|
</Box>
|
|
<Box>
|
|
<Button startIcon={<ContentCopyIcon />} onClick={async () => {
|
|
// robust clipboard copy with fallback
|
|
try {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
await navigator.clipboard.writeText(inv.url);
|
|
} else {
|
|
// fallback for older browsers
|
|
const input = document.createElement('input');
|
|
input.value = inv.url;
|
|
document.body.appendChild(input);
|
|
input.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(input);
|
|
}
|
|
setSnackbarMessage('Copied invite URL to clipboard');
|
|
setSnackbarOpen(true);
|
|
} catch (err) {
|
|
console.error('Clipboard copy failed:', err);
|
|
setSnackbarMessage('Failed to copy — please copy manually');
|
|
setSnackbarOpen(true);
|
|
}
|
|
}}>Copy</Button>
|
|
<Button startIcon={<DeleteIcon />} color="error" disabled={!!deleting[inv.code]} onClick={() => {
|
|
// open confirm dialog for this invite
|
|
setPendingDeleteInvite(inv);
|
|
setConfirmOpen(true);
|
|
}}>Delete</Button>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
{/* Help moved to dedicated Help page */}
|
|
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography variant="h6">Welcome/Leave</Typography>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
{!isBotInServer && <Typography>Invite the bot to enable this feature.</Typography>}
|
|
<Box sx={{ marginTop: '10px' }}>
|
|
<Typography variant="subtitle1">Welcome Messages</Typography>
|
|
<FormControlLabel
|
|
control={<Switch checked={welcomeLeaveSettings.welcome.enabled} onChange={handleToggleChange('welcome')} disabled={!isBotInServer} />}
|
|
label="Enable Welcome Messages"
|
|
/>
|
|
<FormControl fullWidth sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled}>
|
|
<Select
|
|
value={welcomeLeaveSettings.welcome.channel}
|
|
onChange={handleChannelChange('welcome')}
|
|
displayEmpty
|
|
>
|
|
<MenuItem value="" disabled>Select a channel</MenuItem>
|
|
{channels.map(channel => (
|
|
<MenuItem key={channel.id} value={channel.id}>{channel.name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
<FormControl component="fieldset" sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled}>
|
|
<RadioGroup
|
|
value={getMessageValue('welcome')}
|
|
onChange={handleMessageOptionChange('welcome')}
|
|
>
|
|
{defaultWelcomeMessages.map(msg => (
|
|
<FormControlLabel key={msg} value={msg} control={<Radio />} label={msg} />
|
|
))}
|
|
<FormControlLabel value="custom" control={<Radio />} label="Custom" />
|
|
</RadioGroup>
|
|
</FormControl>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: '10px' }} >
|
|
<TextField
|
|
fullWidth
|
|
variant="outlined"
|
|
placeholder="Your custom message"
|
|
value={welcomeLeaveSettings.welcome.customMessage}
|
|
onChange={handleCustomMessageChange('welcome')}
|
|
disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled || getMessageValue('welcome') !== 'custom'}
|
|
/>
|
|
<Button onClick={handleApplyCustomMessage('welcome')} disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled || getMessageValue('welcome') !== 'custom'}>Apply</Button>
|
|
</Box>
|
|
</Box>
|
|
<Box sx={{ marginTop: '20px' }}>
|
|
<Typography variant="subtitle1">Leave Messages</Typography>
|
|
<FormControlLabel
|
|
control={<Switch checked={welcomeLeaveSettings.leave.enabled} onChange={handleToggleChange('leave')} disabled={!isBotInServer} />}
|
|
label="Enable Leave Messages"
|
|
/>
|
|
<FormControl fullWidth sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled}>
|
|
<Select
|
|
value={welcomeLeaveSettings.leave.channel}
|
|
onChange={handleChannelChange('leave')}
|
|
displayEmpty
|
|
>
|
|
<MenuItem value="" disabled>Select a channel</MenuItem>
|
|
{channels.map(channel => (
|
|
<MenuItem key={channel.id} value={channel.id}>{channel.name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
<FormControl component="fieldset" sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled}>
|
|
<RadioGroup
|
|
value={getMessageValue('leave')}
|
|
onChange={handleMessageOptionChange('leave')}
|
|
>
|
|
{defaultLeaveMessages.map(msg => (
|
|
<FormControlLabel key={msg} value={msg} control={<Radio />} label={msg} />
|
|
))}
|
|
<FormControlLabel value="custom" control={<Radio />} label="Custom" />
|
|
</RadioGroup>
|
|
</FormControl>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: '10px' }} >
|
|
<TextField
|
|
fullWidth
|
|
variant="outlined"
|
|
placeholder="Your custom message"
|
|
value={welcomeLeaveSettings.leave.customMessage}
|
|
onChange={handleCustomMessageChange('leave')}
|
|
disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled || getMessageValue('leave') !== 'custom'}
|
|
/>
|
|
<Button onClick={handleApplyCustomMessage('leave')} disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled || getMessageValue('leave') !== 'custom'}>Apply</Button>
|
|
</Box>
|
|
</Box>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography variant="h6">Autorole</Typography>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
{!isBotInServer && <Typography>Invite the bot to enable this feature.</Typography>}
|
|
<Box sx={{ marginTop: '10px' }}>
|
|
<FormControlLabel
|
|
control={<Switch checked={autoroleSettings.enabled} onChange={handleAutoroleToggleChange} disabled={!isBotInServer} />}
|
|
label="Enable Autorole"
|
|
/>
|
|
<FormControl fullWidth sx={{ marginTop: '10px' }} disabled={!isBotInServer || !autoroleSettings.enabled}>
|
|
<Select
|
|
value={autoroleSettings.roleId}
|
|
onChange={handleAutoroleRoleChange}
|
|
displayEmpty
|
|
>
|
|
<MenuItem value="" disabled>Select a role</MenuItem>
|
|
{roles.map(role => (
|
|
<MenuItem key={role.id} value={role.id}><span style={{ color: role.color }}>{role.name}</span></MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography variant="h6">Admin Commands</Typography>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
<Typography>Coming soon...</Typography>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
<ConfirmDialog
|
|
open={dialogOpen}
|
|
onClose={() => setDialogOpen(false)}
|
|
onConfirm={handleConfirmLeave}
|
|
title="Confirm Leave"
|
|
message={`Are you sure you want the bot to leave ${server?.name}?`}
|
|
/>
|
|
{/* Confirm dialog for invite deletion */}
|
|
<ConfirmDialog
|
|
open={confirmOpen}
|
|
onClose={() => { 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 : ''}?`}
|
|
/>
|
|
<Snackbar open={snackbarOpen} autoHideDuration={4000} onClose={() => setSnackbarOpen(false)}>
|
|
<Alert onClose={() => setSnackbarOpen(false)} severity="info" sx={{ width: '100%' }}>
|
|
{snackbarMessage}
|
|
</Alert>
|
|
</Snackbar>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ServerSettings; |