Phase 6
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:
73 files changed
+8400
-53
No files matched your search
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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" },
|
||||
];
|
||||
Reference in new issue
Block a user