⚡️(frontend) improve select share stability
- keep email in search input after unfocus - keep search in memory after unfocus - fixed width to reduce flickering - empty states after validation
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
<IconBG iconName="group_add" />
|
||||
<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;">
|
||||
<Box $css="flex: auto;" $width="15rem">
|
||||
<SearchUsers
|
||||
key={resetKey + 1}
|
||||
doc={doc}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Options } from 'react-select';
|
||||
import { InputActionMeta, Options } from 'react-select';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
@@ -42,7 +42,7 @@ export const SearchUsers = ({
|
||||
|
||||
const options = data?.results;
|
||||
|
||||
useEffect(() => {
|
||||
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<NodeJS.Timeout | null>(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 (
|
||||
<AsyncSelect
|
||||
@@ -125,10 +137,10 @@ export const SearchUsers = ({
|
||||
aria-label={t('Find a member to add to the document')}
|
||||
isMulti
|
||||
loadOptions={loadOptions}
|
||||
defaultOptions={[]}
|
||||
defaultOptions={optionsSelect}
|
||||
onInputChange={onInputChangeHandle}
|
||||
inputValue={input}
|
||||
placeholder={t('Search new members by email')}
|
||||
placeholder={t('Search by email')}
|
||||
noOptionsMessage={() =>
|
||||
input
|
||||
? t("We didn't find a mail matching, try to be more accurate")
|
||||
|
||||
Reference in New Issue
Block a user