From fb494c8c71ee8db206241573ed831052344d6d6a Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 6 Aug 2024 12:48:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F(frontend)=20improve=20select?= =?UTF-8?q?=20share=20stability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - keep email in search input after unfocus - keep search in memory after unfocus - fixed width to reduce flickering - empty states after validation --- CHANGELOG.md | 1 + .../app-impress/doc-member-create.spec.ts | 21 ++++++++-- .../members-add/components/AddMembers.tsx | 22 +++++++--- .../members-add/components/SearchUsers.tsx | 40 ++++++++++++------- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce688142..c1311ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to ## Fixed - 🐛(y-webrtc) fix prob connection #147 +- ⚡️(frontend) improve select share stability #159 ## Changed diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts index be8c8253..fbb0db78 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts @@ -170,7 +170,7 @@ test.describe('Document create member', () => { const inputSearch = page.getByLabel(/Find a member to add to the document/); - const email = randomName('test@test.fr', browserName, 1)[0]; + const [email] = randomName('test@test.fr', browserName, 1); await inputSearch.fill(email); await page.getByRole('option', { name: email }).click(); @@ -191,7 +191,22 @@ test.describe('Document create member', () => { expect(responseCreateInvitation.ok()).toBeTruthy(); await inputSearch.fill(email); - await expect(page.getByText('Loading...')).toBeHidden(); - await expect(page.getByRole('option', { name: email })).toBeHidden(); + await page.getByRole('option', { name: email }).click(); + // Choose a role + await page.getByRole('combobox', { name: /Choose a role/ }).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: 'Validate' }).click(); + await expect( + page.getByText(`"${email}" is already invited to the document.`), + ).toBeVisible(); + const responseCreateInvitationFail = + await responsePromiseCreateInvitationFail; + expect(responseCreateInvitationFail.ok()).toBeFalsy(); }); }); diff --git a/src/frontend/apps/impress/src/features/docs/members/members-add/components/AddMembers.tsx b/src/frontend/apps/impress/src/features/docs/members/members-add/components/AddMembers.tsx index 2dd6448d..d33a5082 100644 --- a/src/frontend/apps/impress/src/features/docs/members/members-add/components/AddMembers.tsx +++ b/src/frontend/apps/impress/src/features/docs/members/members-add/components/AddMembers.tsx @@ -70,14 +70,23 @@ export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => { duration: 4000, }; - const onError = (dataError: APIErrorUser['data']) => { - const messageError = - dataError?.type === OptionType.INVITATION + const onError = (dataError: APIErrorUser) => { + let messageError = + dataError['data']?.type === OptionType.INVITATION ? t(`Failed to create the invitation for {{email}}.`, { - email: dataError?.value, + email: dataError['data']?.value, }) : t(`Failed to add the member in the document.`); + if ( + dataError.cause?.[0] === + 'Document invitation with this Email address and Document already exists.' + ) { + messageError = t('"{{email}}" is already invited to the document.', { + email: dataError['data']?.value, + }); + } + toast(messageError, VariantType.ERROR, toastOptions); }; @@ -106,11 +115,12 @@ export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => { setIsPending(false); setResetKey(resetKey + 1); + setSelectedUsers([]); settledPromises.forEach((settledPromise) => { switch (settledPromise.status) { case 'rejected': - onError((settledPromise.reason as APIErrorUser).data); + onError(settledPromise.reason as APIErrorUser); break; case 'fulfilled': @@ -132,7 +142,7 @@ export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => { - + { + const optionsSelect = useMemo(() => { if (!resolveOptionsRef.current || !options) { return; } @@ -81,6 +81,8 @@ export const SearchUsers = ({ resolveOptionsRef.current(users); resolveOptionsRef.current = null; + + return users; // eslint-disable-next-line react-hooks/exhaustive-deps }, [options, selectedUsers]); @@ -91,16 +93,26 @@ export const SearchUsers = ({ }; const timeout = useRef(null); - const onInputChangeHandle = useCallback((newValue: string) => { - setInput(newValue); - if (timeout.current) { - clearTimeout(timeout.current); - } + const onInputChangeHandle = useCallback( + (newValue: string, actionMeta: InputActionMeta) => { + if ( + actionMeta.action === 'input-blur' || + actionMeta.action === 'menu-close' + ) { + return; + } - timeout.current = setTimeout(() => { - setUserQuery(newValue); - }, 1000); - }, []); + setInput(newValue); + if (timeout.current) { + clearTimeout(timeout.current); + } + + timeout.current = setTimeout(() => { + setUserQuery(newValue); + }, 1000); + }, + [], + ); return ( input ? t("We didn't find a mail matching, try to be more accurate")