diff --git a/packages/sanity/src/structure/components/requestPermissionDialog/index.ts b/packages/sanity/src/structure/components/requestPermissionDialog/index.ts new file mode 100644 index 00000000000..32288078ca6 --- /dev/null +++ b/packages/sanity/src/structure/components/requestPermissionDialog/index.ts @@ -0,0 +1,2 @@ +export * from './RequestPermissionDialog' +export * from './useRoleRequestsStatus' diff --git a/packages/sanity/src/structure/components/requestPermissionDialog/useRoleRequestsStatus.tsx b/packages/sanity/src/structure/components/requestPermissionDialog/useRoleRequestsStatus.tsx new file mode 100644 index 00000000000..ff292adbb50 --- /dev/null +++ b/packages/sanity/src/structure/components/requestPermissionDialog/useRoleRequestsStatus.tsx @@ -0,0 +1,75 @@ +import {addWeeks, isAfter, isBefore} from 'date-fns' +import {useEffect, useState} from 'react' +import {from, of} from 'rxjs' +import {catchError, map} from 'rxjs/operators' +import {useClient, useProjectId} from 'sanity' + +import {type AccessRequest} from '../../../core/studio/screens' + +export const useRoleRequestsStatus = () => { + const client = useClient() + const projectId = useProjectId() + const [status, setStatus] = useState() + + useEffect(() => { + const checkRoleRequests$ = () => { + if (!client || !projectId) { + return of() + } + + return from( + client.request({ + url: `/access/requests/me`, + }), + ).pipe( + map((requests) => { + if (requests && requests.length) { + // Filter requests for the specific project and where type is 'role' + const projectRequests = requests.filter( + (request) => request.resourceId === projectId && request.type === 'role', + ) + + const declinedRequest = projectRequests.find((request) => request.status === 'declined') + if (declinedRequest) { + return 'denied' + } + + const pendingRequest = projectRequests.find( + (request) => + request.status === 'pending' && + isAfter(addWeeks(new Date(request.createdAt), 2), new Date()), + ) + if (pendingRequest) { + return 'pending' + } + + const oldPendingRequest = projectRequests.find( + (request) => + request.status === 'pending' && + isBefore(addWeeks(new Date(request.createdAt), 2), new Date()), + ) + if (oldPendingRequest) { + return 'expired' + } + } + return 'none' // No relevant requests found + }), + catchError((error) => { + console.error(error) + return of('error') // Return 'error' status on request failure + }), + ) + } + + const subscription = checkRoleRequests$().subscribe({ + next: (value) => setStatus(value), + error: (err) => console.error(err), + }) + + return () => { + subscription.unsubscribe() // Cleanup on component unmount + } + }, [client, projectId]) + + return status +} diff --git a/packages/sanity/src/structure/i18n/resources.ts b/packages/sanity/src/structure/i18n/resources.ts index e197a457a94..84f575b3753 100644 --- a/packages/sanity/src/structure/i18n/resources.ts +++ b/packages/sanity/src/structure/i18n/resources.ts @@ -114,6 +114,8 @@ const structureLocaleStrings = defineLocalesResources('structure', { /** The text for the permission check banner if the user only has multiple roles, but they do not allow updating this document */ 'banners.permission-check-banner.missing-permission_update_other': 'Your roles do not have permissions to edit this document.', + /** The pending text for the request permission button that appears for viewer roles */ + 'banners.permission-check-banner.request-permission-button.sent': 'Request sent', /** The text for the request permission button that appears for viewer roles */ 'banners.permission-check-banner.request-permission-button.text': 'Ask to edit', /** The text for the reload button */ diff --git a/packages/sanity/src/structure/panes/document/documentPanel/banners/Banner.tsx b/packages/sanity/src/structure/panes/document/documentPanel/banners/Banner.tsx index 0a508413298..526fca9891c 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/banners/Banner.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/banners/Banner.tsx @@ -10,6 +10,7 @@ interface BannerProps { onClick?: () => void text: string tone?: ButtonTone + disabled?: boolean } content: ReactNode icon?: ComponentType diff --git a/packages/sanity/src/structure/panes/document/documentPanel/banners/PermissionCheckBanner.tsx b/packages/sanity/src/structure/panes/document/documentPanel/banners/PermissionCheckBanner.tsx index e9192387efb..16b61cc3789 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/banners/PermissionCheckBanner.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/banners/PermissionCheckBanner.tsx @@ -3,7 +3,10 @@ import {Text} from '@sanity/ui' import {useState} from 'react' import {Translate, useCurrentUser, useListFormat, useTranslation} from 'sanity' -import {RequestPermissionDialog} from '../../../../components/requestPermissionDialog/RequestPermissionDialog' +import { + RequestPermissionDialog, + useRoleRequestsStatus, +} from '../../../../components/requestPermissionDialog' import {structureLocaleNamespace} from '../../../../i18n' import {Banner} from './Banner' @@ -15,6 +18,7 @@ interface PermissionCheckBannerProps { export function PermissionCheckBanner({granted, requiredPermission}: PermissionCheckBannerProps) { const currentUser = useCurrentUser() + const roleRequestStatus = useRoleRequestsStatus() const currentUserRoles = currentUser?.roles || [] const isOnlyViewer = currentUserRoles.length === 1 && currentUserRoles[0].name === 'viewer' const [showRequestPermissionDialog, setShowRequestPermissionDialog] = useState(false) @@ -47,11 +51,18 @@ export function PermissionCheckBanner({granted, requiredPermission}: PermissionC } center action={ - isOnlyViewer + isOnlyViewer && roleRequestStatus ? { - onClick: () => setShowRequestPermissionDialog(true), - text: t('banners.permission-check-banner.request-permission-button.text'), - tone: 'primary', + onClick: + roleRequestStatus === 'none' + ? () => setShowRequestPermissionDialog(true) + : undefined, + text: + roleRequestStatus === 'none' + ? t('banners.permission-check-banner.request-permission-button.text') + : t('banners.permission-check-banner.request-permission-button.sent'), + tone: roleRequestStatus === 'none' ? 'primary' : 'default', + disabled: roleRequestStatus !== 'none', // mode: 'bleed', } : undefined