first commit
This commit is contained in:
206
src/pages/Settings.jsx
Normal file
206
src/pages/Settings.jsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Tabs,
|
||||
Tab,
|
||||
Box,
|
||||
Stack,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Switch,
|
||||
FormControlLabel,
|
||||
Button,
|
||||
Typography,
|
||||
Divider,
|
||||
Snackbar,
|
||||
Alert
|
||||
} from '@mui/material';
|
||||
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
||||
import TuneOutlinedIcon from '@mui/icons-material/TuneOutlined';
|
||||
import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
|
||||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||
|
||||
import PageHeader from '@/components/PageHeader';
|
||||
import MainCard from '@/components/MainCard';
|
||||
|
||||
const TIMEZONES = ['Asia/Kolkata (IST)', 'Asia/Dubai (GST)', 'UTC', 'America/New_York (EST)'];
|
||||
const LANGUAGES = ['English', 'हिन्दी (Hindi)', 'العربية (Arabic)'];
|
||||
|
||||
function TabPanel({ value, index, children }) {
|
||||
if (value !== index) return null;
|
||||
return <Box sx={{ pt: 1 }}>{children}</Box>;
|
||||
}
|
||||
|
||||
export default function Settings() {
|
||||
const [tab, setTab] = useState(0);
|
||||
const [toast, setToast] = useState(false);
|
||||
|
||||
// General
|
||||
const [general, setGeneral] = useState({
|
||||
orgName: 'Doormile Logistics Pvt. Ltd.',
|
||||
supportEmail: 'support@doormile.in',
|
||||
contact: '+91 98450 11223',
|
||||
timezone: TIMEZONES[0],
|
||||
language: LANGUAGES[0]
|
||||
});
|
||||
|
||||
// Notifications
|
||||
const [notify, setNotify] = useState({
|
||||
newOrders: true,
|
||||
riderStatus: true,
|
||||
invoicePaid: true,
|
||||
weeklyDigest: false,
|
||||
emailAlerts: true,
|
||||
smsAlerts: false
|
||||
});
|
||||
|
||||
// Security
|
||||
const [security, setSecurity] = useState({
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
twoFactor: false
|
||||
});
|
||||
|
||||
const setG = (k) => (e) => setGeneral((p) => ({ ...p, [k]: e.target.value }));
|
||||
const setN = (k) => (e) => setNotify((p) => ({ ...p, [k]: e.target.checked }));
|
||||
const setS = (k) => (e) => setSecurity((p) => ({ ...p, [k]: e.target.value ?? e.target.checked }));
|
||||
|
||||
const save = () => setToast(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Settings"
|
||||
breadcrumbs={[{ label: 'Settings' }]}
|
||||
action={
|
||||
<Button variant="contained" startIcon={<SaveOutlinedIcon />} onClick={save}>
|
||||
Save Changes
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Grid container spacing={2.5}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<MainCard noPadding>
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
value={tab}
|
||||
onChange={(_, v) => setTab(v)}
|
||||
sx={{
|
||||
'& .MuiTab-root': { alignItems: 'flex-start', textTransform: 'none', minHeight: 52, fontWeight: 600 }
|
||||
}}
|
||||
>
|
||||
<Tab icon={<TuneOutlinedIcon fontSize="small" />} iconPosition="start" label="General" />
|
||||
<Tab icon={<NotificationsNoneIcon fontSize="small" />} iconPosition="start" label="Notifications" />
|
||||
<Tab icon={<LockOutlinedIcon fontSize="small" />} iconPosition="start" label="Security" />
|
||||
</Tabs>
|
||||
</MainCard>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={9}>
|
||||
{/* General */}
|
||||
<TabPanel value={tab} index={0}>
|
||||
<MainCard title="Organisation">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Organisation Name" value={general.orgName} onChange={setG('orgName')} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Support Email" value={general.supportEmail} onChange={setG('supportEmail')} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Contact Number" value={general.contact} onChange={setG('contact')} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField select fullWidth label="Timezone" value={general.timezone} onChange={setG('timezone')}>
|
||||
{TIMEZONES.map((t) => (
|
||||
<MenuItem key={t} value={t}>{t}</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField select fullWidth label="Language" value={general.language} onChange={setG('language')}>
|
||||
{LANGUAGES.map((l) => (
|
||||
<MenuItem key={l} value={l}>{l}</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</TabPanel>
|
||||
|
||||
{/* Notifications */}
|
||||
<TabPanel value={tab} index={1}>
|
||||
<MainCard title="Notification Preferences">
|
||||
<Stack divider={<Divider flexItem />} spacing={0}>
|
||||
{[
|
||||
{ k: 'newOrders', t: 'New orders', d: 'Notify when a new order is placed' },
|
||||
{ k: 'riderStatus', t: 'Rider status', d: 'When a rider goes online or offline' },
|
||||
{ k: 'invoicePaid', t: 'Invoice paid', d: 'When a client settles an invoice' },
|
||||
{ k: 'weeklyDigest', t: 'Weekly digest', d: 'A summary of operations every Monday' }
|
||||
].map((row) => (
|
||||
<Stack key={row.k} direction="row" alignItems="center" justifyContent="space-between" sx={{ py: 1.5 }}>
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>{row.t}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">{row.d}</Typography>
|
||||
</Box>
|
||||
<Switch checked={notify[row.k]} onChange={setN(row.k)} />
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1 }}>Channels</Typography>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<FormControlLabel control={<Switch checked={notify.emailAlerts} onChange={setN('emailAlerts')} />} label="Email alerts" />
|
||||
<FormControlLabel control={<Switch checked={notify.smsAlerts} onChange={setN('smsAlerts')} />} label="SMS alerts" />
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</TabPanel>
|
||||
|
||||
{/* Security */}
|
||||
<TabPanel value={tab} index={2}>
|
||||
<Stack spacing={2.5}>
|
||||
<MainCard title="Change Password">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth type="password" label="Current Password" value={security.currentPassword} onChange={setS('currentPassword')} />
|
||||
</Grid>
|
||||
<Grid item xs={12} />
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth type="password" label="New Password" value={security.newPassword} onChange={setS('newPassword')} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth type="password" label="Confirm New Password" value={security.confirmPassword} onChange={setS('confirmPassword')} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
<MainCard title="Two-Factor Authentication">
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>Authenticator app</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Require a one-time code at sign-in for extra security.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Switch checked={security.twoFactor} onChange={(e) => setSecurity((p) => ({ ...p, twoFactor: e.target.checked }))} />
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</Stack>
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Snackbar
|
||||
open={toast}
|
||||
autoHideDuration={2500}
|
||||
onClose={() => setToast(false)}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert severity="success" variant="filled" onClose={() => setToast(false)} sx={{ width: '100%' }}>
|
||||
Settings saved successfully.
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user