Skip to content

Commit

Permalink
Chore: Convert apps/meteor/client/components/UserAutoComplete (#25554)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
  • Loading branch information
juliajforesti and ggazzo committed May 20, 2022
1 parent 8a3c928 commit 45417ea
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const CreateDiscussion = ({ onClose, defaultParentRoom, parentMessageId, nameSug
value={parentRoom}
onChange={handleParentRoom}
placeholder={t('Discussion_target_channel_description')}
disabled={defaultParentRoom}
disabled={Boolean(defaultParentRoom)}
/>
)}
</Field.Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ const ForwardChatModal = ({
<Field.Row>
<UserAutoComplete
conditions={conditions}
flexGrow={1}
placeholder={t('Username')}
onChange={(value: string): void => {
onChange={(value: any): void => {
setValue('username', value);
}}
value={getValues().username}
Expand Down
10 changes: 0 additions & 10 deletions apps/meteor/client/components/RoomAutoComplete/Avatar.js

This file was deleted.

16 changes: 16 additions & 0 deletions apps/meteor/client/components/RoomAutoComplete/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Options } from '@rocket.chat/fuselage';
import React, { ReactElement } from 'react';

import RoomAvatar from '../avatar/RoomAvatar';

type AvatarProps = {
value: string;
type: string;
avatarETag?: string | undefined;
};

const Avatar = ({ value, type, avatarETag, ...props }: AvatarProps): ReactElement => (
<RoomAvatar size={Options.AvatarSize} room={{ t: type, type, _id: value, avatarETag }} {...props} />
);

export default Avatar;
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
import { AutoComplete, Option, Box } from '@rocket.chat/fuselage';
import React, { memo, useMemo, useState } from 'react';
import React, { ComponentProps, memo, ReactElement, useMemo, useState } from 'react';

import { useEndpointData } from '../../hooks/useEndpointData';
import RoomAvatar from '../avatar/RoomAvatar';
import Avatar from './Avatar';

const query = (term = '') => ({ selector: JSON.stringify({ name: term }) });
const query = (
term = '',
): {
selector: string;
} => ({ selector: JSON.stringify({ name: term }) });

const RoomAutoComplete = (props) => {
type RoomAutoCompleteProps = Omit<ComponentProps<typeof AutoComplete>, 'value' | 'filter'> & {
value: any;
};

/* @deprecated */
const RoomAutoComplete = (props: RoomAutoCompleteProps): ReactElement => {
const [filter, setFilter] = useState('');
const { value: data } = useEndpointData(
'rooms.autocomplete.channelAndPrivate',
useMemo(() => query(filter), [filter]),
);
const options = useMemo(
() =>
(data &&
data.items.map(({ name, _id, avatarETag, t }) => ({
value: _id,
label: { name, avatarETag, type: t },
}))) ||
[],
data?.items.map(({ name, _id, avatarETag, t }) => ({
value: _id,
label: { name, avatarETag, type: t },
})) || [],
[data],
);
) as unknown as { value: string; label: string }[];

return (
<AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ value, label }) => (
renderSelected={({ value, label }): ReactElement => (
<>
<Box margin='none' mi='x2'>
<RoomAvatar size='x20' room={{ type: label?.type || 'c', _id: value, ...label }} />{' '}
Expand All @@ -39,7 +46,7 @@ const RoomAutoComplete = (props) => {
</Box>
</>
)}
renderItem={({ value, label, ...props }) => (
renderItem={({ value, label, ...props }): ReactElement => (
<Option key={value} {...props} label={label.name} avatar={<Avatar value={value} {...label} />} />
)}
options={options}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { AutoComplete, Option, Box, Chip, Options } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { memo, useMemo, useState } from 'react';
import React, { ComponentProps, memo, MouseEventHandler, ReactElement, useMemo, useState } from 'react';

import { useEndpointData } from '../../hooks/useEndpointData';
import UserAvatar from '../avatar/UserAvatar';

const query = (term = '', conditions = {}) => ({ selector: JSON.stringify({ term, conditions }) });
const query = (
term = '',
conditions = {},
): {
selector: string;
} => ({ selector: JSON.stringify({ term, conditions }) });

const UserAutoComplete = (props) => {
type UserAutoCompleteProps = Omit<ComponentProps<typeof AutoComplete>, 'value' | 'filter'> &
Omit<ComponentProps<typeof Option>, 'value'> & {
conditions?: { [key: string]: unknown };
onChange?: MouseEventHandler<HTMLButtonElement>;
value: any;
filter?: string;
};

const UserAutoComplete = ({ value, ...props }: UserAutoCompleteProps): ReactElement => {
const { conditions = {} } = props;
const [filter, setFilter] = useState('');
const debouncedFilter = useDebouncedValue(filter, 1000);
Expand All @@ -17,28 +30,29 @@ const UserAutoComplete = (props) => {
useMemo(() => query(debouncedFilter, conditions), [filter]),
);

const options = useMemo(() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [], [data]);
const options = useMemo(() => data?.items.map((user) => ({ value: user.username, label: user.name || user.username })) || [], [data]);

return (
<AutoComplete
{...props}
value={value}
filter={filter}
setFilter={setFilter}
renderSelected={({ value, label }) => {
renderSelected={({ value, label }): ReactElement => {
if (!value) {
return '';
undefined;
}

return (
<Chip height='x20' value={value} onClick={() => props.onChange()} mie='x4'>
<Chip height='x20' value={value} onClick={props.onChange} mie='x4'>
<UserAvatar size='x20' username={value} />
<Box verticalAlign='middle' is='span' margin='none' mi='x4'>
{label}
</Box>
</Chip>
);
}}
renderItem={({ value, ...props }) => (
renderItem={({ value, ...props }): ReactElement => (
<Option key={value} avatar={<UserAvatar size={Options.AvatarSize} username={value} />} {...props} />
)}
options={options}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
setUser(undefined);
reload.current?.();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
dispatchToastMessage({ type: 'error', message: String(error) });
}
});

Expand All @@ -51,6 +51,12 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
return setUser(user);
});

const handleChange = (value: unknown): void => {
if (typeof value === 'string') {
setRid(value);
}
};

return (
<Page>
<Page.Header title={`${t('Users_in_role')} "${description || name}"`}>
Expand All @@ -65,7 +71,7 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
<Field mbe='x4'>
<Field.Label>{t('Choose_a_room')}</Field.Label>
<Field.Row>
<RoomAutoComplete value={rid} onChange={setRid} placeholder={t('User')} />
<RoomAutoComplete value={rid} onChange={handleChange} placeholder={t('User')} />
</Field.Row>
</Field>
)}
Expand Down
9 changes: 8 additions & 1 deletion apps/meteor/client/views/omnichannel/managers/AddManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ const AddManager = ({ reload }: { reload: () => void }): ReactElement => {
reload();
setUsername('');
});

const handleChange = (value: unknown): void => {
if (typeof value === 'string') {
setUsername(value);
}
};

return (
<Box display='flex' alignItems='center' pi='x24'>
<Field>
<Field.Label>{t('Username')}</Field.Label>
<Field.Row>
<UserAutoComplete value={username} onChange={setUsername} />
<UserAutoComplete value={username} onChange={handleChange} />
<Button disabled={!username} onClick={handleSave} mis='x8' primary>
{t('Add')}
</Button>
Expand Down
1 change: 1 addition & 0 deletions packages/core-typings/src/IUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export interface IUser extends IRocketChatRecord {
type: string;
active: boolean;
username?: string;
nickname?: string;
name?: string;
services?: IUserServices;
emails?: IUserEmail[];
Expand Down
4 changes: 3 additions & 1 deletion packages/rest-typings/src/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export type UsersEndpoints = {
POST: (params: { emailOrUsername: string }) => void;
};
'users.autocomplete': {
GET: (params: { selector: string }) => { items: IUser[] };
GET: (params: { selector: string }) => {
items: Required<Pick<IUser, '_id' | 'name' | 'username' | 'nickname' | 'status' | 'avatarETag'>>[];
};
};
'users.listTeams': {
GET: (params: { userId: IUser['_id'] }) => { teams: Array<ITeam> };
Expand Down

0 comments on commit 45417ea

Please sign in to comment.