♻️(frontend) add user from side modal
We move the add user functionality to a side modal. The side modal is opened from the share button.
This commit is contained in:
@@ -80,8 +80,7 @@ export const addNewMember = async (
|
|||||||
response.status() === 200,
|
response.status() === 200,
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||||
|
|
||||||
@@ -98,8 +97,8 @@ export const addNewMember = async (
|
|||||||
await page.getByRole('option', { name: users[index].email }).click();
|
await page.getByRole('option', { name: users[index].email }).click();
|
||||||
|
|
||||||
// Choose a role
|
// Choose a role
|
||||||
await page.getByRole('radio', { name: role }).click();
|
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||||
|
await page.getByRole('option', { name: role }).click();
|
||||||
await page.getByRole('button', { name: 'Validate' }).click();
|
await page.getByRole('button', { name: 'Validate' }).click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -76,5 +76,6 @@ test.describe('Doc Header', () => {
|
|||||||
card.getByText('Owners: super@owner.com / super2@owner.com'),
|
card.getByText('Owners: super@owner.com / super2@owner.com'),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(card.getByText('Your role: Owner')).toBeVisible();
|
await expect(card.getByText('Your role: Owner')).toBeVisible();
|
||||||
|
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ page }) => {
|
|||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Document add users', () => {
|
test.describe('Document create member', () => {
|
||||||
test('it selects 2 users and 1 invitation', async ({ page, browserName }) => {
|
test('it selects 2 users and 1 invitation', async ({ page, browserName }) => {
|
||||||
const responsePromise = page.waitForResponse(
|
const responsePromise = page.waitForResponse(
|
||||||
(response) =>
|
(response) =>
|
||||||
@@ -14,8 +14,7 @@ test.describe('Document add users', () => {
|
|||||||
);
|
);
|
||||||
await createDoc(page, 'select-multi-users', browserName, 1);
|
await createDoc(page, 'select-multi-users', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||||
await expect(inputSearch).toBeVisible();
|
await expect(inputSearch).toBeVisible();
|
||||||
@@ -56,12 +55,14 @@ test.describe('Document add users', () => {
|
|||||||
await expect(page.getByLabel(`Remove ${email}`)).toBeVisible();
|
await expect(page.getByLabel(`Remove ${email}`)).toBeVisible();
|
||||||
|
|
||||||
// Check roles are displayed
|
// Check roles are displayed
|
||||||
await expect(page.getByText(/Choose a role/)).toBeVisible();
|
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||||
await expect(page.getByRole('radio', { name: 'Reader' })).toBeChecked();
|
|
||||||
await expect(page.getByRole('radio', { name: 'Owner' })).toBeVisible();
|
await expect(page.getByRole('option', { name: 'Reader' })).toBeVisible();
|
||||||
|
await expect(page.getByRole('option', { name: 'Editor' })).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('radio', { name: 'Administrator' }),
|
page.getByRole('option', { name: 'Administrator' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole('option', { name: 'Owner' })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it sends a new invitation and adds a new user', async ({
|
test('it sends a new invitation and adds a new user', async ({
|
||||||
@@ -75,8 +76,7 @@ test.describe('Document add users', () => {
|
|||||||
|
|
||||||
await createDoc(page, 'user-invitation', browserName, 1);
|
await createDoc(page, 'user-invitation', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||||
|
|
||||||
@@ -93,7 +93,8 @@ test.describe('Document add users', () => {
|
|||||||
await page.getByRole('option', { name: user.email }).click();
|
await page.getByRole('option', { name: user.email }).click();
|
||||||
|
|
||||||
// Choose a role
|
// Choose a role
|
||||||
await page.getByRole('radio', { name: 'Administrator' }).click();
|
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||||
|
await page.getByRole('option', { name: 'Administrator' }).click();
|
||||||
|
|
||||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||||
(response) =>
|
(response) =>
|
||||||
@@ -127,8 +128,7 @@ test.describe('Document add users', () => {
|
|||||||
|
|
||||||
await createDoc(page, 'user-twice', browserName, 1);
|
await createDoc(page, 'user-twice', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||||
await inputSearch.fill('user');
|
await inputSearch.fill('user');
|
||||||
@@ -139,7 +139,8 @@ test.describe('Document add users', () => {
|
|||||||
await page.getByRole('option', { name: user.email }).click();
|
await page.getByRole('option', { name: user.email }).click();
|
||||||
|
|
||||||
// Choose a role
|
// Choose a role
|
||||||
await page.getByRole('radio', { name: 'Owner' }).click();
|
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||||
|
await page.getByRole('option', { name: 'Owner' }).click();
|
||||||
|
|
||||||
const responsePromiseAddMember = page.waitForResponse(
|
const responsePromiseAddMember = page.waitForResponse(
|
||||||
(response) =>
|
(response) =>
|
||||||
@@ -154,10 +155,8 @@ test.describe('Document add users', () => {
|
|||||||
const responseAddMember = await responsePromiseAddMember;
|
const responseAddMember = await responsePromiseAddMember;
|
||||||
expect(responseAddMember.ok()).toBeTruthy();
|
expect(responseAddMember.ok()).toBeTruthy();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
await inputSearch.fill('user');
|
await inputSearch.fill('user');
|
||||||
|
await expect(page.getByText('Loading...')).toBeHidden();
|
||||||
await expect(page.getByRole('option', { name: user.email })).toBeHidden();
|
await expect(page.getByRole('option', { name: user.email })).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -167,8 +166,7 @@ test.describe('Document add users', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
await createDoc(page, 'invitation-twice', browserName, 1);
|
await createDoc(page, 'invitation-twice', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
const inputSearch = page.getByLabel(/Find a member to add to the document/);
|
||||||
|
|
||||||
@@ -177,7 +175,8 @@ test.describe('Document add users', () => {
|
|||||||
await page.getByRole('option', { name: email }).click();
|
await page.getByRole('option', { name: email }).click();
|
||||||
|
|
||||||
// Choose a role
|
// Choose a role
|
||||||
await page.getByRole('radio', { name: 'Owner' }).click();
|
await page.getByRole('combobox', { name: /Choose a role/ }).click();
|
||||||
|
await page.getByRole('option', { name: 'Owner' }).click();
|
||||||
|
|
||||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||||
(response) =>
|
(response) =>
|
||||||
@@ -191,10 +190,8 @@ test.describe('Document add users', () => {
|
|||||||
const responseCreateInvitation = await responsePromiseCreateInvitation;
|
const responseCreateInvitation = await responsePromiseCreateInvitation;
|
||||||
expect(responseCreateInvitation.ok()).toBeTruthy();
|
expect(responseCreateInvitation.ok()).toBeTruthy();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
|
||||||
|
|
||||||
await inputSearch.fill(email);
|
await inputSearch.fill(email);
|
||||||
|
await expect(page.getByText('Loading...')).toBeHidden();
|
||||||
await expect(page.getByRole('option', { name: email })).toBeHidden();
|
await expect(page.getByRole('option', { name: email })).toBeHidden();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -216,11 +216,10 @@ test.describe('Doc Tools', () => {
|
|||||||
|
|
||||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Add members' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Generate PDF' }),
|
page.getByRole('button', { name: 'Generate PDF' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
@@ -267,11 +266,10 @@ test.describe('Doc Tools', () => {
|
|||||||
|
|
||||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Add members' }),
|
|
||||||
).toBeHidden();
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Generate PDF' }),
|
page.getByRole('button', { name: 'Generate PDF' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
@@ -318,11 +316,11 @@ test.describe('Doc Tools', () => {
|
|||||||
|
|
||||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
|
|
||||||
await expect(
|
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||||
page.getByRole('button', { name: 'Add members' }),
|
|
||||||
).toBeHidden();
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Generate PDF' }),
|
page.getByRole('button', { name: 'Generate PDF' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|||||||
@@ -5,10 +5,23 @@ import { createGlobalStyle } from 'styled-components';
|
|||||||
interface SideModalStyleProps {
|
interface SideModalStyleProps {
|
||||||
side: 'left' | 'right';
|
side: 'left' | 'right';
|
||||||
width: string;
|
width: string;
|
||||||
|
$css?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SideModalStyle = createGlobalStyle<SideModalStyleProps>`
|
const SideModalStyle = createGlobalStyle<SideModalStyleProps>`
|
||||||
|
@keyframes slidein {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& .c__modal{
|
& .c__modal{
|
||||||
|
animation: slidein 0.7s;
|
||||||
|
|
||||||
width: ${({ width }) => width};
|
width: ${({ width }) => width};
|
||||||
${({ side }) => side === 'right' && 'left: auto;'};
|
${({ side }) => side === 'right' && 'left: auto;'};
|
||||||
|
|
||||||
@@ -17,6 +30,8 @@ const SideModalStyle = createGlobalStyle<SideModalStyleProps>`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${({ $css }) => $css}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -28,11 +43,12 @@ export const SideModal = ({
|
|||||||
children,
|
children,
|
||||||
side = 'right',
|
side = 'right',
|
||||||
width = '35vw',
|
width = '35vw',
|
||||||
|
$css,
|
||||||
...modalProps
|
...modalProps
|
||||||
}: PropsWithChildren<SideModalProps>) => {
|
}: PropsWithChildren<SideModalProps>) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SideModalStyle width={width} side={side} />
|
<SideModalStyle width={width} side={side} $css={$css} />
|
||||||
<Modal {...modalProps} size={ModalSize.FULL}>
|
<Modal {...modalProps} size={ModalSize.FULL}>
|
||||||
{children}
|
{children}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export interface TextProps extends BoxProps {
|
|||||||
'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||||
>;
|
>;
|
||||||
$elipsis?: boolean;
|
$elipsis?: boolean;
|
||||||
|
$isMaterialIcon?: boolean;
|
||||||
$weight?: CSSProperties['fontWeight'];
|
$weight?: CSSProperties['fontWeight'];
|
||||||
$textAlign?: CSSProperties['textAlign'];
|
$textAlign?: CSSProperties['textAlign'];
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
@@ -56,9 +57,17 @@ export const TextStyled = styled(Box)<TextProps>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const Text = ({
|
export const Text = ({
|
||||||
|
className,
|
||||||
|
$isMaterialIcon,
|
||||||
...props
|
...props
|
||||||
}: ComponentPropsWithRef<typeof TextStyled>) => {
|
}: ComponentPropsWithRef<typeof TextStyled>) => {
|
||||||
return (
|
return (
|
||||||
<TextStyled as="span" $theme="greyscale" $variation="text" {...props} />
|
<TextStyled
|
||||||
|
as="span"
|
||||||
|
$theme="greyscale"
|
||||||
|
$variation="text"
|
||||||
|
className={`${className || ''}${$isMaterialIcon ? ' material-icons' : ''}`}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -477,6 +477,10 @@ input:-webkit-autofill:focus {
|
|||||||
padding: 1.5rem 1rem;
|
padding: 1.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c__modal--full .c__modal__content {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toast
|
* Toast
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
|||||||
<Box $padding="small" $direction="row" $align="center">
|
<Box $padding="small" $direction="row" $align="center">
|
||||||
<StyledLink href="/">
|
<StyledLink href="/">
|
||||||
<Text
|
<Text
|
||||||
className="material-icons"
|
$isMaterialIcon
|
||||||
$theme="primary"
|
$theme="primary"
|
||||||
$size="2rem"
|
$size="2rem"
|
||||||
$css={`&:hover {background-color: ${colorsTokens()['primary-100']}; };`}
|
$css={`&:hover {background-color: ${colorsTokens()['primary-100']}; };`}
|
||||||
@@ -82,7 +82,9 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
|||||||
{t('Owners:')}{' '}
|
{t('Owners:')}{' '}
|
||||||
<strong>
|
<strong>
|
||||||
{doc.accesses
|
{doc.accesses
|
||||||
.filter((access) => access.role === Role.OWNER)
|
.filter(
|
||||||
|
(access) => access.role === Role.OWNER && access.user.email,
|
||||||
|
)
|
||||||
.map((access, index, accesses) => (
|
.map((access, index, accesses) => (
|
||||||
<Fragment key={`access-${index}`}>
|
<Fragment key={`access-${index}`}>
|
||||||
{access.user.email}{' '}
|
{access.user.email}{' '}
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import { Box, DropButton, IconOptions, Text } from '@/components';
|
|||||||
import {
|
import {
|
||||||
Doc,
|
Doc,
|
||||||
ModalRemoveDoc,
|
ModalRemoveDoc,
|
||||||
|
ModalShare,
|
||||||
ModalUpdateDoc,
|
ModalUpdateDoc,
|
||||||
currentDocRole,
|
|
||||||
} from '@/features/docs/doc-management';
|
} from '@/features/docs/doc-management';
|
||||||
import { ModalAddMembers } from '@/features/docs/members/members-add';
|
|
||||||
import { ModalGridMembers } from '@/features/docs/members/members-grid/';
|
import { ModalGridMembers } from '@/features/docs/members/members-grid/';
|
||||||
|
|
||||||
import { ModalPDF } from './ModalPDF';
|
import { ModalPDF } from './ModalPDF';
|
||||||
@@ -20,15 +19,29 @@ interface DocToolBoxProps {
|
|||||||
|
|
||||||
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isModalAddMembersOpen, setIsModalAddMembersOpen] = useState(false);
|
|
||||||
const [isModalGridMembersOpen, setIsModalGridMembersOpen] = useState(false);
|
const [isModalGridMembersOpen, setIsModalGridMembersOpen] = useState(false);
|
||||||
|
const [isModalShareOpen, setIsModalShareOpen] = useState(false);
|
||||||
const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false);
|
const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false);
|
||||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||||
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
|
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
|
||||||
const [isDropOpen, setIsDropOpen] = useState(false);
|
const [isDropOpen, setIsDropOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box $margin={{ left: 'auto' }}>
|
<Box
|
||||||
|
$margin={{ left: 'auto' }}
|
||||||
|
$direction="row"
|
||||||
|
$align="center"
|
||||||
|
$gap="1rem"
|
||||||
|
>
|
||||||
|
{doc.abilities.manage_accesses && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsModalShareOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Share')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<DropButton
|
<DropButton
|
||||||
button={
|
button={
|
||||||
<IconOptions
|
<IconOptions
|
||||||
@@ -42,17 +55,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
<Box>
|
<Box>
|
||||||
{doc.abilities.manage_accesses && (
|
{doc.abilities.manage_accesses && (
|
||||||
<>
|
<>
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsModalAddMembersOpen(true);
|
|
||||||
setIsDropOpen(false);
|
|
||||||
}}
|
|
||||||
color="primary-text"
|
|
||||||
icon={<span className="material-icons">person_add</span>}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<Text $theme="primary">{t('Add members')}</Text>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalGridMembersOpen(true);
|
setIsModalGridMembersOpen(true);
|
||||||
@@ -105,19 +107,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</DropButton>
|
</DropButton>
|
||||||
|
{isModalShareOpen && (
|
||||||
|
<ModalShare onClose={() => setIsModalShareOpen(false)} doc={doc} />
|
||||||
|
)}
|
||||||
{isModalGridMembersOpen && (
|
{isModalGridMembersOpen && (
|
||||||
<ModalGridMembers
|
<ModalGridMembers
|
||||||
onClose={() => setIsModalGridMembersOpen(false)}
|
onClose={() => setIsModalGridMembersOpen(false)}
|
||||||
doc={doc}
|
doc={doc}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isModalAddMembersOpen && (
|
|
||||||
<ModalAddMembers
|
|
||||||
onClose={() => setIsModalAddMembersOpen(false)}
|
|
||||||
doc={doc}
|
|
||||||
currentRole={currentDocRole(doc.abilities)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isModalPDFOpen && (
|
{isModalPDFOpen && (
|
||||||
<ModalPDF onClose={() => setIsModalPDFOpen(false)} doc={doc} />
|
<ModalPDF onClose={() => setIsModalPDFOpen(false)} doc={doc} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { t } from 'i18next';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { createGlobalStyle } from 'styled-components';
|
||||||
|
|
||||||
|
import { Box, Card, Text } from '@/components';
|
||||||
|
import { SideModal } from '@/components/SideModal';
|
||||||
|
|
||||||
|
import { AddMembers } from '../../members/members-add';
|
||||||
|
import { Doc } from '../types';
|
||||||
|
import { currentDocRole } from '../utils';
|
||||||
|
|
||||||
|
const ModalShareStyle = createGlobalStyle`
|
||||||
|
& .c__modal__scroller{
|
||||||
|
background: #FAFAFA;
|
||||||
|
padding: 1.5rem .5rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ModalShareProps {
|
||||||
|
onClose: () => void;
|
||||||
|
doc: Doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModalShare = ({ onClose, doc }: ModalShareProps) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!doc.abilities.manage_accesses) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, [doc.abilities.manage_accesses, onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ModalShareStyle />
|
||||||
|
<SideModal
|
||||||
|
isOpen
|
||||||
|
closeOnClickOutside
|
||||||
|
hideCloseButton
|
||||||
|
onClose={onClose}
|
||||||
|
width="45vw"
|
||||||
|
$css="min-width: 320px;"
|
||||||
|
title={
|
||||||
|
<Card $direction="row" $align="center" $padding="0.7rem" $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>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddMembers doc={doc} currentRole={currentDocRole(doc.abilities)} />
|
||||||
|
</SideModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './ModalRemoveDoc';
|
|
||||||
export * from './ModalCreateUpdateDoc';
|
export * from './ModalCreateUpdateDoc';
|
||||||
|
export * from './ModalRemoveDoc';
|
||||||
|
export * from './ModalShare';
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const DocsGridStyle = createGlobalStyle`
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
& .c__pagination__goto{
|
& .c__pagination__goto{
|
||||||
display:none;
|
display:none;
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
|
||||||
ModalSize,
|
|
||||||
VariantType,
|
VariantType,
|
||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { createGlobalStyle } from 'styled-components';
|
|
||||||
|
|
||||||
import { APIError } from '@/api';
|
import { APIError } from '@/api';
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Card, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Doc, Role } from '@/features/docs/doc-management';
|
import { Doc, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { useCreateDocAccess, useCreateInvitation } from '../api';
|
import { useCreateDocAccess, useCreateInvitation } from '../api';
|
||||||
import IconAddUser from '../assets/add-user.svg';
|
|
||||||
import {
|
import {
|
||||||
OptionInvitation,
|
OptionInvitation,
|
||||||
OptionNewMember,
|
OptionNewMember,
|
||||||
@@ -27,12 +23,6 @@ import {
|
|||||||
import { ChooseRole } from './ChooseRole';
|
import { ChooseRole } from './ChooseRole';
|
||||||
import { OptionsSelect, SearchUsers } from './SearchUsers';
|
import { OptionsSelect, SearchUsers } from './SearchUsers';
|
||||||
|
|
||||||
const GlobalStyle = createGlobalStyle`
|
|
||||||
.c__modal {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type APIErrorUser = APIError<{
|
type APIErrorUser = APIError<{
|
||||||
value: string;
|
value: string;
|
||||||
type: OptionType;
|
type: OptionType;
|
||||||
@@ -40,26 +30,22 @@ type APIErrorUser = APIError<{
|
|||||||
|
|
||||||
interface ModalAddMembersProps {
|
interface ModalAddMembersProps {
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
onClose: () => void;
|
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalAddMembers = ({
|
export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => {
|
||||||
currentRole,
|
|
||||||
onClose,
|
|
||||||
doc,
|
|
||||||
}: ModalAddMembersProps) => {
|
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const [selectedUsers, setSelectedUsers] = useState<OptionsSelect>([]);
|
const [selectedUsers, setSelectedUsers] = useState<OptionsSelect>([]);
|
||||||
const [selectedRole, setSelectedRole] = useState<Role>(Role.READER);
|
const [selectedRole, setSelectedRole] = useState<Role>();
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const { mutateAsync: createInvitation } = useCreateInvitation();
|
const { mutateAsync: createInvitation } = useCreateInvitation();
|
||||||
const { mutateAsync: createDocAccess } = useCreateDocAccess();
|
const { mutateAsync: createDocAccess } = useCreateDocAccess();
|
||||||
|
const [resetKey, setResetKey] = useState(1);
|
||||||
|
|
||||||
const [isPending, setIsPending] = useState<boolean>(false);
|
const [isPending, setIsPending] = useState<boolean>(false);
|
||||||
|
|
||||||
const switchActions = (selectedUsers: OptionsSelect) =>
|
const switchActions = (selectedUsers: OptionsSelect, selectedRole: Role) =>
|
||||||
selectedUsers.map(async (selectedUser) => {
|
selectedUsers.map(async (selectedUser) => {
|
||||||
switch (selectedUser.type) {
|
switch (selectedUser.type) {
|
||||||
case OptionType.INVITATION:
|
case OptionType.INVITATION:
|
||||||
@@ -112,12 +98,16 @@ export const ModalAddMembers = ({
|
|||||||
const handleValidate = async () => {
|
const handleValidate = async () => {
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
|
|
||||||
|
if (!selectedRole) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const settledPromises = await Promise.allSettled<
|
const settledPromises = await Promise.allSettled<
|
||||||
OptionInvitation | OptionNewMember
|
OptionInvitation | OptionNewMember
|
||||||
>(switchActions(selectedUsers));
|
>(switchActions(selectedUsers, selectedRole));
|
||||||
|
|
||||||
onClose();
|
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
|
setResetKey(resetKey + 1);
|
||||||
|
|
||||||
settledPromises.forEach((settledPromise) => {
|
settledPromises.forEach((settledPromise) => {
|
||||||
switch (settledPromise.status) {
|
switch (settledPromise.status) {
|
||||||
@@ -133,63 +123,57 @@ export const ModalAddMembers = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Card
|
||||||
isOpen
|
$gap="1rem"
|
||||||
leftActions={
|
$padding="1rem"
|
||||||
<Button
|
$margin="1rem 0.7rem"
|
||||||
color="secondary"
|
$direction="row"
|
||||||
fullWidth
|
$align="center"
|
||||||
onClick={onClose}
|
$wrap="wrap"
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
{t('Cancel')}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
onClose={onClose}
|
|
||||||
closeOnClickOutside
|
|
||||||
hideCloseButton
|
|
||||||
rightActions={
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
fullWidth
|
|
||||||
disabled={!selectedUsers.length || isPending}
|
|
||||||
onClick={() => void handleValidate()}
|
|
||||||
>
|
|
||||||
{t('Validate')}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
size={ModalSize.MEDIUM}
|
|
||||||
title={
|
|
||||||
<Box $align="center" $gap="1rem">
|
|
||||||
<IconAddUser width={48} color={colorsTokens()['primary-text']} />
|
|
||||||
<Text $size="h3" $margin="none">
|
|
||||||
{t('Add members to the document')}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<GlobalStyle />
|
<Text
|
||||||
<Box $margin={{ bottom: 'xl', top: 'large' }}>
|
$isMaterialIcon
|
||||||
<SearchUsers
|
$size="44px"
|
||||||
doc={doc}
|
$theme="primary"
|
||||||
setSelectedUsers={setSelectedUsers}
|
$background={colorsTokens()['primary-bg']}
|
||||||
selectedUsers={selectedUsers}
|
$css={`border: 1px solid ${colorsTokens()['primary-200']}`}
|
||||||
disabled={isPending}
|
$radius="12px"
|
||||||
/>
|
$padding="4px"
|
||||||
{selectedUsers.length >= 0 && (
|
$margin="auto"
|
||||||
<Box $margin={{ top: 'small' }}>
|
>
|
||||||
<Text as="h4" $textAlign="left" $margin={{ bottom: 'tiny' }}>
|
group_add
|
||||||
{t('Choose a role')}
|
</Text>
|
||||||
</Text>
|
<Box $gap="0.7rem" $direction="row" $wrap="wrap" $css="flex: 70%;">
|
||||||
|
<Box $gap="0.7rem" $direction="row" $wrap="wrap" $css="flex: 80%;">
|
||||||
|
<Box $css="flex: auto;">
|
||||||
|
<SearchUsers
|
||||||
|
key={resetKey + 1}
|
||||||
|
doc={doc}
|
||||||
|
setSelectedUsers={setSelectedUsers}
|
||||||
|
selectedUsers={selectedUsers}
|
||||||
|
disabled={isPending}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box $css="flex: auto;">
|
||||||
<ChooseRole
|
<ChooseRole
|
||||||
|
key={resetKey}
|
||||||
currentRole={currentRole}
|
currentRole={currentRole}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
defaultRole={Role.READER}
|
|
||||||
setRole={setSelectedRole}
|
setRole={setSelectedRole}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
|
<Box $align="center" $justify="center" $css="flex: auto;">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
disabled={!selectedUsers.length || isPending || !selectedRole}
|
||||||
|
onClick={() => void handleValidate()}
|
||||||
|
style={{ height: '100%', maxHeight: '55px' }}
|
||||||
|
>
|
||||||
|
{t('Validate')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Radio, RadioGroup } from '@openfun/cunningham-react';
|
import { Select } from '@openfun/cunningham-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Role, useTransRole } from '@/features/docs/doc-management';
|
import { Role, useTransRole } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
interface ChooseRoleProps {
|
interface ChooseRoleProps {
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
defaultRole: Role;
|
defaultRole?: Role;
|
||||||
setRole: (role: Role) => void;
|
setRole: (role: Role) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,23 +16,20 @@ export const ChooseRole = ({
|
|||||||
currentRole,
|
currentRole,
|
||||||
setRole,
|
setRole,
|
||||||
}: ChooseRoleProps) => {
|
}: ChooseRoleProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const transRole = useTransRole();
|
const transRole = useTransRole();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioGroup>
|
<Select
|
||||||
{Object.values(Role).map((role) => (
|
label={t('Choose a role')}
|
||||||
<Radio
|
options={Object.values(Role).map((role) => ({
|
||||||
key={role}
|
label: transRole(role),
|
||||||
label={transRole(role)}
|
value: role,
|
||||||
value={role}
|
disabled: currentRole !== Role.OWNER && role === Role.OWNER,
|
||||||
name="role"
|
}))}
|
||||||
onChange={(evt) => setRole(evt.target.value as Role)}
|
onChange={(evt) => setRole(evt.target.value as Role)}
|
||||||
defaultChecked={defaultRole === role}
|
disabled={disabled}
|
||||||
disabled={
|
value={defaultRole}
|
||||||
disabled || (currentRole !== Role.OWNER && role === Role.OWNER)
|
/>
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Options } from 'react-select';
|
import { Options } from 'react-select';
|
||||||
import AsyncSelect from 'react-select/async';
|
import AsyncSelect from 'react-select/async';
|
||||||
|
|
||||||
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Doc } from '@/features/docs/doc-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
import { isValidEmail } from '@/utils';
|
import { isValidEmail } from '@/utils';
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ export const SearchUsers = ({
|
|||||||
setSelectedUsers,
|
setSelectedUsers,
|
||||||
disabled,
|
disabled,
|
||||||
}: SearchUsersProps) => {
|
}: SearchUsersProps) => {
|
||||||
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [userQuery, setUserQuery] = useState('');
|
const [userQuery, setUserQuery] = useState('');
|
||||||
@@ -102,6 +104,23 @@ export const SearchUsers = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncSelect
|
<AsyncSelect
|
||||||
|
styles={{
|
||||||
|
placeholder: (base) => ({
|
||||||
|
...base,
|
||||||
|
fontSize: '14px',
|
||||||
|
color: colorsTokens()['primary-600'],
|
||||||
|
}),
|
||||||
|
control: (base) => ({
|
||||||
|
...base,
|
||||||
|
minHeight: '45px',
|
||||||
|
borderColor: colorsTokens()['primary-600'],
|
||||||
|
}),
|
||||||
|
input: (base) => ({
|
||||||
|
...base,
|
||||||
|
minHeight: '45px',
|
||||||
|
fontSize: '14px',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
aria-label={t('Find a member to add to the document')}
|
aria-label={t('Find a member to add to the document')}
|
||||||
isMulti
|
isMulti
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './AddMembers';
|
||||||
@@ -1 +1 @@
|
|||||||
export * from './components/ModalAddMembers';
|
export * from './components';
|
||||||
|
|||||||
Reference in New Issue
Block a user