diff --git a/apps/client/src/features/app-settings/AppSettings.tsx b/apps/client/src/features/app-settings/AppSettings.tsx index 1a5483822..cc870bb4a 100644 --- a/apps/client/src/features/app-settings/AppSettings.tsx +++ b/apps/client/src/features/app-settings/AppSettings.tsx @@ -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() { {selectedPanel === 'project' && } + {selectedPanel === 'general' && } {selectedPanel === 'sources' && } {selectedPanel === 'integrations' && } {selectedPanel === 'project_settings' && } diff --git a/apps/client/src/features/app-settings/panel/general-panel/GeneralPanel.module.scss b/apps/client/src/features/app-settings/panel/general-panel/GeneralPanel.module.scss new file mode 100644 index 000000000..bfd682999 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/general-panel/GeneralPanel.module.scss @@ -0,0 +1,4 @@ +.actionButtons { + display: flex; + gap: 1em; +} diff --git a/apps/client/src/features/app-settings/panel/general-panel/GeneralPanel.tsx b/apps/client/src/features/app-settings/panel/general-panel/GeneralPanel.tsx new file mode 100644 index 000000000..5071a87a9 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/general-panel/GeneralPanel.tsx @@ -0,0 +1,12 @@ +import * as Panel from '../PanelUtils'; + +import GeneralPanelForm from './GeneralPanelForm'; + +export default function GeneralPanel() { + return ( + <> + Settings + + + ); +} diff --git a/apps/client/src/features/app-settings/panel/general-panel/GeneralPanelForm.tsx b/apps/client/src/features/app-settings/panel/general-panel/GeneralPanelForm.tsx new file mode 100644 index 000000000..e2fcbe408 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/general-panel/GeneralPanelForm.tsx @@ -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({ + 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 ( + + + + General settings +
+ + +
+
+ {submitError && {submitError}} + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/apps/client/src/features/app-settings/panel/general-panel/GeneralPinInput.tsx b/apps/client/src/features/app-settings/panel/general-panel/GeneralPinInput.tsx new file mode 100644 index 000000000..8d5800f1d --- /dev/null +++ b/apps/client/src/features/app-settings/panel/general-panel/GeneralPinInput.tsx @@ -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; + formName: keyof Settings; + isDisabled?: boolean; +} + +export default function GeneralPinInput(props: PropsWithChildren) { + const { register, formName, isDisabled } = props; + const [isVisible, setVisible] = useState(false); + + return ( + + + + setVisible(true)} + onMouseUp={() => setVisible(false)} + size='sm' + variant='ontime-ghosted' + icon={} + aria-label='Show pin code' + /> + + + ); +} diff --git a/apps/client/src/features/app-settings/settingsStore.ts b/apps/client/src/features/app-settings/settingsStore.ts index 69b41b13f..3e0fb78c8 100644 --- a/apps/client/src/features/app-settings/settingsStore.ts +++ b/apps/client/src/features/app-settings/settingsStore.ts @@ -13,6 +13,7 @@ export const settingPanels: Readonly = [ 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', diff --git a/apps/client/src/features/modals/settings-modal/AppSettings.tsx b/apps/client/src/features/modals/settings-modal/AppSettings.tsx deleted file mode 100644 index 458b984db..000000000 --- a/apps/client/src/features/modals/settings-modal/AppSettings.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useEffect } from 'react'; -import { useForm } from 'react-hook-form'; -import { Input, Select } from '@chakra-ui/react'; -import type { Settings } from 'ontime-types'; - -import { postSettings } from '../../../common/api/settings'; -import { logAxiosError } from '../../../common/api/utils'; -import useSettings from '../../../common/hooks-query/useSettings'; -import { isOnlyNumbers } from '../../../common/utils/regex'; -import ModalLoader from '../modal-loader/ModalLoader'; -import ModalSplitInput from '../ModalSplitInput'; -import OntimeModalFooter from '../OntimeModalFooter'; - -import ModalPinInput from './ModalPinInput'; - -import style from './SettingsModal.module.scss'; - -export default function AppSettingsModal() { - const { data, status, isFetching, refetch } = useSettings(); - const { - handleSubmit, - register, - reset, - formState: { errors, isSubmitting, isDirty, isValid }, - } = useForm({ - defaultValues: data, - values: data, - resetOptions: { - keepDirtyValues: true, - }, - }); - - useEffect(() => { - if (data) { - reset(data); - } - }, [data, reset]); - - const onSubmit = async (formData: Settings) => { - try { - await postSettings(formData); - } catch (error) { - logAxiosError('Error saving settings', error); - } finally { - await refetch(); - } - }; - - const onReset = () => { - reset(data); - }; - - const disableInputs = status === 'pending'; - - if (isFetching) { - return ; - } - - return ( -
- - - - - - - - - -
- - - - - - - - - ); -} diff --git a/apps/client/src/features/modals/settings-modal/SettingsModal.tsx b/apps/client/src/features/modals/settings-modal/SettingsModal.tsx index 3b11c9170..31041b095 100644 --- a/apps/client/src/features/modals/settings-modal/SettingsModal.tsx +++ b/apps/client/src/features/modals/settings-modal/SettingsModal.tsx @@ -3,7 +3,6 @@ import { ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/r import ModalWrapper from '../ModalWrapper'; import AliasesForm from './AliasesForm'; -import AppSettingsModal from './AppSettings'; import EditorSettings from './EditorSettings'; import ProjectDataForm from './ProjectDataForm'; import ViewSettingsForm from './ViewSettingsForm'; @@ -20,16 +19,12 @@ export default function SettingsModal(props: ModalManagerProps) { - App Project Data Editor Views URL Aliases - - -