mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-08 16:12:26 +02:00
wip
This commit is contained in:
@@ -67,7 +67,9 @@ export const addNewMember = async (
|
||||
response.status() === 200,
|
||||
);
|
||||
|
||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
|
||||
// Select a new user
|
||||
await inputSearch.fill(fillText);
|
||||
@@ -79,17 +81,19 @@ export const addNewMember = async (
|
||||
}[];
|
||||
|
||||
// Choose user
|
||||
await page.getByRole('option', { name: users[index].email }).click();
|
||||
await page.getByTestId(`search-user-row-${users[index].email}`).click();
|
||||
|
||||
// Choose a role
|
||||
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: role }).click();
|
||||
await page.getByRole('button', { name: 'Validate' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(`User ${users[index].email} added to the document.`),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
const list = page.getByTestId('doc-share-quick-search');
|
||||
const newUser = list.getByTestId(
|
||||
`doc-share-member-row-${users[index].email}`,
|
||||
);
|
||||
await expect(newUser).toBeVisible();
|
||||
return users[index].email;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, randomName } from './common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('Document create member', () => {
|
||||
test('it selects 2 users and 1 invitation', async ({ page, browserName }) => {
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/users/?q=user') && response.status() === 200,
|
||||
);
|
||||
await createDoc(page, 'select-multi-users', browserName, 1);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
await expect(inputSearch).toBeVisible();
|
||||
|
||||
// Select user 1 and verify tag
|
||||
await inputSearch.fill('user');
|
||||
const response = await responsePromise;
|
||||
const users = (await response.json()).results as {
|
||||
email: string;
|
||||
full_name: string;
|
||||
}[];
|
||||
|
||||
const list = page.getByTestId('doc-share-add-member-list');
|
||||
await expect(list).toBeHidden();
|
||||
const quickSearchContent = page.getByTestId('doc-share-quick-search');
|
||||
await quickSearchContent
|
||||
.getByTestId(`search-user-row-${users[0].email}`)
|
||||
.click();
|
||||
|
||||
await expect(list).toBeVisible();
|
||||
await expect(
|
||||
list.getByTestId(`doc-share-add-member-${users[0].email}`),
|
||||
).toBeVisible();
|
||||
await expect(list.getByText(`${users[0].full_name}`)).toBeVisible();
|
||||
|
||||
// Select user 2 and verify tag
|
||||
await inputSearch.fill('user');
|
||||
await quickSearchContent
|
||||
.getByTestId(`search-user-row-${users[1].email}`)
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
list.getByTestId(`doc-share-add-member-${users[1].email}`),
|
||||
).toBeVisible();
|
||||
await expect(list.getByText(`${users[1].full_name}`)).toBeVisible();
|
||||
|
||||
// Select email and verify tag
|
||||
const email = randomName('test@test.fr', browserName, 1)[0];
|
||||
await inputSearch.fill(email);
|
||||
await quickSearchContent.getByText(email).click();
|
||||
await expect(list.getByText(email)).toBeVisible();
|
||||
|
||||
// Check roles are displayed
|
||||
await list.getByLabel('doc-role-dropdown').click();
|
||||
await expect(page.getByRole('option', { name: 'Reader' })).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: 'Editor' })).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: 'Owner' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('option', { name: 'Administrator' }),
|
||||
).toBeVisible();
|
||||
|
||||
// Validate
|
||||
await page.getByRole('option', { name: 'Administrator' }).click();
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
// Check invitation added
|
||||
await expect(
|
||||
quickSearchContent.getByText('Pending invitations'),
|
||||
).toBeVisible();
|
||||
await expect(quickSearchContent.getByText(email).first()).toBeVisible();
|
||||
|
||||
// Check user added
|
||||
await expect(page.getByText('Share with 3 users')).toBeVisible();
|
||||
await expect(
|
||||
quickSearchContent.getByText(users[0].full_name).first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
quickSearchContent.getByText(users[0].email).first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
quickSearchContent.getByText(users[1].email).first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
quickSearchContent.getByText(users[1].full_name).first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('it try to add twice the same invitation', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await createDoc(page, 'invitation-twice', browserName, 1);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
|
||||
const [email] = randomName('test@test.fr', browserName, 1);
|
||||
await inputSearch.fill(email);
|
||||
await page.getByTestId(`search-user-row-${email}`).click();
|
||||
|
||||
// Choose a role
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: 'Owner' }).click();
|
||||
|
||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/invitations/') && response.status() === 201,
|
||||
);
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
// Check invitation sent
|
||||
|
||||
const responseCreateInvitation = await responsePromiseCreateInvitation;
|
||||
expect(responseCreateInvitation.ok()).toBeTruthy();
|
||||
|
||||
await inputSearch.fill(email);
|
||||
await page.getByTestId(`search-user-row-${email}`).click();
|
||||
|
||||
// Choose a role
|
||||
await container.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: 'Owner' }).click();
|
||||
|
||||
const responsePromiseCreateInvitationFail = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/invitations/') && response.status() === 400,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
await expect(
|
||||
page.getByText(`"${email}" is already invited to the document.`),
|
||||
).toBeVisible();
|
||||
const responseCreateInvitationFail =
|
||||
await responsePromiseCreateInvitationFail;
|
||||
expect(responseCreateInvitationFail.ok()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('The invitation endpoint get the language of the website', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await createDoc(page, 'user-invitation', browserName, 1);
|
||||
|
||||
const header = page.locator('header').first();
|
||||
await header.getByRole('combobox').getByText('EN').click();
|
||||
await header.getByRole('option', { name: 'FR' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Partager' }).click();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Trouver un membre à ajouter au document',
|
||||
});
|
||||
|
||||
const email = randomName('test@test.fr', browserName, 1)[0];
|
||||
await inputSearch.fill(email);
|
||||
await page.getByTestId(`search-user-row-${email}`).click();
|
||||
|
||||
// Choose a role
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: 'Administrateur' }).click();
|
||||
|
||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/invitations/') && response.status() === 201,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
// Check invitation sent
|
||||
|
||||
const responseCreateInvitation = await responsePromiseCreateInvitation;
|
||||
expect(responseCreateInvitation.ok()).toBeTruthy();
|
||||
expect(
|
||||
responseCreateInvitation.request().headers()['content-language'],
|
||||
).toBe('fr-fr');
|
||||
});
|
||||
|
||||
test('it manages invitation', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'user-invitation', browserName, 1);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
|
||||
const email = randomName('test@test.fr', browserName, 1)[0];
|
||||
await inputSearch.fill(email);
|
||||
await page.getByTestId(`search-user-row-${email}`).click();
|
||||
|
||||
// Choose a role
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: 'Administrator' }).click();
|
||||
|
||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/invitations/') && response.status() === 201,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
// Check invitation sent
|
||||
const responseCreateInvitation = await responsePromiseCreateInvitation;
|
||||
expect(responseCreateInvitation.ok()).toBeTruthy();
|
||||
|
||||
const listInvitation = page.getByTestId('doc-share-quick-search');
|
||||
const userInvitation = listInvitation.getByTestId(
|
||||
`doc-share-invitation-row-${email}`,
|
||||
);
|
||||
await expect(userInvitation).toBeVisible();
|
||||
|
||||
await userInvitation.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: 'Reader' }).click();
|
||||
|
||||
const moreActions = userInvitation.getByRole('button', {
|
||||
name: 'more_vert',
|
||||
});
|
||||
await moreActions.click();
|
||||
|
||||
await page.getByRole('option', { name: 'delete Delete' }).click();
|
||||
|
||||
await expect(userInvitation).toBeHidden();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,5 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { waitForElementCount } from '../helpers';
|
||||
|
||||
import { addNewMember, createDoc, goToGridDoc, verifyDocName } from './common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -15,10 +13,11 @@ test.describe('Document list members', () => {
|
||||
async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
const pageId = url.searchParams.get('page');
|
||||
const pageId = url.searchParams.get('page') ?? '1';
|
||||
|
||||
const accesses = {
|
||||
count: 100,
|
||||
next: 'http://anything/?page=2',
|
||||
count: 40,
|
||||
next: +pageId < 2 ? 'http://anything/?page=2' : undefined,
|
||||
previous: null,
|
||||
results: Array.from({ length: 20 }, (_, i) => ({
|
||||
id: `2ff1ec07-86c1-4534-a643-f41824a6c53a-${pageId}-${i}`,
|
||||
@@ -48,25 +47,20 @@ test.describe('Document list members', () => {
|
||||
);
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const list = page.getByLabel('List members card').locator('ul');
|
||||
await expect(list.locator('li')).toHaveCount(20);
|
||||
await list.getByText(`impress@impress.world-page-${1}-18`).hover();
|
||||
await page.mouse.wheel(0, 10);
|
||||
const prefix = 'doc-share-member-row';
|
||||
const elements = page.locator(`[data-testid^="${prefix}"]`);
|
||||
const loadMore = page.getByTestId('load-more-members');
|
||||
|
||||
await waitForElementCount(list.locator('li'), 21, 10000);
|
||||
await expect(elements).toHaveCount(20);
|
||||
await expect(page.getByText(`Impress World Page 1-16`)).toBeVisible();
|
||||
|
||||
expect(await list.locator('li').count()).toBeGreaterThan(20);
|
||||
await expect(list.getByText(`Impress World Page 1-16`)).toBeVisible();
|
||||
await expect(
|
||||
list.getByText(`impress@impress.world-page-1-16`),
|
||||
).toBeVisible();
|
||||
await expect(list.getByText(`Impress World Page 2-15`)).toBeVisible();
|
||||
await expect(
|
||||
list.getByText(`impress@impress.world-page-2-15`),
|
||||
).toBeVisible();
|
||||
await loadMore.click();
|
||||
await expect(elements).toHaveCount(40);
|
||||
await expect(page.getByText(`Impress World Page 2-15`)).toBeVisible();
|
||||
|
||||
await expect(loadMore).toBeHidden();
|
||||
});
|
||||
|
||||
test('it checks a big list of invitations', async ({ page }) => {
|
||||
@@ -75,10 +69,10 @@ test.describe('Document list members', () => {
|
||||
async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
const pageId = url.searchParams.get('page');
|
||||
const pageId = url.searchParams.get('page') ?? '1';
|
||||
const accesses = {
|
||||
count: 100,
|
||||
next: 'http://anything/?page=2',
|
||||
count: 40,
|
||||
next: +pageId < 2 ? 'http://anything/?page=2' : null,
|
||||
previous: null,
|
||||
results: Array.from({ length: 20 }, (_, i) => ({
|
||||
id: `2ff1ec07-86c1-4534-a643-f41824a6c53a-${pageId}-${i}`,
|
||||
@@ -105,23 +99,24 @@ test.describe('Document list members', () => {
|
||||
);
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const list = page.getByLabel('List invitation card').locator('ul');
|
||||
await expect(list.locator('li')).toHaveCount(20);
|
||||
await list.getByText(`impress@impress.world-page-${1}-18`).hover();
|
||||
await page.mouse.wheel(0, 10);
|
||||
const prefix = 'doc-share-invitation';
|
||||
const elements = page.locator(`[data-testid^="${prefix}"]`);
|
||||
const loadMore = page.getByTestId('load-more-invitations');
|
||||
|
||||
await waitForElementCount(list.locator('li'), 21, 10000);
|
||||
await expect(elements).toHaveCount(20);
|
||||
await expect(
|
||||
page.getByText(`impress@impress.world-page-1-16`).first(),
|
||||
).toBeVisible();
|
||||
|
||||
expect(await list.locator('li').count()).toBeGreaterThan(20);
|
||||
await loadMore.click();
|
||||
await expect(elements).toHaveCount(40);
|
||||
await expect(
|
||||
list.getByText(`impress@impress.world-page-1-16`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
list.getByText(`impress@impress.world-page-2-15`),
|
||||
page.getByText(`impress@impress.world-page-2-16`).first(),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(loadMore).toBeHidden();
|
||||
});
|
||||
|
||||
test('it checks the role rules', async ({ page, browserName }) => {
|
||||
@@ -130,59 +125,47 @@ test.describe('Document list members', () => {
|
||||
await verifyDocName(page, docTitle);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const list = page.getByLabel('List members card').locator('ul');
|
||||
|
||||
await expect(list.getByText(`user@${browserName}.e2e`)).toBeVisible();
|
||||
|
||||
const soleOwner = list.getByText(
|
||||
const list = page.getByTestId('doc-share-quick-search');
|
||||
await expect(list).toBeVisible();
|
||||
const currentUser = list.getByTestId(
|
||||
`doc-share-member-row-user@chromium.e2e`,
|
||||
);
|
||||
const currentUserRole = currentUser.getByLabel('doc-role-dropdown');
|
||||
await expect(currentUser).toBeVisible();
|
||||
await expect(currentUserRole).toBeVisible();
|
||||
await currentUserRole.click();
|
||||
const soloOwner = page.getByText(
|
||||
`You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.`,
|
||||
);
|
||||
await expect(soloOwner).toBeVisible();
|
||||
await list.click();
|
||||
const newUserEmail = await addNewMember(page, 0, 'Owner');
|
||||
const newUser = list.getByTestId(`doc-share-member-row-${newUserEmail}`);
|
||||
const newUserRoles = newUser.getByLabel('doc-role-dropdown');
|
||||
|
||||
await expect(soleOwner).toBeVisible();
|
||||
await expect(newUser).toBeVisible();
|
||||
|
||||
const username = await addNewMember(page, 0, 'Owner');
|
||||
await currentUserRole.click();
|
||||
await expect(soloOwner).toBeHidden();
|
||||
await list.click();
|
||||
|
||||
await expect(list.getByText(username)).toBeVisible();
|
||||
|
||||
await expect(soleOwner).toBeHidden();
|
||||
|
||||
const otherOwner = list.getByText(
|
||||
const otherOwner = page.getByText(
|
||||
`You cannot update the role or remove other owner.`,
|
||||
);
|
||||
|
||||
await newUserRoles.click();
|
||||
await expect(otherOwner).toBeVisible();
|
||||
await list.click();
|
||||
|
||||
const SelectRoleCurrentUser = list
|
||||
.locator('li')
|
||||
.filter({
|
||||
hasText: `user@${browserName}.e2e`,
|
||||
})
|
||||
.getByRole('combobox', { name: 'Role' });
|
||||
|
||||
await SelectRoleCurrentUser.click();
|
||||
await currentUserRole.click();
|
||||
await page.getByRole('option', { name: 'Administrator' }).click();
|
||||
await expect(page.getByText('The role has been updated')).toBeVisible();
|
||||
await list.click();
|
||||
await expect(currentUserRole).toBeVisible();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
// Admin still have the right to share
|
||||
await expect(
|
||||
shareModal.getByRole('combobox', {
|
||||
name: 'Visibility',
|
||||
}),
|
||||
).not.toHaveAttribute('disabled');
|
||||
|
||||
await SelectRoleCurrentUser.click();
|
||||
await currentUserRole.click();
|
||||
await page.getByRole('option', { name: 'Reader' }).click();
|
||||
await expect(page.getByText('The role has been updated')).toBeVisible();
|
||||
|
||||
// Reader does not have the right to share
|
||||
await expect(
|
||||
shareModal.getByRole('combobox', {
|
||||
name: 'Visibility',
|
||||
}),
|
||||
).toHaveAttribute('disabled');
|
||||
await list.click();
|
||||
await expect(currentUserRole).toBeHidden();
|
||||
});
|
||||
|
||||
test('it checks the delete members', async ({ page, browserName }) => {
|
||||
@@ -192,43 +175,44 @@ test.describe('Document list members', () => {
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const list = page.getByLabel('List members card').locator('ul');
|
||||
const list = page.getByTestId('doc-share-quick-search');
|
||||
|
||||
const nameMyself = `user@${browserName}.e2e`;
|
||||
await expect(list.getByText(nameMyself)).toBeVisible();
|
||||
const emailMyself = `user@${browserName}.e2e`;
|
||||
const mySelf = list.getByTestId(`doc-share-member-row-${emailMyself}`);
|
||||
const mySelfMoreActions = mySelf.getByRole('button', { name: 'more_vert' });
|
||||
|
||||
const userOwner = await addNewMember(page, 0, 'Owner');
|
||||
await expect(list.getByText(userOwner)).toBeVisible();
|
||||
const userOwnerEmail = await addNewMember(page, 0, 'Owner');
|
||||
const userOwner = list.getByTestId(
|
||||
`doc-share-member-row-${userOwnerEmail}`,
|
||||
);
|
||||
const userOwnerMoreActions = userOwner.getByRole('button', {
|
||||
name: 'more_vert',
|
||||
});
|
||||
|
||||
const userReader = await addNewMember(page, 0, 'Reader');
|
||||
await expect(list.getByText(userReader)).toBeVisible();
|
||||
const userReaderEmail = await addNewMember(page, 0, 'Reader');
|
||||
const userReader = list.getByTestId(
|
||||
`doc-share-member-row-${userReaderEmail}`,
|
||||
);
|
||||
const userReaderMoreActions = userReader.getByRole('button', {
|
||||
name: 'more_vert',
|
||||
});
|
||||
|
||||
await list
|
||||
.locator('li')
|
||||
.filter({
|
||||
hasText: userReader,
|
||||
})
|
||||
.getByText('delete')
|
||||
.click();
|
||||
await expect(mySelf).toBeVisible();
|
||||
await expect(userOwner).toBeVisible();
|
||||
await expect(userReader).toBeVisible();
|
||||
|
||||
await expect(list.getByText(userReader)).toBeHidden();
|
||||
await expect(userOwnerMoreActions).toBeVisible();
|
||||
await expect(userReaderMoreActions).toBeVisible();
|
||||
await expect(mySelfMoreActions).toBeVisible();
|
||||
|
||||
await list
|
||||
.locator('li')
|
||||
.filter({
|
||||
hasText: nameMyself,
|
||||
})
|
||||
.getByText('delete')
|
||||
.click();
|
||||
|
||||
await expect(list.getByText(nameMyself)).toBeHidden();
|
||||
await userReaderMoreActions.click();
|
||||
await page.getByRole('option', { name: 'Delete' }).click();
|
||||
await expect(userReader).toBeHidden();
|
||||
|
||||
await mySelfMoreActions.click();
|
||||
await page.getByRole('option', { name: 'Delete' }).click();
|
||||
await expect(
|
||||
page.getByText('The member has been removed from the document').first(),
|
||||
page.getByText('You do not have permission to perform this action.'),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Share', level: 3 }),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,22 +143,22 @@ test.describe('Doc Visibility: Restricted', () => {
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
|
||||
const otherBrowser = browsersName.find((b) => b !== browserName);
|
||||
const username = `user@${otherBrowser}.e2e`;
|
||||
await inputSearch.fill(username);
|
||||
await page.getByRole('option', { name: username }).click();
|
||||
await page.getByTestId(`search-user-row-${username}`).click();
|
||||
|
||||
// Choose a role
|
||||
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('option', { name: 'Administrator' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Validate' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(`User ${username} added to the document.`),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
await page.locator('.c__modal__backdrop').click({
|
||||
position: { x: 0, y: 0 },
|
||||
@@ -434,15 +434,17 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
page.getByText('Read only, you cannot edit this document'),
|
||||
).toBeVisible();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
await expect(
|
||||
shareModal.getByRole('combobox', {
|
||||
page.getByRole('combobox', {
|
||||
name: 'Visibility',
|
||||
}),
|
||||
).toHaveAttribute('disabled');
|
||||
await expect(shareModal.getByText('Search by email')).toBeHidden();
|
||||
await expect(shareModal.getByLabel('List members card')).toBeHidden();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
|
||||
await expect(inputSearch).toBeHidden();
|
||||
});
|
||||
|
||||
test('It checks a authenticated doc in editable mode', async ({
|
||||
@@ -512,14 +514,15 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
page.getByText('Read only, you cannot edit this document'),
|
||||
).toBeHidden();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
await expect(
|
||||
shareModal.getByRole('combobox', {
|
||||
page.getByRole('combobox', {
|
||||
name: 'Visibility',
|
||||
}),
|
||||
).toHaveAttribute('disabled');
|
||||
await expect(shareModal.getByText('Search by email')).toBeHidden();
|
||||
await expect(shareModal.getByLabel('List members card')).toBeHidden();
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Find a member to add to the document',
|
||||
});
|
||||
|
||||
await expect(inputSearch).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ const StyledButton = styled(Button)`
|
||||
export interface DropButtonProps {
|
||||
button: ReactNode;
|
||||
isOpen?: boolean;
|
||||
label?: string;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -34,6 +35,7 @@ export const DropButton = ({
|
||||
button,
|
||||
isOpen = false,
|
||||
onOpenChange,
|
||||
label,
|
||||
children,
|
||||
}: PropsWithChildren<DropButtonProps>) => {
|
||||
const [opacity, setOpacity] = useState(false);
|
||||
@@ -53,7 +55,7 @@ export const DropButton = ({
|
||||
|
||||
return (
|
||||
<DialogTrigger onOpenChange={onOpenChangeHandler} isOpen={isLocalOpen}>
|
||||
<StyledButton>{button}</StyledButton>
|
||||
<StyledButton aria-label={label}>{button}</StyledButton>
|
||||
<StyledPopover
|
||||
style={{ opacity: opacity ? 1 : 0 }}
|
||||
isOpen={isLocalOpen}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxButton, BoxProps, DropButton, Icon } from '@/components';
|
||||
import { Box, BoxButton, BoxProps, DropButton, Icon, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
export type DropdownMenuOption = {
|
||||
@@ -10,13 +10,16 @@ export type DropdownMenuOption = {
|
||||
testId?: string;
|
||||
callback?: () => void | Promise<unknown>;
|
||||
danger?: boolean;
|
||||
isSelected?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownMenuProps = {
|
||||
options: DropdownMenuOption[];
|
||||
showArrow?: boolean;
|
||||
label?: string;
|
||||
arrowCss?: BoxProps['$css'];
|
||||
topMessage?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenu = ({
|
||||
@@ -24,6 +27,8 @@ export const DropdownMenu = ({
|
||||
children,
|
||||
showArrow = false,
|
||||
arrowCss,
|
||||
label,
|
||||
topMessage,
|
||||
}: PropsWithChildren<DropdownMenuProps>) => {
|
||||
const theme = useCunninghamTheme();
|
||||
const spacings = theme.spacingsTokens();
|
||||
@@ -34,10 +39,12 @@ export const DropdownMenu = ({
|
||||
setIsOpen(isOpen);
|
||||
};
|
||||
|
||||
console.log('topMessage', topMessage);
|
||||
return (
|
||||
<DropButton
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
label={label}
|
||||
button={
|
||||
showArrow ? (
|
||||
<Box $direction="row" $align="center">
|
||||
@@ -57,11 +64,23 @@ export const DropdownMenu = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
{options.map((option, index) => {
|
||||
<Box $maxWidth="320px">
|
||||
{topMessage && (
|
||||
<Text
|
||||
$variation="1000"
|
||||
$wrap="wrap"
|
||||
$size="xs"
|
||||
$weight="bold"
|
||||
$padding={{ vertical: 'xs', horizontal: 'base' }}
|
||||
>
|
||||
{topMessage}
|
||||
</Text>
|
||||
)}
|
||||
{options.map((option) => {
|
||||
const isDisabled = option.disabled !== undefined && option.disabled;
|
||||
return (
|
||||
<BoxButton
|
||||
role="option"
|
||||
data-testid={option.testId}
|
||||
$direction="row"
|
||||
disabled={isDisabled}
|
||||
@@ -73,6 +92,7 @@ export const DropdownMenu = ({
|
||||
}}
|
||||
key={option.label}
|
||||
$align="center"
|
||||
$justify="space-between"
|
||||
$background={colors['greyscale-000']}
|
||||
$color={colors['primary-600']}
|
||||
$padding={{ vertical: 'xs', horizontal: 'base' }}
|
||||
@@ -81,28 +101,35 @@ export const DropdownMenu = ({
|
||||
$css={css`
|
||||
border: none;
|
||||
font-size: var(--c--theme--font--sizes--sm);
|
||||
color: var(--c--theme--colors--primary-600);
|
||||
color: var(--c--theme--colors--greyscale-1000);
|
||||
font-weight: 500;
|
||||
cursor: ${isDisabled ? 'not-allowed' : 'pointer'};
|
||||
user-select: none;
|
||||
border-bottom: ${index !== options.length - 1
|
||||
? `1px solid var(--c--theme--colors--greyscale-200)`
|
||||
: 'none'};
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
}
|
||||
`}
|
||||
>
|
||||
{option.icon && (
|
||||
<Icon
|
||||
$size="20px"
|
||||
$theme={!isDisabled ? 'primary' : 'greyscale'}
|
||||
$variation={!isDisabled ? '600' : '400'}
|
||||
iconName={option.icon}
|
||||
/>
|
||||
<Box $direction="row" $align="center" $gap={spacings['base']}>
|
||||
{option.icon && (
|
||||
<Icon
|
||||
$size="20px"
|
||||
$theme="greyscale"
|
||||
$variation={isDisabled ? '400' : '1000'}
|
||||
iconName={option.icon}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
$margin={{ top: '-3px' }}
|
||||
$variation={isDisabled ? '400' : '1000'}
|
||||
>
|
||||
{option.label}
|
||||
</Text>
|
||||
</Box>
|
||||
{option.isSelected && (
|
||||
<Icon iconName="check" $size="20px" $theme="greyscale" />
|
||||
)}
|
||||
{option.label}
|
||||
</BoxButton>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -4,11 +4,18 @@ import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
|
||||
export const LoadMoreText = () => {
|
||||
type LoadMoreTextProps = {
|
||||
['data-testid']?: string;
|
||||
};
|
||||
|
||||
export const LoadMoreText = ({
|
||||
'data-testid': dataTestId,
|
||||
}: LoadMoreTextProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box
|
||||
data-testid={dataTestId}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap="0.4rem"
|
||||
|
||||
@@ -53,7 +53,9 @@ export const QuickSearchInput = ({
|
||||
<Command.Input
|
||||
/* eslint-disable-next-line jsx-a11y/no-autofocus */
|
||||
autoFocus={true}
|
||||
aria-label={t('Find a member to add to the document')}
|
||||
value={inputValue}
|
||||
role="combobox"
|
||||
placeholder={placeholder ?? t('Search')}
|
||||
onValueChange={onFilter}
|
||||
/>
|
||||
|
||||
@@ -7,13 +7,8 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
transition: transform 100ms ease;
|
||||
outline: none;
|
||||
|
||||
.dark & {
|
||||
background: rgba(22, 22, 22, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-input] {
|
||||
@@ -23,9 +18,7 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
padding: 8px;
|
||||
background: white;
|
||||
outline: none;
|
||||
|
||||
color: var(--c--theme--colors--greyscale-1000);
|
||||
|
||||
border-radius: 0;
|
||||
|
||||
&::placeholder {
|
||||
@@ -50,17 +43,12 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
|
||||
[cmdk-item] {
|
||||
content-visibility: auto;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
cursor: pointer;
|
||||
border-radius: var(--c--theme--spacings--xs);
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* padding: var(--c--theme--spacings--2xs) ; */
|
||||
|
||||
user-select: none;
|
||||
will-change: background, color;
|
||||
transition: all 150ms ease;
|
||||
@@ -89,11 +77,11 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
}
|
||||
|
||||
[cmdk-list] {
|
||||
height: 500px;
|
||||
|
||||
padding: 0 var(--c--theme--spacings--sm) var(--c--theme--spacings--sm)
|
||||
var(--c--theme--spacings--sm);
|
||||
max-height: 700px;
|
||||
|
||||
|
||||
flex:1;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
transition: 100ms ease;
|
||||
@@ -161,7 +149,7 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
.c__modal__title {
|
||||
font-size: var(--c--theme--font--sizes--xs);
|
||||
|
||||
padding: var(--c--theme--spacings--200W);
|
||||
padding: var(--c--theme--spacings--base);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,7 @@ import {
|
||||
useEditorStore,
|
||||
usePanelEditorStore,
|
||||
} from '@/features/docs/doc-editor/';
|
||||
import {
|
||||
Doc,
|
||||
ModalRemoveDoc,
|
||||
ModalShare,
|
||||
} from '@/features/docs/doc-management';
|
||||
import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management';
|
||||
import { ModalVersion, Versions } from '@/features/docs/doc-versioning';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
@@ -204,9 +200,9 @@ export const DocToolBox = ({ doc, versionId }: DocToolBoxProps) => {
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</Box>
|
||||
{isModalShareOpen && (
|
||||
{/* {isModalShareOpen && (
|
||||
<ModalShare onClose={() => setIsModalShareOpen(false)} doc={doc} />
|
||||
)}
|
||||
)} */}
|
||||
{modalShare.isOpen && (
|
||||
<DocShareModal doc={doc} onClose={modalShare.onClose} />
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { DropdownMenu, DropdownMenuOption, Text } from '@/components';
|
||||
@@ -9,19 +10,26 @@ type Props = {
|
||||
currentRole: Role;
|
||||
onSelectRole?: (role: Role) => void;
|
||||
canUpdate?: boolean;
|
||||
isLastOwner?: boolean;
|
||||
isOtherOwner?: boolean;
|
||||
};
|
||||
export const DocRoleDropdown = ({
|
||||
canUpdate = true,
|
||||
currentRole,
|
||||
onSelectRole,
|
||||
isLastOwner,
|
||||
isOtherOwner,
|
||||
}: Props) => {
|
||||
const { transRole, translatedRoles } = useTrans();
|
||||
const { t } = useTranslation();
|
||||
const { transRole, translatedRoles, getNotAllowedMessage } = useTrans();
|
||||
|
||||
const roles: DropdownMenuOption[] = Object.keys(translatedRoles).map(
|
||||
(key) => {
|
||||
return {
|
||||
label: transRole(key as Role),
|
||||
callback: () => onSelectRole?.(key as Role),
|
||||
disabled: isLastOwner || isOtherOwner,
|
||||
isSelected: currentRole === (key as Role),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -40,7 +48,16 @@ export const DocRoleDropdown = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu showArrow={true} options={roles}>
|
||||
<DropdownMenu
|
||||
topMessage={getNotAllowedMessage(
|
||||
canUpdate,
|
||||
!!isLastOwner,
|
||||
!!isOtherOwner,
|
||||
)}
|
||||
label="doc-role-dropdown"
|
||||
showArrow={true}
|
||||
options={roles}
|
||||
>
|
||||
<Text
|
||||
$variation="600"
|
||||
$css={css`
|
||||
|
||||
@@ -117,6 +117,7 @@ export const DocShareAddMemberList = ({
|
||||
|
||||
return (
|
||||
<Box
|
||||
data-testid="doc-share-add-member-list"
|
||||
$direction="row"
|
||||
$padding={spacing.sm}
|
||||
$align="center"
|
||||
|
||||
@@ -17,6 +17,7 @@ export const DocShareAddMemberListItem = ({ user, onRemoveUser }: Props) => {
|
||||
const fontSize = fontSizesTokens();
|
||||
return (
|
||||
<Box
|
||||
data-testid={`doc-share-add-member-${user.email}`}
|
||||
$radius={spacing['3xs']}
|
||||
$direction="row"
|
||||
$height="fit-content"
|
||||
|
||||
@@ -32,7 +32,8 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
||||
};
|
||||
|
||||
const { toast } = useToastProvider();
|
||||
const canUpdate = invitation.abilities.partial_update;
|
||||
const canUpdate = doc.abilities.accesses_manage;
|
||||
|
||||
const { mutate: updateDocInvitation } = useUpdateDocInvitation({
|
||||
onError: (error) => {
|
||||
toast(
|
||||
@@ -78,22 +79,30 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<SearchUserRow
|
||||
showRightOnHover={false}
|
||||
user={fakeUser}
|
||||
right={
|
||||
<Box $direction="row" $align="center">
|
||||
<DocRoleDropdown
|
||||
currentRole={invitation.role}
|
||||
onSelectRole={onUpdate}
|
||||
canUpdate={canUpdate}
|
||||
/>
|
||||
<Box
|
||||
$width="100%"
|
||||
data-testid={`doc-share-invitation-row-${invitation.email}`}
|
||||
>
|
||||
<SearchUserRow
|
||||
alwaysShowRight={true}
|
||||
user={fakeUser}
|
||||
right={
|
||||
<Box $direction="row" $align="center">
|
||||
<DocRoleDropdown
|
||||
currentRole={invitation.role}
|
||||
onSelectRole={onUpdate}
|
||||
canUpdate={canUpdate}
|
||||
/>
|
||||
|
||||
<DropdownMenu options={moreActions}>
|
||||
<IconOptions $variation="600" />
|
||||
</DropdownMenu>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<DropdownMenu
|
||||
data-testid="doc-share-invitation-more-actions"
|
||||
options={moreActions}
|
||||
>
|
||||
<IconOptions $variation="600" />
|
||||
</DropdownMenu>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
||||
const { toast } = useToastProvider();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const isNotAllowed =
|
||||
isOtherOwner || isLastOwner || !doc.abilities.accesses_manage;
|
||||
isOtherOwner || !!isLastOwner || !doc.abilities.accesses_manage;
|
||||
|
||||
const { mutate: updateDocAccess } = useUpdateDocAccess({
|
||||
onError: () => {
|
||||
@@ -68,24 +68,34 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<SearchUserRow
|
||||
alwaysShowRight={true}
|
||||
user={access.user}
|
||||
right={
|
||||
<Box $direction="row" $align="center">
|
||||
<DocRoleDropdown
|
||||
currentRole={access.role}
|
||||
onSelectRole={onUpdate}
|
||||
canUpdate={!isNotAllowed}
|
||||
/>
|
||||
<Box
|
||||
$width="100%"
|
||||
data-testid={`doc-share-member-row-${access.user.email}`}
|
||||
>
|
||||
<SearchUserRow
|
||||
alwaysShowRight={true}
|
||||
user={access.user}
|
||||
right={
|
||||
<Box $direction="row" $align="center">
|
||||
<DocRoleDropdown
|
||||
currentRole={access.role}
|
||||
onSelectRole={onUpdate}
|
||||
canUpdate={doc.abilities.accesses_manage}
|
||||
isLastOwner={isLastOwner}
|
||||
isOtherOwner={!!isOtherOwner}
|
||||
/>
|
||||
|
||||
{isDesktop && (
|
||||
<DropdownMenu options={moreActions}>
|
||||
<IconOptions $variation="600" />
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
{isDesktop && doc.abilities.accesses_manage && (
|
||||
<DropdownMenu options={moreActions}>
|
||||
<IconOptions
|
||||
data-testid="doc-share-member-more-actions"
|
||||
$variation="600"
|
||||
/>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@openfun/cunningham-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { Box } from '@/components';
|
||||
@@ -84,7 +85,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
endActions: membersQuery.hasNextPage
|
||||
? [
|
||||
{
|
||||
content: <LoadMoreText />,
|
||||
content: <LoadMoreText data-testid="load-more-members" />,
|
||||
onSelect: () => void membersQuery.fetchNextPage(),
|
||||
},
|
||||
]
|
||||
@@ -102,7 +103,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
endActions: invitationQuery.hasNextPage
|
||||
? [
|
||||
{
|
||||
content: <LoadMoreText />,
|
||||
content: <LoadMoreText data-testid="load-more-invitations" />,
|
||||
onSelect: () => void invitationQuery.fetchNextPage(),
|
||||
},
|
||||
]
|
||||
@@ -121,7 +122,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
};
|
||||
|
||||
return {
|
||||
groupName: t('Share with {{count}} users', { count: users.length }),
|
||||
groupName: t('Search user result', { count: users.length }),
|
||||
elements: users,
|
||||
endActions:
|
||||
isEmail && users.length === 0
|
||||
@@ -154,6 +155,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
closeOnClickOutside
|
||||
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
|
||||
onClose={onClose}
|
||||
title={
|
||||
@@ -162,89 +164,125 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{canShare && selectedUsers.length > 0 && (
|
||||
<Box $padding={{ horizontal: 'base' }} $margin={{ vertical: '11px' }}>
|
||||
<DocShareAddMemberList
|
||||
doc={doc}
|
||||
selectedUsers={selectedUsers}
|
||||
onRemoveUser={onRemoveUser}
|
||||
afterInvite={() => {
|
||||
setUserQuery('');
|
||||
setInputValue('');
|
||||
setSelectedUsers([]);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<QuickSearch
|
||||
data={[]}
|
||||
onFilter={(str) => {
|
||||
setInputValue(str);
|
||||
onFilter(str);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
showInput={canShare}
|
||||
loading={searchUsersQuery.isLoading}
|
||||
renderElement={(user) => <DocShareModalInviteUserRow user={user} />}
|
||||
onSelect={onSelect}
|
||||
placeholder={t('Type a name or email')}
|
||||
<Box
|
||||
aria-label={t('List members card')}
|
||||
$direction="column"
|
||||
$height={isDesktop ? undefined : 'calc(100vh - 50px)'}
|
||||
$justify="space-between"
|
||||
>
|
||||
{!showMemberSection && inputValue !== '' && (
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
onSelect={onSelect}
|
||||
renderElement={(user) => <DocShareModalInviteUserRow user={user} />}
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && (
|
||||
<>
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem doc={doc} invitation={invitation} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</QuickSearch>
|
||||
<HorizontalSeparator />
|
||||
<DocVisibility doc={doc} />
|
||||
<HorizontalSeparator />
|
||||
<Box $direction="row" $justify="space-between" $padding="base">
|
||||
<Button
|
||||
fullWidth={false}
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(window.location.href)
|
||||
.then(() => {
|
||||
toast(t('Link Copied !'), VariantType.SUCCESS, {
|
||||
duration: 3000,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast(t('Failed to copy link'), VariantType.ERROR, {
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
}}
|
||||
color="tertiary"
|
||||
icon={<span className="material-icons">add_link</span>}
|
||||
<Box
|
||||
$flex={1}
|
||||
$css={css`
|
||||
overflow-y: auto;
|
||||
[cmdk-list] {
|
||||
overflow-y: auto;
|
||||
max-height: ${isDesktop ? '400px' : '100%'};
|
||||
}
|
||||
`}
|
||||
>
|
||||
{t('Copy link')}
|
||||
</Button>
|
||||
<Button onClick={onClose} color="primary">
|
||||
{t('Ok')}
|
||||
</Button>
|
||||
{canShare && selectedUsers.length > 0 && (
|
||||
<Box
|
||||
$padding={{ horizontal: 'base' }}
|
||||
$margin={{ vertical: '11px' }}
|
||||
>
|
||||
<DocShareAddMemberList
|
||||
doc={doc}
|
||||
selectedUsers={selectedUsers}
|
||||
onRemoveUser={onRemoveUser}
|
||||
afterInvite={() => {
|
||||
setUserQuery('');
|
||||
setInputValue('');
|
||||
setSelectedUsers([]);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box data-testid="doc-share-quick-search">
|
||||
<QuickSearch
|
||||
data={[]}
|
||||
onFilter={(str) => {
|
||||
setInputValue(str);
|
||||
onFilter(str);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
showInput={canShare}
|
||||
loading={searchUsersQuery.isLoading}
|
||||
renderElement={(user) => (
|
||||
<DocShareModalInviteUserRow user={user} />
|
||||
)}
|
||||
onSelect={onSelect}
|
||||
placeholder={t('Type a name or email')}
|
||||
>
|
||||
{!showMemberSection && inputValue !== '' && (
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
onSelect={onSelect}
|
||||
renderElement={(user) => (
|
||||
<DocShareModalInviteUserRow user={user} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && (
|
||||
<>
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem
|
||||
doc={doc}
|
||||
invitation={invitation}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</QuickSearch>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
$css={css`
|
||||
flex-shrink: 0;
|
||||
`}
|
||||
>
|
||||
<HorizontalSeparator />
|
||||
<DocVisibility doc={doc} />
|
||||
<HorizontalSeparator />
|
||||
<Box $direction="row" $justify="space-between" $padding="base">
|
||||
<Button
|
||||
fullWidth={false}
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(window.location.href)
|
||||
.then(() => {
|
||||
toast(t('Link Copied !'), VariantType.SUCCESS, {
|
||||
duration: 3000,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast(t('Failed to copy link'), VariantType.ERROR, {
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
}}
|
||||
color="tertiary"
|
||||
icon={<span className="material-icons">add_link</span>}
|
||||
>
|
||||
{t('Copy link')}
|
||||
</Button>
|
||||
<Button onClick={onClose} color="primary">
|
||||
{t('Ok')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -11,25 +11,27 @@ type Props = {
|
||||
export const DocShareModalInviteUserRow = ({ user }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SearchUserRow
|
||||
user={user}
|
||||
right={
|
||||
<Box
|
||||
className="right-hover"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$css={css`
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: var(--c--theme--font--sizes--sm);
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
`}
|
||||
>
|
||||
<Text $theme="primary" $variation="600">
|
||||
{t('Add')}
|
||||
</Text>
|
||||
<Icon $theme="primary" $variation="600" iconName="add" />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<Box $width="100%" data-testid={`search-user-row-${user.email}`}>
|
||||
<SearchUserRow
|
||||
user={user}
|
||||
right={
|
||||
<Box
|
||||
className="right-hover"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$css={css`
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: var(--c--theme--font--sizes--sm);
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
`}
|
||||
>
|
||||
<Text $theme="primary" $variation="600">
|
||||
{t('Add')}
|
||||
</Text>
|
||||
<Icon $theme="primary" $variation="600" iconName="add" />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,11 +12,34 @@ export const useTrans = () => {
|
||||
[Role.EDITOR]: t('Editor'),
|
||||
};
|
||||
|
||||
const getNotAllowedMessage = (
|
||||
canUpdate: boolean,
|
||||
isLastOwner: boolean,
|
||||
isOtherOwner: boolean,
|
||||
) => {
|
||||
if (!canUpdate) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isLastOwner) {
|
||||
return t(
|
||||
'You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.',
|
||||
);
|
||||
}
|
||||
|
||||
if (isOtherOwner) {
|
||||
return t('You cannot update the role or remove other owner.');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return {
|
||||
transRole: (role: Role) => {
|
||||
return translatedRoles[role];
|
||||
},
|
||||
untitledDocument: t('Untitled document'),
|
||||
translatedRoles,
|
||||
getNotAllowedMessage,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user