updated navbar and ui
This commit is contained in:
@@ -73,13 +73,34 @@ app.get('/api/servers/:guildId/settings', (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/servers/:guildId/settings', (req, res) => {
|
app.post('/api/servers/:guildId/settings', (req, res) => {
|
||||||
const { guildId } = req.params;
|
const { guildId } = req.params;
|
||||||
const { pingCommand } = req.body;
|
const newSettings = req.body || {};
|
||||||
const db = readDb();
|
const db = readDb();
|
||||||
db[guildId] = { pingCommand };
|
if (!db[guildId]) db[guildId] = {};
|
||||||
|
// Merge incoming settings with existing settings to avoid overwriting unrelated keys
|
||||||
|
db[guildId] = { ...db[guildId], ...newSettings };
|
||||||
writeDb(db);
|
writeDb(db);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle a single command for a guild (preserves other toggles)
|
||||||
|
app.post('/api/servers/:guildId/commands/:cmdName/toggle', (req, res) => {
|
||||||
|
const { guildId, cmdName } = req.params;
|
||||||
|
const { enabled } = req.body; // boolean
|
||||||
|
const protectedCommands = ['help', 'manage-commands'];
|
||||||
|
if (protectedCommands.includes(cmdName)) {
|
||||||
|
return res.status(403).json({ success: false, message: 'This command is locked and cannot be toggled.' });
|
||||||
|
}
|
||||||
|
const db = readDb();
|
||||||
|
if (!db[guildId]) db[guildId] = {};
|
||||||
|
if (!db[guildId].commandToggles) db[guildId].commandToggles = {};
|
||||||
|
if (typeof enabled === 'boolean') {
|
||||||
|
db[guildId].commandToggles[cmdName] = enabled;
|
||||||
|
writeDb(db);
|
||||||
|
return res.json({ success: true, cmdName, enabled });
|
||||||
|
}
|
||||||
|
return res.status(400).json({ success: false, message: 'Missing or invalid "enabled" boolean in request body' });
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/servers/:guildId/bot-status', (req, res) => {
|
app.get('/api/servers/:guildId/bot-status', (req, res) => {
|
||||||
const { guildId } = req.params;
|
const { guildId } = req.params;
|
||||||
const guild = bot.client.guilds.cache.get(guildId);
|
const guild = bot.client.guilds.cache.get(guildId);
|
||||||
|
|||||||
11
checklist.md
11
checklist.md
@@ -33,6 +33,13 @@
|
|||||||
- [x] Further fine-tune card UI
|
- [x] Further fine-tune card UI
|
||||||
- [x] Add Commands section to server settings page
|
- [x] Add Commands section to server settings page
|
||||||
- [x] Refine 'Back to Dashboard' button
|
- [x] Refine 'Back to Dashboard' button
|
||||||
|
- [x] Remove 'Open Help Page' button from individual command controls (moved to dedicated Commands List page)
|
||||||
|
- [x] Rename Help nav/button to 'Commands List' and update page title
|
||||||
|
- [x] Restructure Commands list UI to show per-command toggles and cleaner layout
|
||||||
|
- [x] Ensure frontend persists selections and doesn't overwrite other settings
|
||||||
|
- [x] Improve NavBar layout and styling for clarity and compactness
|
||||||
|
- [x] Implement single-hamburger NavBar (hamburger toggles to X; buttons hidden when collapsed)
|
||||||
|
- [x] Commands List button added above Commands accordion in Server Settings
|
||||||
- [x] Place 'Invite' button beside the server title on dashboard/server cards
|
- [x] Place 'Invite' button beside the server title on dashboard/server cards
|
||||||
- Acceptance criteria: the invite button appears horizontally adjacent to the server title (to the right), remains visible and usable on tablet and desktop layouts, is keyboard-focusable, and has an accessible aria-label (e.g. "Invite bot to SERVER_NAME").
|
- Acceptance criteria: the invite button appears horizontally adjacent to the server title (to the right), remains visible and usable on tablet and desktop layouts, is keyboard-focusable, and has an accessible aria-label (e.g. "Invite bot to SERVER_NAME").
|
||||||
- [x] Show the server name in a rounded "bubble" and render it bold
|
- [x] Show the server name in a rounded "bubble" and render it bold
|
||||||
@@ -108,6 +115,10 @@
|
|||||||
- [x] Ensure the `/help` command is locked (protected) and cannot be disabled via `/manage-commands`.
|
- [x] Ensure the `/help` command is locked (protected) and cannot be disabled via `/manage-commands`.
|
||||||
- [x] Add a Help tab in the frontend Server Settings that lists all bot commands and their descriptions per-server.
|
- [x] Add a Help tab in the frontend Server Settings that lists all bot commands and their descriptions per-server.
|
||||||
- [x] Move Help to a dedicated page within the server dashboard and add a top NavBar (collapsible) with Dashboard, Discord!, Contact, and Help (when on a server) buttons. Ensure Help page has a back arrow to return to the Commands section.
|
- [x] Move Help to a dedicated page within the server dashboard and add a top NavBar (collapsible) with Dashboard, Discord!, Contact, and Help (when on a server) buttons. Ensure Help page has a back arrow to return to the Commands section.
|
||||||
|
- [x] Move Help to a dedicated page within the server dashboard and add a top NavBar (collapsible) with Dashboard, Discord!, and Commands List (when on a server) buttons. Ensure Help page has a back arrow to return to the Commands section.
|
||||||
|
- [x] Remove Contact page from the frontend and App routes (no longer needed).
|
||||||
|
- [x] Redesign NavBar for cleaner layout and prettier appearance.
|
||||||
|
- [x] Redesign NavBar for cleaner layout and prettier appearance. (Title updated to 'ECS - EHDCHADSWORTH')
|
||||||
- [x] Added `/help` slash command that lists commands and their descriptions and shows per-server enable/disable status.
|
- [x] Added `/help` slash command that lists commands and their descriptions and shows per-server enable/disable status.
|
||||||
- [x] **Autorole**
|
- [x] **Autorole**
|
||||||
- [x] Add "Autorole" section to server settings.
|
- [x] Add "Autorole" section to server settings.
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import Dashboard from './components/Dashboard';
|
|||||||
import ServerSettings from './components/ServerSettings';
|
import ServerSettings from './components/ServerSettings';
|
||||||
import NavBar from './components/NavBar';
|
import NavBar from './components/NavBar';
|
||||||
import HelpPage from './components/HelpPage';
|
import HelpPage from './components/HelpPage';
|
||||||
import ContactPage from './components/ContactPage';
|
|
||||||
import DiscordPage from './components/DiscordPage';
|
import DiscordPage from './components/DiscordPage';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -23,7 +22,6 @@ function App() {
|
|||||||
<Route path="/dashboard" element={<Dashboard />} />
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
<Route path="/server/:guildId" element={<ServerSettings />} />
|
<Route path="/server/:guildId" element={<ServerSettings />} />
|
||||||
<Route path="/server/:guildId/help" element={<HelpPage />} />
|
<Route path="/server/:guildId/help" element={<HelpPage />} />
|
||||||
<Route path="/contact" element={<ContactPage />} />
|
|
||||||
<Route path="/discord" element={<DiscordPage />} />
|
<Route path="/discord" element={<DiscordPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useState, useEffect, useLayoutEffect, useContext } from 'react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Grid, Card, CardContent, Typography, Box, CardMedia, IconButton, Snackbar, Alert } from '@mui/material';
|
import { Grid, Card, CardContent, Typography, Box, CardMedia, IconButton, Snackbar, Alert } from '@mui/material';
|
||||||
import { UserContext } from '../contexts/UserContext';
|
import { UserContext } from '../contexts/UserContext';
|
||||||
import UserSettings from './UserSettings';
|
|
||||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||||
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
|
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -119,9 +118,7 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px' }}>
|
<div style={{ padding: '20px' }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
{/* UserSettings moved to NavBar */}
|
||||||
<UserSettings />
|
|
||||||
</Box>
|
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
Dashboard
|
Dashboard
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const HelpPage = () => {
|
|||||||
<div style={{ padding: 20 }}>
|
<div style={{ padding: 20 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
<IconButton onClick={handleBack}><ArrowBackIcon /></IconButton>
|
<IconButton onClick={handleBack}><ArrowBackIcon /></IconButton>
|
||||||
<Typography variant="h5">Help - Commands for this Server</Typography>
|
<Typography variant="h5">Commands List</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ marginTop: 2 }}>
|
<Box sx={{ marginTop: 2 }}>
|
||||||
{commands.length === 0 && <Typography>No commands available.</Typography>}
|
{commands.length === 0 && <Typography>No commands available.</Typography>}
|
||||||
|
|||||||
@@ -1,31 +1,58 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { AppBar, Toolbar, Button, Box, IconButton, Collapse } from '@mui/material';
|
import { AppBar, Toolbar, IconButton, Typography, Box, Menu, MenuItem } from '@mui/material';
|
||||||
|
import UserSettings from './UserSettings';
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { useNavigate, useLocation, useParams } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
const NavBar = () => {
|
const NavBar = () => {
|
||||||
const [open, setOpen] = useState(true);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
// Pull guildId from URL if present
|
const [open, setOpen] = useState(false);
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const guildIdMatch = location.pathname.match(/\/server\/(\d+)/);
|
const guildIdMatch = location.pathname.match(/\/server\/(\d+)/);
|
||||||
const guildId = guildIdMatch ? guildIdMatch[1] : null;
|
const guildId = guildIdMatch ? guildIdMatch[1] : null;
|
||||||
|
|
||||||
|
const toggleOpen = (e) => {
|
||||||
|
if (!open) {
|
||||||
|
setAnchorEl(e.currentTarget);
|
||||||
|
setOpen(true);
|
||||||
|
} else {
|
||||||
|
setAnchorEl(null);
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const closeMenu = () => { setAnchorEl(null); setOpen(false); };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position="static" color="transparent" elevation={0} sx={{ mb: 2 }}>
|
<AppBar position="static" color="transparent" elevation={0} sx={{ mb: 2, borderBottom: '1px solid rgba(0,0,0,0.06)' }}>
|
||||||
<Toolbar>
|
<Toolbar sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', px: { xs: 2, sm: 4 } }}>
|
||||||
<IconButton onClick={() => setOpen(prev => !prev)} aria-label="toggle menu"><MenuIcon /></IconButton>
|
<Box>
|
||||||
<Collapse in={open} orientation="horizontal">
|
<IconButton onClick={toggleOpen} aria-label="menu" size="large" sx={{ bgcolor: open ? 'primary.main' : 'transparent', color: open ? 'white' : 'text.primary' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
{open ? <CloseIcon /> : <MenuIcon />}
|
||||||
<Button onClick={() => navigate('/dashboard')} color="inherit">Dashboard</Button>
|
</IconButton>
|
||||||
<Button onClick={() => navigate('/discord')} color="inherit">Discord!</Button>
|
|
||||||
<Button onClick={() => navigate('/contact')} color="inherit">Contact</Button>
|
|
||||||
{guildId && (
|
|
||||||
<Button startIcon={<HelpOutlineIcon />} onClick={() => navigate(`/server/${guildId}/help`)} color="inherit">Help</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
|
||||||
|
<Typography variant="h6" component="div" sx={{ fontWeight: 800 }} onClick={() => { navigate('/dashboard'); closeMenu(); }}>
|
||||||
|
ECS - EHDCHADSWORTH
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<UserSettings />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={open}
|
||||||
|
onClose={closeMenu}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||||
|
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||||
|
PaperProps={{ sx: { width: { xs: 'calc(100% - 32px)', sm: 300 }, left: { xs: 16, sm: 'auto' } } }}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={() => { navigate('/dashboard'); closeMenu(); }}>Dashboard</MenuItem>
|
||||||
|
<MenuItem onClick={() => { window.open('https://discord.gg/EAqNqWjJMQ', '_blank'); closeMenu(); }}>Join The Discord!</MenuItem>
|
||||||
|
{guildId && <MenuItem onClick={() => { navigate(`/server/${guildId}/help`); closeMenu(); }}>Commands List</MenuItem>}
|
||||||
|
</Menu>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import axios from 'axios';
|
|||||||
import { Button, Typography, Box, IconButton, Switch, Select, MenuItem, FormControl, FormControlLabel, Radio, RadioGroup, TextField, Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
|
import { Button, Typography, Box, IconButton, Switch, Select, MenuItem, FormControl, FormControlLabel, Radio, RadioGroup, TextField, Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import UserSettings from './UserSettings';
|
// UserSettings moved to NavBar
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
|
|
||||||
const ServerSettings = () => {
|
const ServerSettings = () => {
|
||||||
@@ -243,24 +243,48 @@ const ServerSettings = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<UserSettings />
|
{/* UserSettings moved to NavBar */}
|
||||||
</Box>
|
</Box>
|
||||||
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }} expanded={commandsExpanded} onChange={() => setCommandsExpanded(prev => !prev)}>
|
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }} expanded={commandsExpanded} onChange={() => setCommandsExpanded(prev => !prev)}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography variant="h6">Commands</Typography>
|
<Typography variant="h6">Commands</Typography>
|
||||||
</AccordionSummary>
|
</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>
|
<AccordionDetails>
|
||||||
{!isBotInServer && <Typography>Invite the bot to enable commands.</Typography>}
|
{!isBotInServer && <Typography>Invite the bot to enable commands.</Typography>}
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '10px' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, marginTop: '10px' }}>
|
||||||
<Typography>Ping Command</Typography>
|
{commandsList.map(cmd => (
|
||||||
|
<Box key={cmd.name} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: 1, border: '1px solid #eee', borderRadius: 1 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Button variant="contained" onClick={togglePingCommand} disabled={!isBotInServer}>
|
<Typography sx={{ fontWeight: 'bold' }}>{cmd.name}</Typography>
|
||||||
{settings.pingCommand ? 'Disable' : 'Enable'}
|
<Typography variant="body2">{cmd.description}</Typography>
|
||||||
</Button>
|
|
||||||
<Button sx={{ ml: 1 }} variant="outlined" onClick={() => navigate(`/server/${guildId}/help`)}>
|
|
||||||
Open Help Page
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
{['help', 'manage-commands'].includes(cmd.name) ? (
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={true} disabled />}
|
||||||
|
label="Locked"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<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(`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>
|
</Box>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
Reference in New Issue
Block a user