Enforce granular capabilities across cashup
Backend (server-side enforcement, not just UI): - auth.js: read caps from JWT; hasCap() + requireCap() helpers; legacy-token fallback (full access minus settings) so existing sessions keep working until re-login - finalise: submit final, delete draft, bulk-finalise, attachments - reports: multiday report, cash summary, debtors - floats: float management + safe count - settings: settings mutations (was is_admin) - count: draft save, newbook fetch Frontend: - can(user, cap) helper; User.caps from /verify - Nav items, routes and actions (Submit Final, delete, bulk-finalise) gated on capabilities; non-finalisers see a draft-only hint Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43c332be9b
commit
be670f724d
13 changed files with 117 additions and 52 deletions
|
|
@ -2,21 +2,22 @@ import { NavLink, useNavigate } from 'react-router-dom'
|
|||
import {
|
||||
Banknote, ClipboardList, BarChart2, Wallet, Vault, FileText, Settings, LogOut,
|
||||
} from 'lucide-react'
|
||||
import type { User } from '../types'
|
||||
import { can, type User, type CashupCap } from '../types'
|
||||
|
||||
interface Props {
|
||||
user: User
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{ to: '/daily', label: 'Daily Cash Up', icon: Banknote },
|
||||
// `cap` gates the nav item's visibility; undefined = always shown (app access is enough).
|
||||
const navItems: { to: string; label: string; icon: typeof Banknote; cap?: CashupCap }[] = [
|
||||
{ to: '/daily', label: 'Daily Cash Up', icon: Banknote, cap: 'count' },
|
||||
{ to: '/history', label: 'History', icon: ClipboardList },
|
||||
{ to: '/report', label: 'Weekly Report', icon: BarChart2 },
|
||||
{ to: '/floats', label: 'Float Management', icon: Wallet },
|
||||
{ to: '/safe', label: 'Safe Count', icon: Vault },
|
||||
{ to: '/summary', label: 'Cash Summary', icon: FileText },
|
||||
{ to: '/settings',label: 'Settings', icon: Settings },
|
||||
{ to: '/report', label: 'Weekly Report', icon: BarChart2, cap: 'reports' },
|
||||
{ to: '/floats', label: 'Float Management', icon: Wallet, cap: 'floats' },
|
||||
{ to: '/safe', label: 'Safe Count', icon: Vault, cap: 'floats' },
|
||||
{ to: '/summary', label: 'Cash Summary', icon: FileText, cap: 'reports' },
|
||||
{ to: '/settings',label: 'Settings', icon: Settings, cap: 'settings' },
|
||||
]
|
||||
|
||||
export function Layout({ user, children }: Props) {
|
||||
|
|
@ -45,7 +46,7 @@ export function Layout({ user, children }: Props) {
|
|||
</div>
|
||||
|
||||
<div style={{ flex: 1, padding: '0.5rem 0', overflowY: 'auto' }}>
|
||||
{navItems.map(({ to, label, icon: Icon }) => (
|
||||
{navItems.filter(item => !item.cap || can(user, item.cap)).map(({ to, label, icon: Icon }) => (
|
||||
<NavLink key={to} to={to} style={({ isActive }) => ({
|
||||
display: 'flex', alignItems: 'center', gap: '0.625rem',
|
||||
padding: '0.625rem 1rem', textDecoration: 'none',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue