updates on the astryx desing

This commit is contained in:
2026-08-14 16:09:14 +05:30
parent d3dd687a8d
commit a46212f291
106 changed files with 10244 additions and 24738 deletions

View File

@@ -1,78 +1,60 @@
import { useState } from 'react';
import { Avatar, Box, Button, Chip, Grid, InputLabel, Paper, Stack, TextField, Typography } from '@mui/material';
import { useMutation, useQuery } from '@tanstack/react-query';
import { MdBadge, MdPerson, MdLocationOn, MdMail, MdShield, MdVerifiedUser, MdLock } from 'react-icons/md';
import CircularLoader from 'components/CircularLoader';
import { enqueueSnackbar } from 'notistack';
import { Grid } from '@astryxdesign/core/Grid';
import { HStack } from '@astryxdesign/core/HStack';
import { VStack } from '@astryxdesign/core/VStack';
import { Card } from '@astryxdesign/core/Card';
import { Text } from '@astryxdesign/core/Text';
import { Button } from '@astryxdesign/core/Button';
import { TextInput } from '@astryxdesign/core/TextInput';
import { Badge } from '@astryxdesign/core/Badge';
import { Avatar } from '@astryxdesign/core/Avatar';
import { Divider } from '@astryxdesign/core/Divider';
import Loader from 'components/Loader';
import PageHeader from 'components/nearle_components/PageHeader';
import { getusers } from 'pages/api/api';
import { updateProfilePassword } from 'pages/api/doormileApi';
import { enqueueSnackbar } from 'notistack';
import { DT, tint, soft } from 'themes/dt/tokens';
import { DT } from 'themes/dt/tokens';
import { AccentAvatar } from 'themes/dt/primitives';
const ROLE_LABELS = { 1: 'Admin', 3: 'Manager', 4: 'Executive' };
const BRAND = '#000000';
const BRAND_LIGHT = '#3A3A3A';
const initialsOf = (name) =>
(name || '')
.trim()
.split(/\s+/)
.slice(0, 2)
.map((w) => w[0])
.join('')
.toUpperCase() || '–';
const BRAND = DT.brand;
// A single read-only field rendered as an icon + uppercase label + value.
const InfoField = ({ icon: Icon, label, value, accent = BRAND }) => (
<Stack direction="row" spacing={1.5} alignItems="flex-start" sx={{ minWidth: 0 }}>
<Avatar sx={{ width: 36, height: 36, bgcolor: soft(accent), color: accent, flexShrink: 0 }}>
<HStack gap={3} vAlign="start">
<AccentAvatar color={accent} selected size={36}>
<Icon size={18} />
</Avatar>
<Box sx={{ minWidth: 0 }}>
<Typography
variant="overline"
sx={{ display: 'block', color: DT.textMuted, lineHeight: 1.6, letterSpacing: '0.06em' }}
>
</AccentAvatar>
<VStack gap={0} padding={0}>
<Text type="label" weight="semibold" color="secondary">
{label}
</Typography>
<Typography
variant="body1"
sx={{ fontWeight: 600, color: DT.textPrimary, wordBreak: 'break-word' }}
title={value || ''}
>
</Text>
<Text weight="semibold" title={value || ''}>
{value || '—'}
</Typography>
</Box>
</Stack>
</Text>
</VStack>
</HStack>
);
// A titled grouping card.
const SectionCard = ({ title, icon: Icon, children }) => (
<Paper
elevation={0}
sx={{
borderRadius: DT.radiusCard + 'px',
border: `1px solid ${DT.borderSubtle}`,
boxShadow: DT.shadowSoft,
overflow: 'hidden',
height: '100%'
}}
>
<Stack
direction="row"
alignItems="center"
spacing={1.25}
sx={{ px: 2.5, py: 1.75, borderBottom: `1px solid ${DT.divider}`, bgcolor: DT.surfaceAlt }}
>
<Avatar sx={{ width: 28, height: 28, bgcolor: soft(BRAND), color: BRAND }}>
<Card padding={0} elevation="none">
<HStack gap={2} vAlign="center" padding={3} style={{ borderBottom: `1px solid ${DT.divider}`, backgroundColor: DT.surfaceAlt }}>
<AccentAvatar color={BRAND} selected size={28}>
<Icon size={16} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
{title}
</Typography>
</Stack>
<Box sx={{ p: 2.5 }}>{children}</Box>
</Paper>
</AccentAvatar>
<Text weight="bold">{title}</Text>
</HStack>
<VStack gap={0} padding={3}>
{children}
</VStack>
</Card>
);
const ViewProfile = () => {
@@ -85,50 +67,31 @@ const ViewProfile = () => {
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const opentoast = (message, variant = 'error') =>
enqueueSnackbar(message, { variant, anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 });
const changePasswordMutation = useMutation({
mutationFn: () => updateProfilePassword(currentPassword, newPassword),
onSuccess: (res) => {
if (res.success) {
enqueueSnackbar('Password updated successfully', {
variant: 'success',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
opentoast('Password updated successfully', 'success');
setCurrentPassword('');
setNewPassword('');
setConfirmPassword('');
} else {
enqueueSnackbar(res.message || 'Failed to update password', {
variant: 'error',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
opentoast(res.message || 'Failed to update password');
}
},
onError: (err) => {
enqueueSnackbar(err.response?.data?.message || err.message || 'Failed to update password', {
variant: 'error',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
}
onError: (err) => opentoast(err.response?.data?.message || err.message || 'Failed to update password')
});
const handleChangePassword = () => {
if (!currentPassword || !newPassword) {
enqueueSnackbar('Fill current and new password', {
variant: 'error',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
opentoast('Fill current and new password');
return;
}
if (newPassword !== confirmPassword) {
enqueueSnackbar('New password and confirmation do not match', {
variant: 'error',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
opentoast('New password and confirmation do not match');
return;
}
changePasswordMutation.mutate();
@@ -145,146 +108,75 @@ const ViewProfile = () => {
return (
<>
{isLoading && (
<>
<Loader />
<CircularLoader />
</>
)}
{isLoading && <Loader />}
<Box sx={{ p: { xs: 1.5, md: 0 } }}>
{/* ---- Gradient brand header ---- */}
<Paper
elevation={0}
sx={{
borderRadius: DT.radiusCard + 'px',
border: `1px solid ${DT.borderSubtle}`,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowSoft,
p: { xs: 2.5, md: 3.5 },
mb: { xs: 2.5, md: 3 }
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
spacing={{ xs: 2, sm: 3 }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
>
<Avatar
sx={{
width: { xs: 64, md: 76 },
height: { xs: 64, md: 76 },
bgcolor: BRAND,
color: '#fff',
fontSize: { xs: 24, md: 28 },
fontWeight: 700,
boxShadow: '0 10px 24px rgba(0, 0, 0, 0.35)'
}}
>
{initialsOf(fullname)}
</Avatar>
<Box sx={{ minWidth: 0 }}>
<Typography variant="h3" sx={{ color: DT.textPrimary, fontWeight: 700, mb: 0.5 }}>
{fullname}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap alignItems="center">
{roleLabel && (
<Chip
size="small"
icon={<MdVerifiedUser size={14} style={{ color: BRAND }} />}
label={roleLabel}
sx={{
bgcolor: soft(BRAND),
color: BRAND,
fontWeight: 700,
border: `1px solid ${BRAND}40`,
'& .MuiChip-icon': { ml: '6px' }
}}
/>
)}
{(userData?.id != null || userData?.userid != null) && (
<Typography variant="body2" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
ID&nbsp;#{userData.id ?? userData.userid}
</Typography>
)}
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ color: DT.textSecondary }}>
<MdLocationOn size={15} />
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{tenantLabel}
</Typography>
</Stack>
</Stack>
</Box>
</Stack>
</Paper>
<PageHeader title="My Profile" subtitle="Account & Security" />
{/* ---- Grouped detail cards ---- */}
<Grid container spacing={{ xs: 2.5, md: 3 }}>
<Grid item xs={12} md={6}>
<SectionCard title="Account" icon={MdBadge}>
<Stack spacing={2.5}>
<InfoField icon={MdPerson} label="User Name" value={userData?.name || userData?.fullname} />
<InfoField icon={MdBadge} label="User ID" value={userData?.id ?? userData?.userid} />
<InfoField icon={MdMail} label="E-Mail" value={userData?.email} accent="#0ea5e9" />
<InfoField icon={MdVerifiedUser} label="Role" value={roleLabel} />
<InfoField icon={MdLocationOn} label="Tenant" value={tenantLabel} accent="#14b8a6" />
</Stack>
</SectionCard>
</Grid>
<Card padding={4} elevation="none" style={{ marginTop: 16, marginBottom: 20 }}>
<HStack gap={3} vAlign="center" wrap="wrap">
<Avatar name={fullname} size={72} tooltip={false} />
<VStack gap={2} padding={0}>
<Text type="heading" weight="bold">
{fullname}
</Text>
<HStack gap={2} vAlign="center" wrap="wrap">
{roleLabel !== '—' && <Badge variant="neutral" icon={<MdVerifiedUser size={14} />} label={roleLabel} />}
{(userData?.id != null || userData?.userid != null) && (
<Text color="secondary" weight="semibold">
ID #{userData.id ?? userData.userid}
</Text>
)}
<HStack gap={1} vAlign="center">
<MdLocationOn size={15} color={DT.textSecondary} />
<Text color="secondary" weight="semibold">
{tenantLabel}
</Text>
</HStack>
</HStack>
</VStack>
</HStack>
</Card>
<Grid item xs={12} md={6}>
<SectionCard title="Security" icon={MdShield}>
<Stack spacing={2}>
<Stack spacing={1}>
<InputLabel htmlFor="current-password">Current Password</InputLabel>
<TextField
id="current-password"
type="password"
fullWidth
placeholder="Current password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
autoComplete="off"
/>
</Stack>
<Stack spacing={1}>
<InputLabel htmlFor="new-password">New Password</InputLabel>
<TextField
id="new-password"
type="password"
fullWidth
placeholder="New password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
autoComplete="off"
/>
</Stack>
<Stack spacing={1}>
<InputLabel htmlFor="confirm-password">Confirm New Password</InputLabel>
<TextField
id="confirm-password"
type="password"
fullWidth
placeholder="Confirm new password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
autoComplete="off"
/>
</Stack>
<Button
variant="contained"
startIcon={<MdLock size={16} />}
onClick={handleChangePassword}
disabled={changePasswordMutation.isPending}
sx={{ alignSelf: 'flex-start' }}
>
Update Password
</Button>
</Stack>
</SectionCard>
</Grid>
</Grid>
</Box>
<Grid columns={{ minWidth: 320 }} gap={3}>
<SectionCard title="Account" icon={MdBadge}>
<VStack gap={4} padding={0}>
<InfoField icon={MdPerson} label="User Name" value={userData?.name || userData?.fullname} />
<InfoField icon={MdBadge} label="User ID" value={userData?.id ?? userData?.userid} />
<InfoField icon={MdMail} label="E-Mail" value={userData?.email} accent="#0ea5e9" />
<InfoField icon={MdVerifiedUser} label="Role" value={roleLabel} />
<InfoField icon={MdLocationOn} label="Tenant" value={tenantLabel} accent="#14b8a6" />
</VStack>
</SectionCard>
<SectionCard title="Security" icon={MdShield}>
<VStack gap={3} padding={0}>
<TextInput
label="Current Password"
type="password"
value={currentPassword}
onChange={setCurrentPassword}
placeholder="Current password"
/>
<TextInput label="New Password" type="password" value={newPassword} onChange={setNewPassword} placeholder="New password" />
<TextInput
label="Confirm New Password"
type="password"
value={confirmPassword}
onChange={setConfirmPassword}
placeholder="Confirm new password"
/>
<Divider />
<Button
label="Update Password"
variant="primary"
icon={<MdLock size={16} />}
onClick={handleChangePassword}
isLoading={changePasswordMutation.isPending}
style={{ alignSelf: 'flex-start' }}
/>
</VStack>
</SectionCard>
</Grid>
</>
);
};