[FIX] Setup Wizard inputs and Admin Settings (#16147)

pull/16274/head
Tasso Evangelista 5 years ago committed by Diego Sampaio
parent cc9a6fa96d
commit c2ed1e325a
No known key found for this signature in database
GPG Key ID: E060152B30502562
  1. 9
      client/components/admin/info/DescriptionList.stories.js
  2. 22
      client/components/admin/info/InformationPage.js
  3. 83
      client/components/admin/settings/GroupPage.js
  4. 16
      client/components/admin/settings/GroupPage.stories.js
  5. 16
      client/components/admin/settings/GroupSelector.js
  6. 12
      client/components/admin/settings/NotAuthorizedPage.js
  7. 13
      client/components/admin/settings/ResetSettingButton.js
  8. 12
      client/components/admin/settings/Section.js
  9. 86
      client/components/admin/settings/Setting.js
  10. 97
      client/components/admin/settings/Setting.stories.js
  11. 59
      client/components/admin/settings/SettingsState.js
  12. 10
      client/components/admin/settings/groups/AssetsGroupPage.js
  13. 10
      client/components/admin/settings/groups/GenericGroupPage.js
  14. 12
      client/components/admin/settings/groups/OAuthGroupPage.js
  15. 2
      client/components/admin/settings/inputs/ActionSettingInput.js
  16. 6
      client/components/admin/settings/inputs/AssetSettingInput.js
  17. 6
      client/components/admin/settings/inputs/AssetSettingInput.stories.js
  18. 12
      client/components/admin/settings/inputs/BooleanSettingInput.js
  19. 10
      client/components/admin/settings/inputs/CodeSettingInput.js
  20. 4
      client/components/admin/settings/inputs/CodeSettingInput.stories.js
  21. 55
      client/components/admin/settings/inputs/ColorSettingInput.js
  22. 16
      client/components/admin/settings/inputs/FontSettingInput.js
  23. 16
      client/components/admin/settings/inputs/GenericSettingInput.js
  24. 16
      client/components/admin/settings/inputs/IntSettingInput.js
  25. 16
      client/components/admin/settings/inputs/LanguageSettingInput.js
  26. 16
      client/components/admin/settings/inputs/PasswordSettingInput.js
  27. 14
      client/components/admin/settings/inputs/RelativeUrlSettingInput.js
  28. 10
      client/components/admin/settings/inputs/RoomPickSettingInput.js
  29. 13
      client/components/admin/settings/inputs/SelectSettingInput.js
  30. 17
      client/components/admin/settings/inputs/StringSettingInput.js
  31. 0
      client/components/basic/BurgerMenuButton.css
  32. 0
      client/components/basic/BurgerMenuButton.js
  33. 2
      client/components/basic/BurgerMenuButton.stories.js
  34. 35
      client/components/basic/Page.js
  35. 36
      client/components/basic/Page.stories.js
  36. 35
      client/components/header/Header.js
  37. 32
      client/components/header/Header.stories.js
  38. 4
      client/components/pageNotFound/PageNotFound.js
  39. 2
      client/components/setupWizard/SetupWizardPage.js
  40. 2
      client/components/setupWizard/SideBar.js
  41. 19
      client/components/setupWizard/steps/AdminUserInformationStep.js
  42. 6
      client/components/setupWizard/steps/FinalStep.js
  43. 42
      client/components/setupWizard/steps/RegisterServerStep.js
  44. 20
      client/components/setupWizard/steps/SettingsBasedStep.js
  45. 16
      package-lock.json
  46. 2
      package.json
  47. 31
      tests/end-to-end/ui/11-admin.js

@ -1,17 +1,18 @@
import React from 'react';
import { DescriptionList } from './DescriptionList';
import { Page } from '../../basic/Page';
export default {
title: 'admin/info/DescriptionList',
component: DescriptionList,
decorators: [
(fn) => <div className='rc-old'>{fn()}</div>,
(fn) => <section className='page-container page-list'>
<div className='content'>
(fn) => <Page>
<Page.Content>
{fn()}
</div>
</section>,
</Page.Content>
</Page>,
],
};

@ -1,8 +1,8 @@
import { Button, Callout, Icon, Margins } from '@rocket.chat/fuselage';
import { Button, ButtonGroup, Callout, Icon, Margins } from '@rocket.chat/fuselage';
import React from 'react';
import { Link } from '../../basic/Link';
import { Header } from '../../header/Header';
import { Page } from '../../basic/Page';
import { useTranslation } from '../../../contexts/TranslationContext';
import { RocketChatSection } from './RocketChatSection';
import { CommitSection } from './CommitSection';
@ -27,19 +27,19 @@ export function InformationPage({
const alertOplogForMultipleInstances = statistics && statistics.instanceCount > 1 && !statistics.oplogEnabled;
return <section className='page-container' data-qa='admin-info'>
<Header rawSectionName={t('Info')} hideHelp>
return <Page data-qa='admin-info'>
<Page.Header title={t('Info')}>
{canViewStatistics
&& <Header.ActionBlock>
&& <ButtonGroup>
<Button disabled={isLoading} primary type='button' onClick={onClickRefreshButton}>
<Icon name='reload' /> {t('Refresh')}
</Button>
</Header.ActionBlock>}
</Header>
</ButtonGroup>}
</Page.Header>
<div className='content'>
<Page.Content>
{alertOplogForMultipleInstances
&& <Margins blockEnd='16'>
&& <Margins blockEnd='x16'>
<Callout type='danger' title={t('Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances')}>
<p>
{t('Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances_details')}
@ -58,6 +58,6 @@ export function InformationPage({
<BuildEnvironmentSection info={info} />
{canViewStatistics && <UsageSection statistics={statistics} isLoading={isLoading} />}
<InstancesSection instances={instances} />
</div>
</section>;
</Page.Content>
</Page>;
}

@ -1,92 +1,87 @@
import { Accordion, Button, Paragraph, Skeleton } from '@rocket.chat/fuselage';
import React from 'react';
import styled from 'styled-components';
import { Accordion, Box, Button, ButtonGroup, Paragraph, Skeleton } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';
import { Header } from '../../header/Header';
import { useTranslation } from '../../../contexts/TranslationContext';
import { Section } from './Section';
import { Page } from '../../basic/Page';
const Wrapper = styled.div`
margin: 0 auto;
width: 100%;
max-width: 590px;
`;
export function GroupPage({ children, group, headerButtons }) {
export function GroupPage({ children, headerButtons, save, cancel, _id, i18nLabel, i18nDescription, changed }) {
const t = useTranslation();
const handleSubmit = (event) => {
event.preventDefault();
group.save();
save();
};
const handleCancelClick = (event) => {
event.preventDefault();
group.cancel();
cancel();
};
const handleSaveClick = (event) => {
event.preventDefault();
group.save();
save();
};
if (!group) {
return <section className='page-container page-static page-settings'>
<Header />
<div className='content' />
</section>;
if (!_id) {
return <Page>
<Page.Header />
<Page.Content />
</Page>;
}
return <form action='#' className='page-container' method='post' onSubmit={handleSubmit}>
<Header rawSectionName={t(group.i18nLabel)}>
<Header.ButtonSection>
{group.changed && <Button danger primary type='reset' onClick={handleCancelClick}>{t('Cancel')}</Button>}
return <Page is='form' action='#' method='post' onSubmit={handleSubmit}>
<Page.Header title={t(i18nLabel)}>
<ButtonGroup>
{changed && <Button danger primary type='reset' onClick={handleCancelClick}>{t('Cancel')}</Button>}
<Button
children={t('Save_changes')}
className='save'
disabled={!group.changed}
disabled={!changed}
primary
type='submit'
onClick={handleSaveClick}
/>
{headerButtons}
</Header.ButtonSection>
</Header>
</ButtonGroup>
</Page.Header>
<div className='content'>
<Wrapper>
{t.has(group.i18nDescription) && <Paragraph hintColor>{t(group.i18nDescription)}</Paragraph>}
<Page.Content>
<Box style={useMemo(() => ({ margin: '0 auto', width: '100%', maxWidth: '590px' }), [])}>
{t.has(i18nDescription) && <Paragraph hintColor>{t(i18nDescription)}</Paragraph>}
<Accordion className='page-settings'>
{children}
</Accordion>
</Wrapper>
</div>
</form>;
</Box>
</Page.Content>
</Page>;
}
GroupPage.Skeleton = function GroupPageSkeleton() {
export function GroupPageSkeleton() {
const t = useTranslation();
return <div className='page-container'>
<Header rawSectionName={<Skeleton style={{ width: '20rem' }}/>}>
<Header.ButtonSection>
return <Page>
<Page.Header title={<Skeleton style={{ width: '20rem' }}/>}>
<ButtonGroup>
<Button
children={t('Save_changes')}
disabled
primary
/>
</Header.ButtonSection>
</Header>
</ButtonGroup>
</Page.Header>
<div className='content'>
<Wrapper>
<Page.Content>
<Box style={useMemo(() => ({ margin: '0 auto', width: '100%', maxWidth: '590px' }), [])}>
<Paragraph.Skeleton />
<Accordion className='page-settings'>
<Section.Skeleton />
</Accordion>
</Wrapper>
</div>
</div>;
};
</Box>
</Page.Content>
</Page>;
}
GroupPage.Skeleton = GroupPageSkeleton;

@ -7,18 +7,20 @@ export default {
title: 'admin/settings/GroupPage',
component: GroupPage,
decorators: [
(storyFn) => <SettingsState>{storyFn()}</SettingsState>,
(storyFn) => <SettingsState>
{storyFn()}
</SettingsState>,
],
};
export const _default = () => <GroupPage />;
export const _default = () =>
<GroupPage />;
export const withGroup = () =>
<GroupPage
group={{
_id: 'General',
i18nLabel: 'General',
}}
_id='General'
i18nLabel='General'
/>;
export const skeleton = () => <GroupPage.Skeleton />;
export const skeleton = () =>
<GroupPage.Skeleton />;

@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import { AssetsGroupPage } from './groups/AssetsGroupPage';
import { OAuthGroupPage } from './groups/OAuthGroupPage';
@ -9,15 +9,17 @@ import { useGroup } from './SettingsState';
export function GroupSelector({ groupId }) {
const group = useGroup(groupId);
const children = useMemo(() => {
if (!group) {
return <GroupPage.Skeleton />;
}
return (group._id === 'Assets' && <AssetsGroupPage group={group} />)
|| (group._id === 'OAuth' && <OAuthGroupPage group={group} />)
|| <GenericGroupPage group={group} />;
}, [group]);
if (groupId === 'Assets') {
return <AssetsGroupPage {...group} />;
}
if (groupId === 'OAuth') {
return <OAuthGroupPage {...group} />;
}
return children;
return <GenericGroupPage {...group} />;
}

@ -1,13 +1,15 @@
import { Paragraph } from '@rocket.chat/fuselage';
import React from 'react';
import { useTranslation } from '../../../contexts/TranslationContext';
import { Page } from '../../basic/Page';
export function NotAuthorizedPage() {
const t = useTranslation();
return <section className='page-container page-static page-settings'>
<div className='content'>
<p>{t('You_are_not_authorized_to_view_this_page')}</p>
</div>
</section>;
return <Page>
<Page.Content>
<Paragraph>{t('You_are_not_authorized_to_view_this_page')}</Paragraph>
</Page.Content>
</Page>;
}

@ -1,27 +1,20 @@
import { Button, Icon } from '@rocket.chat/fuselage';
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from '../../../contexts/TranslationContext';
// TODO: get rid of it
const StyledResetSettingButton = styled(Button)`
padding-block: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
`;
export function ResetSettingButton(props) {
const t = useTranslation();
return <StyledResetSettingButton
return <Button
aria-label={t('Reset')}
danger
ghost
small
title={t('Reset')}
style={{ padding: 0 }}
{...props}
>
<Icon name='undo' />
</StyledResetSettingButton>;
</Button>;
}

@ -3,10 +3,12 @@ import React from 'react';
import { useTranslation } from '../../../contexts/TranslationContext';
import { Setting } from './Setting';
import { useSection } from './SettingsState';
import { useSection, useSectionChangedState } from './SettingsState';
export function Section({ children, groupId, hasReset = true, help, sectionName, solo }) {
const section = useSection(groupId, sectionName);
const changed = useSectionChangedState(groupId, sectionName);
const t = useTranslation();
const handleResetSectionClick = () => {
@ -23,7 +25,7 @@ export function Section({ children, groupId, hasReset = true, help, sectionName,
</Paragraph>}
<FieldGroup>
{section.settings.map((settingId) => <Setting key={settingId} settingId={settingId} />)}
{section.settings.map((settingId) => <Setting key={settingId} settingId={settingId} sectionChanged={changed} />)}
{hasReset && section.canReset && <Button
children={t('Reset_section_settings')}
@ -38,7 +40,7 @@ export function Section({ children, groupId, hasReset = true, help, sectionName,
</Accordion.Item>;
}
Section.Skeleton = function SectionSkeleton() {
export function SectionSkeleton() {
return <Accordion.Item
noncollapsible
title={<Skeleton />}
@ -51,4 +53,6 @@ Section.Skeleton = function SectionSkeleton() {
{Array.from({ length: 10 }).map((_, i) => <Setting.Skeleton key={i} />)}
</FieldGroup>
</Accordion.Item>;
};
}
Section.Skeleton = SectionSkeleton;

@ -1,6 +1,5 @@
import { Callout, Field, InputBox, Label, Margins, Skeleton } from '@rocket.chat/fuselage';
import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks';
import React, { useEffect, useMemo, useState } from 'react';
import { Callout, Field, Flex, InputBox, Margins, Skeleton } from '@rocket.chat/fuselage';
import React, { memo, useEffect, useMemo, useState, useCallback } from 'react';
import { MarkdownText } from '../../basic/MarkdownText';
import { RawText } from '../../basic/RawText';
@ -21,7 +20,17 @@ import { AssetSettingInput } from './inputs/AssetSettingInput';
import { RoomPickSettingInput } from './inputs/RoomPickSettingInput';
import { useSetting } from './SettingsState';
const getInputComponentByType = (type) => ({
export const MemoizedSetting = memo(function MemoizedSetting({
type,
hint,
callout,
value,
editor,
onChangeValue = () => {},
onChangeEditor = () => {},
...inputProps
}) {
const InputComponent = {
boolean: BooleanSettingInput,
string: StringSettingInput,
relativeUrl: RelativeUrlSettingInput,
@ -35,61 +44,60 @@ const getInputComponentByType = (type) => ({
action: ActionSettingInput,
asset: AssetSettingInput,
roomPick: RoomPickSettingInput,
})[type] || GenericSettingInput;
const MemoizedSetting = React.memo(function MemoizedSetting({
type,
hint,
callout,
...inputProps
}) {
const InputComponent = getInputComponentByType(type);
}[type] || GenericSettingInput;
return <Field>
<InputComponent {...inputProps} />
<InputComponent
value={value}
editor={editor}
onChangeValue={onChangeValue}
onChangeEditor={onChangeEditor}
{...inputProps}
/>
{hint && <Field.Hint>{hint}</Field.Hint>}
<Margins block='16'>
{callout && <Callout type='warning' children={callout} />}
</Margins>
{callout && <Margins block='x16'>
<Callout type='warning'>{callout}</Callout>
</Margins>}
</Field>;
});
export function Setting({ settingId }) {
export function Setting({ settingId, sectionChanged }) {
const {
value: contextValue,
editor: contextEditor,
update,
reset,
...setting
} = useSetting(settingId);
const t = useTranslation();
const [value, setValue] = useState(contextValue);
const setContextValue = useDebouncedCallback((value) => setting.update({ value }), 70, []);
const [editor, setEditor] = useState(contextEditor);
useEffect(() => {
setValue(contextValue);
}, [contextValue]);
const [editor, setEditor] = useState(contextEditor);
const setContextEditor = useDebouncedCallback((editor) => setting.update({ editor }), 70, []);
useEffect(() => {
setEditor(contextEditor);
}, [contextEditor]);
const onChangeValue = (value) => {
const onChangeValue = useCallback((value) => {
setValue(value);
setContextValue(value);
};
update({ value });
}, [update]);
const onChangeEditor = (editor) => {
const onChangeEditor = useCallback((editor) => {
setEditor(editor);
setContextEditor(editor);
};
update({ editor });
}, [update]);
const onResetButtonClick = () => {
setting.reset();
};
const onResetButtonClick = useCallback(() => {
setValue(contextValue);
setEditor(contextEditor);
reset();
}, [contextValue, contextEditor, reset]);
const {
_id,
@ -113,6 +121,7 @@ export function Setting({ settingId }) {
label={label}
hint={hint}
callout={callout}
sectionChanged={sectionChanged}
{...setting}
value={value}
editor={editor}
@ -123,11 +132,18 @@ export function Setting({ settingId }) {
/>;
}
Setting.Skeleton = function SettingSkeleton() {
export function SettingSkeleton() {
return <Field>
<Label>
<Flex.Item align='stretch'>
<Field.Label>
<Skeleton width='25%' />
</Label>
</Field.Label>
</Flex.Item>
<Field.Row>
<InputBox.Skeleton />
</Field.Row>
</Field>;
};
}
Setting.Memoized = MemoizedSetting;
Setting.Skeleton = SettingSkeleton;

@ -0,0 +1,97 @@
import { FieldGroup } from '@rocket.chat/fuselage';
import React from 'react';
import { Setting } from './Setting';
import { SettingsState } from './SettingsState';
export default {
title: 'admin/settings/Setting',
component: Setting,
decorators: [
(storyFn) => <SettingsState>
<div className='rc-old'>
<div className='page-settings'>
{storyFn()}
</div>
</div>
</SettingsState>,
],
};
export const _default = () =>
<Setting.Memoized
_id='setting-id'
label='Label'
hint='Hint'
/>;
export const withCallout = () =>
<Setting.Memoized
_id='setting-id'
label='Label'
hint='Hint'
callout='Callout text'
/>;
export const types = () =>
<FieldGroup>
<Setting.Memoized
_id='setting-id-1'
label='Label'
type='action'
actionText='Action text'
/>
<Setting.Memoized
_id='setting-id-2'
label='Label'
type='asset'
/>
<Setting.Memoized
_id='setting-id-3'
label='Label'
type='boolean'
/>
<Setting.Memoized
_id='setting-id-4'
label='Label'
type='code'
/>
<Setting.Memoized
_id='setting-id-5'
label='Label'
type='font'
/>
<Setting.Memoized
_id='setting-id-6'
label='Label'
type='int'
/>
<Setting.Memoized
_id='setting-id-7'
label='Label'
type='language'
/>
<Setting.Memoized
_id='setting-id-8'
label='Label'
type='password'
/>
<Setting.Memoized
_id='setting-id-9'
label='Label'
type='relativeUrl'
/>
<Setting.Memoized
_id='setting-id-10'
label='Label'
type='select'
/>
<Setting.Memoized
_id='setting-id-11'
label='Label'
type='string'
/>
</FieldGroup>;
export const skeleton = () =>
<Setting.Skeleton />;

@ -1,3 +1,4 @@
import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks';
import { Mongo } from 'meteor/mongo';
import { Tracker } from 'meteor/tracker';
import React, { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
@ -313,8 +314,7 @@ export const useSection = (groupId, sectionName) => {
const filterSettings = (settings) =>
settings.filter(({ group, section }) => group === groupId && ((!sectionName && !section) || (sectionName === section)));
const changed = useSelector((state) => filterSettings(state.settings).some(({ changed }) => changed));
const canReset = useSelector((state) => filterSettings(state.settings).some(({ value, packageValue }) => value !== packageValue));
const canReset = useSelector((state) => filterSettings(state.settings).some(({ value, packageValue }) => JSON.stringify(value) !== JSON.stringify(packageValue)));
const settingsIds = useSelector((state) => filterSettings(state.settings).map(({ _id }) => _id), (a, b) => a.length === b.length && a.join() === b.join());
const { stateRef, hydrate, isDisabled } = useContext(SettingsContext);
@ -339,38 +339,28 @@ export const useSection = (groupId, sectionName) => {
return {
name: sectionName,
changed,
canReset,
settings: settingsIds,
reset,
};
};
export const useSetting = (_id) => {
const { stateRef, hydrate, isDisabled } = useContext(SettingsContext);
const selectSetting = (settings) => settings.find((setting) => setting._id === _id);
const setting = useSelector((state) => selectSetting(state.settings));
const sectionChanged = useSelector((state) => state.settings.some(({ section, changed }) => section === setting.section && changed));
const disabled = useReactiveValue(() => isDisabled(setting), [setting.blocked, setting.enableQuery]);
const update = useEventCallback((selectSetting, { current: state }, hydrate, data) => {
const setting = { ...selectSetting(state.settings), ...data };
const persistedSetting = selectSetting(state.persistedSettings);
export const useSettingActions = (persistedSetting) => {
const { hydrate } = useContext(SettingsContext);
const update = useDebouncedCallback(({ value = persistedSetting.value, editor = persistedSetting.editor }) => {
const changes = [{
_id: setting._id,
value: setting.value,
editor: setting.editor,
changed: (setting.value !== persistedSetting.value) || (setting.editor !== persistedSetting.editor),
_id: persistedSetting._id,
value,
editor,
changed: (value !== persistedSetting.value) || (editor !== persistedSetting.editor),
}];
hydrate(changes);
}, selectSetting, stateRef, hydrate);
}, 70, [hydrate, persistedSetting]);
const reset = useEventCallback((selectSetting, { current: state }, hydrate) => {
const { _id, value, packageValue, editor } = selectSetting(state.persistedSettings);
const reset = useDebouncedCallback(() => {
const { _id, value, packageValue, editor } = persistedSetting;
const changes = [{
_id,
@ -380,11 +370,32 @@ export const useSetting = (_id) => {
}];
hydrate(changes);
}, selectSetting, stateRef, hydrate);
}, 70, [hydrate, persistedSetting]);
return { update, reset };
};
export const useSettingDisabledState = ({ blocked, enableQuery }) => {
const { isDisabled } = useContext(SettingsContext);
return useReactiveValue(() => isDisabled({ blocked, enableQuery }), [blocked, enableQuery]);
};
export const useSectionChangedState = (groupId, sectionName) =>
useSelector((state) =>
state.settings.some(({ group, section, changed }) =>
group === groupId && ((!sectionName && !section) || (sectionName === section)) && changed));
export const useSetting = (_id) => {
const selectSetting = (settings) => settings.find((setting) => setting._id === _id);
const setting = useSelector((state) => selectSetting(state.settings));
const persistedSetting = useSelector((state) => selectSetting(state.persistedSettings));
const { update, reset } = useSettingActions(persistedSetting);
const disabled = useSettingDisabledState(persistedSetting);
return {
...setting,
sectionChanged,
disabled,
update,
reset,

@ -7,8 +7,8 @@ import { useTranslation } from '../../../../contexts/TranslationContext';
import { GroupPage } from '../GroupPage';
import { Section } from '../Section';
export function AssetsGroupPage({ group }) {
const solo = group.sections.length === 1;
export function AssetsGroupPage({ _id, sections, ...group }) {
const solo = sections.length === 1;
const t = useTranslation();
const refreshClients = useMethod('refreshClients');
@ -23,12 +23,12 @@ export function AssetsGroupPage({ group }) {
}
};
return <GroupPage group={group} headerButtons={<>
return <GroupPage _id={_id} {...group} headerButtons={<>
<Button onClick={handleApplyAndRefreshAllClientsButtonClick}>{t('Apply_and_refresh_all_clients')}</Button>
</>}>
{group.sections.map((sectionName) => <Section
{sections.map((sectionName) => <Section
key={sectionName}
groupId={group._id}
groupId={_id}
hasReset={false}
sectionName={sectionName}
solo={solo}

@ -3,13 +3,13 @@ import React from 'react';
import { GroupPage } from '../GroupPage';
import { Section } from '../Section';
export function GenericGroupPage({ group }) {
const solo = group.sections.length === 1;
export function GenericGroupPage({ _id, sections, ...group }) {
const solo = sections.length === 1;
return <GroupPage group={group}>
{group.sections.map((sectionName) => <Section
return <GroupPage _id={_id} {...group}>
{sections.map((sectionName) => <Section
key={sectionName}
groupId={group._id}
groupId={_id}
sectionName={sectionName}
solo={solo}
/>)}

@ -10,8 +10,8 @@ import { useModal } from '../../../../hooks/useModal';
import { GroupPage } from '../GroupPage';
import { Section } from '../Section';
export function OAuthGroupPage({ group }) {
const solo = group.sections.length === 1;
export function OAuthGroupPage({ _id, sections, ...group }) {
const solo = sections.length === 1;
const t = useTranslation();
const sectionIsCustomOAuth = (sectionName) => sectionName && /^Custom OAuth:\s.+/.test(sectionName);
@ -63,11 +63,11 @@ export function OAuthGroupPage({ group }) {
});
};
return <GroupPage group={group} headerButtons={<>
return <GroupPage _id={_id} {...group} headerButtons={<>
<Button onClick={handleRefreshOAuthServicesButtonClick}>{t('Refresh_oauth_services')}</Button>
<Button onClick={handleAddCustomOAuthButtonClick}>{t('Add_custom_oauth')}</Button>
</>}>
{group.sections.map((sectionName) => {
{sections.map((sectionName) => {
if (sectionIsCustomOAuth(sectionName)) {
const id = s.strRight(sectionName, 'Custom OAuth: ').toLowerCase();
@ -91,7 +91,7 @@ export function OAuthGroupPage({ group }) {
return <Section
key={sectionName}
groupId={group._id}
groupId={_id}
help={<RawText>{t('Custom_oauth_helper', callbackURL(sectionName))}</RawText>}
sectionName={sectionName}
solo={solo}
@ -102,7 +102,7 @@ export function OAuthGroupPage({ group }) {
</Section>;
}
return <Section key={sectionName} groupId={group._id} sectionName={sectionName} solo={solo} />;
return <Section key={sectionName} groupId={_id} sectionName={sectionName} solo={solo} />;
})}
</GroupPage>;
}

@ -28,6 +28,7 @@ export function ActionSettingInput({
};
return <>
<Field.Row>
<Button
data-qa-setting-id={_id}
children={t(actionText)}
@ -35,6 +36,7 @@ export function ActionSettingInput({
primary
onClick={handleClick}
/>
</Field.Row>
{sectionChanged && <Field.Hint>{t('Save_to_enable_this_action')}</Field.Hint>}
</>;
}

@ -1,4 +1,4 @@
import { Button, Icon, Label } from '@rocket.chat/fuselage';
import { Button, Field, Icon } from '@rocket.chat/fuselage';
import { Random } from 'meteor/random';
import React from 'react';
@ -56,7 +56,8 @@ export function AssetSettingInput({
};
return <>
<Label htmlFor={_id} text={label} title={_id} />
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
<Field.Row>
<div className='settings-file-preview'>
{value.url
? <div className='preview' style={{ backgroundImage: `url(${ value.url }?_dc=${ Random.id() })` }} />
@ -76,5 +77,6 @@ export function AssetSettingInput({
</div>}
</div>
</div>
</Field.Row>
</>;
}

@ -7,7 +7,11 @@ export default {
title: 'admin/settings/inputs/AssetSettingInput',
component: AssetSettingInput,
decorators: [
(storyFn) => <Field>{storyFn()}</Field>,
(storyFn) => <div className='rc-old'>
<div className='page-settings'>
<Field>{storyFn()}</Field>
</div>
</div>,
],
};

@ -1,8 +1,4 @@
import {
Field,
Label,
ToggleSwitch,
} from '@rocket.chat/fuselage';
import { Field, ToggleSwitch } from '@rocket.chat/fuselage';
import React from 'react';
import { ResetSettingButton } from '../ResetSettingButton';
@ -12,7 +8,6 @@ export function BooleanSettingInput({
label,
disabled,
readonly,
autocomplete,
value,
hasResetButton,
onChangeValue,
@ -24,17 +19,16 @@ export function BooleanSettingInput({
};
return <Field.Row>
<Label position='end' text={label} title={_id}>
<ToggleSwitch
data-qa-setting-id={_id}
id={_id}
value='true'
checked={value === true}
disabled={disabled}
readOnly={readonly}
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
/>
</Label>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>;
}

@ -1,4 +1,4 @@
import { Button, Field, Label } from '@rocket.chat/fuselage';
import { Box, Button, Field, Flex } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import React, { useEffect, useRef, useState } from 'react';
@ -113,10 +113,12 @@ export function CodeSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<div
className={[
'code-mirror-box',

@ -8,7 +8,9 @@ export default {
title: 'admin/settings/inputs/CodeSettingInput',
component: CodeSettingInput,
decorators: [
(storyFn) => <Field>{storyFn()}</Field>,
(storyFn) => <div className='rc-old'>
<Field>{storyFn()}</Field>
</div>,
],
};

@ -1,7 +1,9 @@
import {
Box,
Field,
Flex,
InputBox,
Label,
Margins,
SelectInput,
TextInput,
} from '@rocket.chat/fuselage';
@ -37,20 +39,16 @@ export function ColorSettingInput({
};
return <>
<div
style={{
display: 'flex',
flexFlow: 'row nowrap',
margin: '0 -0.5rem',
}}
>
<Field
style={{
flex: '2 2 0',
margin: '0 0.5rem',
}}
>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Box>
</Flex.Container>
<Margins inline='x4'>
<Field.Row>
<Margins inline='x4'>
<Flex.Item grow={2}>
{editor === 'color' && <InputBox
data-qa-setting-id={_id}
type='color'
@ -61,9 +59,6 @@ export function ColorSettingInput({
readOnly={readonly}
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
style={{
width: '100%',
}}
/>}
{editor === 'expression' && <TextInput
data-qa-setting-id={_id}
@ -74,21 +69,8 @@ export function ColorSettingInput({
readOnly={readonly}
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
style={{
width: '100%',
}}
/>}
</Field>
<Field
style={{
flex: '1 1 0',
margin: '0 0.5rem',
}}
>
<Field.Row>
<Label htmlFor={`${ _id }_editor`} text={t('Type')} title={_id} />
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Flex.Item>
<SelectInput
data-qa-setting-id={`${ _id }_editor`}
type='color'
@ -103,10 +85,9 @@ export function ColorSettingInput({
<SelectInput.Option key={allowedType} value={allowedType}>{t(allowedType)}</SelectInput.Option>,
)}
</SelectInput>
</Field>
</div>
<Field.Hint>
Variable name: {_id.replace(/theme-color-/, '@')}
</Field.Hint>
</Margins>
</Field.Row>
</Margins>
<Field.Hint>Variable name: {_id.replace(/theme-color-/, '@')}</Field.Hint>
</>;
}

@ -1,8 +1,4 @@
import {
Field,
Label,
TextInput,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, TextInput } from '@rocket.chat/fuselage';
import React from 'react';
import { ResetSettingButton } from '../ResetSettingButton';
@ -24,10 +20,13 @@ export function FontSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
<TextInput
data-qa-setting-id={_id}
id={_id}
@ -38,5 +37,6 @@ export function FontSettingInput({
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
/>
</Field.Row>
</>;
}

@ -1,8 +1,4 @@
import {
Field,
Label,
TextInput,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, TextInput } from '@rocket.chat/fuselage';
import React from 'react';
import { ResetSettingButton } from '../ResetSettingButton';
@ -24,10 +20,13 @@ export function GenericSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
<TextInput
data-qa-setting-id={_id}
id={_id}
@ -38,5 +37,6 @@ export function GenericSettingInput({
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
/>
</Field.Row>
</>;
}

@ -1,8 +1,4 @@
import {
Field,
Label,
InputBox,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, InputBox } from '@rocket.chat/fuselage';
import React from 'react';
import { ResetSettingButton } from '../ResetSettingButton';
@ -24,10 +20,13 @@ export function IntSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
<InputBox
data-qa-setting-id={_id}
id={_id}
@ -39,5 +38,6 @@ export function IntSettingInput({
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
/>
</Field.Row>
</>;
}

@ -1,8 +1,4 @@
import {
Field,
Label,
SelectInput,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, SelectInput } from '@rocket.chat/fuselage';
import React from 'react';
import { useLanguages } from '../../../../contexts/TranslationContext';
@ -27,10 +23,13 @@ export function LanguageSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
<SelectInput
data-qa-setting-id={_id}
id={_id}
@ -45,5 +44,6 @@ export function LanguageSettingInput({
<SelectInput.Option key={key} value={key} dir='auto'>{name}</SelectInput.Option>,
)}
</SelectInput>
</Field.Row>
</>;
}

@ -1,8 +1,4 @@
import {
Field,
Label,
PasswordInput,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, PasswordInput } from '@rocket.chat/fuselage';
import React from 'react';
import { ResetSettingButton } from '../ResetSettingButton';
@ -24,10 +20,13 @@ export function PasswordSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
<PasswordInput
data-qa-setting-id={_id}
id={_id}
@ -38,5 +37,6 @@ export function PasswordSettingInput({
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
/>
</Field.Row>
</>;
}

@ -1,8 +1,4 @@
import {
Field,
Label,
UrlInput,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, UrlInput } from '@rocket.chat/fuselage';
import React from 'react';
import { useAbsoluteUrl } from '../../../../contexts/ServerContext';
@ -27,10 +23,12 @@ export function RelativeUrlSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<UrlInput
data-qa-setting-id={_id}
id={_id}

@ -1,4 +1,4 @@
import { Field, Icon, Label } from '@rocket.chat/fuselage';
import { Box, Field, Flex, Icon } from '@rocket.chat/fuselage';
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import React, { useRef, useEffect, useLayoutEffect } from 'react';
@ -72,10 +72,12 @@ export function RoomPickSettingInput({
}, [valueRef]);
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<div style={{ position: 'relative' }} ref={wrapperRef} />
<ul className='selected-rooms'>
{value.map(({ _id, name }) =>

@ -1,6 +1,7 @@
import {
Box,
Field,
Label,
Flex,
SelectInput,
} from '@rocket.chat/fuselage';
import React from 'react';
@ -28,10 +29,13 @@ export function SelectSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
<SelectInput
data-qa-setting-id={_id}
id={_id}
@ -46,5 +50,6 @@ export function SelectSettingInput({
<SelectInput.Option key={key} value={key}>{t(i18nLabel)}</SelectInput.Option>,
)}
</SelectInput>
</Field.Row>
</>;
}

@ -1,9 +1,4 @@
import {
Field,
Label,
TextAreaInput,
TextInput,
} from '@rocket.chat/fuselage';
import { Box, Field, Flex, TextAreaInput, TextInput } from '@rocket.chat/fuselage';
import React from 'react';
import { ResetSettingButton } from '../ResetSettingButton';
@ -26,10 +21,13 @@ export function StringSettingInput({
};
return <>
<Field.Row>
<Label htmlFor={_id} text={label} title={_id} />
<Flex.Container>
<Box>
<Field.Label htmlFor={_id} title={_id}>{label}</Field.Label>
{hasResetButton && <ResetSettingButton data-qa-reset-setting-id={_id} onClick={onResetButtonClick} />}
</Field.Row>
</Box>
</Flex.Container>
<Field.Row>
{multiline
? <TextAreaInput
data-qa-setting-id={_id}
@ -52,5 +50,6 @@ export function StringSettingInput({
autoComplete={autocomplete === false ? 'off' : undefined}
onChange={handleChange}
/> }
</Field.Row>
</>;
}

@ -7,7 +7,7 @@ import { SidebarContext } from '../../contexts/SidebarContext';
import { SessionContext } from '../../contexts/SessionContext';
export default {
title: 'header/BurgerMenuButton',
title: 'basic/BurgerMenuButton',
component: BurgerMenuButton,
decorators: [(fn) => <div style={{ margin: '1rem' }}>{fn()}</div>],
parameters: {

@ -0,0 +1,35 @@
import { Box, Flex, Margins, Scrollable } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';
import { BurgerMenuButton } from './BurgerMenuButton';
export function Page(props) {
return <Flex.Container direction='column'>
<Box is='section' style={useMemo(() => ({ height: '100vh' }), [])} {...props} />
</Flex.Container>;
}
export function PageHeader({ children, title, ...props }) {
return <Margins all='x16'>
<Flex.Container wrap='nowrap' alignItems='center'>
<Box style={{ minHeight: '2.75rem' }} {...props}>
<Margins inlineEnd='x8'>
<BurgerMenuButton />
</Margins>
<Flex.Item grow='1'>
<Box is='h1' textStyle='h1' textColor='default'>{title}</Box>
</Flex.Item>
{children}
</Box>
</Flex.Container>
</Margins>;
}
export function PageContent(props) {
return <Scrollable>
<Box style={useMemo(() => ({ padding: '1rem' }), [])} {...props} />
</Scrollable>;
}
Page.Header = PageHeader;
Page.Content = PageContent;

@ -0,0 +1,36 @@
import { Button, ButtonGroup, Margins, Tile } from '@rocket.chat/fuselage';
import React from 'react';
import { Page } from './Page';
export default {
title: 'basic/Page',
component: Page,
decorators: [
(fn) => <div className='rc-old'>{fn()}</div>,
],
};
export const _default = () =>
<Page>
<Page.Header title='Header' />
<Page.Content>
<Margins block='x16'>
{Array.from({ length: 60 }, (_, i) => <Tile key={i} children='Content slice' />)}
</Margins>
</Page.Content>
</Page>;
export const withButtonsAtTheHeader = () =>
<Page>
<Page.Header title='Header'>
<ButtonGroup>
<Button primary type='button'>Hooray!</Button>
</ButtonGroup>
</Page.Header>
<Page.Content>
<Margins block='x16'>
{Array.from({ length: 60 }, (_, i) => <Tile key={i} children='Content slice' />)}
</Margins>
</Page.Content>
</Page>;

@ -1,35 +0,0 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
import { BurgerMenuButton } from './BurgerMenuButton';
export function Header({
children,
hideHelp,
rawSectionName,
sectionName,
}) {
const t = useTranslation();
return <header className='rc-header'>
<div className='rc-header__wrap'>
<div className='rc-header__block rc-header--burger'>
<BurgerMenuButton />
</div>
<span className='rc-header__block'>
<Box is='h1' textStyle='h1' textColor='default'>
{rawSectionName || t(sectionName)}
</Box>
</span>
{children}
{!hideHelp && <div className='rc-header__section-help' />}
</div>
</header>;
}
Header.ActionBlock = (props) => <div className='rc-header__block rc-header__block-action' {...props} />;
Header.ButtonSection = (props) => <div className='rc-header__section-button' {...props} />;

@ -1,32 +0,0 @@
import { boolean, text } from '@storybook/addon-knobs/react';
import React from 'react';
import { Button } from '../basic/Button';
import { Header } from './Header';
export default {
title: 'header/Header',
component: Header,
};
export const _default = () =>
<Header
hideHelp={boolean('hideHelp')}
rawSectionName={text('rawSectionName')}
sectionName={text('sectionName')}
/>;
export const withRawSectionName = () =>
<Header rawSectionName='Welcome to Rocket.Chat' />;
export const withSectionName = () =>
<Header sectionName='Accounts_Enrollment_Email_Subject_Default' />;
export const withButton = () =>
<Header rawSectionName='Welcome to Rocket.Chat' hideHelp>
<Header.ActionBlock>
<Button primary type='button'>
Hooray!
</Button>
</Header.ActionBlock>
</Header>;

@ -27,7 +27,7 @@ export function PageNotFound() {
<Box is='section' componentClassName='PageNotFound'>
<Flex.Item>
<Box>
<Margins all='12'>
<Margins all='x12'>
<Box componentClassName='PageNotFound__404' textColor='alternative'>404</Box>
<Box textStyle='h1' textColor='alternative'>
@ -39,7 +39,7 @@ export function PageNotFound() {
</Box>
</Margins>
<Margins all='32'>
<Margins all='x32'>
<ButtonGroup align='center'>
<Button type='button' primary onClick={handleGoToPreviousPageClick}>{t('Return_to_previous_page')}</Button>
<Button type='button' primary onClick={handleGoHomeClick}>{t('Return_to_home')}</Button>

@ -45,7 +45,7 @@ export function SetupWizardPage({ currentStep = 1 }) {
/>
<Box className='SetupWizard__wrapper'>
<Scrollable>
<Margins all='16'>
<Margins all='x16'>
<Tile is='section' className='SetupWizard__steps'>
<AdminUserInformationStep step={1} title={t('Admin_Info')} active={currentStep === 1} />
<SettingsBasedStep step={2} title={t('Organization_Info')} active={currentStep === 2} />

@ -21,7 +21,7 @@ export function SideBar({
<Scrollable>
<Box className='SetupWizard__SideBar-content'>
<Headline level={2}>{t('Setup_Wizard')}</Headline>
<Margins blockEnd='16'>
<Margins blockEnd='x16'>
<Box is='p' textColor='hint' textStyle='p1'>{t('Setup_Wizard_Info')}</Box>
</Margins>

@ -3,7 +3,6 @@ import {
Field,
FieldGroup,
Icon,
Label,
Margins,
PasswordInput,
TextInput,
@ -125,10 +124,11 @@ export function AdminUserInformationStep({ step, title, active }) {
return <Step active={active} working={commiting} onSubmit={handleSubmit}>
<StepHeader number={step} title={title} />
<Margins blockEnd='32'>
<Margins blockEnd='x32'>
<FieldGroup>
<Field>
<Label htmlFor={nameInputId} required text={t('Name')} />
<Field.Label htmlFor={nameInputId} required>{t('Name')}</Field.Label>
<Field.Row>
<TextInput
ref={autoFocusRef}
id={nameInputId}
@ -138,9 +138,11 @@ export function AdminUserInformationStep({ step, title, active }) {
onChange={({ currentTarget: { value } }) => setName(value)}
error={!isNameValid}
/>
</Field.Row>
</Field>
<Field>
<Label htmlFor={usernameInputId} required text={t('Username')} />
<Field.Label htmlFor={usernameInputId} required>{t('Username')}</Field.Label>
<Field.Row>
<TextInput
id={usernameInputId}
addon={<Icon name='at' size='20' />}
@ -149,10 +151,12 @@ export function AdminUserInformationStep({ step, title, active }) {
onChange={({ currentTarget: { value } }) => setUsername(value)}
error={!isUsernameValid}
/>
</Field.Row>
{!isUsernameValid && <Field.Error>{t('Invalid_username')}</Field.Error>}
</Field>
<Field>
<Label htmlFor={emailInputId} required text={t('Organization_Email')} />
<Field.Label htmlFor={emailInputId} required>{t('Organization_Email')}</Field.Label>
<Field.Row>
<EmailInput
id={emailInputId}
addon={<Icon name='mail' size='20' />}
@ -161,10 +165,12 @@ export function AdminUserInformationStep({ step, title, active }) {
onChange={({ currentTarget: { value } }) => setEmail(value)}
error={!isEmailValid}
/>
</Field.Row>
{!isEmailValid && <Field.Error>{t('Invalid_email')}</Field.Error>}
</Field>
<Field>
<Label htmlFor={passwordInputId} required text={t('Password')} />
<Field.Label htmlFor={passwordInputId} required>{t('Password')}</Field.Label>
<Field.Row>
<PasswordInput
id={passwordInputId}
addon={<Icon name='key' size='20' />}
@ -173,6 +179,7 @@ export function AdminUserInformationStep({ step, title, active }) {
onChange={({ currentTarget: { value } }) => setPassword(value)}
error={!isPasswordValid}
/>
</Field.Row>
</Field>
</FieldGroup>
</Margins>

@ -15,13 +15,13 @@ export function FinalStep() {
};
return <Box is='section' className='SetupWizard__FinalStep'>
<Tile is='main' padding='44'>
<Margins all='32'>
<Tile is='main' padding='x40'>
<Margins all='x32'>
<Box>
<Box is='span' textColor='hint' className='SetupWizard__FinalStep-runningHead'>
{t('Launched_successfully')}
</Box>
<Margins blockEnd='32'>
<Margins blockEnd='x32'>
<Box is='h1' textColor='default' className='SetupWizard__FinalStep-title'>{t('Your_workspace_is_ready')}</Box>
</Margins>
<Box textColor='default' textStyle='micro' className='SetupWizard__FinalStep-linkLabel'>{t('Your_server_link')}</Box>

@ -1,11 +1,11 @@
import {
Box,
CheckBox,
Label,
Field,
Margins,
RadioButton,
} from '@rocket.chat/fuselage';
import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
import { useMergedRefs, useUniqueId } from '@rocket.chat/fuselage-hooks';
import React, { useRef, useState } from 'react';
import { useMethod } from '../../../contexts/ServerContext';
@ -23,6 +23,7 @@ import './RegisterServerStep.css';
const Option = React.forwardRef(({ children, label, selected, disabled, ...props }, ref) => {
const innerRef = useRef();
const mergedRef = useMergedRefs(ref, innerRef);
const id = useUniqueId();
return <span
className={[
@ -34,9 +35,12 @@ const Option = React.forwardRef(({ children, label, selected, disabled, ...props
innerRef.current.click();
}}
>
<Label text={label} position='end'>
<RadioButton ref={mergedRef} checked={selected} disabled={disabled} {...props} />
</Label>
<Field>
<Field.Row>
<RadioButton ref={mergedRef} id={id} checked={selected} disabled={disabled} {...props} />
<Field.Label htmlFor={id}>{label}</Field.Label>
</Field.Row>
</Field>
{children}
</span>;
});
@ -117,10 +121,13 @@ export function RegisterServerStep({ step, title, active }) {
const autoFocusRef = useFocus(active);
const agreeTermsAndPrivacyId = useUniqueId();
const optInMarketingEmailsId = useUniqueId();
return <Step active={active} working={commiting} onSubmit={handleSubmit}>
<StepHeader number={step} title={title} />
<Margins blockEnd='32'>
<Margins blockEnd='x32'>
<Box>
<p className='SetupWizard__RegisterServerStep-text'>{t('Register_Server_Info')}</p>
@ -143,8 +150,10 @@ export function RegisterServerStep({ step, title, active }) {
<Item icon='check'>{t('Register_Server_Registered_OAuth')}</Item>
<Item icon='check'>{t('Register_Server_Registered_Marketplace')}</Item>
</Items>
<Label text={t('Register_Server_Opt_In')} position='end' className='SetupWizard__RegisterServerStep__optIn'>
<Field>
<Field.Row>
<CheckBox
id={optInMarketingEmailsId}
name='optInMarketingEmails'
value='true'
disabled={!registerServer}
@ -153,7 +162,12 @@ export function RegisterServerStep({ step, title, active }) {
setOptInMarketingEmails(checked);
}}
/>
</Label>
<Field.Label htmlFor={optInMarketingEmailsId}>{t('Register_Server_Opt_In')}</Field.Label>
</Field.Row>
</Field>
<Field.Label text={t('Register_Server_Opt_In')} position='end' className='SetupWizard__RegisterServerStep__optIn'>
</Field.Label>
</Option>
<Option
data-qa='register-server-standalone'
@ -175,8 +189,11 @@ export function RegisterServerStep({ step, title, active }) {
</Items>
</Option>
<Label text={<>{t('Register_Server_Registered_I_Agree')} <a href='https://rocket.chat/terms'>{t('Terms')}</a> & <a href='https://rocket.chat/privacy'>{t('Privacy_Policy')}</a></>} position='end' className='SetupWizard__RegisterServerStep__PrivacyTerms'>
<Margins all='x16'>
<Field>
<Field.Row>
<CheckBox
id={agreeTermsAndPrivacyId}
name='agreeTermsAndPrivacy'
data-qa='agree-terms-and-privacy'
disabled={!registerServer}
@ -185,7 +202,12 @@ export function RegisterServerStep({ step, title, active }) {
setAgreeTermsAndPrivacy(checked);
}}
/>
</Label>
<Field.Label htmlFor={agreeTermsAndPrivacyId}>
{t('Register_Server_Registered_I_Agree')} <a href='https://rocket.chat/terms'>{t('Terms')}</a> & <a href='https://rocket.chat/privacy'>{t('Privacy_Policy')}</a>
</Field.Label>
</Field.Row>
</Field>
</Margins>
</div>
</Box>
</Margins>

@ -1,8 +1,8 @@
import {
Field,
FieldGroup,
Flex,
InputBox,
Label,
Margins,
SelectInput,
Skeleton,
@ -90,10 +90,14 @@ export function SettingsBasedStep({ step, title, active }) {
return <Step active={active} working={commiting} onSubmit={handleSubmit}>
<StepHeader number={step} title={title} />
<Margins blockEnd='32'>
<Margins blockEnd='x32'>
<FieldGroup>
{Array.from({ length: 5 }, (_, i) => <Field key={i}>
<Label text={<Skeleton />} />
<Flex.Item align='stretch'>
<Field.Label>
{<Skeleton width='50%' />}
</Field.Label>
</Flex.Item>
<InputBox.Skeleton />
</Field>)}
</FieldGroup>
@ -104,11 +108,12 @@ export function SettingsBasedStep({ step, title, active }) {
return <Step active={active} working={commiting} onSubmit={handleSubmit}>
<StepHeader number={step} title={title} />
<Margins blockEnd='32'>
<Margins blockEnd='x32'>
<FieldGroup>
{fields.map(({ _id, type, i18nLabel, value, values }, i) =>
<Field key={i}>
<Label htmlFor={_id} text={t(i18nLabel)} />
<Field.Label htmlFor={_id}>{t(i18nLabel)}</Field.Label>
<Field.Row>
{type === 'string' && <TextInput
type='text'
data-qa={_id}
@ -146,9 +151,10 @@ export function SettingsBasedStep({ step, title, active }) {
>
{Object.entries(languages)
.map(([key, { name }]) => ({ label: name, value: key }))
.sort((a, b) => a.key - b.key)
.map(({ label, value }) => <SelectInput.Option key={value} value={value}>{label}</SelectInput.Option>)}
.sort((a, b) => a.value - b.value)
.map(({ label, value }) => <SelectInput.Option key={value} value={value} dir='auto'>{label}</SelectInput.Option>)}
</SelectInput>}
</Field.Row>
</Field>,
)}
</FieldGroup>

16
package-lock.json generated

@ -2674,12 +2674,12 @@
}
},
"@rocket.chat/fuselage": {
"version": "0.2.0-dev.67",
"resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.2.0-dev.67.tgz",
"integrity": "sha512-3wxlbd3vOurF8fKAZfksF2cmvrMGGDbA99BP4vYYG87TmWcCwlY59MmEBKNtAavae00n+8/Z2XMPtk6CbB14Dw==",
"version": "0.2.0-alpha.19",
"resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.2.0-alpha.19.tgz",
"integrity": "sha512-E1B9IiorcJADK2kX8SA2Sta1cbojqEKAPlH3Pue5tfZuQUNE0q//CbP90IHnvyrmY4uvYZLEkv0Fhq5PqIRwNQ==",
"requires": {
"@rocket.chat/fuselage-tokens": "^0.2.0-alpha.15",
"@rocket.chat/icons": "^0.2.0-alpha.16"
"@rocket.chat/fuselage-tokens": "^0.2.0-alpha.19",
"@rocket.chat/icons": "^0.2.0-alpha.19"
}
},
"@rocket.chat/fuselage-hooks": {
@ -2688,9 +2688,9 @@
"integrity": "sha512-58SuGnso0EvZlmSMjwm6Mhuv27VYdGtPV4RfU7MKtPLzgfzW5VFxLdHBCfiHLIvMlu4hQKEKsfk544Eob5ztag=="
},
"@rocket.chat/fuselage-tokens": {
"version": "0.2.0-alpha.15",
"resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.2.0-alpha.15.tgz",
"integrity": "sha512-vMaBpfFCYJFe4oS1YhYhOJfTNMxwwkv2Rl0KDo2OuWp4kRyNhfQG2sEDvGTM/EcZsCh0//mcbJ6neufh3jA4iQ=="
"version": "0.2.0-alpha.19",
"resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.2.0-alpha.19.tgz",
"integrity": "sha512-N7T7c2Dqas9N7RZukUwYPuFsUt8GERk/4UeS9YR366sQXTNS0YpDtULvTZOEUP0LTmzuwMlYF237Vmh62XW9Kw=="
},
"@rocket.chat/icons": {
"version": "0.2.0-dev.49",

@ -120,7 +120,7 @@
"@google-cloud/storage": "^2.3.1",
"@google-cloud/vision": "^0.23.0",
"@rocket.chat/apps-engine": "^1.11.0",
"@rocket.chat/fuselage": "^0.2.0-dev.67",
"@rocket.chat/fuselage": "^0.2.0-alpha.19",
"@rocket.chat/fuselage-hooks": "^0.2.0-dev.50",
"@rocket.chat/icons": "^0.2.0-dev.49",
"@slack/client": "^4.8.0",

@ -555,6 +555,8 @@ describe('[Administration]', () => {
});
it('it should show the force SSL toggle', () => {
browser.pause(500);
admin.generalForceSSL.moveToObject();
admin.generalForceSSL.$('..').isVisible().should.be.true;
});
@ -565,7 +567,8 @@ describe('[Administration]', () => {
});
it('it should show the reset button', () => {
admin.generalForceSSLReset.waitForVisible(5000);
browser.pause(500);
admin.generalForceSSLReset.moveToObject();
admin.generalForceSSLReset.isVisible().should.be.true;
});
@ -574,6 +577,8 @@ describe('[Administration]', () => {
});
it('it should show google tag id field', () => {
admin.generalGoogleTagId.scroll();
admin.generalGoogleTagId.waitForVisible(5000);
admin.generalGoogleTagId.isVisible().should.be.true;
});
@ -582,6 +587,7 @@ describe('[Administration]', () => {
});
it('it should show the reset button', () => {
admin.generalGoogleTagIdReset.scroll();
admin.generalGoogleTagIdReset.waitForVisible(5000);
admin.generalGoogleTagIdReset.isVisible().should.be.true;
});
@ -591,6 +597,8 @@ describe('[Administration]', () => {
});
it.skip('it should show bugsnag key field', () => {
admin.generalGoogleTagIdReset.scroll();
admin.generalBugsnagKey.waitForVisible(5000);
admin.generalBugsnagKey.isVisible().should.be.true;
});
@ -599,6 +607,7 @@ describe('[Administration]', () => {
});
it.skip('it should show the reset button', () => {
admin.generalBugsnagKeyReset.scroll();
admin.generalBugsnagKeyReset.waitForVisible(5000);
admin.generalBugsnagKeyReset.isVisible().should.be.true;
});
@ -761,6 +770,7 @@ describe('[Administration]', () => {
it('it should show the mobile notifications select field', () => {
admin.accountsMobileNotifications.click();
admin.accountsMobileNotifications.isVisible().should.be.true;
admin.accountsMobileNotifications.click();
});
it('the mobile notifications field value should be all', () => {
admin.accountsMobileNotifications.getValue().should.equal('all');
@ -815,7 +825,7 @@ describe('[Administration]', () => {
});
it('it should show the hide usernames field', () => {
admin.accountsHideUsernames.$('..').scroll();
admin.accountsHideUsernames.scroll();
admin.accountsHideUsernames.$('..').isVisible().should.be.true;
});
it('the hide usernames field value should be false', () => {
@ -823,24 +833,28 @@ describe('[Administration]', () => {
});
it('it should show the hide roles field', () => {
admin.accountsHideRoles.$('..').scroll();
admin.accountsHideRoles.$('..').isVisible().should.be.true;
admin.accountsHideRoles.moveToObject();
admin.accountsHideRoles.waitForVisible(5000);
admin.accountsHideRoles.isVisible().should.be.true;
});
it('the hide roles field value should be false', () => {
admin.accountsHideRoles.isSelected().should.be.false;
});
it('it should show the hide right sidebar with click field', () => {
admin.accountsHideFlexTab.$('..').scroll();
admin.accountsHideFlexTab.$('..').isVisible().should.be.true;
admin.accountsHideFlexTab.moveToObject();
admin.accountsHideFlexTab.waitForVisible(5000);
admin.accountsHideFlexTab.isVisible().should.be.true;
});
it('the hide right sidebar with click field value should be false', () => {
admin.accountsHideFlexTab.isSelected().should.be.false;
});
it('it should show the hide avatars field', () => {
admin.accountsHideAvatars.$('..').scroll();
admin.accountsHideAvatars.$('..').isVisible().should.be.true;
admin.accountsHideAvatars.scroll();
admin.accountsHideAvatars.waitForVisible(5000);
admin.accountsHideAvatars.isVisible().should.be.true;
});
it('the hide avatars field value should be false', () => {
admin.accountsHideAvatars.isSelected().should.be.false;
@ -857,6 +871,7 @@ describe('[Administration]', () => {
it('it should show the messagebox view mode field', () => {
admin.accountsMessageViewMode.moveToObject();
admin.accountsMessageViewMode.waitForVisible(5000);
admin.accountsMessageViewMode.click();
admin.accountsMessageViewMode.isVisible().should.be.true;
});

Loading…
Cancel
Save