(frontend) button access request on share modal

When a document is in public or connected mode,
users can now request access to the document.
This commit is contained in:
Anthony LC
2025-06-25 11:21:32 +02:00
committed by Manuel Raynaud
parent 2360a832af
commit 388f71d9d0
5 changed files with 100 additions and 36 deletions

View File

@@ -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(

View File

@@ -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 = ({
</Box>
);
};
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 (
<Button
onClick={() => createRequest({ docId })}
disabled={hasRequested}
{...buttonProps}
>
{buttonProps.children || t('Request access')}
</Button>
);
};

View File

@@ -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) => {
<Box data-testid="doc-share-quick-search">
{!canViewAccesses && (
<Box $height={listHeight} $align="center" $justify="center">
<Box
$height={listHeight}
$align="center"
$justify="center"
$gap="1rem"
>
<Text
$maxWidth="320px"
$textAlign="center"
@@ -162,6 +170,11 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
'You do not have permission to view users sharing this document or modify link settings.',
)}
</Text>
<ButtonAccessRequest
docId={doc.id}
color="tertiary"
size="small"
/>
</Box>
)}
{canViewAccesses && (

View File

@@ -1 +1,2 @@
export * from './DocShareModal';
export * from './DocShareAccessRequest';

View File

@@ -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 <Loading />;
}
@@ -137,12 +124,7 @@ const DocPage403 = ({ id }: DocProps) => {
{t('Home')}
</StyledButton>
</StyledLink>
<Button
onClick={() => createRequest({ docId: id })}
disabled={hasRequested}
>
{t('Request access')}
</Button>
<ButtonAccessRequest docId={id} />
</Box>
</Box>
</Box>