ui phase 8
This commit is contained in:
@@ -107,4 +107,13 @@ export const profilesClientApi: ProfilesApi = {
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
// ui-phase-8: the real, previously-unwired go-live switch — flips `is_accepting_bookings`
|
||||
// independently of `is_verified`; the server reindexes the nurse's search rows in-transaction.
|
||||
setAcceptingBookings: async (accepting: boolean): Promise<void> => {
|
||||
await clientFetch<ApiEnvelope<boolean>>(`${BASE}/nurse_profiles/set_accepting_bookings`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ accepting }),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,4 +70,11 @@ export const profilesMockApi: ProfilesApi = {
|
||||
// object-storage URL (REQ-006).
|
||||
return { url: URL.createObjectURL(file) };
|
||||
},
|
||||
|
||||
// Mirrors the real endpoint's flip: `isVerified` is never touched, matching the guarded server field.
|
||||
setAcceptingBookings: async (accepting: boolean): Promise<void> => {
|
||||
await sleep(MOCK_LATENCY_MS);
|
||||
if (!nurseProfile) return;
|
||||
nurseProfile = { ...nurseProfile, isAcceptingBookings: accepting };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { profilesApi } from '../apis';
|
||||
import { profileKeys } from '../keys';
|
||||
|
||||
/**
|
||||
* The real go-live switch (`POST nurse_profiles/set_accepting_bookings`) — flips
|
||||
* `isAcceptingBookings` independently of verification. Invalidates the nurse profile so the
|
||||
* activation checklist / publish gate re-render the live state from the server, never an optimistic
|
||||
* "published" toast before the mutation actually succeeds.
|
||||
*/
|
||||
export function useSetAcceptingBookings() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (accepting: boolean) => profilesApi.setAcceptingBookings(accepting),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: profileKeys.nurse() }),
|
||||
});
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export { useUpsertCustomerProfile } from './hooks/useUpsertCustomerProfile';
|
||||
export { useNurseProfile } from './hooks/useNurseProfile';
|
||||
export { useUpsertNurseProfile } from './hooks/useUpsertNurseProfile';
|
||||
export { useUploadAvatar } from './hooks/useUploadAvatar';
|
||||
export { useSetAcceptingBookings } from './hooks/useSetAcceptingBookings';
|
||||
|
||||
@@ -73,4 +73,10 @@ export interface ProfilesApi {
|
||||
getNurseProfile(): Promise<NurseProfile | null>;
|
||||
upsertNurseProfile(input: UpsertNurseProfileInput): Promise<NurseProfile>;
|
||||
uploadAvatar(file: File): Promise<AvatarUploadResult>;
|
||||
/**
|
||||
* The real, previously-unwired `POST nurse_profiles/set_accepting_bookings` — flips the
|
||||
* nurse's go-live switch independently of `isVerified` (the server reindexes every one of the
|
||||
* nurse's search rows in the same transaction). Never a client-side simulation of "publish".
|
||||
*/
|
||||
setAcceptingBookings(accepting: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ let nextStepId = 1;
|
||||
let nextDocId = 1;
|
||||
let boundNationalId = '';
|
||||
let approvedAt: string | null = null;
|
||||
// REQ-055: stamped once when the nurse first leaves `not_started` — stands in for the not-yet-served
|
||||
// nurse-facing `submittedAt` (B6's timestamp line).
|
||||
let submittedAt: string | null = null;
|
||||
// REQ-056: the structured B5 fields, kept so a returning nurse's form hydrates instead of re-prompting
|
||||
// blank — never the raw INO number, which the real server would never re-serve either.
|
||||
let credentialSubmission: NonNullable<VerificationStatus['credentialSubmission']> | null = null;
|
||||
|
||||
const nationalIdShape = new RegExp(`^\\d{${NATIONAL_ID_LENGTH}}$`);
|
||||
|
||||
@@ -56,13 +62,20 @@ function setStepStatus(code: string, status: VerificationStepStatus, failureReas
|
||||
/** Re-aggregate exactly as the server would: approved only when every step passes; blockers are the rest. */
|
||||
function aggregate(): VerificationStatus {
|
||||
if (steps.length === 0) {
|
||||
return { status: 'not_started', isBookable: false, blockingSteps: [], steps: [] };
|
||||
return { status: 'not_started', isBookable: false, blockingSteps: [], steps: [], submittedAt: null };
|
||||
}
|
||||
const blockingSteps = steps.filter((step) => step.status !== 'passed').map((step) => step.code);
|
||||
const allPassed = blockingSteps.length === 0;
|
||||
const anyInReview = steps.some((step) => step.status === 'in_review');
|
||||
const status = allPassed ? 'approved' : anyInReview ? 'in_review' : 'pending';
|
||||
return { status, isBookable: allPassed, blockingSteps, steps: steps.map((step) => ({ ...step })) };
|
||||
return {
|
||||
status,
|
||||
isBookable: allPassed,
|
||||
blockingSteps,
|
||||
steps: steps.map((step) => ({ ...step })),
|
||||
submittedAt,
|
||||
credentialSubmission,
|
||||
};
|
||||
}
|
||||
|
||||
function seedSteps(): void {
|
||||
@@ -76,6 +89,7 @@ function seedSteps(): void {
|
||||
expiresAt: null,
|
||||
failureReason: null,
|
||||
}));
|
||||
submittedAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
/* --- Admin review queue fixtures + state ---------------------------------------------------------
|
||||
@@ -351,6 +365,14 @@ export const verificationMockApi: VerificationApi = {
|
||||
if (input.inoNumber.trim().length === 0) {
|
||||
throw new ApiError(400, 'INO number is required', 'ino_number_required');
|
||||
}
|
||||
// Stands in for REQ-056 — the mock persists what a real read-back would serve (never the raw number).
|
||||
credentialSubmission = {
|
||||
inoNumberSubmitted: true,
|
||||
specialties: input.specialties,
|
||||
issuingAuthority: input.issuingAuthority ?? null,
|
||||
issuedAt: input.issuedAt ?? null,
|
||||
expiresAt: input.expiresAt ?? null,
|
||||
};
|
||||
},
|
||||
|
||||
getTrustBadge: async (nurseId) => {
|
||||
|
||||
@@ -77,6 +77,27 @@ export interface VerificationStatus {
|
||||
/** Step codes still blocking go-live. */
|
||||
blockingSteps: string[];
|
||||
steps: VerificationStep[];
|
||||
/**
|
||||
* When the nurse first submitted (left `not_started`). **Not on the nurse-facing wire yet** — the
|
||||
* admin queue DTO already serves it, but `VerificationStatusDto` doesn't (REQ-055, filed by
|
||||
* ui-phase-8). Mock-tolerant: `undefined` on the real path omits the B6 timestamp line rather than
|
||||
* fake one.
|
||||
*/
|
||||
submittedAt?: string | null;
|
||||
/**
|
||||
* A nurse-facing read-back of the structured credential details B5 collects — so a returning nurse
|
||||
* sees a submitted summary instead of blank fields. **Not on the wire yet** (REQ-011 delivered only
|
||||
* the write; there is no nurse-facing read — REQ-056, filed by ui-phase-8). `inoNumberSubmitted` is a
|
||||
* boolean, never the number itself — the raw value is encrypted server-side by design and never
|
||||
* re-served. Mock-tolerant: `undefined` on the real path renders every B5 field blank, as today.
|
||||
*/
|
||||
credentialSubmission?: {
|
||||
inoNumberSubmitted: boolean;
|
||||
specialties: string[];
|
||||
issuingAuthority: string | null;
|
||||
issuedAt: string | null;
|
||||
expiresAt: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
/** `UploadUrlResult` — a signed PUT target for a manual step's document. */
|
||||
|
||||
Reference in New Issue
Block a user