import { ReactNode } from "react"; import { SanityClient } from "@sanity/client"; /** * An access request as returned by the Access API (`GET /access/requests/me`). * * Mirrors the DTO from the Access API service. The generated `@sanity/access-api` * client is only published to Sanity's internal registry, so this package inlines * the small slice of the wire contract it needs. * * @public */ interface AccessRequest { id: string; status: 'pending' | 'accepted' | 'declined'; resourceId: string; resourceType: 'organization' | 'project'; createdAt: string; updatedAt: string; updatedByUserId: string; requestedByUserId: string; requestedRole?: string; type: 'access' | 'role'; note?: string; } /** * The kind of resource an access request targets. * * @public */ type AccessResourceType = 'organization' | 'project'; /** * Where the caller stands on requesting access to a resource: * - `pending` — a request is in review (less than 2 weeks old) * - `denied` — a recent request was declined (less than 2 weeks old); can't re-request yet * - `expired` — a prior request aged out; can request again * - `none` — no relevant request; can request * * @public */ type AccessRequestState = 'pending' | 'denied' | 'expired' | 'none'; /** * Outcome of submitting an access request, mapping the Access API's error * contract to a discriminated union: * - `submitted` — the request was created * - `denied` — 409; a recent request was declined or is already pending * - `over-limit` — 429; the caller is over their cross-project request limit * - `email-domain-blocked` — 409; the caller's email domain may not request access * - `requests-disabled` — 409; the organization has disabled access requests * - `sso-enforced` — 403 `saml_enforcement_required`; the organization only admits * members through its SSO login flow, so the request can never be approved. * `redirectUrl` is the IdP login URL when the API provides one. * - `error` — any other failure * * @public */ type SubmitAccessRequestResult = { type: 'submitted'; request: AccessRequest | null; } | { type: 'denied'; message?: string; } | { type: 'over-limit'; message?: string; } | { type: 'email-domain-blocked'; message?: string; } | { type: 'requests-disabled'; message?: string; } | { type: 'sso-enforced'; redirectUrl?: string; message?: string; } | { type: 'error'; error: unknown; }; /** * What the Access API says the request-access screen should show * (`GET /access/{resourceType}/{resourceId}/requests/state`). * * Answered before the user writes anything, so a futile form is never offered. * Distinct from {@link AccessRequestState}, which this package derives from the * caller's own request history: this one is the server's verdict. * * - `eligible` — offer the form. Creating can still fail: the create path has * its own gates, which this endpoint does not resolve. * - `saml-required` — the organization only admits members through its SSO * login flow, so no administrator could ever approve a request. `redirectUrl` * is where to send them to log in: normally the organization's SSO form, * which submits itself, but a confirmation page for an organization with no * slug. Both end at the identity provider, so the card treats them alike. It * is absent only when neither resolves, leaving no way forward. * - `resource-not-available` — the target project or organization is gone. * * @public */ type AccessRequestEligibilityState = { state: 'eligible'; } | { state: 'saml-required'; redirectUrl?: string; } | { state: 'resource-not-available'; }; /** * The current user rendered in the request-access screen. A structural subset of * `CurrentUser` from `@sanity/types`, so both studio and app callers can pass * their own user object without an extra dependency. * * @public */ interface AccessUser { name?: string; email?: string; provider?: string; profileImage?: string; } /** * The Access API only accepts notes up to this length. * * @public */ export declare const MAX_ACCESS_REQUEST_NOTE_LENGTH = 150; /** * Fetches the caller's own access requests across all resources * (`GET /access/requests/me`). * * @public */ export declare function listMyAccessRequests(client: SanityClient): Promise; /** * Asks what the request-access screen should show * (`GET /access/{resourceType}/{resourceId}/requests/state`). * * Runs before the form is offered, so a user in a SAML-enforced organization is * pointed at SSO instead of writing a note no administrator can action. * * `origin` is carried opaquely to the login page so the user returns where they * started. Never throws: an unreachable or older API answers `eligible`, * leaving the form in place and the submit-time 403 as the backstop. * * @public */ export declare function fetchAccessRequestStatus(options: { client: SanityClient; resourceType: AccessResourceType; resourceId: string; origin?: string; }): Promise; /** * Submits an access request (`POST /access/{resourceType}/{resourceId}/requests`) * and maps the Access API's error contract to a {@link SubmitAccessRequestResult}. * Never throws for API rejections; unexpected failures come back as * `{type: 'error'}` so callers decide how to surface them. * * @public */ export declare function submitAccessRequest(options: { client: SanityClient; resourceType: AccessResourceType; resourceId: string; note?: string; requestUrl?: string; }): Promise; /** * Derives where the caller stands on requesting access to a resource from * their existing access requests. * * A declined request blocks re-requesting for two weeks. A pending request * younger than two weeks is in review; older pending requests count as * expired, and the caller may request again. * * @public */ export declare function deriveAccessRequestState(requests: AccessRequest[] | null | undefined, resourceId: string, now?: number): AccessRequestState; /** * All user-facing strings in the request-access screen. Every label can be * overridden, so hosts with their own i18n stack (studio i18n, react-i18next) * inject translated copy while standalone hosts get the English defaults. * * @public */ interface RequestAccessLabels { title: ReactNode; sentTitle: ReactNode; deniedTitle: ReactNode; errorTitle: ReactNode; describeNoAccess: (context: { email?: string; }) => ReactNode; promptProject: ReactNode; promptOrganization: ReactNode; notePlaceholder: string; noteAriaLabel: string; submit: ReactNode; sentDescription: ReactNode; pendingMessage: ReactNode; deniedMessage: (context: { message?: string; }) => ReactNode; overLimitMessage: (context: { message?: string; }) => ReactNode; expiredMessage: ReactNode; ssoEnforcedTitle: ReactNode; ssoEnforcedMessage: (context: { providerTitle?: string; }) => ReactNode; ssoSignInCta: ReactNode; resourceNotAvailableTitle: ReactNode; resourceNotAvailableMessage: ReactNode; submitFailedMessage: ReactNode; wrongAccount: ReactNode; signOut: ReactNode; } /** * Human-readable title for a login provider id, e.g. `google` → `Google`, * `saml-xyz` → `SAML/SSO`. * * @public */ export declare function getProviderTitle(provider?: string): string | undefined; /** @public */ interface RequestAccessFormProps { /** Client authenticated as the requesting user. The Access API version is applied internally. */ client: SanityClient; resourceType?: AccessResourceType; /** Project or organization id to request access to. */ resourceId: string; /** The signed-in user, rendered in the description and account footer. */ currentUser?: AccessUser | null; /** * Called when the user chooses "Sign out". The account footer's sign-out * action is only rendered when provided; hosts own the actual sign-out * mechanism (studio: `auth.logout()`, dashboard: logout route navigation). */ onSignOut?: () => void; /** Called after a request is successfully submitted, e.g. for analytics. */ onRequestSubmitted?: (details: { note?: string; }) => void; /** Optional slot rendered above the title, e.g. a resource preview. */ preview?: ReactNode; /** * Renders an optional action area at the bottom of the card's content, e.g. * a navigation CTA. Called with the current view so the action can differ * per state (or be omitted for some); return null to render nothing. */ renderAction?: (context: { view: RequestAccessView; }) => ReactNode; /** Label overrides for hosts with their own i18n stack. */ labels?: Partial; } /** * The shared request-access screen: explains that the signed-in account lacks * access, lets the user request it with an optional note, and reflects the * request lifecycle (pending, denied, expired, over-limit, SSO-enforced). * * Fetches the caller's existing requests on mount and suspends while loading; * an internal `Suspense` boundary renders a spinner, so hosts can mount it * directly. Remount with a `key` when `client` or `resourceId` change. * * @public */ export declare function RequestAccessForm(props: RequestAccessFormProps): import("react").JSX.Element; /** * The view the request-access card is currently showing. * * @public */ type RequestAccessView = 'form' | 'sent' | 'pending' | 'blocked' | 'sso-enforced'; export type { AccessRequest, AccessRequestEligibilityState, AccessRequestState, AccessResourceType, AccessUser, RequestAccessFormProps, RequestAccessLabels, RequestAccessView, SubmitAccessRequestResult }; //# sourceMappingURL=index.d.ts.map