Refactor settings management and add mutation error handling
All checks were successful
Deploy to production / build (push) Successful in 18s

This commit is contained in:
2026-10-20 14:16:20 -05:00
parent 69dae97a79
commit ecf061da1f
43 changed files with 476 additions and 440 deletions

View File

@@ -15,14 +15,7 @@ import {
} from './components';
export function Homepage() {
const {
appFilter,
bookmarkFilter,
isMobile,
setCurrentUser,
currentLocation,
setCurrentLocation,
} = useCommonHooks();
const { isMobile, settings, updateSetting } = useCommonHooks();
const dateOptions: Intl.DateTimeFormatOptions = isMobile
? {}
: {
@@ -34,7 +27,7 @@ export function Homepage() {
const date = new Date().toLocaleDateString('en-US', dateOptions);
const { data: personalData, isFetched: personalIsFetched } = useQueryAPI({
const { data: personalData } = useQueryAPI({
endpoint: 'api/personalData',
});
const { data: messageData } = useQueryAPI({
@@ -44,8 +37,8 @@ export function Homepage() {
address: personalData?.[0]?.physicalAddress,
});
const { data: meteoWeather } = useOpenMeteoAPI({
lat: weatherMetadata?.[0].lat ?? currentLocation.lat,
lon: weatherMetadata?.[0].lon ?? currentLocation.lon,
lat: weatherMetadata?.[0].lat ?? settings.lat,
lon: weatherMetadata?.[0].lon ?? settings.lon,
});
const personal: PersonalData = personalData?.[0];
@@ -59,11 +52,6 @@ export function Homepage() {
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
useDisclosure(false);
useEffect(() => {
if (personalData && personalIsFetched) {
setCurrentUser(personalData[0]);
}
}, [personalData, personalIsFetched]);
useEffect(() => {
if (personal) {
setDisplayedGreeting(() => {
@@ -87,21 +75,23 @@ export function Homepage() {
useEffect(() => {
if (meteoWeather) {
setMeteoCurrent(meteoWeather);
setCurrentLocation({
lat: weatherMetadata?.[0].lat,
lon: weatherMetadata?.[0].lon,
});
updateSetting('lat', weatherMetadata?.[0].lat);
updateSetting('lon', weatherMetadata?.[0].lon);
}
}, [meteoWeather]);
return (
<Stack
gap={0}
h={appFilter.length > 0 || bookmarkFilter.length > 0 ? undefined : '100%'}
h={
settings.appFilter.length > 0 || settings.bookmarkFilter.length > 0
? undefined
: '100%'
}
className={stackClasses.containerStack}
justify='center'
>
{appFilter.length > 0 || bookmarkFilter.length > 0 ? (
{settings.appFilter.length > 0 || settings.bookmarkFilter.length > 0 ? (
<>
<Search
current={meteoCurrent}
@@ -120,8 +110,8 @@ export function Homepage() {
current={meteoCurrent}
openWeatherModal={openWeatherModal}
/>
<Applications contentType={appFilter} />
<Bookmarks contentType={bookmarkFilter} />
<Applications contentType={settings.appFilter} />
<Bookmarks contentType={settings.bookmarkFilter} />
</>
) : (
<Box h={'100%'} w={'100%'}>

View File

@@ -8,10 +8,10 @@ export function Applications({ contentType }: { contentType: string[] }) {
const { data: applicationData } = useQueryAPI({
endpoint: 'api/enabledApplications',
});
const { appSort, appDensity, appScale, theme, textShadowStyle } = useCommonHooks();
const { settings, theme, textShadowStyle } = useCommonHooks();
const applications: Application[] = applicationData
?.sort((a: Application, b: Application) => {
switch (appSort) {
switch (settings.appSort) {
case 'A>Z':
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
case 'Z>A':
@@ -54,7 +54,7 @@ export function Applications({ contentType }: { contentType: string[] }) {
style={{
gap: '.25rem',
display: 'grid',
gridTemplateColumns: `repeat(${appDensity}, 1fr)`,
gridTemplateColumns: `repeat(${settings.appDensity}, 1fr)`,
}}
>
{applications?.map((application: Application) => {
@@ -65,7 +65,7 @@ export function Applications({ contentType }: { contentType: string[] }) {
url={application.url}
icon={application.icon}
enabled={application.enabled}
scale={appScale}
scale={settings.appScale}
linksActive
/>
);

View File

@@ -11,15 +11,14 @@ export function Bookmarks({ contentType }: { contentType: string[] }) {
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: 'api/bookmarkCategories',
});
const { bookmarkSort, bookmarkDensity, bookmarkScale, theme, textShadowStyle } =
useCommonHooks();
const { settings, theme, textShadowStyle } = useCommonHooks();
const groupList: BookmarkGroup[] = groupData?.map((category: BookmarkGroup) => {
return category;
});
const bookmarks: Bookmark[] = bookmarkData || [];
const groups: BookmarkGroup[] = groupList
?.toSorted((a: BookmarkGroup, b: BookmarkGroup) => {
switch (bookmarkSort) {
switch (settings.bookmarkSort) {
case 'A>Z':
return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase()
? 1
@@ -71,7 +70,7 @@ export function Bookmarks({ contentType }: { contentType: string[] }) {
style={{
display: 'grid',
gridRowGap: '0px',
gridTemplateColumns: `repeat(${bookmarkDensity}, 1fr)`,
gridTemplateColumns: `repeat(${settings.bookmarkDensity}, 1fr)`,
}}
>
{groups
@@ -88,7 +87,7 @@ export function Bookmarks({ contentType }: { contentType: string[] }) {
(bookmark: Bookmark) =>
bookmark.category === category.categoryName,
)}
bookmarkScale={bookmarkScale}
bookmarkScale={settings.bookmarkScale}
/>
);
})}

View File

@@ -18,13 +18,12 @@ export function Header({
const [time, setTime] = useState(new Date());
const {
isMobile,
currentUser,
fixDigits,
theme,
textShadowStyle,
largeTextShadowStyle,
timeFormat,
setTimeFormat,
settings,
updateSetting,
} = useCommonHooks();
useEffect(() => {
@@ -51,12 +50,12 @@ export function Header({
}}
>
{greet
?.replace('$firstName', currentUser.firstName)
.replace('$lastName', currentUser.lastName)
.replace('$nickname', currentUser.nickname)
.replace('$^firstName', currentUser.firstName.toUpperCase())
.replace('$^lastName', currentUser.lastName.toUpperCase())
.replace('$^nickname', currentUser.nickname?.toUpperCase())}
?.replace('$firstName', settings.firstName)
.replace('$lastName', settings.lastName)
.replace('$nickname', settings.nickname)
.replace('$^firstName', settings.firstName.toUpperCase())
.replace('$^lastName', settings.lastName.toUpperCase())
.replace('$^nickname', settings.nickname?.toUpperCase())}
</Text>
</Stack>
<Stack
@@ -123,10 +122,12 @@ export function Header({
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
}}
onClick={() =>
timeFormat === '12h' ? setTimeFormat('24h') : setTimeFormat('12h')
settings.timeFormat === '12h'
? updateSetting('timeFormat', '24h')
: updateSetting('timeFormat', '12h')
}
>
{timeFormat === '12h'
{settings.timeFormat === '12h'
? `${
time.getHours() === 0
? 12

View File

@@ -13,13 +13,12 @@ export function LargeHeader({
const [time, setTime] = useState(new Date());
const {
isMobile,
currentUser,
fixDigits,
theme,
textShadowStyle,
largeTextShadowStyle,
timeFormat,
setTimeFormat,
settings,
updateSetting,
} = useCommonHooks();
useEffect(() => {
@@ -40,12 +39,12 @@ export function LargeHeader({
}}
>
{greet
?.replace('$firstName', currentUser.firstName)
.replace('$lastName', currentUser.lastName)
.replace('$nickname', currentUser.nickname)
.replace('$^firstName', currentUser.firstName.toUpperCase())
.replace('$^lastName', currentUser.lastName.toUpperCase())
.replace('$^nickname', currentUser.nickname?.toUpperCase())}
?.replace('$firstName', settings.firstName)
.replace('$lastName', settings.lastName)
.replace('$nickname', settings.nickname)
.replace('$^firstName', settings.firstName.toUpperCase())
.replace('$^lastName', settings.lastName.toUpperCase())
.replace('$^nickname', settings.nickname?.toUpperCase())}
</Text>
<Text
fz={'2.25rem'}
@@ -69,10 +68,12 @@ export function LargeHeader({
zIndex: 1,
}}
onClick={() =>
timeFormat === '12h' ? setTimeFormat('24h') : setTimeFormat('12h')
settings.timeFormat === '12h'
? updateSetting('timeFormat', '24h')
: updateSetting('timeFormat', '12h')
}
>
{timeFormat === '12h'
{settings.timeFormat === '12h'
? `${
time.getHours() === 0
? 12

View File

@@ -63,14 +63,8 @@ export function Search({
setProviderIcon,
searchValue,
setSearchValue,
defaultProviderURL,
setDefaultProviderURL,
defaultProviderLabel,
setDefaultProviderLabel,
defaultProviderPrefix,
setDefaultProviderPrefix,
defaultProviderIcon,
setDefaultProviderIcon,
settings,
updateSetting,
} = useCommonHooks();
const { ref, height } = useElementSize();
const { mutateAsync } = useMutationAPI();
@@ -146,8 +140,8 @@ export function Search({
setSearchValue('');
setListIndex(-1);
setHistoryMultiplier(1);
setProviderLabel(defaultProviderLabel);
setProviderURL(defaultProviderURL);
setProviderLabel(settings.defaultProviderLabel);
setProviderURL(settings.defaultProviderURL);
};
const handleGuacamoleClientLaunchItem = ({ option }: { option: any }) => {
@@ -176,9 +170,13 @@ export function Search({
method: 'POST',
endpoint: searchEndpoint,
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
}).then(() => {
open(`${providerURL}${subtext}`, '_self');
});
})
.then(() => {
open(`${providerURL}${subtext}`, '_self');
})
.catch((error) => {
console.error(error);
});
} else if (providerLabel === 'Show weather') {
setSearchValue('');
closeDropdown();
@@ -211,9 +209,13 @@ export function Search({
method: 'POST',
endpoint: searchEndpoint,
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
}).then(() => {
open(`${providerURL}${subtext}`, '_self');
});
})
.then(() => {
open(`${providerURL}${subtext}`, '_self');
})
.catch((error) => {
console.error(error);
});
} else {
open(`${providerURL}${subtext}`, '_self');
}
@@ -372,10 +374,10 @@ export function Search({
setProviderIcon('');
}
} else {
setProviderLabel(defaultProviderLabel);
setProviderURL(defaultProviderURL);
setProviderPrefix(defaultProviderPrefix);
setProviderIcon(defaultProviderIcon);
setProviderLabel(settings.defaultProviderLabel);
setProviderURL(settings.defaultProviderURL);
setProviderPrefix(settings.defaultProviderPrefix);
setProviderIcon(settings.defaultProviderIcon);
}
};
@@ -460,12 +462,16 @@ export function Search({
useEffect(() => {
if (defaultProviderData && defaultProviderDataIsFetched) {
setDefaultProviderURL(
updateSetting(
'defaultProviderURL',
defaultProviderData[0]?.url ?? 'https://www.google.com/search?q=',
);
setDefaultProviderPrefix(defaultProviderData[0]?.prefix ?? '/g');
setDefaultProviderLabel(defaultProviderData[0]?.name ?? 'Google Search');
setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle');
updateSetting('defaultProviderPrefix', defaultProviderData[0]?.prefix ?? '/g');
updateSetting('defaultProviderIcon', defaultProviderData[0]?.icon ?? 'FaGoogle');
updateSetting(
'defaultProviderLabel',
defaultProviderData[0]?.name ?? 'Google Search',
);
}
}, [defaultProviderData, defaultProviderDataIsFetched]);

View File

@@ -24,11 +24,11 @@ export default function RenderItem({
setHistoryMultiplier: React.Dispatch<React.SetStateAction<number>>;
searchEndpoint: string;
}) {
const { defaultProviderURL } = useCommonHooks();
const { settings } = useCommonHooks();
const handleSearchHistoryItem = ({ option }: { option: SearchHistoryItem }) => {
const url =
providers.find((provider: Provider) => provider.prefix === option.prefix)?.url ??
defaultProviderURL;
settings.defaultProviderURL;
open(`${url}${option.searchTerm}`, '_self');
};

View File

@@ -130,6 +130,8 @@ function SearchSelectItem({
method: 'DELETE',
endpoint: searchEndpoint,
body: { id: item.option.id },
}).catch((error) => {
console.error(error);
});
}}
>

View File

@@ -33,26 +33,7 @@ export function SettingsButton({ openProviderModal }: { openProviderModal: () =>
const [selectedClient, setSelectedClient] = useState<WolClient>(wolClientData?.[0]);
const navigate = useNavigate();
const [isOpen, setIsOpen] = useState(false);
const {
theme,
isMobile,
appFilter,
setAppFilter,
appSort,
setAppSort,
appDensity,
setAppDensity,
appScale,
setAppScale,
bookmarkSort,
setBookmarkSort,
bookmarkFilter,
setBookmarkFilter,
bookmarkDensity,
setBookmarkDensity,
bookmarkScale,
setBookmarkScale,
} = useCommonHooks();
const { theme, isMobile, settings } = useCommonHooks();
const handleWakeRequest = () => {
mutateAsync({
@@ -123,26 +104,18 @@ export function SettingsButton({ openProviderModal }: { openProviderModal: () =>
<SettingsCard
categories={categories}
sectionTitle={'Applications'}
filter={appFilter}
setFilter={setAppFilter}
sort={appSort}
setSort={setAppSort}
density={appDensity}
setDensity={setAppDensity}
scale={appScale}
setScale={setAppScale}
filter={settings.appFilter}
sort={settings.appSort}
density={settings.appDensity}
scale={settings.appScale}
/>
<SettingsCard
categories={categories}
sectionTitle={'Bookmarks'}
filter={bookmarkFilter}
setFilter={setBookmarkFilter}
sort={bookmarkSort}
setSort={setBookmarkSort}
density={bookmarkDensity}
setDensity={setBookmarkDensity}
scale={bookmarkScale}
setScale={setBookmarkScale}
filter={settings.bookmarkFilter}
sort={settings.bookmarkSort}
density={settings.bookmarkDensity}
scale={settings.bookmarkScale}
/>
</Stack>
</Group>

View File

@@ -14,26 +14,18 @@ function SettingsCard({
categories,
sectionTitle,
filter,
setFilter,
sort,
setSort,
density,
setDensity,
scale,
setScale,
}: {
categories: ComboboxItem[];
sectionTitle: string;
filter: any;
setFilter: any;
sort: any;
setSort: any;
density: any;
setDensity: any;
scale: any;
setScale: any;
}) {
const { theme } = useCommonHooks();
const { theme, updateSetting } = useCommonHooks();
return (
<Stack
gap={0}
@@ -70,11 +62,23 @@ function SettingsCard({
className={sliderClasses.default}
classNames={sliderClasses}
value={density}
onChange={(value) => setDensity(value)}
onChange={(value) => {
if (sectionTitle === 'Applications') {
updateSetting('appDensity', value);
} else if (sectionTitle === 'Bookmarks') {
updateSetting('bookmarkDensity', value);
}
}}
min={1}
max={16}
marks={[{ value: 6, label: 'Default' }]}
onDoubleClick={() => setDensity(6)}
onDoubleClick={() => {
if (sectionTitle === 'Applications') {
updateSetting('appDensity', 6);
} else if (sectionTitle === 'Bookmarks') {
updateSetting('bookmarkDensity', 6);
}
}}
/>
</Stack>
{scale !== undefined && (
@@ -86,12 +90,24 @@ function SettingsCard({
className={sliderClasses.default}
classNames={sliderClasses}
value={scale}
onChange={(value) => setScale(value)}
onChange={(value) => {
if (sectionTitle === 'Applications') {
updateSetting('appScale', value);
} else if (sectionTitle === 'Bookmarks') {
updateSetting('bookmarkScale', value);
}
}}
step={0.1}
min={0.2}
max={3}
marks={[{ value: 1, label: 'Default' }]}
onDoubleClick={() => setScale(1)}
onDoubleClick={() => {
if (sectionTitle === 'Applications') {
updateSetting('appScale', 1);
} else if (sectionTitle === 'Bookmarks') {
updateSetting('bookmarkScale', 1);
}
}}
/>
</Stack>
)}
@@ -113,7 +129,11 @@ function SettingsCard({
}
defaultValue={filter}
onChange={(data: string[]) => {
setFilter(data);
if (sectionTitle === 'Applications') {
updateSetting('appFilter', data);
} else if (sectionTitle === 'Bookmarks') {
updateSetting('bookmarkFilter', data);
}
}}
classNames={multiSelectClasses}
comboboxProps={{
@@ -147,7 +167,11 @@ function SettingsCard({
position: 'top',
}}
onChange={(_value, option: ComboboxItem) => {
setSort(option.value);
if (sectionTitle === 'Applications') {
updateSetting('appSort', option.value);
} else if (sectionTitle === 'Bookmarks') {
updateSetting('bookmarkSort', option.value);
}
}}
labelProps={{ top: 8 }}
styles={

View File

@@ -31,16 +31,8 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
});
const allThemes: Theme[] = allThemeData || [];
const {
isMobile,
currentTheme,
setCurrentTheme,
setThemeName,
handleThemeChange,
theme,
currentThemePlaylist,
setCurrentThemePlaylist,
} = useCommonHooks();
const { isMobile, handleThemeChange, theme, settings, updateSetting } =
useCommonHooks();
const [current, setCurrent] = useState<string>(theme.name);
@@ -128,11 +120,11 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
borderRadius: '50%',
},
}}
disabled={currentThemePlaylist === 'Time of Day' || isAnimating}
disabled={settings.currentThemePlaylist === 'Time of Day' || isAnimating}
onClick={() => {
const themeList = JSON.parse(
themePlaylistData?.find(
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
(t: ThemePlaylist) => t?.name === settings.currentThemePlaylist,
)?.themeList ?? '[]',
);
if (themeList.length > 0) {
@@ -140,16 +132,16 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
themeList.includes(t.id),
);
handleThemeChange(filteredThemes, false);
} else if (currentThemePlaylist === 'All') {
} else if (settings.currentThemePlaylist === 'All') {
handleThemeChange(allThemes, true);
} else if (currentThemePlaylist === 'Backgrounds') {
} else if (settings.currentThemePlaylist === 'Backgrounds') {
handleThemeChange(
allThemes.filter(
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
),
false,
);
} else if (currentThemePlaylist === 'No Backgrounds') {
} else if (settings.currentThemePlaylist === 'No Backgrounds') {
handleThemeChange(
allThemes.filter((a: any) =>
a.backgroundImage.toLowerCase().includes('none'),
@@ -169,7 +161,7 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
placeholder='Pick a playlist...'
mb={'.25rem'}
ml={'.25rem'}
value={currentThemePlaylist}
value={settings.currentThemePlaylist}
data={[
{ value: 'All', label: 'All' },
{
@@ -226,20 +218,21 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
: undefined
}
onChange={(_value, option: ComboboxItem) => {
setCurrentThemePlaylist(option.value);
updateSetting('currentThemePlaylist', option.value);
if (option.value === 'Time of Day') {
const themeList = JSON.parse(
themePlaylistData.find((t: ThemePlaylist) => t.name === 'Time of Day')
.themeList,
);
setThemeName(
updateSetting(
'theme',
allThemeData.find(
(t: Theme) =>
t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
).name,
);
} else {
setThemeName('random');
updateSetting('theme', 'random');
}
}}
/>
@@ -252,12 +245,12 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
<ActionIcon
className={actionIconClasses.menuActions}
classNames={actionIconClasses}
disabled={currentThemePlaylist === 'Time of Day' || isAnimating}
disabled={settings.currentThemePlaylist === 'Time of Day' || isAnimating}
h={40}
style={{
backgroundColor: 'transparent',
color:
currentTheme === 'random'
settings.theme === 'random'
? 'var(--mantine-color-accent-0)'
: 'var(--mantine-color-primary-0)',
borderTopRightRadius: 0,
@@ -271,7 +264,7 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
styles={{
icon: {
backgroundColor:
currentTheme === 'random'
settings.theme === 'random'
? `${theme?.searchColor === theme?.primaryColor ? theme.searchColor?.substring(0, 7) : theme.primaryColor?.substring(0, 7)}88`
: 'transparent',
width: 'fit-content',
@@ -284,8 +277,8 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
},
}}
onClick={() => {
setCurrentTheme(currentTheme === 'random' ? theme.name : 'random');
setCurrentThemePlaylist('All');
updateSetting('theme', 'random');
updateSetting('currentThemePlaylist', 'All');
}}
>
<FiShuffle size={20} />

View File

@@ -128,10 +128,12 @@ export function Applications() {
useEffect(() => {
if (applicationList.length > 0 && applicationList !== applications) {
void mutateAsync({
mutateAsync({
method: 'PATCH',
endpoint: appsOrderEndpoint,
body: applicationList,
}).catch((error) => {
console.error(error);
});
}
}, [applicationList]);

View File

@@ -38,7 +38,13 @@ function DebouncedApplication({
};
useEffect(() => {
if (debounced && Object.keys(debounced).length > 0) {
mutateAsync({ method: 'PATCH', endpoint: 'api/applications', body: debounced });
mutateAsync({
method: 'PATCH',
endpoint: 'api/applications',
body: debounced,
}).catch((error) => {
console.error(error);
});
}
}, [debounced]);

View File

@@ -142,10 +142,12 @@ export function Bookmarks() {
useEffect(() => {
if (groupList.length > 0 && groupList !== groups) {
void mutateAsync({
mutateAsync({
method: 'PATCH',
endpoint: 'api/bookmarkCategoryOrder',
body: groupList,
}).catch((error) => {
console.error(error);
});
}
}, [groupList]);
@@ -160,13 +162,15 @@ export function Bookmarks() {
body={
<form
onSubmit={form.onSubmit((values) => {
void mutateAsync({
mutateAsync({
method: 'POST',
endpoint: categoriesEndpoint,
body: {
...values,
categoryIndex: groups.length,
},
}).catch((error) => {
console.error(error);
});
handleClose();
})}

View File

@@ -33,7 +33,11 @@ function DebouncedBookmarkItem({
};
useEffect(() => {
if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced });
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced }).catch(
(error) => {
console.error(error);
},
);
}
}, [debounced]);

View File

@@ -94,6 +94,8 @@ function DebouncedGroupSettingsItem({
method: 'PATCH',
endpoint: 'api/bookmarkCategories',
body: debounced,
}).catch((error) => {
console.error(error);
});
setUpdate(undefined);
}
@@ -107,7 +109,13 @@ function DebouncedGroupSettingsItem({
useEffect(() => {
if (bookmarkList && bookmarkList !== bookmarks) {
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarkOrder', body: bookmarkList });
mutateAsync({
method: 'PATCH',
endpoint: 'api/bookmarkOrder',
body: bookmarkList,
}).catch((error) => {
console.error(error);
});
}
}, [bookmarkList]);
@@ -351,6 +359,8 @@ function DebouncedGroupSettingsItem({
method: 'POST',
endpoint: 'api/bookmarks',
body: { ...values, index: bookmarks.length },
}).catch((error) => {
console.error(error);
});
setPopoverOpened(false);
form.reset();

View File

@@ -47,7 +47,13 @@ function DebouncedGuacamoleClient({
useEffect(() => {
if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/guacamoleClients', body: debounced });
mutateAsync({
method: 'PATCH',
endpoint: 'api/guacamoleClients',
body: debounced,
}).catch((error) => {
console.error(error);
});
}
}, [debounced]);

View File

@@ -108,10 +108,12 @@ export function GuacamoleClients() {
body={
<form
onSubmit={form.onSubmit((values) => {
void mutateAsync({
mutateAsync({
method: 'POST',
endpoint: endpoint,
body: { ...values, enabled: 1 },
}).catch((error) => {
console.error(error);
});
handleClose();
})}

View File

@@ -71,7 +71,11 @@ export function Messages() {
useEffect(() => {
if (Object.keys(debounced).length) {
void mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }).catch(
(error) => {
console.error(error);
},
);
}
}, [debounced]);
@@ -116,10 +120,12 @@ export function Messages() {
body={
<form
onSubmit={form.onSubmit((values) => {
void mutateAsync({
mutateAsync({
method: 'POST',
endpoint: endpoint,
body: { message: values.message },
}).catch((error) => {
console.error(error);
});
handleClose();
})}

View File

@@ -37,7 +37,11 @@ export function Personalization() {
const { isMobile, theme } = useCommonHooks();
useEffect(() => {
if (debounced && Object.keys(debounced).length) {
void mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }).catch(
(error) => {
console.error(error);
},
);
setUpdate(undefined);
}
}, [debounced]);

View File

@@ -40,7 +40,11 @@ function DebouncedSearchProvider({
useEffect(() => {
if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced });
mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced }).catch(
(error) => {
console.error(error);
},
);
}
}, [debounced]);

View File

@@ -116,10 +116,12 @@ export function SearchProviders() {
body={
<form
onSubmit={form.onSubmit((values) => {
void mutateAsync({
mutateAsync({
method: 'POST',
endpoint: endpoint,
body: values,
}).catch((error) => {
console.error(error);
});
handleClose();
})}

View File

@@ -59,6 +59,8 @@ export function DeleteBackground({
method: 'DELETE',
endpoint: 'api/backgrounds',
body: { fileName },
}).catch((error) => {
console.error(error);
});
}}
>

View File

@@ -72,6 +72,8 @@ export function EditBackground({
fileName.length,
)}`,
},
}).catch((error) => {
console.error(error);
});
} else {
mutateAsync({
@@ -81,6 +83,8 @@ export function EditBackground({
fileName,
newFileName: rename,
},
}).catch((error) => {
console.error(error);
});
}
handleEditBackgroundModalClose();

View File

@@ -41,7 +41,7 @@ export function ThemePlaylists() {
const [opened, { open, close }] = useDisclosure(false);
const [selectedPlaylist, setSelectedPlaylist] =
useState<ThemePlaylist>(defaultPlaylist);
const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
const { settings, theme, isMobile } = useCommonHooks();
const [isLoading, setIsLoading] = useState(false);
const handleOpen = useCallback(
@@ -58,10 +58,12 @@ export function ThemePlaylists() {
}, [close]);
const handleSavePlaylist = useCallback(() => {
void mutateAsync({
mutateAsync({
method: selectedPlaylist?.id === undefined ? 'POST' : 'PATCH',
endpoint: themePlaylistsEndpoint,
body: selectedPlaylist ?? {},
}).catch((error) => {
console.error(error);
});
close();
}, [selectedPlaylist, mutateAsync, themePlaylistsEndpoint, close]);
@@ -189,7 +191,7 @@ export function ThemePlaylists() {
</Text>
<Group>
<Group pos={'relative'}>
{'All' === currentThemePlaylist && (
{'All' === settings.currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
@@ -210,7 +212,7 @@ export function ThemePlaylists() {
/>
</Group>
<Group pos={'relative'}>
{'Backgrounds' === currentThemePlaylist && (
{'Backgrounds' === settings.currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
@@ -234,7 +236,7 @@ export function ThemePlaylists() {
/>
</Group>
<Group pos={'relative'}>
{'No Backgrounds' === currentThemePlaylist && (
{'No Backgrounds' === settings.currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
@@ -258,7 +260,7 @@ export function ThemePlaylists() {
/>
</Group>
<Group pos={'relative'}>
{'Time of Day' === currentThemePlaylist && (
{'Time of Day' === settings.currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
@@ -311,7 +313,7 @@ export function ThemePlaylists() {
.map((cardPlaylist: ThemePlaylist) => {
return (
<Group key={cardPlaylist.name} pos={'relative'}>
{cardPlaylist.name === currentThemePlaylist && (
{cardPlaylist.name === settings.currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',

View File

@@ -18,7 +18,7 @@ export function ThemePlaylistCard({
allThemes: Theme[];
}) {
const [hover, setHover] = useState(false);
const { setThemeName, setCurrentThemePlaylist, handleDelete } = useCommonHooks();
const { updateSetting, handleDelete } = useCommonHooks();
const defaultLists = ['All', 'Backgrounds', 'No Backgrounds'];
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const themeList = JSON.parse(
@@ -117,8 +117,8 @@ export function ThemePlaylistCard({
border: 'solid var(--mantine-color-border-0)',
}}
onClick={() => {
setCurrentThemePlaylist(cardPlaylist.name);
setThemeName('random');
updateSetting('currentThemePlaylist', cardPlaylist.name);
updateSetting('theme', 'random');
}}
disabled={themeList.length === 0}
>

View File

@@ -25,8 +25,7 @@ export function ThemePlaylistStack({
setPressed(true);
});
const [hover, setHover] = useState(false);
const { setThemeName, setCurrentThemePlaylist, handleDelete, currentUser } =
useCommonHooks();
const { settings, updateSetting, handleDelete } = useCommonHooks();
const [indexOffset, setIndexOffset] = useState(0);
const [movingIndex, setMovingIndex] = useState(0);
const missingTheme = {
@@ -41,7 +40,7 @@ export function ThemePlaylistStack({
randomEnabled: 1,
textShadowsEnabled: 0,
appShadowsEnabled: 0,
themeFont: currentUser.defaultFont,
themeFont: settings.defaultFont,
};
const [topTheme, setTopTheme] = useState<Theme>(missingTheme);
const defaultLists = ['All', 'Backgrounds', 'No Backgrounds'];
@@ -156,8 +155,8 @@ export function ThemePlaylistStack({
borderRadius: '100%',
}}
onClick={() => {
setCurrentThemePlaylist(cardPlaylist.name);
setThemeName('random');
updateSetting('currentThemePlaylist', cardPlaylist.name);
updateSetting('theme', 'random');
}}
>
{modalOpened && (

View File

@@ -27,8 +27,7 @@ export function ThemeCard({
}) {
const { hovered, ref: hoverRef } = useHover();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { currentTheme, setCurrentTheme, setCurrentThemePlaylist, handleDelete } =
useCommonHooks();
const { settings, updateSetting, handleDelete } = useCommonHooks();
const [hoverPreview, setHoverPreview] = useDebouncedState(false, 1000);
const [transition, setTransition] = useDebouncedState(false, 200);
@@ -113,7 +112,7 @@ export function ThemeCard({
<Button
w={'275px'}
h={'115px'}
disabled={currentTheme === 'random'}
disabled={settings.theme === 'random'}
style={{
backgroundImage:
cardTheme?.backgroundImage?.toLocaleLowerCase() === 'none'
@@ -137,12 +136,12 @@ export function ThemeCard({
}}
onClick={() => {
if (enabled !== false) {
setCurrentTheme(cardTheme.name);
setCurrentThemePlaylist('All');
updateSetting('theme', cardTheme.name);
updateSetting('currentThemePlaylist', 'All');
}
}}
>
{hovered && enabled && currentTheme !== 'random' && (
{hovered && enabled && settings.theme !== 'random' && (
<Overlay
style={{
opacity: !hoverPreview || transition ? 0.5 : 0,

View File

@@ -29,14 +29,13 @@ export function ThemeManagementPill({
transition: boolean;
setTransition: (transition: boolean) => void;
}) {
const { currentTheme, currentThemePlaylist, handleThemeChange, theme } =
useCommonHooks();
const { settings, handleThemeChange, theme } = useCommonHooks();
const [shiftEdit, setShiftEdit] = useState<number>(0);
const [shiftShuffle, setShiftShuffle] = useState<number>(0);
const { mutateAsync } = useMutationAPI();
return (
<>
{currentTheme === 'random' && cardTheme.name === theme.name ? (
{settings.theme === 'random' && cardTheme.name === theme.name ? (
<Group>
<Group
onMouseEnter={() => {
@@ -81,16 +80,16 @@ export function ThemeManagementPill({
setShiftShuffle(0);
}}
onClick={() => {
if (currentThemePlaylist === 'All') {
if (settings.currentThemePlaylist === 'All') {
handleThemeChange(allThemes, false);
} else if (currentThemePlaylist === 'Backgrounds') {
} else if (settings.currentThemePlaylist === 'Backgrounds') {
handleThemeChange(
allThemes.filter(
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
),
false,
);
} else if (currentThemePlaylist === 'No Backgrounds') {
} else if (settings.currentThemePlaylist === 'No Backgrounds') {
handleThemeChange(
allThemes.filter((a: any) =>
a.backgroundImage.toLowerCase().includes('none'),
@@ -100,7 +99,7 @@ export function ThemeManagementPill({
} else {
const themeList = JSON.parse(
themePlaylists?.find(
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
(t: ThemePlaylist) => t?.name === settings.currentThemePlaylist,
)?.themeList ?? '[]',
);
const filteredThemes = allThemes.filter((t: Theme) =>
@@ -203,6 +202,8 @@ export function ThemeManagementPill({
method: 'PATCH',
endpoint: 'api/themes',
body: { ...cardTheme, randomEnabled: !cardTheme.randomEnabled },
}).catch((error) => {
console.error(error);
});
}}
>

View File

@@ -30,7 +30,7 @@ export function ThemeModal({
themePlaylists: ThemePlaylist[];
}) {
const themesEndpoint = 'api/themes';
const { currentUser, isMobile } = useCommonHooks();
const { settings, isMobile } = useCommonHooks();
const [defaultTheme, setDefaultTheme] = useState<Theme>({ ...selectedTheme });
const [designerTheme, setDesignerTheme] = useState<Theme>({
...selectedTheme,
@@ -68,7 +68,7 @@ export function ThemeModal({
randomEnabled: 1,
textShadowsEnabled: 0,
appShadowsEnabled: 0,
themeFont: currentUser.defaultFont,
themeFont: settings.defaultFont,
});
};
@@ -77,6 +77,8 @@ export function ThemeModal({
method: designerTheme?.id === undefined ? 'POST' : 'PATCH',
endpoint: themesEndpoint,
body: designerTheme,
}).catch((error) => {
console.error(error);
});
handleClose();
};

View File

@@ -25,7 +25,7 @@ export default function ThemeChanges({
designerTheme: Theme;
setDesignerTheme: (theme: Theme) => void;
}) {
const { currentUser, getObjectDiff } = useCommonHooks();
const { settings, getObjectDiff } = useCommonHooks();
return (
<Stack
gap={0}
@@ -148,7 +148,7 @@ export default function ThemeChanges({
}}
>
{defaultTheme[key as keyof Theme] ||
`Default (${currentUser.defaultFont})`}
`Default (${settings.defaultFont})`}
</Text>
<Text>→</Text>
<Text
@@ -158,7 +158,7 @@ export default function ThemeChanges({
}}
>
{designerTheme[key as keyof Theme] ||
`Default (${currentUser.defaultFont})`}
`Default (${settings.defaultFont})`}
</Text>
</Group>
)}
@@ -197,9 +197,11 @@ export default function ThemeChanges({
<ColorSwatch
h={50}
onClick={() => {
navigator.clipboard.writeText(
defaultTheme[key as keyof Theme] as string,
);
navigator.clipboard
.writeText(defaultTheme[key as keyof Theme] as string)
.catch((error) => {
console.error(error);
});
notifications.show({
title: (
<Text fw={600} c={'var(--mantine-color-accent-0)'}>
@@ -250,9 +252,11 @@ export default function ThemeChanges({
<ColorSwatch
h={50}
onClick={() => {
navigator.clipboard.writeText(
defaultTheme[key as keyof Theme] as string,
);
navigator.clipboard
.writeText(defaultTheme[key as keyof Theme] as string)
.catch((error) => {
console.error(error);
});
notifications.show({
title: (
<Text fw={600} c={'var(--mantine-color-accent-0)'}>

View File

@@ -37,7 +37,7 @@ export function ThemeEditor({
duplicate: boolean;
setDuplicate: (duplicate: boolean) => void;
}) {
const { currentUser, isMobile } = useCommonHooks();
const { settings, isMobile } = useCommonHooks();
const [colorId, setColorId] = useState<string>('');
const [colorValue, setColorValue] = useState('#000000');
const [rgba, setRgba] = useState<{ r: number; g: number; b: number; a?: number }>({
@@ -80,7 +80,7 @@ export function ThemeEditor({
} else if (font && !customFontEnabled) {
setDesignerTheme({
...designerTheme,
themeFont: currentUser.defaultFont || 'Jost',
themeFont: settings.defaultFont || 'Jost',
});
}
}, [font]);

View File

@@ -5,7 +5,7 @@ import { selectClasses, textClasses } from 'styles';
import type { Theme } from 'types';
export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
const { currentUser, textShadowStyle, largeTextShadowStyle } = useCommonHooks();
const { settings, textShadowStyle, largeTextShadowStyle } = useCommonHooks();
return (
<Stack
style={{
@@ -31,7 +31,7 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
'--mantine-color-border-0': selectedTheme?.borderColor,
'--mantine-color-search-0': selectedTheme?.searchColor,
transition: 'all 0.2s ease',
fontFamily: selectedTheme.themeFont ?? currentUser.defaultFont,
fontFamily: selectedTheme.themeFont ?? settings.defaultFont,
}}
align='center'
gap={0}
@@ -47,7 +47,7 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
color: selectedTheme?.primaryColor,
borderBottom: `thin solid ${selectedTheme?.borderColor}`,
backgroundColor: `color-mix(in srgb, ${selectedTheme?.searchColor}, transparent 50%)`,
fontFamily: selectedTheme.themeFont ?? currentUser.defaultFont,
fontFamily: selectedTheme.themeFont ?? settings.defaultFont,
},
}}
data={[

View File

@@ -35,7 +35,7 @@ export default function ThemeOptions({
duplicate: boolean;
randomColor: (withAlpha?: boolean) => string;
}) {
const { currentUser } = useCommonHooks();
const { settings } = useCommonHooks();
return (
<Stack h={'100%'} justify='space-between' align='center' gap={4}>
<Stack w={'100%'} align='center' gap={0} h={'min-content'}>
@@ -137,7 +137,7 @@ export default function ThemeOptions({
onClick={() => {
setCustomFontEnabled(!customFontEnabled);
setFont(
customFontEnabled ? currentUser.defaultFont : defaultTheme.themeFont,
customFontEnabled ? settings.defaultFont : defaultTheme.themeFont,
);
}}
style={{
@@ -163,7 +163,7 @@ export default function ThemeOptions({
autoLoad
defaultValue={
designerTheme.themeFont === null
? currentUser.defaultFont
? settings.defaultFont
: designerTheme.themeFont
}
value={(pickerFont: string) => {
@@ -183,7 +183,7 @@ export default function ThemeOptions({
color={'red'}
size={32}
onClick={() => {
setFont(currentUser.defaultFont);
setFont(settings.defaultFont);
setCustomFontEnabled(false);
}}
style={{

View File

@@ -84,15 +84,7 @@ export function Themes() {
),
] as string[];
const {
currentTheme,
currentUser,
setCurrentTheme,
theme,
isMobile,
currentThemePlaylist,
setCurrentThemePlaylist,
} = useCommonHooks();
const { settings, updateSetting, theme, isMobile } = useCommonHooks();
const [selectedTheme, setSelectedTheme] = useState<Theme>({
id: undefined,
@@ -106,7 +98,7 @@ export function Themes() {
randomEnabled: 1,
textShadowsEnabled: 0,
appShadowsEnabled: 0,
themeFont: currentUser.defaultFont,
themeFont: settings.defaultFont,
});
const [isLoading, setIsLoading] = useState(false);
const [isNew, setIsNew] = useState(false);
@@ -184,7 +176,7 @@ export function Themes() {
>
<Overlay
display={
currentTheme === 'random' && debouncedSearch === '' ? 'flex' : 'none'
settings.theme === 'random' && debouncedSearch === '' ? 'flex' : 'none'
}
blur={5}
style={{
@@ -199,7 +191,7 @@ export function Themes() {
{allThemes?.length > 0 ? (
allThemes.map((cardTheme: Theme) => (
<Group key={cardTheme.name} pos={'relative'}>
{cardTheme.name === currentTheme && (
{cardTheme.name === settings.theme && (
<FiChevronRight
style={{
position: 'absolute',
@@ -219,11 +211,12 @@ export function Themes() {
<Box
style={{
zIndex:
currentTheme === 'random' &&
settings.theme === 'random' &&
(themePlaylists
.find(
(t: ThemePlaylist) =>
t.name.toLowerCase() === currentThemePlaylist.toLowerCase(),
t.name.toLowerCase() ===
settings.currentThemePlaylist.toLowerCase(),
)
?.themeList.includes(`${cardTheme.id}`) ||
cardTheme.name === theme.name)
@@ -310,7 +303,9 @@ export function Themes() {
fontSize: isMobile ? '.8rem' : '1.2rem',
}}
>
{currentThemePlaylist === 'All' ? 'All' : currentThemePlaylist}
{settings.currentThemePlaylist === 'All'
? 'All'
: settings.currentThemePlaylist}
</Text>
</Group>
<Divider
@@ -320,13 +315,13 @@ export function Themes() {
/>
<Button
onClick={() => {
if (currentThemePlaylist === 'All') {
if (settings.currentThemePlaylist === 'All') {
void navigate(
`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`,
);
} else {
setCurrentThemePlaylist('All');
setCurrentTheme(theme.name);
updateSetting('currentThemePlaylist', 'All');
updateSetting('theme', theme.name);
}
}}
style={{
@@ -334,7 +329,7 @@ export function Themes() {
padding: isMobile ? 0 : undefined,
}}
>
{currentThemePlaylist === 'All' ? (
{settings.currentThemePlaylist === 'All' ? (
isMobile ? (
<FiMousePointer />
) : (
@@ -350,11 +345,11 @@ export function Themes() {
className={toolTipClasses.default}
classNames={toolTipClasses}
label='Shuffle is always on when a playlist is active.'
disabled={currentThemePlaylist === 'All'}
disabled={settings.currentThemePlaylist === 'All'}
>
<Group>
<Switch
disabled={currentThemePlaylist !== 'All'}
disabled={settings.currentThemePlaylist !== 'All'}
label={
isMobile ? (
<FaDice size={20} color={'var(--mantine-color-primary-0)'} />
@@ -363,10 +358,10 @@ export function Themes() {
)
}
labelPosition='left'
checked={currentTheme === 'random'}
checked={settings.theme === 'random'}
onChange={(e) => {
setCurrentTheme(e.target.checked ? 'random' : theme.name);
setCurrentThemePlaylist('All');
updateSetting('theme', e.target.checked ? 'random' : theme.name);
updateSetting('currentThemePlaylist', 'All');
}}
className={switchClasses.default}
classNames={switchClasses}

View File

@@ -28,7 +28,11 @@ function DebouncedWolClient({
useEffect(() => {
if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced });
mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced }).catch(
(error) => {
console.error(error);
},
);
}
}, [debounced]);

View File

@@ -99,10 +99,12 @@ export function WolClients() {
body={
<form
onSubmit={form.onSubmit((values) => {
void mutateAsync({
mutateAsync({
method: 'POST',
endpoint: endpoint,
body: values,
}).catch((error) => {
console.error(error);
});
handleClose();
})}