✨(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:
committed by
Manuel Raynaud
parent
2360a832af
commit
388f71d9d0
@@ -1,14 +1,13 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BROWSERS,
|
||||||
createDoc,
|
createDoc,
|
||||||
expectLoginPage,
|
expectLoginPage,
|
||||||
keyCloakSignIn,
|
keyCloakSignIn,
|
||||||
verifyDocName,
|
verifyDocName,
|
||||||
} from './common';
|
} from './common';
|
||||||
|
|
||||||
const browsersName = ['chromium', 'webkit', 'firefox'];
|
|
||||||
|
|
||||||
test.describe('Doc Visibility', () => {
|
test.describe('Doc Visibility', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -118,18 +117,20 @@ test.describe('Doc Visibility: Restricted', () => {
|
|||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
const otherBrowser = browsersName.find((b) => b !== browserName);
|
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||||
|
|
||||||
await keyCloakSignIn(page, otherBrowser!);
|
await keyCloakSignIn(page, otherBrowser!);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||||
).toBeVisible();
|
).toBeVisible({
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
await page.goto(urlDoc);
|
await page.goto(urlDoc);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('You do not have permission to view this document.'),
|
page.getByText('Insufficient access rights to view the document.'),
|
||||||
).toBeVisible({
|
).toBeVisible({
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
});
|
});
|
||||||
@@ -150,7 +151,7 @@ test.describe('Doc Visibility: Restricted', () => {
|
|||||||
name: 'Quick search input',
|
name: 'Quick search input',
|
||||||
});
|
});
|
||||||
|
|
||||||
const otherBrowser = browsersName.find((b) => b !== browserName);
|
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||||
const username = `user@${otherBrowser}.test`;
|
const username = `user@${otherBrowser}.test`;
|
||||||
await inputSearch.fill(username);
|
await inputSearch.fill(username);
|
||||||
await page.getByRole('option', { name: username }).click();
|
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.locator('h2').getByText(docTitle)).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'search' })).toBeHidden();
|
await expect(page.getByRole('button', { name: 'search' })).toBeHidden();
|
||||||
await expect(page.getByRole('button', { name: 'New doc' })).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');
|
const card = page.getByLabel('It is the card information');
|
||||||
await expect(card).toBeVisible();
|
await expect(card).toBeVisible();
|
||||||
|
|
||||||
await expect(card.getByText('Reader')).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 ({
|
test('It checks a public doc in editable mode', async ({
|
||||||
@@ -430,7 +440,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
|||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
const otherBrowser = browsersName.find((b) => b !== browserName);
|
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||||
await keyCloakSignIn(page, otherBrowser!);
|
await keyCloakSignIn(page, otherBrowser!);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@@ -443,6 +453,18 @@ test.describe('Doc Visibility: Authenticated', () => {
|
|||||||
await page.getByRole('button', { name: 'Share' }).click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
await page.getByRole('button', { name: 'Copy link' }).click();
|
await page.getByRole('button', { name: 'Copy link' }).click();
|
||||||
await expect(page.getByText('Link Copied !')).toBeVisible();
|
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 ({
|
test('It checks a authenticated doc in editable mode', async ({
|
||||||
@@ -490,7 +512,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
|||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
const otherBrowser = browsersName.find((b) => b !== browserName);
|
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||||
await keyCloakSignIn(page, otherBrowser!);
|
await keyCloakSignIn(page, otherBrowser!);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
ButtonProps,
|
||||||
VariantType,
|
VariantType,
|
||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
@@ -11,10 +12,13 @@ import { Box, BoxButton, Icon, LoadMoreText } from '@/components';
|
|||||||
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
|
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { AccessRequest, Doc } from '@/docs/doc-management/';
|
import { AccessRequest, Doc } from '@/docs/doc-management/';
|
||||||
|
import { useAuth } from '@/features/auth';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useAcceptDocAccessRequest,
|
useAcceptDocAccessRequest,
|
||||||
|
useCreateDocAccessRequest,
|
||||||
useDeleteDocAccessRequest,
|
useDeleteDocAccessRequest,
|
||||||
|
useDocAccessRequests,
|
||||||
useDocAccessRequestsInfinite,
|
useDocAccessRequestsInfinite,
|
||||||
} from '../api/useDocAccessRequest';
|
} from '../api/useDocAccessRequest';
|
||||||
|
|
||||||
@@ -147,3 +151,45 @@ export const QuickSearchGroupAccessRequest = ({
|
|||||||
</Box>
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import { isValidEmail } from '@/utils';
|
|||||||
|
|
||||||
import { KEY_LIST_USER, useUsers } from '../api';
|
import { KEY_LIST_USER, useUsers } from '../api';
|
||||||
|
|
||||||
import { QuickSearchGroupAccessRequest } from './DocShareAccessRequest';
|
import {
|
||||||
|
ButtonAccessRequest,
|
||||||
|
QuickSearchGroupAccessRequest,
|
||||||
|
} from './DocShareAccessRequest';
|
||||||
import { DocShareAddMemberList } from './DocShareAddMemberList';
|
import { DocShareAddMemberList } from './DocShareAddMemberList';
|
||||||
import {
|
import {
|
||||||
DocShareModalInviteUserRow,
|
DocShareModalInviteUserRow,
|
||||||
@@ -151,7 +154,12 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
|||||||
|
|
||||||
<Box data-testid="doc-share-quick-search">
|
<Box data-testid="doc-share-quick-search">
|
||||||
{!canViewAccesses && (
|
{!canViewAccesses && (
|
||||||
<Box $height={listHeight} $align="center" $justify="center">
|
<Box
|
||||||
|
$height={listHeight}
|
||||||
|
$align="center"
|
||||||
|
$justify="center"
|
||||||
|
$gap="1rem"
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
$maxWidth="320px"
|
$maxWidth="320px"
|
||||||
$textAlign="center"
|
$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.',
|
'You do not have permission to view users sharing this document or modify link settings.',
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
<ButtonAccessRequest
|
||||||
|
docId={doc.id}
|
||||||
|
color="tertiary"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{canViewAccesses && (
|
{canViewAccesses && (
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './DocShareModal';
|
export * from './DocShareModal';
|
||||||
|
export * from './DocShareAccessRequest';
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Button } from '@openfun/cunningham-react';
|
||||||
Button,
|
|
||||||
VariantType,
|
|
||||||
useToastProvider,
|
|
||||||
} from '@openfun/cunningham-react';
|
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
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 { Box, Icon, Loading, StyledLink, Text } from '@/components';
|
||||||
import { DEFAULT_QUERY_RETRY } from '@/core';
|
import { DEFAULT_QUERY_RETRY } from '@/core';
|
||||||
import { KEY_DOC, useDoc } from '@/features/docs';
|
import { KEY_DOC, useDoc } from '@/features/docs';
|
||||||
import {
|
import { ButtonAccessRequest } from '@/features/docs/doc-share';
|
||||||
useCreateDocAccessRequest,
|
import { useDocAccessRequests } from '@/features/docs/doc-share/api/useDocAccessRequest';
|
||||||
useDocAccessRequests,
|
|
||||||
} from '@/features/docs/doc-share/api/useDocAccessRequest';
|
|
||||||
import { MainLayout } from '@/layouts';
|
import { MainLayout } from '@/layouts';
|
||||||
import { NextPageWithLayout } from '@/types/next';
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
|
|
||||||
@@ -54,16 +48,9 @@ const DocPage403 = ({ id }: DocProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: requests, isLoading: isLoadingRequest } = useDocAccessRequests({
|
const { data: requests, isLoading: isLoadingRequest } = useDocAccessRequests({
|
||||||
docId: id,
|
docId: id,
|
||||||
|
page: 1,
|
||||||
});
|
});
|
||||||
const { replace } = useRouter();
|
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(
|
const hasRequested = !!requests?.results.find(
|
||||||
(request) => request.document === id,
|
(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}`);
|
void replace(`/docs/${id}`);
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
@@ -137,12 +124,7 @@ const DocPage403 = ({ id }: DocProps) => {
|
|||||||
{t('Home')}
|
{t('Home')}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<Button
|
<ButtonAccessRequest docId={id} />
|
||||||
onClick={() => createRequest({ docId: id })}
|
|
||||||
disabled={hasRequested}
|
|
||||||
>
|
|
||||||
{t('Request access')}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user