// ======================================================================= // MariusVPN Admin — Shared UI primitives (cards, buttons, inputs, // toasts, modal, table, charts, icons). // ======================================================================= const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React; // --------------- Icons (Lucide-style inline SVG) ------------------------- const Icon = ({ d, size = 18, stroke = 'currentColor', sw = 1.8, fill = 'none', className = '' }) => ( {Array.isArray(d) ? d.map((p, i) => ) : } ); const ICONS = { dashboard: 'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z', users: ['M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2', 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z', 'M22 21v-2a4 4 0 0 0-3-3.87', 'M17 3.13a4 4 0 0 1 0 7.75'], finance: ['M12 1v22', 'M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6'], server: ['M2 3h20v6H2zM2 15h20v6H2z', 'M6 6h.01', 'M6 18h.01'], traffic: ['M3 17l6-6 4 4 8-8', 'M14 7h7v7'], hwid: ['M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z', 'M3.27 6.96L12 12.01l8.73-5.05', 'M12 22.08V12'], broadcast: ['M3 11l18-8-3 18-6-7-9-3z'], franchise: ['M3 21V7l9-4 9 4v14', 'M9 22V12h6v10'], settings: ['M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z','M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z'], search: ['M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z', 'M21 21l-4.35-4.35'], plus: 'M12 5v14M5 12h14', minus: 'M5 12h14', close: 'M18 6L6 18M6 6l12 12', check: 'M20 6L9 17l-5-5', chevR: 'M9 18l6-6-6-6', chevL: 'M15 18l-6-6 6-6', chevD: 'M6 9l6 6 6-6', logout: ['M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4', 'M16 17l5-5-5-5', 'M21 12H9'], warn: ['M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z', 'M12 9v4', 'M12 17h.01'], ban: ['M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z', 'M4.93 4.93l14.14 14.14'], edit: ['M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7', 'M18.5 2.5a2.12 2.12 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z'], trash: ['M3 6h18', 'M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6', 'M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2'], msg: ['M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'], gift: ['M20 12v10H4V12','M2 7h20v5H2z','M12 22V7','M12 7H7.5a2.5 2.5 0 1 1 0-5C11 2 12 7 12 7z','M12 7h4.5a2.5 2.5 0 1 0 0-5C13 2 12 7 12 7z'], refresh: ['M23 4v6h-6', 'M1 20v-6h6', 'M3.51 9a9 9 0 0 1 14.85-3.36L23 10', 'M1 14l4.64 4.36A9 9 0 0 0 20.49 15'], shield: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'], bell: ['M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9', 'M13.73 21a2 2 0 0 1-3.46 0'], send: ['M22 2L11 13', 'M22 2l-7 20-4-9-9-4 20-7z'], link: ['M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71', 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'], download: ['M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4', 'M7 10l5 5 5-5', 'M12 15V3'], filter: ['M22 3H2l8 9.46V19l4 2v-8.54L22 3z'], calendar: ['M19 4H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z', 'M16 2v4', 'M8 2v4', 'M3 10h18'], copy: ['M20 9h-9a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2z', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'], globe: ['M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z','M2 12h20','M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'], wallet: ['M21 12V7H5a2 2 0 0 1 0-4h14v4', 'M3 5v14a2 2 0 0 0 2 2h16v-5', 'M18 12a2 2 0 0 0 0 4h4v-4z'], trending: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'], picture: ['M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z','M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z','M21 15l-5-5L5 21'], sparkles: ['M12 3l1.91 5.92L20 11l-6.09 1.91L12 19l-1.91-6.09L4 11l5.91-2.08L12 3z'], zap: ['M13 2L3 14h9l-1 8 10-12h-9l1-8z'], }; // --------------- Toasts --------------------------------------------------- const ToastCtx = createContext(null); function ToastProvider({ children }) { const [list, setList] = useState([]); const idRef = useRef(0); const push = useCallback((msg, kind = 'ok') => { const id = ++idRef.current; setList(l => [...l, { id, msg, kind }]); setTimeout(() => setList(l => l.filter(t => t.id !== id)), 3200); }, []); const api = useMemo(() => ({ ok: (m) => push(m, 'ok'), err: (m) => push(m, 'err'), info: (m) => push(m, 'info'), }), [push]); return ( {children}
{list.map(t => (
{t.kind === 'err' ? : t.kind === 'info' ? : }
{t.msg}
))}
); } const useToast = () => useContext(ToastCtx); // --------------- Card / Section ------------------------------------------ const Card = ({ className = '', children, padding = 'p-5', ...rest }) => (
{children}
); const SectionHeader = ({ title, subtitle, right }) => (

{title}

{subtitle &&

{subtitle}

}
{right &&
{right}
}
); // --------------- Buttons ------------------------------------------------- const Button = ({ variant = 'primary', size = 'md', icon, children, className = '', loading, disabled, ...rest }) => { const base = 'inline-flex items-center justify-center gap-2 font-medium rounded-xl transition active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed select-none'; const sizes = { sm: 'h-8 px-3 text-[13px]', md: 'h-10 px-4 text-sm', lg: 'h-12 px-5 text-[15px]' }; const variants = { primary: 'bg-m-accent text-[#062029] hover:bg-cyan-300', secondary: 'bg-white/[0.04] hover:bg-white/[0.08] text-m-text border border-m-line', ghost: 'text-m-mute hover:text-m-text hover:bg-white/[0.04]', danger: 'bg-m-err/15 text-[#FCA5A5] hover:bg-m-err/25 border border-m-err/30', success: 'bg-m-ok/15 text-[#86EFAC] hover:bg-m-ok/25 border border-m-ok/30', gold: 'bg-m-gold/15 text-m-gold hover:bg-m-gold/25 border border-m-gold/30', }; return ( ); }; const IconButton = ({ icon, onClick, title, variant = 'ghost', className = '' }) => { const variants = { ghost: 'text-m-mute hover:text-m-text hover:bg-white/[0.06]', danger: 'text-m-err/80 hover:text-m-err hover:bg-m-err/10', }; return ( ); }; // --------------- Inputs -------------------------------------------------- const fieldCls = 'w-full h-10 px-3.5 rounded-xl bg-m-bg2 border border-m-line text-m-text placeholder:text-m-dim text-sm focus:outline-none focus:border-m-accent/50 focus:bg-m-bg2 transition'; const Field = ({ label, hint, error, children }) => ( ); const Input = React.forwardRef(({ icon, className = '', ...rest }, ref) => (
{icon && }
)); const Textarea = ({ rows = 4, className = '', ...rest }) => (