Admin Panel (Next.js): - Dashboard with stats - Users list with relationships (watches/watched_by) - User detail pages - Deployments list and detail pages - Devices, Orders, Subscriptions pages - OTP-based admin authentication Backend Optimizations: - Fixed N+1 query problem in admin APIs - Added pagination support - Added .range() and count support to Supabase wrapper - Optimized batch queries with lookup maps Database: - Added migrations for schema evolution - New tables: push_tokens, notification_settings - Updated access model iOS Build Scripts: - build-ios.sh, clear-apple-cache.sh - EAS configuration updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
129 lines
2.9 KiB
JavaScript
129 lines
2.9 KiB
JavaScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
|
|
const navItems = [
|
|
{ href: '/dashboard', label: 'Dashboard', icon: '📊' },
|
|
{ href: '/orders', label: 'Orders', icon: '📦' },
|
|
{ href: '/users', label: 'Users', icon: '👥' },
|
|
{ href: '/deployments', label: 'Deployments', icon: '🏠' },
|
|
{ href: '/devices', label: 'Devices', icon: '📡' },
|
|
{ href: '/subscriptions', label: 'Subscriptions', icon: '💳' },
|
|
];
|
|
|
|
export default function Sidebar() {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem('adminToken');
|
|
localStorage.removeItem('adminUser');
|
|
window.location.href = '/admin/login';
|
|
};
|
|
|
|
return (
|
|
<aside style={styles.sidebar}>
|
|
<div style={styles.logo}>
|
|
<span style={styles.logoText}>WellNuo</span>
|
|
<span style={styles.badge}>Admin</span>
|
|
</div>
|
|
|
|
<nav style={styles.nav}>
|
|
{navItems.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
style={{
|
|
...styles.navItem,
|
|
...(pathname === item.href ? styles.navItemActive : {}),
|
|
}}
|
|
>
|
|
<span style={styles.navIcon}>{item.icon}</span>
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
<div style={styles.footer}>
|
|
<button onClick={handleLogout} style={styles.logoutBtn}>
|
|
Sign Out
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
const styles = {
|
|
sidebar: {
|
|
width: '240px',
|
|
height: '100vh',
|
|
background: 'white',
|
|
borderRight: '1px solid var(--border)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
position: 'fixed',
|
|
left: 0,
|
|
top: 0,
|
|
},
|
|
logo: {
|
|
padding: '24px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px',
|
|
borderBottom: '1px solid var(--border)',
|
|
},
|
|
logoText: {
|
|
fontSize: '20px',
|
|
fontWeight: '700',
|
|
color: 'var(--primary)',
|
|
},
|
|
badge: {
|
|
fontSize: '11px',
|
|
fontWeight: '600',
|
|
color: 'var(--text-muted)',
|
|
background: 'var(--surface)',
|
|
padding: '2px 8px',
|
|
borderRadius: '4px',
|
|
},
|
|
nav: {
|
|
flex: 1,
|
|
padding: '16px 12px',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '4px',
|
|
},
|
|
navItem: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '12px',
|
|
padding: '12px 16px',
|
|
borderRadius: '8px',
|
|
fontSize: '14px',
|
|
fontWeight: '500',
|
|
color: 'var(--text-secondary)',
|
|
transition: 'all 0.2s',
|
|
},
|
|
navItemActive: {
|
|
background: 'var(--surface)',
|
|
color: 'var(--primary)',
|
|
},
|
|
navIcon: {
|
|
fontSize: '18px',
|
|
},
|
|
footer: {
|
|
padding: '16px',
|
|
borderTop: '1px solid var(--border)',
|
|
},
|
|
logoutBtn: {
|
|
width: '100%',
|
|
padding: '10px',
|
|
fontSize: '14px',
|
|
color: 'var(--text-muted)',
|
|
background: 'transparent',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: '8px',
|
|
cursor: 'pointer',
|
|
},
|
|
};
|