Files
TriggerFrontend/src/pages/settings/components/Personalization/Personalization.tsx
Sean Edmunds e24f1ece32
All checks were successful
Deploy to production / build (push) Successful in 1m4s
Reworking Personalization
2026-02-13 12:51:09 -06:00

363 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 });
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={() => {}}
/>
<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>
);
}