🛂(frontend) readers and editors can access share modal
Readers and editors of a document can access the share modal and see the list of members and their roles.
This commit is contained in:
@@ -21,6 +21,7 @@ and this project adheres to
|
||||
|
||||
- 💄(frontend) error alert closeable on editor #284
|
||||
- ♻️(backend) Change email content #283
|
||||
- 🛂(frontend) viewers and editors can access share modal #302
|
||||
|
||||
## Fixed
|
||||
|
||||
|
||||
@@ -140,7 +140,13 @@ export const goToGridDoc = async (
|
||||
export const mockedDocument = async (page: Page, json: object) => {
|
||||
await page.route('**/documents/**/', async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('GET') && !request.url().includes('page=')) {
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
!request.url().includes('page=') &&
|
||||
!request.url().includes('versions') &&
|
||||
!request.url().includes('accesses') &&
|
||||
!request.url().includes('invitations')
|
||||
) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
id: 'mocked-document-id',
|
||||
@@ -168,3 +174,82 @@ export const mockedDocument = async (page: Page, json: object) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const mockedInvitations = async (page: Page, json?: object) => {
|
||||
await page.route('**/invitations/**/', async (route) => {
|
||||
const request = route.request();
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
request.url().includes('invitations') &&
|
||||
request.url().includes('page=')
|
||||
) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
count: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [
|
||||
{
|
||||
id: '120ec765-43af-4602-83eb-7f4e1224548a',
|
||||
abilities: {
|
||||
destroy: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
created_at: '2024-10-03T12:19:26.107687Z',
|
||||
email: 'test@invitation.test',
|
||||
document: '4888c328-8406-4412-9b0b-c0ba5b9e5fb6',
|
||||
role: 'editor',
|
||||
issuer: '7380f42f-02eb-4ad5-b8f0-037a0e66066d',
|
||||
is_expired: false,
|
||||
...json,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const mockedAccesses = async (page: Page, json?: object) => {
|
||||
await page.route('**/accesses/**/', async (route) => {
|
||||
const request = route.request();
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
request.url().includes('accesses') &&
|
||||
request.url().includes('page=')
|
||||
) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
count: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [
|
||||
{
|
||||
id: 'bc8bbbc5-a635-4f65-9817-fd1e9ec8ef87',
|
||||
user: {
|
||||
id: 'b4a21bb3-722e-426c-9f78-9d190eda641c',
|
||||
email: 'test@accesses.test',
|
||||
},
|
||||
team: '',
|
||||
role: 'reader',
|
||||
abilities: {
|
||||
destroy: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
set_role_to: ['administrator', 'editor'],
|
||||
},
|
||||
...json,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, goToGridDoc, mockedDocument } from './common';
|
||||
import {
|
||||
createDoc,
|
||||
goToGridDoc,
|
||||
mockedAccesses,
|
||||
mockedDocument,
|
||||
mockedInvitations,
|
||||
} from './common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -182,20 +188,55 @@ test.describe('Doc Header', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await mockedInvitations(page);
|
||||
await mockedAccesses(page);
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
await expect(
|
||||
page.locator('h2').getByText('Mocked document'),
|
||||
).toHaveAttribute('contenteditable');
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Export' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Delete document' }),
|
||||
).toBeHidden();
|
||||
|
||||
// Click somewhere else to close the options
|
||||
await page.click('body', { position: { x: 0, y: 0 } });
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
await expect(shareModal.getByLabel('Doc private')).toBeEnabled();
|
||||
await expect(shareModal.getByText('Search by email')).toBeVisible();
|
||||
|
||||
const invitationCard = shareModal.getByLabel('List invitation card');
|
||||
await expect(
|
||||
invitationCard.getByText('test@invitation.test'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
invitationCard.getByRole('combobox', { name: 'Role' }),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
invitationCard.getByRole('button', {
|
||||
name: 'delete',
|
||||
}),
|
||||
).toBeEnabled();
|
||||
|
||||
const memberCard = shareModal.getByLabel('List members card');
|
||||
await expect(memberCard.getByText('test@accesses.test')).toBeVisible();
|
||||
await expect(
|
||||
memberCard.getByRole('combobox', { name: 'Role' }),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
memberCard.getByRole('button', {
|
||||
name: 'delete',
|
||||
}),
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test('it checks the options available if editor', async ({ page }) => {
|
||||
@@ -213,20 +254,62 @@ test.describe('Doc Header', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await mockedInvitations(page, {
|
||||
abilities: {
|
||||
destroy: false,
|
||||
update: false,
|
||||
partial_update: false,
|
||||
retrieve: true,
|
||||
},
|
||||
});
|
||||
await mockedAccesses(page);
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
await expect(
|
||||
page.locator('h2').getByText('Mocked document'),
|
||||
).toHaveAttribute('contenteditable');
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Export' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Delete document' }),
|
||||
).toBeHidden();
|
||||
|
||||
// Click somewhere else to close the options
|
||||
await page.click('body', { position: { x: 0, y: 0 } });
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
await expect(shareModal.getByLabel('Doc private')).toBeDisabled();
|
||||
await expect(shareModal.getByText('Search by email')).toBeHidden();
|
||||
|
||||
const invitationCard = shareModal.getByLabel('List invitation card');
|
||||
await expect(
|
||||
invitationCard.getByText('test@invitation.test'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
invitationCard.getByRole('combobox', { name: 'Role' }),
|
||||
).toHaveAttribute('disabled');
|
||||
await expect(
|
||||
invitationCard.getByRole('button', {
|
||||
name: 'delete',
|
||||
}),
|
||||
).toBeHidden();
|
||||
|
||||
const memberCard = shareModal.getByLabel('List members card');
|
||||
await expect(memberCard.getByText('test@accesses.test')).toBeVisible();
|
||||
await expect(
|
||||
memberCard.getByRole('combobox', { name: 'Role' }),
|
||||
).toHaveAttribute('disabled');
|
||||
await expect(
|
||||
memberCard.getByRole('button', {
|
||||
name: 'delete',
|
||||
}),
|
||||
).toBeHidden();
|
||||
});
|
||||
|
||||
test('it checks the options available if reader', async ({ page }) => {
|
||||
@@ -244,20 +327,61 @@ test.describe('Doc Header', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await mockedInvitations(page, {
|
||||
abilities: {
|
||||
destroy: false,
|
||||
update: false,
|
||||
partial_update: false,
|
||||
retrieve: true,
|
||||
},
|
||||
});
|
||||
await mockedAccesses(page);
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
await expect(
|
||||
page.locator('h2').getByText('Mocked document'),
|
||||
).not.toHaveAttribute('contenteditable');
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'Export' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Delete document' }),
|
||||
).toBeHidden();
|
||||
|
||||
// Click somewhere else to close the options
|
||||
await page.click('body', { position: { x: 0, y: 0 } });
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
await expect(shareModal.getByLabel('Doc private')).toBeDisabled();
|
||||
await expect(shareModal.getByText('Search by email')).toBeHidden();
|
||||
|
||||
const invitationCard = shareModal.getByLabel('List invitation card');
|
||||
await expect(
|
||||
invitationCard.getByText('test@invitation.test'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
invitationCard.getByRole('combobox', { name: 'Role' }),
|
||||
).toHaveAttribute('disabled');
|
||||
await expect(
|
||||
invitationCard.getByRole('button', {
|
||||
name: 'delete',
|
||||
}),
|
||||
).toBeHidden();
|
||||
|
||||
const memberCard = shareModal.getByLabel('List members card');
|
||||
await expect(memberCard.getByText('test@accesses.test')).toBeVisible();
|
||||
await expect(
|
||||
memberCard.getByRole('combobox', { name: 'Role' }),
|
||||
).toHaveAttribute('disabled');
|
||||
await expect(
|
||||
memberCard.getByRole('button', {
|
||||
name: 'delete',
|
||||
}),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,15 +106,17 @@ test.describe('Document list members', () => {
|
||||
await page.getByRole('option', { name: 'Administrator' }).click();
|
||||
await expect(page.getByText('The role has been updated')).toBeVisible();
|
||||
|
||||
const shareModal = page.getByLabel('Share modal');
|
||||
|
||||
// Admin still have the right to share
|
||||
await expect(page.locator('h3').getByText('Share')).toBeVisible();
|
||||
await expect(shareModal.getByLabel('Doc private')).toBeEnabled();
|
||||
|
||||
await SelectRoleCurrentUser.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(page.locator('h3').getByText('Share')).toBeHidden();
|
||||
await expect(shareModal.getByLabel('Doc private')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('it checks the delete members', async ({ page, browserName }) => {
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface BoxProps {
|
||||
$padding?: MarginPadding;
|
||||
$position?: CSSProperties['position'];
|
||||
$radius?: CSSProperties['borderRadius'];
|
||||
$shrink?: CSSProperties['flexShrink'];
|
||||
$transition?: CSSProperties['transition'];
|
||||
$width?: CSSProperties['width'];
|
||||
$wrap?: CSSProperties['flexWrap'];
|
||||
@@ -68,6 +69,7 @@ export const Box = styled('div')<BoxProps>`
|
||||
${({ $padding }) => $padding && stylesPadding($padding)}
|
||||
${({ $position }) => $position && `position: ${$position};`}
|
||||
${({ $radius }) => $radius && `border-radius: ${$radius};`}
|
||||
${({ $shrink }) => $shrink && `flex-shrink: ${$shrink};`}
|
||||
${({ $transition }) => $transition && `transition: ${$transition};`}
|
||||
${({ $width }) => $width && `width: ${$width};`}
|
||||
${({ $wrap }) => $wrap && `flex-wrap: ${$wrap};`}
|
||||
|
||||
@@ -150,6 +150,12 @@ input:-webkit-autofill:focus {
|
||||
border-color: var(--c--components--forms-select--border-color-disabled-hover);
|
||||
}
|
||||
|
||||
.c__select--disabled .c__select__wrapper label,
|
||||
.c__select--disabled .c__select__wrapper input,
|
||||
.c__select--disabled .c__select__wrapper {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.c__select__menu__item {
|
||||
transition: all var(--c--theme--transitions--duration)
|
||||
var(--c--theme--transitions--ease-out);
|
||||
@@ -313,6 +319,14 @@ input:-webkit-autofill:focus {
|
||||
font-size: var(--c--components--forms-checkbox--text--size);
|
||||
}
|
||||
|
||||
.c__checkbox.c__checkbox--disabled .c__field__text {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
}
|
||||
|
||||
.c__switch.c__checkbox--disabled .c__switch__rail {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Button
|
||||
*/
|
||||
|
||||
@@ -31,15 +31,13 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
$align="center"
|
||||
$gap="1rem"
|
||||
>
|
||||
{doc.abilities.manage_accesses && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsModalShareOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Share')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsModalShareOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Share')}
|
||||
</Button>
|
||||
<DropButton
|
||||
button={
|
||||
<IconOptions
|
||||
|
||||
@@ -44,7 +44,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
$align="center"
|
||||
$justify="space-between"
|
||||
>
|
||||
<Box $direction="row" $gap="1rem">
|
||||
<Box $direction="row" $gap="1rem" $align="center">
|
||||
<IconBG iconName="public" $margin="none" />
|
||||
<Switch
|
||||
label={t(docPublic ? 'Doc public' : 'Doc private')}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { t } from 'i18next';
|
||||
import { useEffect } from 'react';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { Box, Card, SideModal, Text } from '@/components';
|
||||
@@ -30,12 +29,6 @@ interface ModalShareProps {
|
||||
}
|
||||
|
||||
export const ModalShare = ({ onClose, doc }: ModalShareProps) => {
|
||||
useEffect(() => {
|
||||
if (!doc.abilities.manage_accesses) {
|
||||
onClose();
|
||||
}
|
||||
}, [doc.abilities.manage_accesses, onClose]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalShareStyle />
|
||||
@@ -47,29 +40,40 @@ export const ModalShare = ({ onClose, doc }: ModalShareProps) => {
|
||||
width="70vw"
|
||||
$css="min-width: 320px;max-width: 777px;"
|
||||
>
|
||||
<Card
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$margin={{ horizontal: 'tiny', top: 'none', bottom: 'big' }}
|
||||
$padding="tiny"
|
||||
$gap="1rem"
|
||||
>
|
||||
<Text $isMaterialIcon $size="48px" $theme="primary">
|
||||
share
|
||||
</Text>
|
||||
<Box $align="flex-start">
|
||||
<Text as="h3" $size="26px" $margin="none">
|
||||
{t('Share')}
|
||||
</Text>
|
||||
<Text $size="small" $weight="normal" $textAlign="left">
|
||||
{doc.title}
|
||||
</Text>
|
||||
<Box aria-label={t('Share modal')}>
|
||||
<Box $shrink="0">
|
||||
<Card
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$margin={{ horizontal: 'tiny', top: 'none', bottom: 'big' }}
|
||||
$padding="tiny"
|
||||
$gap="1rem"
|
||||
>
|
||||
<Text $isMaterialIcon $size="48px" $theme="primary">
|
||||
share
|
||||
</Text>
|
||||
<Box $align="flex-start">
|
||||
<Text as="h3" $size="26px" $margin="none">
|
||||
{t('Share')}
|
||||
</Text>
|
||||
<Text $size="small" $weight="normal" $textAlign="left">
|
||||
{doc.title}
|
||||
</Text>
|
||||
</Box>
|
||||
</Card>
|
||||
<DocVisibility doc={doc} />
|
||||
{doc.abilities.manage_accesses && (
|
||||
<AddMembers
|
||||
doc={doc}
|
||||
currentRole={currentDocRole(doc.abilities)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
<DocVisibility doc={doc} />
|
||||
<AddMembers doc={doc} currentRole={currentDocRole(doc.abilities)} />
|
||||
<InvitationList doc={doc} />
|
||||
<MemberList doc={doc} />
|
||||
<Box $minHeight="0">
|
||||
<InvitationList doc={doc} />
|
||||
<MemberList doc={doc} />
|
||||
</Box>
|
||||
</Box>
|
||||
</SideModal>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -93,6 +93,7 @@ export const TableContent = ({ doc, headings }: TableContentProps) => {
|
||||
block: 'start',
|
||||
});
|
||||
}}
|
||||
$align="start"
|
||||
>
|
||||
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
|
||||
{t('Back to top')}
|
||||
@@ -110,6 +111,7 @@ export const TableContent = ({ doc, headings }: TableContentProps) => {
|
||||
block: 'start',
|
||||
});
|
||||
}}
|
||||
$align="start"
|
||||
>
|
||||
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
|
||||
{t('Go to bottom')}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, IconBG, Text, TextErrors } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Role } from '@/features/docs/doc-management';
|
||||
import { Doc, Role } from '@/features/docs/doc-management';
|
||||
import { ChooseRole } from '@/features/docs/members/members-add/';
|
||||
|
||||
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
|
||||
@@ -19,11 +19,11 @@ interface InvitationItemProps {
|
||||
role: Role;
|
||||
currentRole: Role;
|
||||
invitation: Invitation;
|
||||
docId: string;
|
||||
doc: Doc;
|
||||
}
|
||||
|
||||
export const InvitationItem = ({
|
||||
docId,
|
||||
doc,
|
||||
role,
|
||||
invitation,
|
||||
currentRole,
|
||||
@@ -95,29 +95,34 @@ export const InvitationItem = ({
|
||||
setRole={(role) => {
|
||||
setLocalRole(role);
|
||||
updateDocInvitation({
|
||||
docId,
|
||||
docId: doc.id,
|
||||
invitationId: invitation.id,
|
||||
role,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$theme={!canDelete ? 'greyscale' : 'primary'}
|
||||
$variation={!canDelete ? '500' : 'text'}
|
||||
>
|
||||
delete
|
||||
</Text>
|
||||
}
|
||||
disabled={!canDelete}
|
||||
onClick={() =>
|
||||
removeDocInvitation({ docId, invitationId: invitation.id })
|
||||
}
|
||||
/>
|
||||
{doc.abilities.manage_accesses && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$theme={!canDelete ? 'greyscale' : 'primary'}
|
||||
$variation={!canDelete ? '500' : 'text'}
|
||||
>
|
||||
delete
|
||||
</Text>
|
||||
}
|
||||
disabled={!canDelete}
|
||||
onClick={() =>
|
||||
removeDocInvitation({
|
||||
docId: doc.id,
|
||||
invitationId: invitation.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -58,7 +58,7 @@ const InvitationListState = ({
|
||||
<InvitationItem
|
||||
invitation={invitation}
|
||||
role={invitation.role}
|
||||
docId={doc.id}
|
||||
doc={doc}
|
||||
currentRole={currentDocRole(doc.abilities)}
|
||||
/>
|
||||
</Box>
|
||||
@@ -97,9 +97,9 @@ export const InvitationList = ({ doc }: InvitationListProps) => {
|
||||
return (
|
||||
<Card
|
||||
$margin="tiny"
|
||||
$padding="tiny"
|
||||
$maxHeight="40%"
|
||||
$overflow="auto"
|
||||
$maxHeight="50vh"
|
||||
$padding="tiny"
|
||||
aria-label={t('List invitation card')}
|
||||
>
|
||||
<Box ref={containerRef} $overflow="auto">
|
||||
@@ -111,8 +111,9 @@ export const InvitationList = ({ doc }: InvitationListProps) => {
|
||||
}}
|
||||
scrollContainer={containerRef.current}
|
||||
as="ul"
|
||||
className="p-0 mt-0"
|
||||
role="listbox"
|
||||
$padding="none"
|
||||
$margin="none"
|
||||
>
|
||||
<InvitationListState
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -153,14 +153,14 @@ export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => {
|
||||
doc={doc}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
selectedUsers={selectedUsers}
|
||||
disabled={isPending}
|
||||
disabled={isPending || !doc.abilities.manage_accesses}
|
||||
/>
|
||||
</Box>
|
||||
<Box $css="flex: auto;">
|
||||
<ChooseRole
|
||||
key={resetKey}
|
||||
currentRole={currentRole}
|
||||
disabled={isPending}
|
||||
disabled={isPending || !doc.abilities.manage_accesses}
|
||||
setRole={setSelectedRole}
|
||||
/>
|
||||
</Box>
|
||||
@@ -168,7 +168,12 @@ export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => {
|
||||
<Box $align="center" $justify="center" $css="flex: auto;">
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={!selectedUsers.length || isPending || !selectedRole}
|
||||
disabled={
|
||||
!selectedUsers.length ||
|
||||
isPending ||
|
||||
!selectedRole ||
|
||||
!doc.abilities.manage_accesses
|
||||
}
|
||||
onClick={() => void handleValidate()}
|
||||
style={{ height: '100%', maxHeight: '55px' }}
|
||||
>
|
||||
|
||||
@@ -120,12 +120,17 @@ export const SearchUsers = ({
|
||||
placeholder: (base) => ({
|
||||
...base,
|
||||
fontSize: '14px',
|
||||
color: colorsTokens()['primary-600'],
|
||||
color: disabled
|
||||
? colorsTokens()['greyscale-300']
|
||||
: colorsTokens()['primary-600'],
|
||||
}),
|
||||
control: (base) => ({
|
||||
...base,
|
||||
minHeight: '45px',
|
||||
borderColor: colorsTokens()['primary-600'],
|
||||
borderColor: disabled
|
||||
? colorsTokens()['greyscale-300']
|
||||
: colorsTokens()['primary-600'],
|
||||
backgroundColor: 'white',
|
||||
}),
|
||||
input: (base) => ({
|
||||
...base,
|
||||
|
||||
@@ -10,7 +10,7 @@ import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, IconBG, Text, TextErrors } from '@/components';
|
||||
import { Access, Role } from '@/features/docs/doc-management';
|
||||
import { Access, Doc, Role } from '@/features/docs/doc-management';
|
||||
import { ChooseRole } from '@/features/docs/members/members-add/';
|
||||
|
||||
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
|
||||
@@ -20,11 +20,11 @@ interface MemberItemProps {
|
||||
role: Role;
|
||||
currentRole: Role;
|
||||
access: Access;
|
||||
docId: string;
|
||||
doc: Doc;
|
||||
}
|
||||
|
||||
export const MemberItem = ({
|
||||
docId,
|
||||
doc,
|
||||
role,
|
||||
access,
|
||||
currentRole,
|
||||
@@ -58,7 +58,8 @@ export const MemberItem = ({
|
||||
},
|
||||
});
|
||||
|
||||
const isNotAllowed = isOtherOwner || isLastOwner;
|
||||
const isNotAllowed =
|
||||
isOtherOwner || isLastOwner || !doc.abilities.manage_accesses;
|
||||
|
||||
if (!access.user) {
|
||||
return (
|
||||
@@ -91,34 +92,40 @@ export const MemberItem = ({
|
||||
setRole={(role) => {
|
||||
setLocalRole(role);
|
||||
updateDocAccess({
|
||||
docId,
|
||||
docId: doc.id,
|
||||
accessId: access.id,
|
||||
role,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$theme={isNotAllowed ? 'greyscale' : 'primary'}
|
||||
$variation={isNotAllowed ? '500' : 'text'}
|
||||
>
|
||||
delete
|
||||
</Text>
|
||||
}
|
||||
disabled={isNotAllowed}
|
||||
onClick={() => removeDocAccess({ docId, accessId: access.id })}
|
||||
/>
|
||||
{doc.abilities.manage_accesses && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$theme={isNotAllowed ? 'greyscale' : 'primary'}
|
||||
$variation={isNotAllowed ? '500' : 'text'}
|
||||
>
|
||||
delete
|
||||
</Text>
|
||||
}
|
||||
disabled={isNotAllowed}
|
||||
onClick={() =>
|
||||
removeDocAccess({ docId: doc.id, accessId: access.id })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{(errorUpdate || errorDelete) && (
|
||||
<TextErrors causes={errorUpdate?.cause || errorDelete?.cause} />
|
||||
<Box $margin={{ top: 'tiny' }}>
|
||||
<TextErrors causes={errorUpdate?.cause || errorDelete?.cause} />
|
||||
</Box>
|
||||
)}
|
||||
{(isLastOwner || isOtherOwner) && (
|
||||
{(isLastOwner || isOtherOwner) && doc.abilities.manage_accesses && (
|
||||
<Box $margin={{ top: 'tiny' }}>
|
||||
<Alert
|
||||
canClose={false}
|
||||
|
||||
@@ -57,7 +57,7 @@ const MemberListState = ({
|
||||
<MemberItem
|
||||
access={access}
|
||||
role={access.role}
|
||||
docId={doc.id}
|
||||
doc={doc}
|
||||
currentRole={currentDocRole(doc.abilities)}
|
||||
/>
|
||||
</Box>
|
||||
@@ -92,9 +92,9 @@ export const MemberList = ({ doc }: MemberListProps) => {
|
||||
return (
|
||||
<Card
|
||||
$margin="tiny"
|
||||
$padding="tiny"
|
||||
$maxHeight="69%"
|
||||
$overflow="auto"
|
||||
$maxHeight="80vh"
|
||||
$padding="tiny"
|
||||
aria-label={t('List members card')}
|
||||
>
|
||||
<Box ref={containerRef} $overflow="auto">
|
||||
@@ -106,7 +106,8 @@ export const MemberList = ({ doc }: MemberListProps) => {
|
||||
}}
|
||||
scrollContainer={containerRef.current}
|
||||
as="ul"
|
||||
className="p-0 mt-0"
|
||||
$padding="none"
|
||||
$margin="none"
|
||||
role="listbox"
|
||||
>
|
||||
<MemberListState
|
||||
|
||||
Reference in New Issue
Block a user