RocketChat/Rocket.Chat

View on GitHub
apps/meteor/client/views/account/accessibility/AccessibilityPage.tsx

Summary

Maintainability
F
3 days
Test Coverage
import { css } from '@rocket.chat/css-in-js';
import type { SelectOption } from '@rocket.chat/fuselage';
import {
    FieldDescription,
    Accordion,
    Box,
    Button,
    ButtonGroup,
    Field,
    FieldGroup,
    FieldHint,
    FieldLabel,
    FieldRow,
    RadioButton,
    Select,
    ToggleSwitch,
} from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { ExternalLink } from '@rocket.chat/ui-client';
import { useTranslation, useToastMessageDispatch, useEndpoint, useSetting } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
import React, { useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';

import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '../../../components/Page';
import { getDirtyFields } from '../../../lib/getDirtyFields';
import { fontSizes } from './fontSizes';
import type { AccessibilityPreferencesData } from './hooks/useAcessibilityPreferencesValues';
import { useAccessiblityPreferencesValues } from './hooks/useAcessibilityPreferencesValues';
import { useCreateFontStyleElement } from './hooks/useCreateFontStyleElement';
import { themeItems as themes } from './themeItems';

const AccessibilityPage = () => {
    const t = useTranslation();
    const dispatchToastMessage = useToastMessageDispatch();
    const preferencesValues = useAccessiblityPreferencesValues();

    const createFontStyleElement = useCreateFontStyleElement();
    const displayRolesEnabled = useSetting('UI_DisplayRoles');

    const timeFormatOptions = useMemo(
        (): SelectOption[] => [
            ['0', t('Default')],
            ['1', t('12_Hour')],
            ['2', t('24_Hour')],
        ],
        [t],
    );

    const pageFormId = useUniqueId();
    const fontSizeId = useUniqueId();
    const mentionsWithSymbolId = useUniqueId();
    const clockModeId = useUniqueId();
    const hideUsernamesId = useUniqueId();
    const hideRolesId = useUniqueId();
    const linkListId = useUniqueId();

    const {
        formState: { isDirty, dirtyFields, isSubmitting },
        handleSubmit,
        control,
        reset,
        watch,
    } = useForm({
        defaultValues: preferencesValues,
    });

    const currentData = watch();

    const setUserPreferencesEndpoint = useEndpoint('POST', '/v1/users.setPreferences');

    const setPreferencesAction = useMutation({
        mutationFn: setUserPreferencesEndpoint,
        onSuccess: () => dispatchToastMessage({ type: 'success', message: t('Preferences_saved') }),
        onError: (error) => dispatchToastMessage({ type: 'error', message: error }),
        onSettled: (_data, _error, { data: { fontSize } }) => {
            reset(currentData);
            dirtyFields.fontSize && fontSize && createFontStyleElement(fontSize);
        },
    });

    const handleSaveData = (formData: AccessibilityPreferencesData) => {
        const data = getDirtyFields(formData, dirtyFields);
        setPreferencesAction.mutateAsync({ data });
    };

    return (
        <Page>
            <PageHeader title={t('Accessibility_and_Appearance')} />
            <PageScrollableContentWithShadow>
                <Box is='form' id={pageFormId} onSubmit={handleSubmit(handleSaveData)} maxWidth='x600' w='full' alignSelf='center' mb={40} mi={36}>
                    <Box fontScale='p1' mbe={24}>
                        <Box pb={16} is='p'>
                            {t('Accessibility_activation')}
                        </Box>
                        <p id={linkListId}>{t('Learn_more_about_accessibility')}</p>
                        <ul aria-labelledby={linkListId}>
                            <li>
                                <ExternalLink to='https://go.rocket.chat/i/accessibility-statement'>{t('Accessibility_statement')}</ExternalLink>
                            </li>
                            <li>
                                <ExternalLink to='https://go.rocket.chat/i/glossary'>{t('Glossary_of_simplified_terms')}</ExternalLink>
                            </li>
                            <li>
                                <ExternalLink to='https://go.rocket.chat/i/accessibility-and-appearance'>
                                    {t('Accessibility_feature_documentation')}
                                </ExternalLink>
                            </li>
                        </ul>
                    </Box>
                    <Accordion>
                        <Accordion.Item defaultExpanded={true} title={t('Theme')}>
                            {themes.map(({ id, title, description }, index) => {
                                return (
                                    <Field key={id} pbe={themes.length - 1 ? undefined : 'x28'} pbs={index === 0 ? undefined : 'x28'}>
                                        <FieldRow>
                                            <FieldLabel display='flex' alignItems='center' htmlFor={id}>
                                                {t(title)}
                                            </FieldLabel>
                                            <Controller
                                                control={control}
                                                name='themeAppearence'
                                                render={({ field: { onChange, value, ref } }) => (
                                                    <RadioButton id={id} ref={ref} onChange={() => onChange(id)} checked={value === id} />
                                                )}
                                            />
                                        </FieldRow>
                                        <FieldHint mbs={12} style={{ whiteSpace: 'break-spaces' }}>
                                            {t(description)}
                                        </FieldHint>
                                    </Field>
                                );
                            })}
                        </Accordion.Item>
                        <Accordion.Item title={t('Adjustable_layout')}>
                            <FieldGroup>
                                <Field>
                                    <FieldLabel htmlFor={fontSizeId} mbe={12}>
                                        {t('Font_size')}
                                    </FieldLabel>
                                    <FieldRow>
                                        <Controller
                                            control={control}
                                            name='fontSize'
                                            render={({ field: { onChange, value } }) => (
                                                <Select id={fontSizeId} value={value} onChange={onChange} options={fontSizes} />
                                            )}
                                        />
                                    </FieldRow>
                                    <FieldDescription mb={12}>{t('Adjustable_font_size_description')}</FieldDescription>
                                </Field>
                                <Field>
                                    <FieldRow>
                                        <FieldLabel htmlFor={fontSizeId}>{t('Mentions_with_@_symbol')}</FieldLabel>
                                        <Controller
                                            control={control}
                                            name='mentionsWithSymbol'
                                            render={({ field: { onChange, value, ref } }) => (
                                                <ToggleSwitch id={mentionsWithSymbolId} ref={ref} checked={value} onChange={onChange} />
                                            )}
                                        />
                                    </FieldRow>
                                    <FieldDescription
                                        className={css`
                                            white-space: break-spaces;
                                        `}
                                        mb={12}
                                    >
                                        {t('Mentions_with_@_symbol_description')}
                                    </FieldDescription>
                                </Field>
                                <Field>
                                    <FieldLabel htmlFor={clockModeId}>{t('Message_TimeFormat')}</FieldLabel>
                                    <FieldRow>
                                        <Controller
                                            name='clockMode'
                                            control={control}
                                            render={({ field: { value, onChange } }) => (
                                                <Select id={clockModeId} value={`${value}`} onChange={onChange} options={timeFormatOptions} />
                                            )}
                                        />
                                    </FieldRow>
                                </Field>
                                <Field>
                                    <FieldRow>
                                        <FieldLabel htmlFor={hideUsernamesId}>{t('Show_usernames')}</FieldLabel>
                                        <Controller
                                            name='hideUsernames'
                                            control={control}
                                            render={({ field: { value, onChange, ref } }) => (
                                                <ToggleSwitch
                                                    id={hideUsernamesId}
                                                    ref={ref}
                                                    checked={!value}
                                                    onChange={(e) => onChange(!(e.target as HTMLInputElement).checked)}
                                                />
                                            )}
                                        />
                                    </FieldRow>
                                    <FieldDescription>{t('Show_or_hide_the_username_of_message_authors')}</FieldDescription>
                                </Field>
                                {displayRolesEnabled && (
                                    <Field>
                                        <FieldRow>
                                            <FieldLabel htmlFor={hideRolesId}>{t('Show_roles')}</FieldLabel>
                                            <Controller
                                                name='hideRoles'
                                                control={control}
                                                render={({ field: { value, onChange, ref } }) => (
                                                    <ToggleSwitch
                                                        id={hideRolesId}
                                                        ref={ref}
                                                        checked={!value}
                                                        onChange={(e) => onChange(!(e.target as HTMLInputElement).checked)}
                                                    />
                                                )}
                                            />
                                        </FieldRow>
                                        <FieldDescription>{t('Show_or_hide_the_user_roles_of_message_authors')}</FieldDescription>
                                    </Field>
                                )}
                            </FieldGroup>
                        </Accordion.Item>
                    </Accordion>
                </Box>
            </PageScrollableContentWithShadow>
            <PageFooter isDirty={isDirty}>
                <ButtonGroup>
                    <Button onClick={() => reset(preferencesValues)}>{t('Cancel')}</Button>
                    <Button primary disabled={!isDirty} loading={isSubmitting} form={pageFormId} type='submit'>
                        {t('Save_changes')}
                    </Button>
                </ButtonGroup>
            </PageFooter>
        </Page>
    );
};

export default AccessibilityPage;