From 388f71d9d078521b9f457dd99346d6aedbb2d1fa Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 25 Jun 2025 11:21:32 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20button=20access=20request?= =?UTF-8?q?=20on=20share=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a document is in public or connected mode, users can now request access to the document. --- .../app-impress/doc-visibility.spec.ts | 42 +++++++++++++---- .../components/DocShareAccessRequest.tsx | 46 +++++++++++++++++++ .../doc-share/components/DocShareModal.tsx | 17 ++++++- .../docs/doc-share/components/index.ts | 1 + .../apps/impress/src/pages/docs/[id]/403.tsx | 30 +++--------- 5 files changed, 100 insertions(+), 36 deletions(-) diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts index e4191f0b..5915d697 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts @@ -1,14 +1,13 @@ import { expect, test } from '@playwright/test'; import { + BROWSERS, createDoc, expectLoginPage, keyCloakSignIn, verifyDocName, } from './common'; -const browsersName = ['chromium', 'webkit', 'firefox']; - test.describe('Doc Visibility', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); @@ -118,18 +117,20 @@ test.describe('Doc Visibility: Restricted', () => { }) .click(); - const otherBrowser = browsersName.find((b) => b !== browserName); + const otherBrowser = BROWSERS.find((b) => b !== browserName); await keyCloakSignIn(page, otherBrowser!); await expect( page.getByRole('link', { name: 'Docs Logo Docs' }), - ).toBeVisible(); + ).toBeVisible({ + timeout: 10000, + }); await page.goto(urlDoc); await expect( - page.getByText('You do not have permission to view this document.'), + page.getByText('Insufficient access rights to view the document.'), ).toBeVisible({ timeout: 10000, }); @@ -150,7 +151,7 @@ test.describe('Doc Visibility: Restricted', () => { name: 'Quick search input', }); - const otherBrowser = browsersName.find((b) => b !== browserName); + const otherBrowser = BROWSERS.find((b) => b !== browserName); const username = `user@${otherBrowser}.test`; await inputSearch.fill(username); await page.getByRole('option', { name: username }).click(); @@ -262,11 +263,20 @@ test.describe('Doc Visibility: Public', () => { await expect(page.locator('h2').getByText(docTitle)).toBeVisible(); await expect(page.getByRole('button', { name: 'search' })).toBeHidden(); await expect(page.getByRole('button', { name: 'New doc' })).toBeHidden(); - await expect(page.getByRole('button', { name: 'Share' })).toBeVisible(); const card = page.getByLabel('It is the card information'); await expect(card).toBeVisible(); - await expect(card.getByText('Reader')).toBeVisible(); + + await page.getByRole('button', { name: 'Share' }).click(); + await expect( + page.getByText( + 'You do not have permission to view users sharing this document or modify link settings.', + ), + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Request access' }), + ).toBeHidden(); }); test('It checks a public doc in editable mode', async ({ @@ -430,7 +440,7 @@ test.describe('Doc Visibility: Authenticated', () => { }) .click(); - const otherBrowser = browsersName.find((b) => b !== browserName); + const otherBrowser = BROWSERS.find((b) => b !== browserName); await keyCloakSignIn(page, otherBrowser!); await expect( @@ -443,6 +453,18 @@ test.describe('Doc Visibility: Authenticated', () => { await page.getByRole('button', { name: 'Share' }).click(); await page.getByRole('button', { name: 'Copy link' }).click(); await expect(page.getByText('Link Copied !')).toBeVisible(); + + await expect( + page.getByText( + 'You do not have permission to view users sharing this document or modify link settings.', + ), + ).toBeVisible(); + + await page.getByRole('button', { name: 'Request access' }).click(); + + await expect( + page.getByRole('button', { name: 'Request access' }), + ).toBeDisabled(); }); test('It checks a authenticated doc in editable mode', async ({ @@ -490,7 +512,7 @@ test.describe('Doc Visibility: Authenticated', () => { }) .click(); - const otherBrowser = browsersName.find((b) => b !== browserName); + const otherBrowser = BROWSERS.find((b) => b !== browserName); await keyCloakSignIn(page, otherBrowser!); await expect( diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAccessRequest.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAccessRequest.tsx index 4def720d..31b83f39 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAccessRequest.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAccessRequest.tsx @@ -1,5 +1,6 @@ import { Button, + ButtonProps, VariantType, useToastProvider, } from '@openfun/cunningham-react'; @@ -11,10 +12,13 @@ import { Box, BoxButton, Icon, LoadMoreText } from '@/components'; import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search'; import { useCunninghamTheme } from '@/cunningham'; import { AccessRequest, Doc } from '@/docs/doc-management/'; +import { useAuth } from '@/features/auth'; import { useAcceptDocAccessRequest, + useCreateDocAccessRequest, useDeleteDocAccessRequest, + useDocAccessRequests, useDocAccessRequestsInfinite, } from '../api/useDocAccessRequest'; @@ -147,3 +151,45 @@ export const QuickSearchGroupAccessRequest = ({ ); }; + +type ButtonAccessRequestProps = { + docId: Doc['id']; +} & ButtonProps; + +export const ButtonAccessRequest = ({ + docId, + ...buttonProps +}: ButtonAccessRequestProps) => { + const { authenticated } = useAuth(); + const { data: requests } = useDocAccessRequests({ + docId, + page: 1, + }); + const { t } = useTranslation(); + const { toast } = useToastProvider(); + const { mutate: createRequest } = useCreateDocAccessRequest({ + onSuccess: () => { + toast(t('Access request sent successfully.'), VariantType.SUCCESS, { + duration: 3000, + }); + }, + }); + + const hasRequested = !!( + requests && requests?.results.find((request) => request.document === docId) + ); + + if (!authenticated) { + return null; + } + + return ( + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index a207010e..19d0fcc6 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -17,7 +17,10 @@ import { isValidEmail } from '@/utils'; import { KEY_LIST_USER, useUsers } from '../api'; -import { QuickSearchGroupAccessRequest } from './DocShareAccessRequest'; +import { + ButtonAccessRequest, + QuickSearchGroupAccessRequest, +} from './DocShareAccessRequest'; import { DocShareAddMemberList } from './DocShareAddMemberList'; import { DocShareModalInviteUserRow, @@ -151,7 +154,12 @@ export const DocShareModal = ({ doc, onClose }: Props) => { {!canViewAccesses && ( - + { 'You do not have permission to view users sharing this document or modify link settings.', )} + )} {canViewAccesses && ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/index.ts b/src/frontend/apps/impress/src/features/docs/doc-share/components/index.ts index aa4746c3..b85c96e3 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/index.ts @@ -1 +1,2 @@ export * from './DocShareModal'; +export * from './DocShareAccessRequest'; diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/403.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/403.tsx index 3eb96173..145c03bc 100644 --- a/src/frontend/apps/impress/src/pages/docs/[id]/403.tsx +++ b/src/frontend/apps/impress/src/pages/docs/[id]/403.tsx @@ -1,8 +1,4 @@ -import { - Button, - VariantType, - useToastProvider, -} from '@openfun/cunningham-react'; +import { Button } from '@openfun/cunningham-react'; import Head from 'next/head'; import Image from 'next/image'; import { useRouter } from 'next/router'; @@ -13,10 +9,8 @@ import img403 from '@/assets/icons/icon-403.png'; import { Box, Icon, Loading, StyledLink, Text } from '@/components'; import { DEFAULT_QUERY_RETRY } from '@/core'; import { KEY_DOC, useDoc } from '@/features/docs'; -import { - useCreateDocAccessRequest, - useDocAccessRequests, -} from '@/features/docs/doc-share/api/useDocAccessRequest'; +import { ButtonAccessRequest } from '@/features/docs/doc-share'; +import { useDocAccessRequests } from '@/features/docs/doc-share/api/useDocAccessRequest'; import { MainLayout } from '@/layouts'; import { NextPageWithLayout } from '@/types/next'; @@ -54,16 +48,9 @@ const DocPage403 = ({ id }: DocProps) => { const { t } = useTranslation(); const { data: requests, isLoading: isLoadingRequest } = useDocAccessRequests({ docId: id, + page: 1, }); const { replace } = useRouter(); - const { toast } = useToastProvider(); - const { mutate: createRequest } = useCreateDocAccessRequest({ - onSuccess: () => { - toast(t('Access request sent successfully.'), VariantType.SUCCESS, { - duration: 3000, - }); - }, - }); const hasRequested = !!requests?.results.find( (request) => request.document === id, @@ -84,7 +71,7 @@ const DocPage403 = ({ id }: DocProps) => { }, ); - if (error?.status !== 403) { + if (!isLoadingDoc && error?.status !== 403) { void replace(`/docs/${id}`); return ; } @@ -137,12 +124,7 @@ const DocPage403 = ({ id }: DocProps) => { {t('Home')} - +