Files
pn-new-crm/src/hooks/use-onboarding-status.ts
Matt adc9802361
All checks were successful
Build & Push Docker Images / lint (push) Successful in 3m3s
Build & Push Docker Images / build-and-push (push) Successful in 8m23s
fix(rbac): sales/operational roles see deal alerts; quiet admin-only onboarding probe
UAT findings from the Sales-role functional walkthrough:

F1 — The deal-alert feed (stale interest, hot-lead-silent, EOI unsigned,
signer overdue, reservation-needs-agreement, berth stalled, expense dupes)
was gated on admin.view_audit_log, so salespeople got a 403 on the Alerts
inbox. None of the 9 alert rules are audit/security signals — they're all
operational — so re-gate the list route to interests.view (sales, director,
viewer get it; external residential partners don't) and hide the Alerts
section in the inbox for users without it instead of letting the query 403.

F2 — Non-admins triggered /api/v1/admin/onboarding/status (admin-only) and
ate a 403 in the console. Make useOnboardingStatus strictly opt-in
(enabled: opts.enabled === true) so a transient/stale isSuperAdmin during
permission hydration can't fire the privileged request.

1664 vitest pass; tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:49:12 +02:00

48 lines
1.4 KiB
TypeScript

'use client';
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api/client';
export interface OnboardingStatusStep {
id: string;
href: string;
label: string;
description: string;
done: boolean;
auto: boolean;
}
export interface OnboardingStatusPayload {
steps: OnboardingStatusStep[];
completed: number;
total: number;
percent: number;
isComplete: boolean;
nextStep: { id: string; label: string; href: string } | null;
}
/**
* Shared onboarding-status query. Drives the topbar banner, dashboard tile,
* and the admin checklist summary. Cached for 60s so all three surfaces
* share a single fetch on first paint.
*
* Defaults to OFF: the endpoint is admin-only (admin.manage_settings), so
* callers must opt in with `enabled: true` once they've confirmed the user is
* a super_admin. This prevents a transient 403 (e.g. a stale `isSuperAdmin`
* during permission hydration) from firing the privileged request for
* non-admins.
*/
export function useOnboardingStatus(opts: { enabled?: boolean } = {}) {
return useQuery<OnboardingStatusPayload>({
queryKey: ['admin', 'onboarding-status'],
queryFn: () =>
apiFetch<{ data: OnboardingStatusPayload }>('/api/v1/admin/onboarding/status').then(
(r) => r.data,
),
staleTime: 60_000,
enabled: opts.enabled === true,
retry: false,
});
}