feat: user invite flow, deactivate/delete, favicon, dashboard loading fix, admin full-width

- Invite flow: admin can invite users by email with role selection; accept-invite page
  sets password and creates the account; 72-hour token expiry; E2E tests
- User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation
  revokes all active sessions immediately; delete cascades vacation/broadcast records;
  isActive field added via migration 20260402000000_user_isactive
- Auth: block login for inactive users with audit entry
- Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated
- Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area
  on first login before react-grid-layout chunk is cached
- Admin users: remove max-w-5xl constraint so table uses full page width
- Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate
  must run inside the container after schema changes (named node_modules volume)

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-04-02 20:19:26 +02:00
parent dc5bbdc47d
commit 41eb722369
33 changed files with 6755 additions and 169 deletions
@@ -760,7 +760,7 @@ describe("user profile and TOTP self-service", () => {
});
describe("user dashboard and favorites", () => {
it("falls back to the normalized default dashboard layout when stored data is invalid", async () => {
it("returns null layout when stored data has no valid widget types (bug #27 guard)", async () => {
const findUnique = vi.fn().mockResolvedValue({
dashboardLayout: {
widgets: [
@@ -781,12 +781,9 @@ describe("user dashboard and favorites", () => {
where: { id: "user_admin" },
select: { dashboardLayout: true, updatedAt: true },
});
// Widgets with unknown types are dropped → empty → client uses default layout
expect(result).toEqual({
layout: {
version: 2,
gridCols: 12,
widgets: [],
},
layout: null,
updatedAt: new Date("2026-03-30T18:00:00.000Z"),
});
});