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>
48 lines
1.4 KiB
TypeScript
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,
|
|
});
|
|
}
|