Move app settings (#765)

* refactor: migrate app settings

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
Bianca Procopio
2024-03-03 11:06:21 -07:00
committed by GitHub
parent 088927bbbb
commit 10d3986832
8 changed files with 216 additions and 143 deletions
@@ -3,6 +3,7 @@ import { ErrorBoundary } from '@sentry/react';
import { useKeyDown } from '../../common/hooks/useKeyDown';
import AboutPanel from './panel/about-panel/AboutPanel';
import GeneralPanel from './panel/general-panel/GeneralPanel';
import IntegrationsPanel from './panel/integrations-panel/IntegrationsPanel';
import LogPanel from './panel/log-panel/LogPanel';
import ProjectPanel from './panel/project-panel/ProjectPanel';
@@ -29,6 +30,7 @@ export default function AppSettings() {
<PanelList />
<PanelContent onClose={closeSettings}>
{selectedPanel === 'project' && <ProjectPanel />}
{selectedPanel === 'general' && <GeneralPanel />}
{selectedPanel === 'sources' && <SourcesPanel />}
{selectedPanel === 'integrations' && <IntegrationsPanel />}
{selectedPanel === 'project_settings' && <ProjectSettingsPanel />}
@@ -0,0 +1,4 @@
.actionButtons {
display: flex;
gap: 1em;
}
@@ -0,0 +1,12 @@
import * as Panel from '../PanelUtils';
import GeneralPanelForm from './GeneralPanelForm';
export default function GeneralPanel() {
return (
<>
<Panel.Header>Settings</Panel.Header>
<GeneralPanelForm />
</>
);
}
@@ -0,0 +1,158 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, Input, Select } from '@chakra-ui/react';
import { Settings } from 'ontime-types';
import { postSettings } from '../../../../common/api/settings';
import { maybeAxiosError } from '../../../../common/api/utils';
import useSettings from '../../../../common/hooks-query/useSettings';
import { isOnlyNumbers } from '../../../../common/utils/regex';
import * as Panel from '../PanelUtils';
import GeneralPinInput from './GeneralPinInput';
import style from './GeneralPanel.module.scss';
export type GeneralPanelFormValues = {
filename: string;
};
export default function GeneralPanelForm() {
const { data, status, refetch } = useSettings();
const {
handleSubmit,
register,
reset,
setError,
formState: { isSubmitting, isDirty, isValid, errors },
} = useForm<Settings>({
defaultValues: data,
values: data,
resetOptions: {
keepDirtyValues: true,
},
});
// update form if we get new data from server
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (formData: Settings) => {
try {
await postSettings(formData);
} catch (error) {
const message = maybeAxiosError(error);
setError('root', { message });
} finally {
await refetch();
}
};
const disableInputs = status === 'pending';
const disableSubmit = isSubmitting || !isDirty || !isValid;
const submitError = '';
const onReset = () => {
reset(data);
};
return (
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} id='app-settings'>
<Panel.Card>
<Panel.SubHeader>
General settings
<div className={style.actionButtons}>
<Button isDisabled={!isDirty || isSubmitting} variant='ontime-ghost' size='sm' onClick={onReset}>
Revert to saved
</Button>
<Button
type='submit'
form='app-settings'
isLoading={isSubmitting}
isDisabled={disableSubmit}
variant='ontime-filled'
size='sm'
>
Save
</Button>
</div>
</Panel.SubHeader>
{submitError && <Panel.Error>{submitError}</Panel.Error>}
<Panel.Divider />
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field
title='Ontime server port'
description='Port ontime server listens in. Defaults to 4001 (needs app restart)'
error={errors.serverPort?.message}
/>
<Input
id='serverPort'
size='sm'
type='number'
variant='ontime-filled'
maxLength={5}
width='75px'
{...register('serverPort', {
required: { value: true, message: 'Required field' },
max: { value: 65535, message: 'Port must be within range 1024 - 65535' },
min: { value: 1024, message: 'Port must be within range 1024 - 65535' },
pattern: {
value: isOnlyNumbers,
message: 'Value should be numeric',
},
})}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Editor pin code'
description='Protect the editor view with a pin code'
error={errors.editorKey?.message}
/>
<GeneralPinInput register={register} formName='editorKey' isDisabled={disableInputs} />
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Operator pin code'
description='Protect the operator and cuesheet views with a pin code'
error={errors.operatorKey?.message}
/>
<GeneralPinInput register={register} formName='operatorKey' isDisabled={disableInputs} />
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Time format'
description='Default time format to show in views 12 /24 hours'
error={errors.timeFormat?.message}
/>
<Select variant='ontime' size='sm' width='auto' isDisabled={disableInputs} {...register('timeFormat')}>
<option value='12'>12 hours 11:00:10 PM</option>
<option value='24'>24 hours 23:00:10</option>
</Select>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Views language'
description='Language to be displayed in views'
error={errors.language?.message}
/>
<Select variant='ontime' size='sm' width='auto' isDisabled={disableInputs} {...register('language')}>
<option value='en'>English</option>
<option value='fr'>French</option>
<option value='de'>German</option>
<option value='it'>Italian</option>
<option value='no'>Norwegian</option>
<option value='pt'>Portuguese</option>
<option value='es'>Spanish</option>
<option value='sv'>Swedish</option>
</Select>
</Panel.ListItem>
</Panel.ListGroup>
</Panel.Card>
</Panel.Section>
);
}
@@ -0,0 +1,39 @@
import { PropsWithChildren, useState } from 'react';
import { UseFormRegister } from 'react-hook-form';
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
import { IoEyeOutline } from '@react-icons/all-files/io5/IoEyeOutline';
import { Settings } from 'ontime-types';
interface GeneralPinInputProps {
register: UseFormRegister<Settings>;
formName: keyof Settings;
isDisabled?: boolean;
}
export default function GeneralPinInput(props: PropsWithChildren<GeneralPinInputProps>) {
const { register, formName, isDisabled } = props;
const [isVisible, setVisible] = useState(false);
return (
<InputGroup size='sm' width='100px'>
<Input
variant='ontime-filled'
type={isVisible ? 'text' : 'password'}
maxLength={4}
{...register(formName)}
placeholder='-'
isDisabled={isDisabled}
/>
<InputRightElement>
<IconButton
onMouseDown={() => setVisible(true)}
onMouseUp={() => setVisible(false)}
size='sm'
variant='ontime-ghosted'
icon={<IoEyeOutline />}
aria-label='Show pin code'
/>
</InputRightElement>
</InputGroup>
);
}
@@ -13,6 +13,7 @@ export const settingPanels: Readonly<SettingsOption[]> = [
label: 'Project',
secondary: [{ id: 'project__manage', label: 'Manage project files' }],
},
{ id: 'general', label: 'General', secondary: [{ id: 'general__manage', label: 'Manage app settings' }] },
{
id: 'project_settings',
label: 'Project Settings',