All checks were successful
Deploy to production / build (push) Successful in 18s
370 lines
11 KiB
TypeScript
370 lines
11 KiB
TypeScript
import {
|
|
Box,
|
|
Card,
|
|
Grid,
|
|
GridCol,
|
|
Group,
|
|
LoadingOverlay,
|
|
Overlay,
|
|
Stack,
|
|
Switch,
|
|
Text,
|
|
TextInput,
|
|
} from '@mantine/core';
|
|
import { useDebouncedValue } from '@mantine/hooks';
|
|
import { SettingsHeader } from 'components';
|
|
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
|
import { useEffect, useState } from 'react';
|
|
import FontPicker from 'react-fontpicker-ts';
|
|
import { cardClasses, switchClasses, textClasses, textInputClasses } from 'styles';
|
|
import type { PersonalData } from 'types';
|
|
|
|
export function Personalization() {
|
|
const endpoint = 'api/personalData';
|
|
const { data: personalData, isFetching: personalDataIsFetching } = useQueryAPI({
|
|
endpoint: endpoint,
|
|
});
|
|
const personal: PersonalData = personalData?.[0] || {};
|
|
const [wolEnabled, setWolEnabled] = useState<boolean>(personal?.enableWol === 1);
|
|
const [guacamoleEnabled, setGuacamoleEnabled] = useState<boolean>(
|
|
personal?.enableGuacamole === 1,
|
|
);
|
|
const [defaultFont, setDefaultFont] = useState<string>(personal?.defaultFont);
|
|
const [customFont, setCustomFont] = useState<boolean>();
|
|
const [update, setUpdate] = useState<PersonalData>();
|
|
const [debounced] = useDebouncedValue(update, 1000);
|
|
const { mutateAsync } = useMutationAPI();
|
|
const { isMobile, theme } = useCommonHooks();
|
|
useEffect(() => {
|
|
if (debounced && Object.keys(debounced).length) {
|
|
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }).catch(
|
|
(error) => {
|
|
console.error(error);
|
|
},
|
|
);
|
|
setUpdate(undefined);
|
|
}
|
|
}, [debounced]);
|
|
|
|
useEffect(() => {
|
|
if (Object.keys(personal).length > 0) {
|
|
setCustomFont(personal.defaultFont !== 'Jost');
|
|
setWolEnabled(personal.enableWol === 1);
|
|
setGuacamoleEnabled(personal.enableGuacamole === 1);
|
|
setDefaultFont(personal.defaultFont);
|
|
}
|
|
}, [personal]);
|
|
useEffect(() => {
|
|
if (
|
|
Object.keys(personal).length > 0 &&
|
|
defaultFont &&
|
|
defaultFont !== personal.defaultFont
|
|
) {
|
|
setUpdate({
|
|
...personal,
|
|
defaultFont: defaultFont,
|
|
});
|
|
}
|
|
}, [defaultFont]);
|
|
|
|
return (
|
|
<Stack>
|
|
<SettingsHeader
|
|
setDragging={undefined}
|
|
openModal={undefined}
|
|
title={'Personalization'}
|
|
tooltip={{
|
|
title: 'The personalization section allows you to update basic information.',
|
|
highlightList: [
|
|
'first name',
|
|
'last name',
|
|
'name',
|
|
'first',
|
|
'last',
|
|
'physical address',
|
|
],
|
|
description:
|
|
'The first and last name, and nickname fields will be used for greetings, and the physical address field will be used for weather.\n\nThe selected font will be used for all text unless the current theme has a font selected.\n\nThe Wol and Guacamole switches will enable their respective features and enable the tabs. The Guacamole Host field should be the exact connection string used for your instance, eg. "https://guacamole.example.com/guacamole" including subpath and no trialing /.',
|
|
}}
|
|
searchValue={undefined}
|
|
setSearchValue={() => {}}
|
|
searchPlaceholder={undefined}
|
|
isLoading={false}
|
|
setIsLoading={() => {}}
|
|
sortBy={''}
|
|
setSortBy={undefined}
|
|
customSearchEnabled={false}
|
|
/>
|
|
<Stack align='center' mt={'6rem'} pos={'relative'}>
|
|
<LoadingOverlay
|
|
visible={personalDataIsFetching}
|
|
zIndex={180}
|
|
overlayProps={{ blur: 5 }}
|
|
loaderProps={{
|
|
color: 'var(--mantine-color-primary-0)',
|
|
type: 'bars',
|
|
size: 'xl',
|
|
}}
|
|
/>
|
|
<Grid w={'75%'}>
|
|
{Object.keys(personal).length > 0 && (
|
|
<>
|
|
<GridCol span={isMobile ? 12 : 4}>
|
|
<Card
|
|
className={cardClasses.settingsItem}
|
|
classNames={cardClasses}
|
|
pt={0}
|
|
>
|
|
<TextInput
|
|
label={'First Name'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={personal?.firstName}
|
|
onChange={(event) => {
|
|
const updatedPersonal = {
|
|
...personal,
|
|
firstName: event.currentTarget.value,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Card>
|
|
</GridCol>
|
|
<GridCol span={isMobile ? 12 : 4}>
|
|
<Card
|
|
className={cardClasses.settingsItem}
|
|
classNames={cardClasses}
|
|
pt={0}
|
|
>
|
|
<TextInput
|
|
label={'Last Name'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={personal?.lastName}
|
|
onChange={(event) => {
|
|
const updatedPersonal = {
|
|
...personal,
|
|
lastName: event.currentTarget.value,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Card>
|
|
</GridCol>
|
|
<GridCol span={isMobile ? 12 : 4}>
|
|
<Card
|
|
className={cardClasses.settingsItem}
|
|
classNames={cardClasses}
|
|
pt={0}
|
|
>
|
|
<TextInput
|
|
label={'Nickname'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={personal?.nickname || ''}
|
|
onChange={(event) => {
|
|
const updatedPersonal = {
|
|
...personal,
|
|
nickname: event.currentTarget.value,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Card>
|
|
</GridCol>
|
|
<GridCol span={4}>
|
|
<Card
|
|
className={cardClasses.settingsItem}
|
|
classNames={cardClasses}
|
|
pt={0}
|
|
>
|
|
<TextInput
|
|
label={'Email'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={personal?.email}
|
|
onChange={(event) => {
|
|
const updatedPersonal = {
|
|
...personal,
|
|
email: event.currentTarget.value,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Card>
|
|
</GridCol>
|
|
<GridCol span={8}>
|
|
<Card
|
|
className={cardClasses.settingsItem}
|
|
classNames={cardClasses}
|
|
pt={0}
|
|
>
|
|
<TextInput
|
|
label={'Physical Address'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={personal?.physicalAddress}
|
|
onChange={(event) => {
|
|
const updatedPersonal = {
|
|
...personal,
|
|
physicalAddress: event.currentTarget.value,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Card>
|
|
</GridCol>
|
|
<GridCol span={12}>
|
|
<Card
|
|
w={'100%'}
|
|
className={cardClasses.settingsItem}
|
|
classNames={cardClasses}
|
|
style={{ overflow: 'visible' }}
|
|
>
|
|
<Stack w={'100%'} px={16}>
|
|
<Text
|
|
top={-12}
|
|
className={textClasses.currentTheme}
|
|
classNames={textClasses}
|
|
>
|
|
Extras
|
|
</Text>
|
|
<Group justify='center'>
|
|
<Switch
|
|
label={'Enable Custom Default Font'}
|
|
className={switchClasses.default}
|
|
classNames={switchClasses}
|
|
checked={customFont}
|
|
onChange={(event) => {
|
|
setCustomFont(!customFont);
|
|
if (!event.target.checked) {
|
|
setDefaultFont('Jost');
|
|
}
|
|
}}
|
|
/>
|
|
<Switch
|
|
label={'Enable Wake on LAN'}
|
|
className={switchClasses.default}
|
|
classNames={switchClasses}
|
|
checked={wolEnabled}
|
|
onChange={(event) => {
|
|
setWolEnabled(!wolEnabled);
|
|
const updatedPersonal = {
|
|
...personal,
|
|
enableWol: event.target.checked ? 1 : 0,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
/>
|
|
<Switch
|
|
label={'Enable Guacamole Clients'}
|
|
className={switchClasses.default}
|
|
classNames={switchClasses}
|
|
checked={guacamoleEnabled}
|
|
onChange={(event) => {
|
|
setGuacamoleEnabled(!guacamoleEnabled);
|
|
const updatedPersonal = {
|
|
...personal,
|
|
enableGuacamole: event.target.checked ? 1 : 0,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
/>
|
|
</Group>
|
|
{defaultFont && (
|
|
<Box pos={'relative'} p={'1rem'}>
|
|
{!customFont && (
|
|
<Overlay
|
|
color='var(--mantine-color-background-0)'
|
|
blur={10}
|
|
backgroundOpacity={0.75}
|
|
style={{ borderRadius: '.5rem' }}
|
|
/>
|
|
)}
|
|
|
|
<Text>Default Font</Text>
|
|
<FontPicker
|
|
autoLoad
|
|
defaultValue={defaultFont || 'Jost'}
|
|
value={(font: string) => setDefaultFont(font)}
|
|
/>
|
|
</Box>
|
|
)}
|
|
<Box pos={'relative'} p={'1rem'} style={{ overflow: 'clip' }}>
|
|
{!personal?.enableGuacamole && (
|
|
<Overlay
|
|
color='var(--mantine-color-background-0)'
|
|
zIndex={1}
|
|
blur={10}
|
|
backgroundOpacity={0.75}
|
|
style={{ borderRadius: '.5rem' }}
|
|
/>
|
|
)}
|
|
<TextInput
|
|
label={'Guacamole Host'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={personal?.guacamoleHost}
|
|
onChange={(event) => {
|
|
const updatedPersonal = {
|
|
...personal,
|
|
guacamoleHost: event.currentTarget.value,
|
|
};
|
|
setUpdate(updatedPersonal);
|
|
}}
|
|
styles={{
|
|
label: {
|
|
zIndex: personal?.enableGuacamole ? 1 : -1,
|
|
boxShadow: theme.textShadowsEnabled
|
|
? '2px 2px var(--mantine-color-border-0)'
|
|
: undefined,
|
|
},
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Stack>
|
|
</Card>
|
|
</GridCol>
|
|
</>
|
|
)}
|
|
</Grid>
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
}
|