@sanity/access-ui 6.18.1-next.3 → 6.19.0-next.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -259,9 +259,12 @@ interface RequestAccessFormProps {
259
259
  * access, lets the user request it with an optional note, and reflects the
260
260
  * request lifecycle (pending, denied, expired, over-limit, SSO-enforced).
261
261
  *
262
- * Fetches the caller's existing requests on mount and suspends while loading;
263
- * an internal `Suspense` boundary renders a spinner, so hosts can mount it
264
- * directly. Remount with a `key` when `client` or `resourceId` change.
262
+ * Fetches the caller's existing requests once it is mounted and suspends while
263
+ * loading; an internal `Suspense` boundary renders a spinner, so hosts can
264
+ * mount it directly. The fetch starts when the card commits, not while it
265
+ * renders, so a host whose own `Suspense` boundary retries around it does not
266
+ * refetch, and the result is kept while a hidden `<Activity>` conceals the
267
+ * card. Remount with a `key` when `client` or `resourceId` change.
265
268
  *
266
269
  * @public
267
270
  */
package/lib/index.js CHANGED
@@ -2,6 +2,8 @@ import { c } from "react/compiler-runtime";
2
2
  import { LaunchIcon } from "@sanity/icons/Launch";
3
3
  import { Avatar, Button, Card, Spinner, Text, TextArea } from "@sanity/ui";
4
4
  import { Suspense, use, useId, useState, useTransition } from "react";
5
+ import { useObservablePromise } from "react-rx";
6
+ import { catchError, combineLatest, defer, of } from "rxjs";
5
7
  import { Box, Flex, VStack } from "ui5";
6
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
9
  /**
@@ -183,49 +185,52 @@ const defaultLabels = {
183
185
  * access, lets the user request it with an optional note, and reflects the
184
186
  * request lifecycle (pending, denied, expired, over-limit, SSO-enforced).
185
187
  *
186
- * Fetches the caller's existing requests on mount and suspends while loading;
187
- * an internal `Suspense` boundary renders a spinner, so hosts can mount it
188
- * directly. Remount with a `key` when `client` or `resourceId` change.
188
+ * Fetches the caller's existing requests once it is mounted and suspends while
189
+ * loading; an internal `Suspense` boundary renders a spinner, so hosts can
190
+ * mount it directly. The fetch starts when the card commits, not while it
191
+ * renders, so a host whose own `Suspense` boundary retries around it does not
192
+ * refetch, and the result is kept while a hidden `<Activity>` conceals the
193
+ * card. Remount with a `key` when `client` or `resourceId` change.
189
194
  *
190
195
  * @public
191
196
  */
192
197
  function RequestAccessForm(props) {
193
- let $ = c(11), { client, resourceType: t0, resourceId } = props, resourceType = t0 === void 0 ? "project" : t0, t1;
194
- $[0] === client ? t1 = $[1] : (t1 = () => listMyAccessRequests(client).catch(_temp), $[0] = client, $[1] = t1);
195
- let [requestsPromise] = useState(t1), t2;
196
- $[2] !== client || $[3] !== resourceId || $[4] !== resourceType ? (t2 = () => fetchAccessRequestStatus({
197
- client,
198
- resourceType,
199
- resourceId,
200
- origin: getRequestUrl()
201
- }), $[2] = client, $[3] = resourceId, $[4] = resourceType, $[5] = t2) : t2 = $[5];
202
- let [statusPromise] = useState(t2), t3;
203
- $[6] === Symbol.for("react.memo_cache_sentinel") ? (t3 = /* @__PURE__ */ jsx(Flex, {
198
+ let $ = c(8), { client, resourceType: t0, resourceId } = props, resourceType = t0 === void 0 ? "project" : t0, t1;
199
+ $[0] !== client || $[1] !== resourceId || $[2] !== resourceType ? (t1 = () => combineLatest({
200
+ accessRequestsHistory: defer(() => listMyAccessRequests(client)).pipe(catchError(_temp)),
201
+ accessRequestEligibilityState: defer(() => fetchAccessRequestStatus({
202
+ client,
203
+ resourceType,
204
+ resourceId,
205
+ origin: getRequestUrl()
206
+ }))
207
+ }), $[0] = client, $[1] = resourceId, $[2] = resourceType, $[3] = t1) : t1 = $[3];
208
+ let [load$] = useState(t1), loadPromise = useObservablePromise(load$), t2;
209
+ $[4] === Symbol.for("react.memo_cache_sentinel") ? (t2 = /* @__PURE__ */ jsx(Flex, {
204
210
  alignItems: "center",
205
211
  height: "100%",
206
212
  justifyContent: "center",
207
213
  padding: 5,
208
214
  children: /* @__PURE__ */ jsx(Spinner, { muted: !0 })
209
- }), $[6] = t3) : t3 = $[6];
210
- let t4;
211
- return $[7] !== props || $[8] !== requestsPromise || $[9] !== statusPromise ? (t4 = /* @__PURE__ */ jsx(Card, {
215
+ }), $[4] = t2) : t2 = $[4];
216
+ let t3;
217
+ return $[5] !== loadPromise || $[6] !== props ? (t3 = /* @__PURE__ */ jsx(Card, {
212
218
  border: !0,
213
219
  height: "fill",
214
220
  overflow: "hidden",
215
221
  radius: 3,
216
222
  tone: "default",
217
223
  children: /* @__PURE__ */ jsx(Suspense, {
218
- fallback: t3,
224
+ fallback: t2,
219
225
  children: /* @__PURE__ */ jsx(RequestAccessFormContent, {
220
226
  ...props,
221
- requestsPromise,
222
- statusPromise
227
+ loadPromise
223
228
  })
224
229
  })
225
- }), $[7] = props, $[8] = requestsPromise, $[9] = statusPromise, $[10] = t4) : t4 = $[10], t4;
230
+ }), $[5] = loadPromise, $[6] = props, $[7] = t3) : t3 = $[7], t3;
226
231
  }
227
232
  function _temp() {
228
- return null;
233
+ return of(null);
229
234
  }
230
235
  /**
231
236
  * The server's verdict. `null` hands the decision back to the caller's own
@@ -301,10 +306,10 @@ function deriveViewState(options) {
301
306
  };
302
307
  }
303
308
  function RequestAccessFormContent(props) {
304
- let $ = c(9), { client, resourceType: t0, resourceId, currentUser, onSignOut, onRequestSubmitted, preview, renderAction, requestsPromise, statusPromise } = props, resourceType = t0 === void 0 ? "project" : t0, labels = {
309
+ let $ = c(9), { client, resourceType: t0, resourceId, currentUser, onSignOut, onRequestSubmitted, preview, renderAction, loadPromise } = props, resourceType = t0 === void 0 ? "project" : t0, { accessRequestsHistory, accessRequestEligibilityState } = use(loadPromise), labels = {
305
310
  ...defaultLabels,
306
311
  ...props.labels
307
- }, accessRequestsHistory = use(requestsPromise), accessRequestEligibilityState = use(statusPromise), titleId = useId(), [note, setNote] = useState(""), [submitAccessRequestResult, setSubmitAccessRequestResult] = useState(null), [isSubmitting, startSubmit] = useTransition(), providerTitle = getProviderTitle(currentUser?.provider), state = deriveViewState({
312
+ }, titleId = useId(), [note, setNote] = useState(""), [submitAccessRequestResult, setSubmitAccessRequestResult] = useState(null), [isSubmitting, startSubmit] = useTransition(), providerTitle = getProviderTitle(currentUser?.provider), state = deriveViewState({
308
313
  accessRequestsHistory,
309
314
  accessRequestEligibilityState,
310
315
  resourceId,
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["_Fragment","resourceType = 'project'","() =>\n listMyAccessRequests(client).catch((): AccessRequest[] | null => null)","(): AccessRequest[] | null => null","() =>\n fetchAccessRequestStatus({\n client,\n resourceType,\n resourceId,\n origin: getRequestUrl(),\n })","<Flex alignItems=\"center\" height=\"100%\" justifyContent=\"center\" padding={5}>\n <Spinner muted />\n </Flex>","<Card border height=\"fill\" overflow=\"hidden\" radius={3} tone=\"default\">\n <Suspense\n fallback={\n <Flex alignItems=\"center\" height=\"100%\" justifyContent=\"center\" padding={5}>\n <Spinner muted />\n </Flex>\n }\n >\n <RequestAccessFormContent\n {...props}\n requestsPromise={requestsPromise}\n statusPromise={statusPromise}\n />\n </Suspense>\n </Card>","(event: SubmitEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (isSubmitting) return\n startSubmit(async () => {\n const trimmedNote = note.trim() || undefined\n const result = await submitAccessRequest({\n client,\n resourceType,\n resourceId,\n note: trimmedNote,\n requestUrl: getRequestUrl(),\n })\n setSubmitAccessRequestResult(result)\n if (result.type === 'submitted') onRequestSubmitted?.({note: trimmedNote})\n })\n }","preview ? (\n <Flex justifyContent=\"center\" padding={2}>\n {preview}\n </Flex>\n ) : null","event","_c"],"sources":["../src/accessRequests.ts","../src/deriveAccessRequestState.ts","../src/providerTitle.ts","../src/labels.tsx","../src/RequestAccessForm.tsx"],"sourcesContent":["import {type SanityClient} from '@sanity/client'\n\nimport {\n type AccessRequest,\n type AccessRequestEligibilityState,\n type AccessResourceType,\n type SubmitAccessRequestResult,\n} from './types'\n\n/**\n * The Access API only accepts notes up to this length.\n *\n * @public\n */\nexport const MAX_ACCESS_REQUEST_NOTE_LENGTH = 150\n\nconst ACCESS_API_VERSION = '2024-07-01'\n\n/**\n * Structured 403 code thrown by the Access API when the target organization\n * only admits members through its SSO login flow.\n */\nconst SAML_ENFORCEMENT_REQUIRED = 'saml_enforcement_required'\n\nfunction withAccessApiVersion(client: SanityClient): SanityClient {\n return client.withConfig({apiVersion: ACCESS_API_VERSION})\n}\n\n/**\n * Fetches the caller's own access requests across all resources\n * (`GET /access/requests/me`).\n *\n * @public\n */\nexport async function listMyAccessRequests(client: SanityClient): Promise<AccessRequest[]> {\n const requests = await withAccessApiVersion(client).request<AccessRequest[] | null>({\n url: '/access/requests/me',\n tag: 'access-ui.list-requests',\n })\n return requests ?? []\n}\n\n/**\n * Asks what the request-access screen should show\n * (`GET /access/{resourceType}/{resourceId}/requests/state`).\n *\n * Runs before the form is offered, so a user in a SAML-enforced organization is\n * pointed at SSO instead of writing a note no administrator can action.\n *\n * `origin` is carried opaquely to the login page so the user returns where they\n * started. Never throws: an unreachable or older API answers `eligible`,\n * leaving the form in place and the submit-time 403 as the backstop.\n *\n * @public\n */\nexport async function fetchAccessRequestStatus(options: {\n client: SanityClient\n resourceType: AccessResourceType\n resourceId: string\n origin?: string\n}): Promise<AccessRequestEligibilityState> {\n const {client, resourceType, resourceId, origin} = options\n try {\n const status = await withAccessApiVersion(client).request<AccessRequestEligibilityState | null>(\n {\n url: `/access/${resourceType}/${resourceId}/requests/state`,\n tag: 'access-ui.request-state',\n query: origin ? {returnQuery: new URLSearchParams({origin}).toString()} : undefined,\n },\n )\n return status ?? {state: 'eligible'}\n } catch {\n return {state: 'eligible'}\n }\n}\n\ninterface ErrorResponseDetails {\n statusCode?: number\n message?: string\n code?: string\n redirectUrl?: string\n}\n\nfunction getErrorResponseDetails(err: unknown): ErrorResponseDetails {\n if (typeof err !== 'object' || err === null) return {}\n const response = (err as {response?: unknown}).response\n if (typeof response !== 'object' || response === null) return {}\n const {statusCode} = response as {statusCode?: unknown}\n const body = (response as {body?: unknown}).body\n const details: ErrorResponseDetails = {\n statusCode: typeof statusCode === 'number' ? statusCode : undefined,\n }\n if (typeof body === 'object' && body !== null) {\n const {message, code, redirectUrl} = body as {\n message?: unknown\n code?: unknown\n redirectUrl?: unknown\n }\n details.message = typeof message === 'string' ? message : undefined\n details.code = typeof code === 'string' ? code : undefined\n details.redirectUrl = typeof redirectUrl === 'string' ? redirectUrl : undefined\n }\n return details\n}\n\nfunction mapSubmitError(err: unknown): SubmitAccessRequestResult {\n const {statusCode, message, code, redirectUrl} = getErrorResponseDetails(err)\n\n if (statusCode === 403 && code === SAML_ENFORCEMENT_REQUIRED) {\n return {type: 'sso-enforced', redirectUrl, message}\n }\n if (statusCode === 429) {\n return {type: 'over-limit', message}\n }\n if (statusCode === 409) {\n if (message?.includes('email domain')) return {type: 'email-domain-blocked', message}\n if (message?.includes('disabled for organization')) return {type: 'requests-disabled', message}\n return {type: 'denied', message: message?.replace(/^Conflict -\\s*/, '')}\n }\n return {type: 'error', error: err}\n}\n\n/**\n * Submits an access request (`POST /access/{resourceType}/{resourceId}/requests`)\n * and maps the Access API's error contract to a {@link SubmitAccessRequestResult}.\n * Never throws for API rejections; unexpected failures come back as\n * `{type: 'error'}` so callers decide how to surface them.\n *\n * @public\n */\nexport async function submitAccessRequest(options: {\n client: SanityClient\n resourceType: AccessResourceType\n resourceId: string\n note?: string\n requestUrl?: string\n}): Promise<SubmitAccessRequestResult> {\n const {client, resourceType, resourceId, note, requestUrl} = options\n try {\n const request = await withAccessApiVersion(client).request<AccessRequest | null>({\n url: `/access/${resourceType}/${resourceId}/requests`,\n method: 'post',\n tag: 'access-ui.submit-request',\n body: {note, requestUrl, type: 'access'},\n })\n return {type: 'submitted', request}\n } catch (err) {\n return mapSubmitError(err)\n }\n}\n","import {type AccessRequest, type AccessRequestState} from './types'\n\n/**\n * Access requests are considered active for two weeks, matching the Access\n * API's request lifetime.\n */\nconst REQUEST_LIFETIME_MS = 14 * 24 * 60 * 60 * 1000\n\n/**\n * Derives where the caller stands on requesting access to a resource from\n * their existing access requests.\n *\n * A declined request blocks re-requesting for two weeks. A pending request\n * younger than two weeks is in review; older pending requests count as\n * expired, and the caller may request again.\n *\n * @public\n */\nexport function deriveAccessRequestState(\n requests: AccessRequest[] | null | undefined,\n resourceId: string,\n now: number = Date.now(),\n): AccessRequestState {\n if (!requests || requests.length === 0) return 'none'\n\n const isRecent = (request: AccessRequest) =>\n now - new Date(request.createdAt).getTime() < REQUEST_LIFETIME_MS\n\n const forResource = requests.filter((request) => request.resourceId === resourceId)\n\n if (forResource.some((request) => request.status === 'declined' && isRecent(request))) {\n return 'denied'\n }\n if (forResource.some((request) => request.status === 'pending' && isRecent(request))) {\n return 'pending'\n }\n if (forResource.some((request) => request.status === 'pending')) {\n return 'expired'\n }\n return 'none'\n}\n","/**\n * Human-readable title for a login provider id, e.g. `google` → `Google`,\n * `saml-xyz` → `SAML/SSO`.\n *\n * @public\n */\nexport function getProviderTitle(provider?: string): string | undefined {\n if (provider === 'google') return 'Google'\n if (provider === 'github') return 'GitHub'\n if (provider === 'sanity') return 'Sanity'\n if (provider === 'vercel') return 'Vercel'\n if (provider?.startsWith('saml-')) return 'SAML/SSO'\n return undefined\n}\n","import {type ReactNode} from 'react'\n\n/**\n * All user-facing strings in the request-access screen. Every label can be\n * overridden, so hosts with their own i18n stack (studio i18n, react-i18next)\n * inject translated copy while standalone hosts get the English defaults.\n *\n * @public\n */\nexport interface RequestAccessLabels {\n title: ReactNode\n sentTitle: ReactNode\n deniedTitle: ReactNode\n errorTitle: ReactNode\n describeNoAccess: (context: {email?: string}) => ReactNode\n promptProject: ReactNode\n promptOrganization: ReactNode\n notePlaceholder: string\n noteAriaLabel: string\n submit: ReactNode\n sentDescription: ReactNode\n pendingMessage: ReactNode\n deniedMessage: (context: {message?: string}) => ReactNode\n overLimitMessage: (context: {message?: string}) => ReactNode\n expiredMessage: ReactNode\n ssoEnforcedTitle: ReactNode\n ssoEnforcedMessage: (context: {providerTitle?: string}) => ReactNode\n ssoSignInCta: ReactNode\n resourceNotAvailableTitle: ReactNode\n resourceNotAvailableMessage: ReactNode\n submitFailedMessage: ReactNode\n wrongAccount: ReactNode\n signOut: ReactNode\n}\n\n/** @internal */\nexport const defaultLabels: RequestAccessLabels = {\n title: 'Request access',\n sentTitle: 'Access request sent',\n deniedTitle: 'Access request declined',\n errorTitle: 'Access request couldn’t be sent',\n describeNoAccess: ({email}) =>\n email ? (\n <>\n Your account <strong>({email})</strong> doesn’t have access to this content.\n </>\n ) : (\n <>Your account doesn’t have access to this content.</>\n ),\n promptProject: 'Send a request to the project admin(s).',\n promptOrganization: 'Send a request to the organization admin(s).',\n notePlaceholder: 'Message (optional)',\n noteAriaLabel: 'Message',\n submit: 'Request access',\n sentDescription:\n 'Your request has been sent. You will receive a notification if access is approved.',\n pendingMessage: 'Your request to access this content is pending approval.',\n deniedMessage: ({message}) => message ?? 'Your request to access this content has been declined.',\n overLimitMessage: ({message}) =>\n message ??\n 'You’ve reached the limit for access requests across all projects. Please wait before submitting more requests, or contact an admin.',\n expiredMessage: 'Your previous request has expired. You may request access again below.',\n // Reached both before and after a submit, so it must not claim a send failed.\n ssoEnforcedTitle: 'Sign in with SSO required',\n ssoEnforcedMessage: ({providerTitle}) =>\n providerTitle ? (\n <>\n You’re signed in with <strong>{providerTitle}</strong>, but this organization requires\n signing in with SSO. Access can’t be requested with this account.\n </>\n ) : (\n <>\n This organization requires signing in with SSO. Access can’t be requested with this account.\n </>\n ),\n ssoSignInCta: 'Sign in with SSO',\n resourceNotAvailableTitle: 'Access can’t be requested',\n resourceNotAvailableMessage: 'The resource currently being requested is no longer available.',\n submitFailedMessage: 'There was a problem submitting your request. Please try again.',\n wrongAccount: 'Wrong account?',\n signOut: 'Sign out',\n}\n","import {type SanityClient} from '@sanity/client'\nimport {LaunchIcon} from '@sanity/icons/Launch'\nimport {Avatar, Button, Card, Spinner, Text, TextArea} from '@sanity/ui'\nimport {\n type ReactNode,\n type SubmitEvent,\n Suspense,\n use,\n useId,\n useState,\n useTransition,\n} from 'react'\nimport {Flex, Box, VStack} from 'ui5'\n\nimport {\n fetchAccessRequestStatus,\n listMyAccessRequests,\n MAX_ACCESS_REQUEST_NOTE_LENGTH,\n submitAccessRequest,\n} from './accessRequests'\nimport {deriveAccessRequestState} from './deriveAccessRequestState'\nimport {defaultLabels, type RequestAccessLabels} from './labels'\nimport {getProviderTitle} from './providerTitle'\nimport {\n type AccessRequest,\n type AccessRequestEligibilityState,\n type AccessResourceType,\n type AccessUser,\n type SubmitAccessRequestResult,\n} from './types'\n\n/** @public */\nexport interface RequestAccessFormProps {\n /** Client authenticated as the requesting user. The Access API version is applied internally. */\n client: SanityClient\n resourceType?: AccessResourceType\n /** Project or organization id to request access to. */\n resourceId: string\n /** The signed-in user, rendered in the description and account footer. */\n currentUser?: AccessUser | null\n /**\n * Called when the user chooses \"Sign out\". The account footer's sign-out\n * action is only rendered when provided; hosts own the actual sign-out\n * mechanism (studio: `auth.logout()`, dashboard: logout route navigation).\n */\n onSignOut?: () => void\n /** Called after a request is successfully submitted, e.g. for analytics. */\n onRequestSubmitted?: (details: {note?: string}) => void\n /** Optional slot rendered above the title, e.g. a resource preview. */\n preview?: ReactNode\n /**\n * Renders an optional action area at the bottom of the card's content, e.g.\n * a navigation CTA. Called with the current view so the action can differ\n * per state (or be omitted for some); return null to render nothing.\n */\n renderAction?: (context: {view: RequestAccessView}) => ReactNode\n /** Label overrides for hosts with their own i18n stack. */\n labels?: Partial<RequestAccessLabels>\n}\n\n/**\n * The shared request-access screen: explains that the signed-in account lacks\n * access, lets the user request it with an optional note, and reflects the\n * request lifecycle (pending, denied, expired, over-limit, SSO-enforced).\n *\n * Fetches the caller's existing requests on mount and suspends while loading;\n * an internal `Suspense` boundary renders a spinner, so hosts can mount it\n * directly. Remount with a `key` when `client` or `resourceId` change.\n *\n * @public\n */\nexport function RequestAccessForm(props: RequestAccessFormProps) {\n const {client, resourceType = 'project', resourceId} = props\n\n // Created once (lazy init): recreating the promise per render would refetch\n // and re-suspend forever. Callers remount with `key` to reset.\n const [requestsPromise] = useState(() =>\n listMyAccessRequests(client).catch((): AccessRequest[] | null => null),\n )\n const [statusPromise] = useState(() =>\n fetchAccessRequestStatus({\n client,\n resourceType,\n resourceId,\n origin: getRequestUrl(),\n }),\n )\n\n return (\n <Card border height=\"fill\" overflow=\"hidden\" radius={3} tone=\"default\">\n <Suspense\n fallback={\n <Flex alignItems=\"center\" height=\"100%\" justifyContent=\"center\" padding={5}>\n <Spinner muted />\n </Flex>\n }\n >\n <RequestAccessFormContent\n {...props}\n requestsPromise={requestsPromise}\n statusPromise={statusPromise}\n />\n </Suspense>\n </Card>\n )\n}\n\n/**\n * The view the request-access card is currently showing.\n *\n * @public\n */\nexport type RequestAccessView = 'form' | 'sent' | 'pending' | 'blocked' | 'sso-enforced'\n\n/**\n * Everything the card renders, copy included: the same state that picks a view\n * is the only thing that knows which words that view needs. Keying copy off\n * `view` alone cannot work, because `blocked` covers a prior decline, a gone\n * resource and four submit failures, each with its own copy.\n */\ntype ViewState = {title: ReactNode; description: ReactNode | null} & (\n | {view: 'form'; expired: boolean}\n | {view: 'sent'}\n | {view: 'pending'}\n | {view: 'blocked'; message: ReactNode}\n | {view: 'sso-enforced'; message: ReactNode; redirectUrl?: string}\n)\n\n/**\n * The server's verdict. `null` hands the decision back to the caller's own\n * request history, which resolves the states this endpoint does not.\n */\nfunction deriveServerViewState(\n status: AccessRequestEligibilityState,\n labels: RequestAccessLabels,\n providerTitle?: string,\n): ViewState | null {\n switch (status.state) {\n case 'saml-required':\n return ssoEnforcedState({labels, providerTitle, redirectUrl: status.redirectUrl})\n case 'resource-not-available':\n // Nothing was submitted, so the submit-failure copy would misdescribe it.\n return {\n view: 'blocked',\n title: labels.resourceNotAvailableTitle,\n description: null,\n message: labels.resourceNotAvailableMessage,\n }\n case 'eligible':\n return null\n default:\n return null\n }\n}\n\n// Reached on mount and after a submit 403, so the title claims neither.\nfunction ssoEnforcedState(options: {\n labels: RequestAccessLabels\n providerTitle?: string\n redirectUrl?: string\n}): ViewState {\n const {labels, providerTitle, redirectUrl} = options\n return {\n view: 'sso-enforced',\n title: labels.ssoEnforcedTitle,\n description: null,\n message: labels.ssoEnforcedMessage({providerTitle}),\n redirectUrl,\n }\n}\n\nfunction deriveViewState(options: {\n accessRequestsHistory: AccessRequest[] | null\n accessRequestEligibilityState: AccessRequestEligibilityState\n resourceId: string\n submitAccessRequestResult: SubmitAccessRequestResult | null\n currentUser?: AccessUser | null\n providerTitle?: string\n labels: RequestAccessLabels\n}): ViewState {\n const {\n accessRequestsHistory,\n accessRequestEligibilityState,\n resourceId,\n submitAccessRequestResult,\n currentUser,\n providerTitle,\n labels,\n } = options\n const submitFailure = (message: ReactNode): ViewState => ({\n view: 'blocked',\n title: labels.errorTitle,\n description: null,\n message,\n })\n\n if (submitAccessRequestResult) {\n switch (submitAccessRequestResult.type) {\n case 'submitted':\n return {view: 'sent', title: labels.sentTitle, description: labels.sentDescription}\n case 'sso-enforced':\n return ssoEnforcedState({\n labels,\n providerTitle,\n redirectUrl: submitAccessRequestResult.redirectUrl,\n })\n case 'denied':\n return submitFailure(labels.deniedMessage({message: submitAccessRequestResult.message}))\n case 'over-limit':\n return submitFailure(labels.overLimitMessage({message: submitAccessRequestResult.message}))\n case 'email-domain-blocked':\n case 'requests-disabled':\n return submitFailure(submitAccessRequestResult.message)\n case 'error':\n // Fall through to the fetched state; the form stays up with an inline error.\n break\n default:\n }\n }\n\n // The server's verdict outranks the request history: a pending request in an\n // enforced org is already dead, so \"pending approval\" would be a false\n // promise. It answers `eligible` when it has nothing to say.\n const serverState = deriveServerViewState(accessRequestEligibilityState, labels, providerTitle)\n if (serverState) return serverState\n\n // TODO: `pending` and `denied` will be replaced by future content in `accessRequestEligibilityState`\n const state = deriveAccessRequestState(accessRequestsHistory, resourceId)\n if (state === 'pending') {\n return {view: 'pending', title: labels.sentTitle, description: labels.pendingMessage}\n }\n // Derived from prefetch: the user hasn't submitted anything this session,\n // so the title must describe the prior decline, not a failed send.\n if (state === 'denied') {\n return {\n view: 'blocked',\n title: labels.deniedTitle,\n description: null,\n message: labels.deniedMessage({}),\n }\n }\n return {\n view: 'form',\n title: labels.title,\n description: labels.describeNoAccess({email: currentUser?.email}),\n expired: state === 'expired',\n }\n}\n\nfunction RequestAccessFormContent(\n props: RequestAccessFormProps & {\n requestsPromise: Promise<AccessRequest[] | null>\n statusPromise: Promise<AccessRequestEligibilityState>\n },\n) {\n const {\n client,\n resourceType = 'project',\n resourceId,\n currentUser,\n onSignOut,\n onRequestSubmitted,\n preview,\n renderAction,\n requestsPromise,\n statusPromise,\n } = props\n\n const labels = {...defaultLabels, ...props.labels}\n const accessRequestsHistory = use(requestsPromise)\n const accessRequestEligibilityState = use(statusPromise)\n const titleId = useId()\n\n const [note, setNote] = useState('')\n const [submitAccessRequestResult, setSubmitAccessRequestResult] =\n useState<SubmitAccessRequestResult | null>(null)\n const [isSubmitting, startSubmit] = useTransition()\n\n const providerTitle = getProviderTitle(currentUser?.provider)\n const state = deriveViewState({\n accessRequestsHistory,\n accessRequestEligibilityState,\n resourceId,\n submitAccessRequestResult,\n currentUser,\n providerTitle,\n labels,\n })\n\n const submitFailed = submitAccessRequestResult?.type === 'error'\n\n const handleSubmit = (event: SubmitEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (isSubmitting) return\n startSubmit(async () => {\n const trimmedNote = note.trim() || undefined\n const result = await submitAccessRequest({\n client,\n resourceType,\n resourceId,\n note: trimmedNote,\n requestUrl: getRequestUrl(),\n })\n setSubmitAccessRequestResult(result)\n if (result.type === 'submitted') onRequestSubmitted?.({note: trimmedNote})\n })\n }\n\n return (\n <Flex flexDirection=\"column\" height=\"100%\">\n <Flex flexDirection=\"column\" flexBasis=\"0%\" flexGrow={1} gap={4} padding={4}>\n {preview ? (\n <Flex justifyContent=\"center\" padding={2}>\n {preview}\n </Flex>\n ) : null}\n\n <Text as=\"h1\" id={titleId} size={2} weight=\"semibold\">\n {state.title}\n </Text>\n\n {state.description !== null ? (\n <Text as=\"p\" muted size={1}>\n {state.description}\n </Text>\n ) : null}\n\n {state.view === 'blocked' || state.view === 'sso-enforced' ? (\n <VStack gap={4}>\n <Card border padding={3} radius={2} role=\"alert\" tone=\"caution\">\n <Text as=\"p\" muted size={1}>\n {state.message}\n </Text>\n </Card>\n {state.view === 'sso-enforced' && state.redirectUrl ? (\n <Button\n as=\"a\"\n href={state.redirectUrl}\n iconRight={LaunchIcon}\n mode=\"ghost\"\n text={labels.ssoSignInCta}\n width=\"fill\"\n />\n ) : null}\n </VStack>\n ) : null}\n\n {state.view === 'form' ? (\n <VStack as=\"form\" aria-labelledby={titleId} onSubmit={handleSubmit} gap={4}>\n <Text as=\"p\" size={1}>\n {state.expired\n ? labels.expiredMessage\n : resourceType === 'organization'\n ? labels.promptOrganization\n : labels.promptProject}\n </Text>\n <VStack gap={2}>\n <TextArea\n aria-label={labels.noteAriaLabel}\n disabled={isSubmitting}\n fontSize={1}\n maxLength={MAX_ACCESS_REQUEST_NOTE_LENGTH}\n onChange={(event) => setNote(event.currentTarget.value)}\n placeholder={labels.notePlaceholder}\n rows={3}\n value={note}\n />\n <Text align=\"right\" muted size={0}>\n {`${note.length}/${MAX_ACCESS_REQUEST_NOTE_LENGTH}`}\n </Text>\n </VStack>\n {submitFailed ? (\n <Card border padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text as=\"p\" muted size={1}>\n {labels.submitFailedMessage}\n </Text>\n </Card>\n ) : null}\n <Button\n disabled={isSubmitting}\n loading={isSubmitting}\n text={labels.submit}\n type=\"submit\"\n width=\"fill\"\n />\n </VStack>\n ) : null}\n\n {renderAction?.({view: state.view})}\n </Flex>\n\n {currentUser ? (\n <Card borderTop padding={3}>\n <Flex alignItems=\"center\" flexDirection=\"column\" gap={3}>\n <Flex alignItems=\"center\" gap={2} justifyContent=\"center\">\n <Avatar initials={getInitials(currentUser)} size={0} src={currentUser.profileImage} />\n <Box>\n <Text muted size={1} textOverflow=\"ellipsis\">\n {currentUser.email ?? currentUser.name}\n {providerTitle ? ` · ${providerTitle}` : ''}\n </Text>\n </Box>\n </Flex>\n {onSignOut ? (\n <Button\n fontSize={0}\n mode=\"bleed\"\n onClick={onSignOut}\n padding={2}\n textWeight=\"regular\"\n >\n <Text muted size={1}>\n {labels.wrongAccount} <strong>{labels.signOut}</strong>\n </Text>\n </Button>\n ) : null}\n </Flex>\n </Card>\n ) : null}\n </Flex>\n )\n}\n\n// The URL fragment can carry auth tokens (e.g. the #token= login handoff),\n// so it must never reach the Access API's logs.\nfunction getRequestUrl(): string | undefined {\n if (typeof window === 'undefined') return undefined\n const url = new URL(window.location.href)\n url.hash = ''\n return url.toString()\n}\n\nfunction getInitials(user: AccessUser): string | undefined {\n const source = user.name ?? user.email\n if (!source) return undefined\n const parts = source.trim().split(/\\s+/)\n const initials = parts\n .slice(0, 2)\n .map((part) => part[0])\n .join('')\n return initials ? initials.toUpperCase() : undefined\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["_Fragment","resourceType = 'project'","() =>\n combineLatest({\n accessRequestsHistory: defer(() => listMyAccessRequests(client)).pipe(\n catchError(() => of(null)),\n ),\n accessRequestEligibilityState: defer(() =>\n fetchAccessRequestStatus({client, resourceType, resourceId, origin: getRequestUrl()}),\n ),\n })","() => of(null)","<Flex alignItems=\"center\" height=\"100%\" justifyContent=\"center\" padding={5}>\n <Spinner muted />\n </Flex>","<Card border height=\"fill\" overflow=\"hidden\" radius={3} tone=\"default\">\n <Suspense\n fallback={\n <Flex alignItems=\"center\" height=\"100%\" justifyContent=\"center\" padding={5}>\n <Spinner muted />\n </Flex>\n }\n >\n <RequestAccessFormContent {...props} loadPromise={loadPromise} />\n </Suspense>\n </Card>","(event: SubmitEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (isSubmitting) return\n startSubmit(async () => {\n const trimmedNote = note.trim() || undefined\n const result = await submitAccessRequest({\n client,\n resourceType,\n resourceId,\n note: trimmedNote,\n requestUrl: getRequestUrl(),\n })\n setSubmitAccessRequestResult(result)\n if (result.type === 'submitted') onRequestSubmitted?.({note: trimmedNote})\n })\n }","preview ? (\n <Flex justifyContent=\"center\" padding={2}>\n {preview}\n </Flex>\n ) : null","event","_c"],"sources":["../src/accessRequests.ts","../src/deriveAccessRequestState.ts","../src/providerTitle.ts","../src/labels.tsx","../src/RequestAccessForm.tsx"],"sourcesContent":["import {type SanityClient} from '@sanity/client'\n\nimport {\n type AccessRequest,\n type AccessRequestEligibilityState,\n type AccessResourceType,\n type SubmitAccessRequestResult,\n} from './types'\n\n/**\n * The Access API only accepts notes up to this length.\n *\n * @public\n */\nexport const MAX_ACCESS_REQUEST_NOTE_LENGTH = 150\n\nconst ACCESS_API_VERSION = '2024-07-01'\n\n/**\n * Structured 403 code thrown by the Access API when the target organization\n * only admits members through its SSO login flow.\n */\nconst SAML_ENFORCEMENT_REQUIRED = 'saml_enforcement_required'\n\nfunction withAccessApiVersion(client: SanityClient): SanityClient {\n return client.withConfig({apiVersion: ACCESS_API_VERSION})\n}\n\n/**\n * Fetches the caller's own access requests across all resources\n * (`GET /access/requests/me`).\n *\n * @public\n */\nexport async function listMyAccessRequests(client: SanityClient): Promise<AccessRequest[]> {\n const requests = await withAccessApiVersion(client).request<AccessRequest[] | null>({\n url: '/access/requests/me',\n tag: 'access-ui.list-requests',\n })\n return requests ?? []\n}\n\n/**\n * Asks what the request-access screen should show\n * (`GET /access/{resourceType}/{resourceId}/requests/state`).\n *\n * Runs before the form is offered, so a user in a SAML-enforced organization is\n * pointed at SSO instead of writing a note no administrator can action.\n *\n * `origin` is carried opaquely to the login page so the user returns where they\n * started. Never throws: an unreachable or older API answers `eligible`,\n * leaving the form in place and the submit-time 403 as the backstop.\n *\n * @public\n */\nexport async function fetchAccessRequestStatus(options: {\n client: SanityClient\n resourceType: AccessResourceType\n resourceId: string\n origin?: string\n}): Promise<AccessRequestEligibilityState> {\n const {client, resourceType, resourceId, origin} = options\n try {\n const status = await withAccessApiVersion(client).request<AccessRequestEligibilityState | null>(\n {\n url: `/access/${resourceType}/${resourceId}/requests/state`,\n tag: 'access-ui.request-state',\n query: origin ? {returnQuery: new URLSearchParams({origin}).toString()} : undefined,\n },\n )\n return status ?? {state: 'eligible'}\n } catch {\n return {state: 'eligible'}\n }\n}\n\ninterface ErrorResponseDetails {\n statusCode?: number\n message?: string\n code?: string\n redirectUrl?: string\n}\n\nfunction getErrorResponseDetails(err: unknown): ErrorResponseDetails {\n if (typeof err !== 'object' || err === null) return {}\n const response = (err as {response?: unknown}).response\n if (typeof response !== 'object' || response === null) return {}\n const {statusCode} = response as {statusCode?: unknown}\n const body = (response as {body?: unknown}).body\n const details: ErrorResponseDetails = {\n statusCode: typeof statusCode === 'number' ? statusCode : undefined,\n }\n if (typeof body === 'object' && body !== null) {\n const {message, code, redirectUrl} = body as {\n message?: unknown\n code?: unknown\n redirectUrl?: unknown\n }\n details.message = typeof message === 'string' ? message : undefined\n details.code = typeof code === 'string' ? code : undefined\n details.redirectUrl = typeof redirectUrl === 'string' ? redirectUrl : undefined\n }\n return details\n}\n\nfunction mapSubmitError(err: unknown): SubmitAccessRequestResult {\n const {statusCode, message, code, redirectUrl} = getErrorResponseDetails(err)\n\n if (statusCode === 403 && code === SAML_ENFORCEMENT_REQUIRED) {\n return {type: 'sso-enforced', redirectUrl, message}\n }\n if (statusCode === 429) {\n return {type: 'over-limit', message}\n }\n if (statusCode === 409) {\n if (message?.includes('email domain')) return {type: 'email-domain-blocked', message}\n if (message?.includes('disabled for organization')) return {type: 'requests-disabled', message}\n return {type: 'denied', message: message?.replace(/^Conflict -\\s*/, '')}\n }\n return {type: 'error', error: err}\n}\n\n/**\n * Submits an access request (`POST /access/{resourceType}/{resourceId}/requests`)\n * and maps the Access API's error contract to a {@link SubmitAccessRequestResult}.\n * Never throws for API rejections; unexpected failures come back as\n * `{type: 'error'}` so callers decide how to surface them.\n *\n * @public\n */\nexport async function submitAccessRequest(options: {\n client: SanityClient\n resourceType: AccessResourceType\n resourceId: string\n note?: string\n requestUrl?: string\n}): Promise<SubmitAccessRequestResult> {\n const {client, resourceType, resourceId, note, requestUrl} = options\n try {\n const request = await withAccessApiVersion(client).request<AccessRequest | null>({\n url: `/access/${resourceType}/${resourceId}/requests`,\n method: 'post',\n tag: 'access-ui.submit-request',\n body: {note, requestUrl, type: 'access'},\n })\n return {type: 'submitted', request}\n } catch (err) {\n return mapSubmitError(err)\n }\n}\n","import {type AccessRequest, type AccessRequestState} from './types'\n\n/**\n * Access requests are considered active for two weeks, matching the Access\n * API's request lifetime.\n */\nconst REQUEST_LIFETIME_MS = 14 * 24 * 60 * 60 * 1000\n\n/**\n * Derives where the caller stands on requesting access to a resource from\n * their existing access requests.\n *\n * A declined request blocks re-requesting for two weeks. A pending request\n * younger than two weeks is in review; older pending requests count as\n * expired, and the caller may request again.\n *\n * @public\n */\nexport function deriveAccessRequestState(\n requests: AccessRequest[] | null | undefined,\n resourceId: string,\n now: number = Date.now(),\n): AccessRequestState {\n if (!requests || requests.length === 0) return 'none'\n\n const isRecent = (request: AccessRequest) =>\n now - new Date(request.createdAt).getTime() < REQUEST_LIFETIME_MS\n\n const forResource = requests.filter((request) => request.resourceId === resourceId)\n\n if (forResource.some((request) => request.status === 'declined' && isRecent(request))) {\n return 'denied'\n }\n if (forResource.some((request) => request.status === 'pending' && isRecent(request))) {\n return 'pending'\n }\n if (forResource.some((request) => request.status === 'pending')) {\n return 'expired'\n }\n return 'none'\n}\n","/**\n * Human-readable title for a login provider id, e.g. `google` → `Google`,\n * `saml-xyz` → `SAML/SSO`.\n *\n * @public\n */\nexport function getProviderTitle(provider?: string): string | undefined {\n if (provider === 'google') return 'Google'\n if (provider === 'github') return 'GitHub'\n if (provider === 'sanity') return 'Sanity'\n if (provider === 'vercel') return 'Vercel'\n if (provider?.startsWith('saml-')) return 'SAML/SSO'\n return undefined\n}\n","import {type ReactNode} from 'react'\n\n/**\n * All user-facing strings in the request-access screen. Every label can be\n * overridden, so hosts with their own i18n stack (studio i18n, react-i18next)\n * inject translated copy while standalone hosts get the English defaults.\n *\n * @public\n */\nexport interface RequestAccessLabels {\n title: ReactNode\n sentTitle: ReactNode\n deniedTitle: ReactNode\n errorTitle: ReactNode\n describeNoAccess: (context: {email?: string}) => ReactNode\n promptProject: ReactNode\n promptOrganization: ReactNode\n notePlaceholder: string\n noteAriaLabel: string\n submit: ReactNode\n sentDescription: ReactNode\n pendingMessage: ReactNode\n deniedMessage: (context: {message?: string}) => ReactNode\n overLimitMessage: (context: {message?: string}) => ReactNode\n expiredMessage: ReactNode\n ssoEnforcedTitle: ReactNode\n ssoEnforcedMessage: (context: {providerTitle?: string}) => ReactNode\n ssoSignInCta: ReactNode\n resourceNotAvailableTitle: ReactNode\n resourceNotAvailableMessage: ReactNode\n submitFailedMessage: ReactNode\n wrongAccount: ReactNode\n signOut: ReactNode\n}\n\n/** @internal */\nexport const defaultLabels: RequestAccessLabels = {\n title: 'Request access',\n sentTitle: 'Access request sent',\n deniedTitle: 'Access request declined',\n errorTitle: 'Access request couldn’t be sent',\n describeNoAccess: ({email}) =>\n email ? (\n <>\n Your account <strong>({email})</strong> doesn’t have access to this content.\n </>\n ) : (\n <>Your account doesn’t have access to this content.</>\n ),\n promptProject: 'Send a request to the project admin(s).',\n promptOrganization: 'Send a request to the organization admin(s).',\n notePlaceholder: 'Message (optional)',\n noteAriaLabel: 'Message',\n submit: 'Request access',\n sentDescription:\n 'Your request has been sent. You will receive a notification if access is approved.',\n pendingMessage: 'Your request to access this content is pending approval.',\n deniedMessage: ({message}) => message ?? 'Your request to access this content has been declined.',\n overLimitMessage: ({message}) =>\n message ??\n 'You’ve reached the limit for access requests across all projects. Please wait before submitting more requests, or contact an admin.',\n expiredMessage: 'Your previous request has expired. You may request access again below.',\n // Reached both before and after a submit, so it must not claim a send failed.\n ssoEnforcedTitle: 'Sign in with SSO required',\n ssoEnforcedMessage: ({providerTitle}) =>\n providerTitle ? (\n <>\n You’re signed in with <strong>{providerTitle}</strong>, but this organization requires\n signing in with SSO. Access can’t be requested with this account.\n </>\n ) : (\n <>\n This organization requires signing in with SSO. Access can’t be requested with this account.\n </>\n ),\n ssoSignInCta: 'Sign in with SSO',\n resourceNotAvailableTitle: 'Access can’t be requested',\n resourceNotAvailableMessage: 'The resource currently being requested is no longer available.',\n submitFailedMessage: 'There was a problem submitting your request. Please try again.',\n wrongAccount: 'Wrong account?',\n signOut: 'Sign out',\n}\n","import {type SanityClient} from '@sanity/client'\nimport {LaunchIcon} from '@sanity/icons/Launch'\nimport {Avatar, Button, Card, Spinner, Text, TextArea} from '@sanity/ui'\nimport {\n type ReactNode,\n type SubmitEvent,\n Suspense,\n use,\n useId,\n useState,\n useTransition,\n} from 'react'\nimport {type ObservablePromise, useObservablePromise} from 'react-rx'\nimport {catchError, combineLatest, defer, of} from 'rxjs'\nimport {Flex, Box, VStack} from 'ui5'\n\nimport {\n fetchAccessRequestStatus,\n listMyAccessRequests,\n MAX_ACCESS_REQUEST_NOTE_LENGTH,\n submitAccessRequest,\n} from './accessRequests'\nimport {deriveAccessRequestState} from './deriveAccessRequestState'\nimport {defaultLabels, type RequestAccessLabels} from './labels'\nimport {getProviderTitle} from './providerTitle'\nimport {\n type AccessRequest,\n type AccessRequestEligibilityState,\n type AccessResourceType,\n type AccessUser,\n type SubmitAccessRequestResult,\n} from './types'\n\n/** @public */\nexport interface RequestAccessFormProps {\n /** Client authenticated as the requesting user. The Access API version is applied internally. */\n client: SanityClient\n resourceType?: AccessResourceType\n /** Project or organization id to request access to. */\n resourceId: string\n /** The signed-in user, rendered in the description and account footer. */\n currentUser?: AccessUser | null\n /**\n * Called when the user chooses \"Sign out\". The account footer's sign-out\n * action is only rendered when provided; hosts own the actual sign-out\n * mechanism (studio: `auth.logout()`, dashboard: logout route navigation).\n */\n onSignOut?: () => void\n /** Called after a request is successfully submitted, e.g. for analytics. */\n onRequestSubmitted?: (details: {note?: string}) => void\n /** Optional slot rendered above the title, e.g. a resource preview. */\n preview?: ReactNode\n /**\n * Renders an optional action area at the bottom of the card's content, e.g.\n * a navigation CTA. Called with the current view so the action can differ\n * per state (or be omitted for some); return null to render nothing.\n */\n renderAction?: (context: {view: RequestAccessView}) => ReactNode\n /** Label overrides for hosts with their own i18n stack. */\n labels?: Partial<RequestAccessLabels>\n}\n\n/**\n * The shared request-access screen: explains that the signed-in account lacks\n * access, lets the user request it with an optional note, and reflects the\n * request lifecycle (pending, denied, expired, over-limit, SSO-enforced).\n *\n * Fetches the caller's existing requests once it is mounted and suspends while\n * loading; an internal `Suspense` boundary renders a spinner, so hosts can\n * mount it directly. The fetch starts when the card commits, not while it\n * renders, so a host whose own `Suspense` boundary retries around it does not\n * refetch, and the result is kept while a hidden `<Activity>` conceals the\n * card. Remount with a `key` when `client` or `resourceId` change.\n *\n * @public\n */\nexport function RequestAccessForm(props: RequestAccessFormProps) {\n const {client, resourceType = 'project', resourceId} = props\n\n // Frozen at first render, so a new client for the same resource neither\n // refetches nor resets the form; hosts remount with `key` to reload. `defer`\n // keeps the requests out of render: react-rx subscribes once this component\n // commits, so a render React discards never fetches, and one subscription is\n // shared across StrictMode's double effects and an `<Activity>` hide/show.\n // Both requests complete, so the hook never resubscribes the source and\n // keeps the settled promise for the life of this instance.\n const [load$] = useState(() =>\n combineLatest({\n accessRequestsHistory: defer(() => listMyAccessRequests(client)).pipe(\n catchError(() => of(null)),\n ),\n accessRequestEligibilityState: defer(() =>\n fetchAccessRequestStatus({client, resourceType, resourceId, origin: getRequestUrl()}),\n ),\n }),\n )\n // Read with `use()` in the child below the boundary, never here: suspending\n // this component would stop the commit that starts the fetch.\n const loadPromise = useObservablePromise(load$)\n\n return (\n <Card border height=\"fill\" overflow=\"hidden\" radius={3} tone=\"default\">\n <Suspense\n fallback={\n <Flex alignItems=\"center\" height=\"100%\" justifyContent=\"center\" padding={5}>\n <Spinner muted />\n </Flex>\n }\n >\n <RequestAccessFormContent {...props} loadPromise={loadPromise} />\n </Suspense>\n </Card>\n )\n}\n\ninterface LoadedAccessRequestData {\n accessRequestsHistory: AccessRequest[] | null\n accessRequestEligibilityState: AccessRequestEligibilityState\n}\n\n/**\n * The view the request-access card is currently showing.\n *\n * @public\n */\nexport type RequestAccessView = 'form' | 'sent' | 'pending' | 'blocked' | 'sso-enforced'\n\n/**\n * Everything the card renders, copy included: the same state that picks a view\n * is the only thing that knows which words that view needs. Keying copy off\n * `view` alone cannot work, because `blocked` covers a prior decline, a gone\n * resource and four submit failures, each with its own copy.\n */\ntype ViewState = {title: ReactNode; description: ReactNode | null} & (\n | {view: 'form'; expired: boolean}\n | {view: 'sent'}\n | {view: 'pending'}\n | {view: 'blocked'; message: ReactNode}\n | {view: 'sso-enforced'; message: ReactNode; redirectUrl?: string}\n)\n\n/**\n * The server's verdict. `null` hands the decision back to the caller's own\n * request history, which resolves the states this endpoint does not.\n */\nfunction deriveServerViewState(\n status: AccessRequestEligibilityState,\n labels: RequestAccessLabels,\n providerTitle?: string,\n): ViewState | null {\n switch (status.state) {\n case 'saml-required':\n return ssoEnforcedState({labels, providerTitle, redirectUrl: status.redirectUrl})\n case 'resource-not-available':\n // Nothing was submitted, so the submit-failure copy would misdescribe it.\n return {\n view: 'blocked',\n title: labels.resourceNotAvailableTitle,\n description: null,\n message: labels.resourceNotAvailableMessage,\n }\n case 'eligible':\n return null\n default:\n return null\n }\n}\n\n// Reached on mount and after a submit 403, so the title claims neither.\nfunction ssoEnforcedState(options: {\n labels: RequestAccessLabels\n providerTitle?: string\n redirectUrl?: string\n}): ViewState {\n const {labels, providerTitle, redirectUrl} = options\n return {\n view: 'sso-enforced',\n title: labels.ssoEnforcedTitle,\n description: null,\n message: labels.ssoEnforcedMessage({providerTitle}),\n redirectUrl,\n }\n}\n\nfunction deriveViewState(options: {\n accessRequestsHistory: AccessRequest[] | null\n accessRequestEligibilityState: AccessRequestEligibilityState\n resourceId: string\n submitAccessRequestResult: SubmitAccessRequestResult | null\n currentUser?: AccessUser | null\n providerTitle?: string\n labels: RequestAccessLabels\n}): ViewState {\n const {\n accessRequestsHistory,\n accessRequestEligibilityState,\n resourceId,\n submitAccessRequestResult,\n currentUser,\n providerTitle,\n labels,\n } = options\n const submitFailure = (message: ReactNode): ViewState => ({\n view: 'blocked',\n title: labels.errorTitle,\n description: null,\n message,\n })\n\n if (submitAccessRequestResult) {\n switch (submitAccessRequestResult.type) {\n case 'submitted':\n return {view: 'sent', title: labels.sentTitle, description: labels.sentDescription}\n case 'sso-enforced':\n return ssoEnforcedState({\n labels,\n providerTitle,\n redirectUrl: submitAccessRequestResult.redirectUrl,\n })\n case 'denied':\n return submitFailure(labels.deniedMessage({message: submitAccessRequestResult.message}))\n case 'over-limit':\n return submitFailure(labels.overLimitMessage({message: submitAccessRequestResult.message}))\n case 'email-domain-blocked':\n case 'requests-disabled':\n return submitFailure(submitAccessRequestResult.message)\n case 'error':\n // Fall through to the fetched state; the form stays up with an inline error.\n break\n default:\n }\n }\n\n // The server's verdict outranks the request history: a pending request in an\n // enforced org is already dead, so \"pending approval\" would be a false\n // promise. It answers `eligible` when it has nothing to say.\n const serverState = deriveServerViewState(accessRequestEligibilityState, labels, providerTitle)\n if (serverState) return serverState\n\n // TODO: `pending` and `denied` will be replaced by future content in `accessRequestEligibilityState`\n const state = deriveAccessRequestState(accessRequestsHistory, resourceId)\n if (state === 'pending') {\n return {view: 'pending', title: labels.sentTitle, description: labels.pendingMessage}\n }\n // Derived from prefetch: the user hasn't submitted anything this session,\n // so the title must describe the prior decline, not a failed send.\n if (state === 'denied') {\n return {\n view: 'blocked',\n title: labels.deniedTitle,\n description: null,\n message: labels.deniedMessage({}),\n }\n }\n return {\n view: 'form',\n title: labels.title,\n description: labels.describeNoAccess({email: currentUser?.email}),\n expired: state === 'expired',\n }\n}\n\nfunction RequestAccessFormContent(\n props: RequestAccessFormProps & {loadPromise: ObservablePromise<LoadedAccessRequestData>},\n) {\n const {\n client,\n resourceType = 'project',\n resourceId,\n currentUser,\n onSignOut,\n onRequestSubmitted,\n preview,\n renderAction,\n loadPromise,\n } = props\n const {accessRequestsHistory, accessRequestEligibilityState} = use(loadPromise)\n\n const labels = {...defaultLabels, ...props.labels}\n const titleId = useId()\n\n const [note, setNote] = useState('')\n const [submitAccessRequestResult, setSubmitAccessRequestResult] =\n useState<SubmitAccessRequestResult | null>(null)\n const [isSubmitting, startSubmit] = useTransition()\n\n const providerTitle = getProviderTitle(currentUser?.provider)\n const state = deriveViewState({\n accessRequestsHistory,\n accessRequestEligibilityState,\n resourceId,\n submitAccessRequestResult,\n currentUser,\n providerTitle,\n labels,\n })\n\n const submitFailed = submitAccessRequestResult?.type === 'error'\n\n const handleSubmit = (event: SubmitEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (isSubmitting) return\n startSubmit(async () => {\n const trimmedNote = note.trim() || undefined\n const result = await submitAccessRequest({\n client,\n resourceType,\n resourceId,\n note: trimmedNote,\n requestUrl: getRequestUrl(),\n })\n setSubmitAccessRequestResult(result)\n if (result.type === 'submitted') onRequestSubmitted?.({note: trimmedNote})\n })\n }\n\n return (\n <Flex flexDirection=\"column\" height=\"100%\">\n <Flex flexDirection=\"column\" flexBasis=\"0%\" flexGrow={1} gap={4} padding={4}>\n {preview ? (\n <Flex justifyContent=\"center\" padding={2}>\n {preview}\n </Flex>\n ) : null}\n\n <Text as=\"h1\" id={titleId} size={2} weight=\"semibold\">\n {state.title}\n </Text>\n\n {state.description !== null ? (\n <Text as=\"p\" muted size={1}>\n {state.description}\n </Text>\n ) : null}\n\n {state.view === 'blocked' || state.view === 'sso-enforced' ? (\n <VStack gap={4}>\n <Card border padding={3} radius={2} role=\"alert\" tone=\"caution\">\n <Text as=\"p\" muted size={1}>\n {state.message}\n </Text>\n </Card>\n {state.view === 'sso-enforced' && state.redirectUrl ? (\n <Button\n as=\"a\"\n href={state.redirectUrl}\n iconRight={LaunchIcon}\n mode=\"ghost\"\n text={labels.ssoSignInCta}\n width=\"fill\"\n />\n ) : null}\n </VStack>\n ) : null}\n\n {state.view === 'form' ? (\n <VStack as=\"form\" aria-labelledby={titleId} onSubmit={handleSubmit} gap={4}>\n <Text as=\"p\" size={1}>\n {state.expired\n ? labels.expiredMessage\n : resourceType === 'organization'\n ? labels.promptOrganization\n : labels.promptProject}\n </Text>\n <VStack gap={2}>\n <TextArea\n aria-label={labels.noteAriaLabel}\n disabled={isSubmitting}\n fontSize={1}\n maxLength={MAX_ACCESS_REQUEST_NOTE_LENGTH}\n onChange={(event) => setNote(event.currentTarget.value)}\n placeholder={labels.notePlaceholder}\n rows={3}\n value={note}\n />\n <Text align=\"right\" muted size={0}>\n {`${note.length}/${MAX_ACCESS_REQUEST_NOTE_LENGTH}`}\n </Text>\n </VStack>\n {submitFailed ? (\n <Card border padding={3} radius={2} role=\"alert\" tone=\"critical\">\n <Text as=\"p\" muted size={1}>\n {labels.submitFailedMessage}\n </Text>\n </Card>\n ) : null}\n <Button\n disabled={isSubmitting}\n loading={isSubmitting}\n text={labels.submit}\n type=\"submit\"\n width=\"fill\"\n />\n </VStack>\n ) : null}\n\n {renderAction?.({view: state.view})}\n </Flex>\n\n {currentUser ? (\n <Card borderTop padding={3}>\n <Flex alignItems=\"center\" flexDirection=\"column\" gap={3}>\n <Flex alignItems=\"center\" gap={2} justifyContent=\"center\">\n <Avatar initials={getInitials(currentUser)} size={0} src={currentUser.profileImage} />\n <Box>\n <Text muted size={1} textOverflow=\"ellipsis\">\n {currentUser.email ?? currentUser.name}\n {providerTitle ? ` · ${providerTitle}` : ''}\n </Text>\n </Box>\n </Flex>\n {onSignOut ? (\n <Button\n fontSize={0}\n mode=\"bleed\"\n onClick={onSignOut}\n padding={2}\n textWeight=\"regular\"\n >\n <Text muted size={1}>\n {labels.wrongAccount} <strong>{labels.signOut}</strong>\n </Text>\n </Button>\n ) : null}\n </Flex>\n </Card>\n ) : null}\n </Flex>\n )\n}\n\n// The URL fragment can carry auth tokens (e.g. the #token= login handoff),\n// so it must never reach the Access API's logs.\nfunction getRequestUrl(): string | undefined {\n if (typeof window === 'undefined') return undefined\n const url = new URL(window.location.href)\n url.hash = ''\n return url.toString()\n}\n\nfunction getInitials(user: AccessUser): string | undefined {\n const source = user.name ?? user.email\n if (!source) return undefined\n const parts = source.trim().split(/\\s+/)\n const initials = parts\n .slice(0, 2)\n .map((part) => part[0])\n .join('')\n return initials ? initials.toUpperCase() : undefined\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/access-ui",
3
- "version": "6.18.1-next.3",
3
+ "version": "6.19.0-next.100",
4
4
  "description": "Shared request-access screen and access-request logic for Sanity applications",
5
5
  "keywords": [
6
6
  "access",
@@ -35,7 +35,9 @@
35
35
  "dependencies": {
36
36
  "@sanity/client": "^8.9.0",
37
37
  "@sanity/icons": "^5.2.3",
38
- "@sanity/ui": "^4.3.3",
38
+ "@sanity/ui": "^4.4.4",
39
+ "react-rx": "^7.0.1",
40
+ "rxjs": "^7.8.2",
39
41
  "ui5": "npm:@sanity/ui@5.0.0-alpha.14"
40
42
  },
41
43
  "devDependencies": {
@@ -46,15 +48,16 @@
46
48
  "@testing-library/react": "^16.3.3",
47
49
  "@testing-library/user-event": "^14.6.7",
48
50
  "@types/react": "^19.3.0",
51
+ "@types/react-dom": "^19.3.0",
49
52
  "@vitejs/plugin-react": "^6.1.2",
50
53
  "jsdom": "^29.1.1",
51
- "oxc-transform-react": "^0.153.0",
54
+ "oxc-transform-react": "^0.154.0",
52
55
  "react": "^19.3.0",
53
56
  "react-dom": "^19.3.0",
54
- "styled-components": "^6.5.3",
55
- "tsdown": "^0.23.0",
57
+ "styled-components": "^6.6.0",
58
+ "tsdown": "^0.23.1",
56
59
  "typescript": "^7.0.2",
57
- "vite": "^8.3.3",
60
+ "vite": "^8.3.4",
58
61
  "vitest": "^5.0.3"
59
62
  },
60
63
  "peerDependencies": {