Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into new/…
Browse files Browse the repository at this point in the history
…homepage

* 'develop' of github.com:RocketChat/Rocket.Chat:
  Chore: Convert UserCardWithData to ts (#26192)
  Chore: cleanup startup of test and put wizard in setup function (#26306)
  Chore: Convert AccountPreferencesPage to ts (#26096)
  [FIX] Missing bio field UI validation (#26345)
  Chore: Remove square prop from IconButton (#26343)
  Chore: Rewrite VerticalBarOldActions to TS (#26277)
  Chore: Replace direct multiple icon (#26342)
  • Loading branch information
gabriellsh committed Jul 26, 2022
2 parents 9c1eaf4 + 45ee02d commit 0409034
Show file tree
Hide file tree
Showing 25 changed files with 189 additions and 369 deletions.
2 changes: 1 addition & 1 deletion apps/meteor/app/ui-message/client/actionButtons/tabbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const onAdded = (button: IUIActionButton): void =>
applyButtonFilters(button, room)
? {
id: button.actionId,
icon: '', // Apps won't provide icons for now
icon: undefined, // Apps won't provide icons for now
order: 300, // Make sure the button only shows up inside the room toolbox
title: t(Utilities.getI18nKeyForApp(button.labelI18n, button.appId)) as any,
// Filters were applied in the applyButtonFilters function
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/components/UserCard/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from '@rocket.chat/css-in-js';
import { Box, IconButton, Skeleton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { forwardRef, ReactNode, ComponentProps } from 'react';
import React, { forwardRef, ReactNode, ComponentProps, MouseEvent } from 'react';

import MarkdownText from '../MarkdownText';
import * as Status from '../UserStatus';
Expand All @@ -22,7 +22,7 @@ const clampStyle = css`
type UserCardProps = {
className?: string;
style?: ComponentProps<typeof Box>['style'];
open?: () => void;
open?: (e: MouseEvent<HTMLElement>) => void;
name?: string;
username?: string;
etag?: string;
Expand Down
7 changes: 2 additions & 5 deletions apps/meteor/client/components/UserCard/UserCardAction.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { IconButton, Icon } from '@rocket.chat/fuselage';
import { IconButton } from '@rocket.chat/fuselage';
import React, { ReactElement, ComponentProps } from 'react';

type UserCardActionProps = {
label?: string;
icon: ComponentProps<typeof Icon>['name'];
};
type UserCardActionProps = ComponentProps<typeof IconButton>;

const UserCardAction = ({ label, icon, ...props }: UserCardActionProps): ReactElement => (
<IconButton icon={icon} small title={label} {...props} mi='x2' />
Expand Down
2 changes: 0 additions & 2 deletions apps/meteor/client/components/UserStatus/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export const USER_STATUS_TEXT_MAX_LENGTH = 120;

export const colors = {
busy: 'danger-500',
away: 'warning-600',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ function UserAvatarEditor({ currentUsername, username, setAvatarObj, suggestions
<Button square mis='none' onClick={clickReset} disabled={disabled} mie='x4' title={t('Accounts_SetDefaultAvatar')}>
<Avatar url={`/avatar/%40${username}`} />
</Button>
<IconButton icon='upload' square secondary onClick={clickUpload} disabled={disabled} title={t('Upload')} />
<IconButton icon='permalink' square secondary onClick={clickUrl} disabled={disabled || urlEmpty} title={t('Add URL')} />
<IconButton icon='upload' secondary onClick={clickUpload} disabled={disabled} title={t('Upload')} />
<IconButton icon='permalink' secondary onClick={clickUrl} disabled={disabled || urlEmpty} title={t('Add URL')} />
{suggestions && (
<UserAvatarSuggestions
suggestions={suggestions}
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/client/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const USER_STATUS_TEXT_MAX_LENGTH = 120;
export const BIO_TEXT_MAX_LENGTH = 260;
export const VIDEOCONF_STACK_MAX_USERS = 6;
2 changes: 1 addition & 1 deletion apps/meteor/client/sidebar/header/EditStatusModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, useState, ChangeEvent, useCallback } from 'react';

import { USER_STATUS_TEXT_MAX_LENGTH } from '../../components/UserStatus';
import UserStatusMenu from '../../components/UserStatusMenu';
import { USER_STATUS_TEXT_MAX_LENGTH } from '../../lib/constants';

type EditStatusModalProps = {
onClose: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ButtonGroup, Button, Box, Accordion } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useState, useCallback, useRef } from 'react';
import React, { useState, useCallback, useRef, ReactElement } from 'react';

import Page from '../../../components/Page';
import PreferencesGlobalSection from './PreferencesGlobalSection';
Expand All @@ -12,24 +12,63 @@ import PreferencesNotificationsSection from './PreferencesNotificationsSection';
import PreferencesSoundSection from './PreferencesSoundSection';
import PreferencesUserPresenceSection from './PreferencesUserPresenceSection';

const AccountPreferencesPage = () => {
type CurrentData = {
enableNewMessageTemplate: boolean;
language: string;
newRoomNotification: string;
newMessageNotification: string;
clockMode: number;
useEmojis: boolean;
convertAsciiEmoji: boolean;
saveMobileBandwidth: boolean;
collapseMediaByDefault: boolean;
autoImageLoad: boolean;
emailNotificationMode: string;
unreadAlert: boolean;
notificationsSoundVolume: number;
desktopNotifications: string;
pushNotifications: string;
enableAutoAway: boolean;
highlights: string;
messageViewMode: number;
hideUsernames: boolean;
hideRoles: boolean;
displayAvatars: boolean;
hideFlexTab: boolean;
sendOnEnter: string;
idleTimeLimit: number;
sidebarShowFavorites: boolean;
sidebarShowUnread: boolean;
sidebarSortby: string;
sidebarViewMode: string;
sidebarDisplayAvatar: boolean;
sidebarGroupByType: boolean;
muteFocusedConversations: boolean;
dontAskAgainList: [action: string, label: string][];
};

type FormatedData = Omit<Partial<CurrentData>, 'dontAskAgainList' | 'highlights'>;

const AccountPreferencesPage = (): ReactElement => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();

const [hasAnyChange, setHasAnyChange] = useState(false);

const saveData = useRef({});
const saveData = useRef<Partial<CurrentData>>({});
const commitRef = useRef({});

const dataDownloadEnabled = useSetting('UserData_EnableDownload');

const onChange = useCallback(
({ initialValue, value, key }) => {
const { current } = saveData;
if (JSON.stringify(initialValue) !== JSON.stringify(value)) {
current[key] = value;
} else {
delete current[key];
if (current) {
if (JSON.stringify(initialValue) !== JSON.stringify(value)) {
current[key as keyof CurrentData] = value;
} else {
delete current[key as keyof CurrentData];
}
}

const anyChange = !!Object.values(current).length;
Expand All @@ -45,7 +84,7 @@ const AccountPreferencesPage = () => {
const handleSave = useCallback(async () => {
try {
const { current: data } = saveData;
if (data.highlights || data.highlights === '') {
if (data?.highlights || data?.highlights === '') {
Object.assign(data, {
highlights: data.highlights
.split(/,|\n/)
Expand All @@ -54,22 +93,22 @@ const AccountPreferencesPage = () => {
});
}

if (data.dontAskAgainList) {
if (data?.dontAskAgainList) {
const list =
Array.isArray(data.dontAskAgainList) && data.dontAskAgainList.length > 0
? data.dontAskAgainList.map(([action, label]) => ({ action, label }))
: [];
Object.assign(data, { dontAskAgainList: list });
}

await saveFn(data);
await saveFn(data as FormatedData);
saveData.current = {};
setHasAnyChange(false);
Object.values(commitRef.current).forEach((fn) => fn());
Object.values(commitRef.current).forEach((fn) => (fn as () => void)());

dispatchToastMessage({ type: 'success', message: t('Preferences_saved') });
} catch (e) {
dispatchToastMessage({ type: 'error', message: e });
dispatchToastMessage({ type: 'error', message: String(e) });
}
}, [dispatchToastMessage, saveFn, t]);

Expand Down
18 changes: 15 additions & 3 deletions apps/meteor/client/views/account/profile/AccountProfileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import React, { Dispatch, ReactElement, SetStateAction, useCallback, useMemo, us
import { validateEmail } from '../../../../lib/emailValidator';
import { getUserEmailAddress } from '../../../../lib/getUserEmailAddress';
import CustomFieldsForm from '../../../components/CustomFieldsForm';
import { USER_STATUS_TEXT_MAX_LENGTH } from '../../../components/UserStatus';
import UserStatusMenu from '../../../components/UserStatusMenu';
import UserAvatarEditor from '../../../components/avatar/UserAvatarEditor';
import { USER_STATUS_TEXT_MAX_LENGTH, BIO_TEXT_MAX_LENGTH } from '../../../lib/constants';
import { AccountFormValues } from './AccountProfilePage';

type AccountProfileFormProps = {
Expand All @@ -32,6 +32,7 @@ type AccountProfileFormProps = {
onSaveStateChange: Dispatch<SetStateAction<boolean>>;
};

// TODO: Replace this form using React Hook Form
const AccountProfileForm = ({ values, handlers, user, settings, onSaveStateChange, ...props }: AccountProfileFormProps): ReactElement => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
Expand Down Expand Up @@ -142,11 +143,20 @@ const AccountProfileForm = ({ values, handlers, user, settings, onSaveStateChang

return undefined;
}, [statusText, t]);

const bioError = useMemo(() => {
if (bio && bio.length > BIO_TEXT_MAX_LENGTH) {
return t('Max_length_is', BIO_TEXT_MAX_LENGTH);
}

return undefined;
}, [bio, t]);

const {
emails: [{ verified = false } = { verified: false }],
} = user as any;

const canSave = !![!!passwordError, !!emailError, !!usernameError, !!nameError, !!statusTextError].filter(Boolean);
const canSave = !![!!passwordError, !!emailError, !!usernameError, !!nameError, !!statusTextError, !!bioError].filter(Boolean);

useEffect(() => {
onSaveStateChange(canSave);
Expand Down Expand Up @@ -251,16 +261,18 @@ const AccountProfileForm = ({ values, handlers, user, settings, onSaveStateChang
<Field.Label>{t('Bio')}</Field.Label>
<Field.Row>
<TextAreaInput
error={bioError}
rows={3}
flexGrow={1}
value={bio}
onChange={handleBio}
addon={<Icon name='edit' size='x20' alignSelf='center' />}
/>
</Field.Row>
<Field.Error>{bioError}</Field.Error>
</Field>
),
[bio, handleBio, t],
[bio, handleBio, bioError, t],
)}
<Field>
<Grid>
Expand Down
4 changes: 3 additions & 1 deletion apps/meteor/client/views/hooks/useActionSpread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const mapOptions = ([key, { action, label, icon }]: [string, Action]): [string,
];

export const useActionSpread = (
actions: Action[],
actions: {
[key: string]: Action;
},
size = 2,
): { actions: [string, Action][]; menu: { [id: string]: MenuOption } | undefined } =>
useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IRoom } from '@rocket.chat/core-typings';
import { PositionAnimated, AnimatedVisibility, Menu, Option } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetting, useRolesDescription, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo, useRef } from 'react';
import { useSetting, useRolesDescription } from '@rocket.chat/ui-contexts';
import React, { useMemo, useRef, ReactElement } from 'react';

import { Backdrop } from '../../../components/Backdrop';
import LocalTime from '../../../components/LocalTime';
Expand All @@ -12,17 +13,20 @@ import { useEndpointData } from '../../../hooks/useEndpointData';
import { useActionSpread } from '../../hooks/useActionSpread';
import { useUserInfoActions } from '../hooks/useUserInfoActions';

const UserCardWithData = ({ username, onClose, target, open, rid }) => {
const ref = useRef(target);
type UserCardWithDataProps = {
username: string;
onClose: () => void;
target: Element;
open: (e: Event) => void;
rid: IRoom['_id'];
};

const UserCardWithData = ({ username, onClose, target, open, rid }: UserCardWithDataProps): ReactElement => {
const ref = useRef(target);
const getRoles = useRolesDescription();

const t = useTranslation();

const showRealNames = useSetting('UI_Use_Real_Name');

const query = useMemo(() => ({ username }), [username]);

const { value: data, phase: state } = useEndpointData('/v1/users.info', query);

ref.current = target;
Expand All @@ -31,19 +35,16 @@ const UserCardWithData = ({ username, onClose, target, open, rid }) => {
const loading = state === AsyncStatePhase.LOADING;
const defaultValue = loading ? undefined : null;

const { user } = data || { user: {} };

const {
_id,
name = username,
roles = defaultValue,
status = null,
statusText = status,
statusText,
bio = defaultValue,
utcOffset = defaultValue,
nickname,
avatarETag,
} = user;
} = data?.user || {};

return {
_id,
Expand All @@ -52,19 +53,20 @@ const UserCardWithData = ({ username, onClose, target, open, rid }) => {
roles: roles && getRoles(roles).map((role, index) => <UserCard.Role key={index}>{role}</UserCard.Role>),
bio,
etag: avatarETag,
localTime: Number.isInteger(utcOffset) && <LocalTime utcOffset={utcOffset} />,
status: <ReactiveUserStatus uid={_id} />,
localTime: utcOffset && Number.isInteger(utcOffset) && <LocalTime utcOffset={utcOffset} />,
status: _id && <ReactiveUserStatus uid={_id} />,
customStatus: statusText,
nickname,
};
}, [data, username, showRealNames, state, getRoles]);

const handleOpen = useMutableCallback((e) => {
open && open(e);
onClose && onClose();
open?.(e);
onClose?.();
});

const { actions: actionsDefinition, menu: menuOptions } = useActionSpread(useUserInfoActions(user, rid));
const userActions = useUserInfoActions({ _id: user._id ?? '', username: user.username }, rid);
const { actions: actionsDefinition, menu: menuOptions } = useActionSpread(userActions);

const menu = useMemo(() => {
if (!menuOptions) {
Expand All @@ -74,17 +76,17 @@ const UserCardWithData = ({ username, onClose, target, open, rid }) => {
return (
<Menu
flexShrink={0}
maxHeight='initial'
mi='x2'
key='menu'
ghost={false}
renderItem={({ label: { label, icon }, ...props }) => <Option {...props} label={label} icon={icon} />}
renderItem={({ label: { label, icon }, ...props }): ReactElement => <Option {...props} label={label} icon={icon} />}
options={menuOptions}
/>
);
}, [menuOptions]);

const actions = useMemo(() => {
const mapAction = ([key, { label, icon, action }]) => (
const mapAction = ([key, { label, icon, action }]: any): ReactElement => (
<UserCard.Action key={key} label={label} aria-label={label} onClick={action} icon={icon} />
);

Expand All @@ -95,7 +97,7 @@ const UserCardWithData = ({ username, onClose, target, open, rid }) => {
<>
<Backdrop bg='transparent' onClick={onClose} />
<PositionAnimated anchor={ref} placement='top-start' margin={8} visible={AnimatedVisibility.UNHIDING}>
<UserCard {...user} onClose={onClose} open={handleOpen} actions={actions} t={t} />
<UserCard {...user} onClose={onClose} open={handleOpen} actions={actions} />
</PositionAnimated>
</>
);
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/client/views/room/UserCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './UserCardWithData';
Loading

0 comments on commit 0409034

Please sign in to comment.