fix(audit): UI — L18 (decorative emoji -> Lucide icons), L19 (gated NotesList timer + create-from-url ref-in-effect)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 13:30:25 +02:00
parent e7fdf75a6c
commit 8c4c9b967e
40 changed files with 277 additions and 130 deletions

View File

@@ -1,11 +1,12 @@
import type { Metadata, Viewport } from 'next';
import { redirect } from 'next/navigation';
import { notFound, redirect } from 'next/navigation';
import { headers } from 'next/headers';
import { eq } from 'drizzle-orm';
import { and, eq } from 'drizzle-orm';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { ports as portsTable } from '@/lib/db/schema/ports';
import { userPortRoles, userProfiles } from '@/lib/db/schema/users';
import { QueryProvider } from '@/providers/query-provider';
import { PortProvider } from '@/providers/port-provider';
@@ -60,7 +61,22 @@ export default async function ScannerLayout({
const port = await db.query.ports.findFirst({
where: eq(portsTable.slug, portSlug),
});
if (!port) redirect('/login');
if (!port) notFound();
// Membership gate (mirrors the dashboard layout): super admins reach
// every port; everyone else needs an explicit user_port_roles row for
// THIS port. Without this the scanner resolved the port by slug alone,
// so any authenticated user could scan receipts into a port they have
// no role on.
const profile = await db.query.userProfiles.findFirst({
where: eq(userProfiles.userId, session.user.id),
});
if (!profile?.isSuperAdmin) {
const membership = await db.query.userPortRoles.findFirst({
where: and(eq(userPortRoles.userId, session.user.id), eq(userPortRoles.portId, port.id)),
});
if (!membership) notFound();
}
return (
<QueryProvider>