Owner Administration is complete. Summary of this final stretch of work:

Seed data extended with an inactive staff member, a pending invitation, a redeemed-reward fixture, a service with no eligible staff, and a future-appointment fixture for conflict testing — all idempotent, verified against the dev DB.

Testing brought to 179 unit / 86 integration / 27 E2E tests, all passing together on a freshly seeded, disposable Postgres:

Unit tests for every new pure validator (business hours, categories/services, booking rules, loyalty config, staff creation).
Integration tests covering opening-hours conflicts, last-owner protection, staff deactivation/eligibility conflicts, loyalty redemption's atomic race-guard, and owner-vs-barber/business-isolation authorization — all against a real Postgres.
12 new Playwright owner journeys, which surfaced and fixed two genuine issues along the way: a cookie-clearing race condition in the sign-out-mid-test flow (a real, reproducible timing bug, not just a test artifact), and confirmation that seed fixtures upserted by deterministic ID go stale once "today" has moved on — now handled defensively.
Desktop responsiveness: converted the single-column Business Details, Booking Rules, and Loyalty Configuration forms to a genuine two-column layout at the lg: breakpoint (verified via measured element positions, not just screenshots), confirmed mobile stacking is unaffected.

Final verification: clean tsc, eslint, and next build, plus the full three-suite run against one fresh database as the authoritative pass.
This commit is contained in:
Stefan Lazic committed 2026-08-03 12:56:55 +01:00
1 parent ffee9d6754
commit ec5583f972
73 files changed
+8400 -53

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
import type { Prisma, PrismaClient } from "@/generated/prisma/client";
/**
* A thin, typed wrapper over the (previously unused, see schema.prisma)
* AuditLog table — used for owner actions that change shared business
* configuration (staff, roles, services, hours, booking rules, loyalty,
* reward redemption). Never stores passwords, invitation tokens, or other
* secrets in `metadata` — callers pass only display-safe before/after
* values.
*/
export async function writeAuditLog(
prisma: PrismaClient,
params: {
actorUserId: string | null;
action: string;
entityType: string;
entityId: string;
metadata?: Prisma.InputJsonValue;
},
): Promise<void> {
await prisma.auditLog.create({
data: {
actorUserId: params.actorUserId,
action: params.action,
entityType: params.entityType,
entityId: params.entityId,
metadata: params.metadata,
},
});
}
+16 -6
View File
@@ -1,14 +1,15 @@
import type { BottomNavItem } from "@/components/navigation/bottom-nav";
/**
* Single source of truth for the staff/owner bottom nav. The templates had
* two competing 4-item sets for what is conceptually the same nav
* Single source of truth for the staff bottom nav. The templates had two
* competing 4-item sets for what is conceptually the same nav
* (Calendar/Current/Clients/Settings on most staff+owner screens vs.
* Dashboard/Staff/Calendar/Admin on the Team Management screen — see
* docs/implementation-plan.md §5/§8). Owner-only screens (team, business
* settings, etc.) are reached from Settings rather than getting their own
* top-level tab, so this one list serves both the (staff) and (owner)
* route groups.
* docs/implementation-plan.md §5/§8). Phase 5 resolved that by having
* (owner) reuse this same list; Phase 6 gives (owner) its own
* OWNER_NAV_ITEMS below instead, per that phase's own explicit nav
* requirement (Dashboard/Calendar/Team/Services/Settings) — see
* docs/phase-6-summary.md, "Owner navigation".
*
* Icons are string keys, not component references — this module is
* imported by Server Component layouts, and a Lucide icon component isn't
@@ -21,3 +22,12 @@ export const STAFF_NAV_ITEMS: BottomNavItem[] = [
{ href: "/staff/clients", label: "Clients", icon: "users" },
{ href: "/staff/settings", label: "Settings", icon: "settings" },
];
/** The (owner) route group's own nav — distinct from STAFF_NAV_ITEMS per the Phase 6 brief. "Team" covers staff management (incl. individual staff detail); "Services" covers categories+services; business/booking-rules/loyalty/notifications live under Settings. */
export const OWNER_NAV_ITEMS: BottomNavItem[] = [
{ href: "/owner", label: "Dashboard", icon: "chart-line" },
{ href: "/owner/calendar", label: "Calendar", icon: "calendar-days" },
{ href: "/owner/team", label: "Team", icon: "users" },
{ href: "/owner/services", label: "Services", icon: "scissors" },
{ href: "/owner/settings", label: "Settings", icon: "settings" },
];