mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
- Permission store (Zustand) with wildcard matching and role level checks - PermissionRoute guard for route-level access control - PermissionGate component for element-level visibility - Admin Roles page: CRUD, permission editor, role assignment - Admin Policies page: ABAC policy management (time/IP conditions) - Admin Audit Log page: filterable log viewer with CSV export - AdminPanel sidebar navigation gated by permissions - 4 locale files updated (en, ru, zh, fa) with RBAC translations - API client module for all RBAC endpoints
668 lines
28 KiB
TypeScript
668 lines
28 KiB
TypeScript
import { Link } from 'react-router';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { RemnawaveIcon, ArrowPathIcon } from '../components/icons';
|
|
import { usePermissionStore } from '@/store/permissions';
|
|
|
|
// Group header icons
|
|
const AnalyticsGroupIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5m.75-9l3-3 2.148 2.148A12.061 12.061 0 0116.5 7.605"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const UsersGroupIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const TariffsGroupIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const MarketingGroupIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const SystemGroupIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
|
/>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
);
|
|
|
|
// Modern icons with consistent styling
|
|
const ChartBarIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const BanknotesIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const UsersIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ChatBubbleIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const NoSymbolIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const CreditCardIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const TicketIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const GiftIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M21 11.25v8.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 109.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1114.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const UserGroupIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const MegaphoneIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const PinnedMessageIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"
|
|
/>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
);
|
|
|
|
const PaperAirplaneIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const SparklesIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const HandshakeIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const CogIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
|
/>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
);
|
|
|
|
const DevicePhoneMobileIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ServerStackIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const EnvelopeIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ArrowsUpDownIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ChevronRightIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
|
</svg>
|
|
);
|
|
|
|
const SecurityGroupIcon = () => (
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ShieldIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const UserPlusIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const DocumentCheckIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M10.125 2.25h-4.5c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625l-2.625 2.625-1.125-1.125"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
const ClipboardDocumentListIcon = () => (
|
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15a2.25 2.25 0 012.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
interface AdminItem {
|
|
to: string;
|
|
icon: React.ReactNode;
|
|
title: string;
|
|
description: string;
|
|
permission: string;
|
|
}
|
|
|
|
interface AdminGroup {
|
|
id: string;
|
|
title: string;
|
|
icon: React.ReactNode;
|
|
gradient: string;
|
|
borderColor: string;
|
|
iconBg: string;
|
|
iconColor: string;
|
|
items: AdminItem[];
|
|
}
|
|
|
|
function AdminCard({
|
|
to,
|
|
icon,
|
|
title,
|
|
description,
|
|
iconBg,
|
|
iconColor,
|
|
}: AdminItem & { iconBg: string; iconColor: string }) {
|
|
return (
|
|
<Link
|
|
to={to}
|
|
className="group flex items-center gap-3 rounded-xl border border-dark-700/50 bg-dark-800/40 p-3 transition-all duration-200 hover:border-dark-600 hover:bg-dark-800/80"
|
|
>
|
|
<div
|
|
className={`h-10 w-10 rounded-lg ${iconBg} ${iconColor} flex shrink-0 items-center justify-center transition-transform group-hover:scale-105`}
|
|
>
|
|
{icon}
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="text-sm font-medium text-dark-100 transition-colors group-hover:text-white">
|
|
{title}
|
|
</h3>
|
|
<p className="truncate text-xs text-dark-500">{description}</p>
|
|
</div>
|
|
<div className="text-dark-600 transition-colors group-hover:text-dark-400">
|
|
<ChevronRightIcon />
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function GroupSection({ group }: { group: AdminGroup }) {
|
|
const hasPermission = usePermissionStore((state) => state.hasPermission);
|
|
const visibleItems = group.items.filter((item) => hasPermission(item.permission));
|
|
|
|
if (visibleItems.length === 0) return null;
|
|
|
|
return (
|
|
<div className="overflow-hidden rounded-2xl border border-dark-700/50 bg-dark-900/30 backdrop-blur">
|
|
{/* Group Header */}
|
|
<div className={`bg-gradient-to-r px-4 py-3 ${group.gradient} border-b ${group.borderColor}`}>
|
|
<div className="flex items-center gap-2.5">
|
|
<div className={`rounded-lg p-1.5 ${group.iconBg} ${group.iconColor}`}>{group.icon}</div>
|
|
<h2 className="text-sm font-semibold text-dark-100">{group.title}</h2>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Group Items */}
|
|
<div className="space-y-1.5 p-2">
|
|
{visibleItems.map((item) => (
|
|
<AdminCard key={item.to} {...item} iconBg={group.iconBg} iconColor={group.iconColor} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function AdminPanel() {
|
|
const { t } = useTranslation();
|
|
|
|
const groups: AdminGroup[] = [
|
|
{
|
|
id: 'analytics',
|
|
title: t('admin.groups.analytics'),
|
|
icon: <AnalyticsGroupIcon />,
|
|
gradient: 'from-success-500/10 to-success-500/5',
|
|
borderColor: 'border-success-500/20',
|
|
iconBg: 'bg-success-500/20',
|
|
iconColor: 'text-success-400',
|
|
items: [
|
|
{
|
|
to: '/admin/dashboard',
|
|
icon: <ChartBarIcon />,
|
|
title: t('admin.nav.dashboard'),
|
|
description: t('admin.panel.dashboardDesc'),
|
|
permission: 'stats:read',
|
|
},
|
|
{
|
|
to: '/admin/payments',
|
|
icon: <BanknotesIcon />,
|
|
title: t('admin.nav.payments'),
|
|
description: t('admin.panel.paymentsDesc'),
|
|
permission: 'payments:read',
|
|
},
|
|
{
|
|
to: '/admin/traffic-usage',
|
|
icon: <ArrowsUpDownIcon />,
|
|
title: t('admin.nav.trafficUsage'),
|
|
description: t('admin.panel.trafficUsageDesc'),
|
|
permission: 'traffic:read',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'users',
|
|
title: t('admin.groups.users'),
|
|
icon: <UsersGroupIcon />,
|
|
gradient: 'from-accent-500/10 to-accent-500/5',
|
|
borderColor: 'border-accent-500/20',
|
|
iconBg: 'bg-accent-500/20',
|
|
iconColor: 'text-accent-400',
|
|
items: [
|
|
{
|
|
to: '/admin/users',
|
|
icon: <UsersIcon />,
|
|
title: t('admin.nav.users'),
|
|
description: t('admin.panel.usersDesc'),
|
|
permission: 'users:read',
|
|
},
|
|
{
|
|
to: '/admin/tickets',
|
|
icon: <ChatBubbleIcon />,
|
|
title: t('admin.nav.tickets'),
|
|
description: t('admin.panel.ticketsDesc'),
|
|
permission: 'tickets:read',
|
|
},
|
|
{
|
|
to: '/admin/ban-system',
|
|
icon: <NoSymbolIcon />,
|
|
title: t('admin.nav.banSystem'),
|
|
description: t('admin.panel.banSystemDesc'),
|
|
permission: 'ban_system:read',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'tariffs',
|
|
title: t('admin.groups.tariffs'),
|
|
icon: <TariffsGroupIcon />,
|
|
gradient: 'from-warning-500/10 to-warning-500/5',
|
|
borderColor: 'border-warning-500/20',
|
|
iconBg: 'bg-warning-500/20',
|
|
iconColor: 'text-warning-400',
|
|
items: [
|
|
{
|
|
to: '/admin/tariffs',
|
|
icon: <CreditCardIcon />,
|
|
title: t('admin.nav.tariffs'),
|
|
description: t('admin.panel.tariffsDesc'),
|
|
permission: 'tariffs:read',
|
|
},
|
|
{
|
|
to: '/admin/promocodes',
|
|
icon: <TicketIcon />,
|
|
title: t('admin.nav.promocodes'),
|
|
description: t('admin.panel.promocodesDesc'),
|
|
permission: 'promocodes:read',
|
|
},
|
|
{
|
|
to: '/admin/promo-groups',
|
|
icon: <UserGroupIcon />,
|
|
title: t('admin.nav.promoGroups'),
|
|
description: t('admin.panel.promoGroupsDesc'),
|
|
permission: 'promo_groups:read',
|
|
},
|
|
{
|
|
to: '/admin/promo-offers',
|
|
icon: <GiftIcon />,
|
|
title: t('admin.nav.promoOffers'),
|
|
description: t('admin.panel.promoOffersDesc'),
|
|
permission: 'promo_offers:read',
|
|
},
|
|
{
|
|
to: '/admin/payment-methods',
|
|
icon: <BanknotesIcon />,
|
|
title: t('admin.nav.paymentMethods'),
|
|
description: t('admin.panel.paymentMethodsDesc'),
|
|
permission: 'payment_methods:read',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'marketing',
|
|
title: t('admin.groups.marketing'),
|
|
icon: <MarketingGroupIcon />,
|
|
gradient: 'from-error-500/10 to-error-500/5',
|
|
borderColor: 'border-error-500/20',
|
|
iconBg: 'bg-error-500/20',
|
|
iconColor: 'text-error-400',
|
|
items: [
|
|
{
|
|
to: '/admin/campaigns',
|
|
icon: <MegaphoneIcon />,
|
|
title: t('admin.nav.campaigns'),
|
|
description: t('admin.panel.campaignsDesc'),
|
|
permission: 'campaigns:read',
|
|
},
|
|
{
|
|
to: '/admin/broadcasts',
|
|
icon: <PaperAirplaneIcon />,
|
|
title: t('admin.nav.broadcasts'),
|
|
description: t('admin.panel.broadcastsDesc'),
|
|
permission: 'broadcasts:read',
|
|
},
|
|
{
|
|
to: '/admin/pinned-messages',
|
|
icon: <PinnedMessageIcon />,
|
|
title: t('admin.nav.pinnedMessages'),
|
|
description: t('admin.panel.pinnedMessagesDesc'),
|
|
permission: 'pinned_messages:read',
|
|
},
|
|
{
|
|
to: '/admin/wheel',
|
|
icon: <SparklesIcon />,
|
|
title: t('admin.nav.wheel'),
|
|
description: t('admin.panel.wheelDesc'),
|
|
permission: 'wheel:read',
|
|
},
|
|
{
|
|
to: '/admin/partners',
|
|
icon: <HandshakeIcon />,
|
|
title: t('admin.nav.partners'),
|
|
description: t('admin.panel.partnersDesc'),
|
|
permission: 'partners:read',
|
|
},
|
|
{
|
|
to: '/admin/withdrawals',
|
|
icon: <BanknotesIcon />,
|
|
title: t('admin.nav.withdrawals'),
|
|
description: t('admin.panel.withdrawalsDesc'),
|
|
permission: 'withdrawals:read',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'system',
|
|
title: t('admin.groups.system'),
|
|
icon: <SystemGroupIcon />,
|
|
gradient: 'from-violet-500/10 to-violet-500/5',
|
|
borderColor: 'border-violet-500/20',
|
|
iconBg: 'bg-violet-500/20',
|
|
iconColor: 'text-violet-400',
|
|
items: [
|
|
{
|
|
to: '/admin/channel-subscriptions',
|
|
icon: <MegaphoneIcon />,
|
|
title: t('admin.nav.channelSubscriptions'),
|
|
description: t('admin.panel.channelSubscriptionsDesc'),
|
|
permission: 'channels:read',
|
|
},
|
|
{
|
|
to: '/admin/settings',
|
|
icon: <CogIcon />,
|
|
title: t('admin.nav.settings'),
|
|
description: t('admin.panel.settingsDesc'),
|
|
permission: 'settings:read',
|
|
},
|
|
{
|
|
to: '/admin/apps',
|
|
icon: <DevicePhoneMobileIcon />,
|
|
title: t('admin.nav.apps'),
|
|
description: t('admin.panel.appsDesc'),
|
|
permission: 'apps:read',
|
|
},
|
|
{
|
|
to: '/admin/servers',
|
|
icon: <ServerStackIcon />,
|
|
title: t('admin.nav.servers'),
|
|
description: t('admin.panel.serversDesc'),
|
|
permission: 'servers:read',
|
|
},
|
|
{
|
|
to: '/admin/remnawave',
|
|
icon: <RemnawaveIcon />,
|
|
title: t('admin.nav.remnawave'),
|
|
description: t('admin.panel.remnawaveDesc'),
|
|
permission: 'remnawave:read',
|
|
},
|
|
{
|
|
to: '/admin/email-templates',
|
|
icon: <EnvelopeIcon />,
|
|
title: t('admin.nav.emailTemplates'),
|
|
description: t('admin.panel.emailTemplatesDesc'),
|
|
permission: 'email_templates:read',
|
|
},
|
|
{
|
|
to: '/admin/updates',
|
|
icon: <ArrowPathIcon className="h-5 w-5" />,
|
|
title: t('admin.nav.updates'),
|
|
description: t('admin.panel.updatesDesc'),
|
|
permission: 'updates:read',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'security',
|
|
title: t('admin.groups.security'),
|
|
icon: <SecurityGroupIcon />,
|
|
gradient: 'from-red-500/10 to-orange-500/5',
|
|
borderColor: 'border-red-500/20',
|
|
iconBg: 'bg-red-500/20',
|
|
iconColor: 'text-red-400',
|
|
items: [
|
|
{
|
|
to: '/admin/roles',
|
|
icon: <ShieldIcon />,
|
|
title: t('admin.nav.roles'),
|
|
description: t('admin.panel.rolesDesc'),
|
|
permission: 'roles:read',
|
|
},
|
|
{
|
|
to: '/admin/roles/assign',
|
|
icon: <UserPlusIcon />,
|
|
title: t('admin.nav.roleAssign'),
|
|
description: t('admin.panel.roleAssignDesc'),
|
|
permission: 'roles:assign',
|
|
},
|
|
{
|
|
to: '/admin/policies',
|
|
icon: <DocumentCheckIcon />,
|
|
title: t('admin.nav.policies'),
|
|
description: t('admin.panel.policiesDesc'),
|
|
permission: 'roles:read',
|
|
},
|
|
{
|
|
to: '/admin/audit-log',
|
|
icon: <ClipboardDocumentListIcon />,
|
|
title: t('admin.nav.auditLog'),
|
|
description: t('admin.panel.auditLogDesc'),
|
|
permission: 'audit_log:read',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="animate-fade-in space-y-4">
|
|
{/* Header */}
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold text-dark-100">{t('admin.panel.title')}</h1>
|
|
<p className="mt-1 text-sm text-dark-400">{t('admin.panel.subtitle')}</p>
|
|
</div>
|
|
|
|
{/* Groups Grid */}
|
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
{groups.map((group) => (
|
|
<GroupSection key={group.id} group={group} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|