Small refactoring
All checks were successful
Deploy to production / build (push) Successful in 1m1s

This commit is contained in:
Sean Edmunds
2026-02-10 12:59:54 -06:00
parent 28802c8698
commit dd5e3dd4f3
38 changed files with 968 additions and 974 deletions

View File

@@ -5,7 +5,7 @@ import type { SearchHistoryItem } from 'types';
const columnHelper = createColumnHelper<SearchHistoryItem>(); const columnHelper = createColumnHelper<SearchHistoryItem>();
export const SearchHistoryColumns = ( export const SearchHistoryColumns = (
setSelectedItem: React.Dispatch<React.SetStateAction<any>>, setSelectedItem: React.Dispatch<React.SetStateAction<SearchHistoryItem | undefined>>,
deleteItemOpen: () => void, deleteItemOpen: () => void,
filtersEnabled: boolean, filtersEnabled: boolean,
setFiltersEnabled: React.Dispatch<React.SetStateAction<boolean>>, setFiltersEnabled: React.Dispatch<React.SetStateAction<boolean>>,

View File

@@ -6,14 +6,14 @@ import HelpSection from './HelpSection';
export function HelpModal({ export function HelpModal({
handleResetSearch, handleResetSearch,
providerModalOpened, modalOpened,
closeProviderModal, closeProviderModal,
applicationData, applicationData,
providerData, providerData,
clientData, clientData,
}: { }: {
handleResetSearch: () => void; handleResetSearch: () => void;
providerModalOpened: boolean; modalOpened: boolean;
closeProviderModal: () => void; closeProviderModal: () => void;
applicationData: Application[]; applicationData: Application[];
providerData: Provider[]; providerData: Provider[];
@@ -25,7 +25,7 @@ export function HelpModal({
size={'60%'} size={'60%'}
className={modalClasses.helpModal} className={modalClasses.helpModal}
classNames={modalClasses} classNames={modalClasses}
opened={providerModalOpened} opened={modalOpened}
fullScreen={isMobile} fullScreen={isMobile}
centered centered
onClose={() => { onClose={() => {

View File

@@ -1,5 +1,6 @@
export { AddItemModal } from './AddItemModal'; export { AddItemModal } from './AddItemModal';
export { AppItem } from './Applications/AppItem'; export { AppItem } from './Applications/AppItem';
export { SearchHistoryColumns } from './Columns/SearchHistoryColumns';
export { DataTable } from './DataTable/DataTable'; export { DataTable } from './DataTable/DataTable';
export { DebouncedInput } from './DebouncedInput'; export { DebouncedInput } from './DebouncedInput';
export { DeleteItemModal } from './DeleteItemModal'; export { DeleteItemModal } from './DeleteItemModal';

View File

@@ -10,12 +10,14 @@ import type {
Message, Message,
PersonalData, PersonalData,
} from 'types'; } from 'types';
import Applications from './components/Applications/Applications'; import {
import Bookmarks from './components/Bookmarks/Bookmarks'; Applications,
import Header from './components/Header/Header'; Bookmarks,
import LargeHeader from './components/Header/LargeHeader'; Header,
import Search from './components/Search/Search'; LargeHeader,
import SettingsButton from './components/SettingsButton/SettingsButton'; Search,
SettingsButton,
} from './components';
export function Homepage() { export function Homepage() {
const { const {

View File

@@ -4,7 +4,7 @@ import { useCommonHooks, useQueryAPI } from 'hooks';
import { textClasses } from 'styles'; import { textClasses } from 'styles';
import type { Application } from 'types'; import type { Application } from 'types';
function Applications({ contentType }: { contentType: string[] }) { export function Applications({ contentType }: { contentType: string[] }) {
const { data: applicationData } = useQueryAPI({ const { data: applicationData } = useQueryAPI({
endpoint: 'api/enabledApplications', endpoint: 'api/enabledApplications',
}); });
@@ -76,5 +76,3 @@ function Applications({ contentType }: { contentType: string[] }) {
</> </>
); );
} }
export default Applications;

View File

@@ -4,7 +4,7 @@ import { textClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types'; import type { Bookmark, BookmarkGroup } from 'types';
import BookmarkGroups from './BookmarkGroups'; import BookmarkGroups from './BookmarkGroups';
function Bookmarks({ contentType }: { contentType: string[] }) { export function Bookmarks({ contentType }: { contentType: string[] }) {
const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({ const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({
endpoint: 'api/bookmarks', endpoint: 'api/bookmarks',
}); });
@@ -95,5 +95,3 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
</> </>
); );
} }
export default Bookmarks;

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { textClasses } from 'styles'; import { textClasses } from 'styles';
import type { CurrentWeather } from 'types'; import type { CurrentWeather } from 'types';
function Header({ export function Header({
greet, greet,
date, date,
current, current,
@@ -140,5 +140,3 @@ function Header({
</Stack> </Stack>
); );
} }
export default Header;

View File

@@ -3,7 +3,13 @@ import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { textClasses } from 'styles'; import { textClasses } from 'styles';
function LargeHeader({ greet, date }: { greet: string | undefined; date: string }) { export function LargeHeader({
greet,
date,
}: {
greet: string | undefined;
date: string;
}) {
const [time, setTime] = useState(new Date()); const [time, setTime] = useState(new Date());
const { const {
isMobile, isMobile,
@@ -79,5 +85,3 @@ function LargeHeader({ greet, date }: { greet: string | undefined; date: string
</Stack> </Stack>
); );
} }
export default LargeHeader;

View File

@@ -30,7 +30,7 @@ import RecommendedBookmarks from './components/RecommendedBookmarks';
import RecommendedClients from './components/RecommendedClients'; import RecommendedClients from './components/RecommendedClients';
import RenderItem from './components/RenderItem'; import RenderItem from './components/RenderItem';
export default function Search({ export function Search({
current, current,
extendedForecast, extendedForecast,
weatherModalOpened, weatherModalOpened,
@@ -634,21 +634,25 @@ export default function Search({
</Card> </Card>
)} )}
</Stack> </Stack>
<HelpModal {providerModalOpened && (
handleResetSearch={handleResetSearch} <HelpModal
applicationData={applications} handleResetSearch={handleResetSearch}
providerData={providers} applicationData={applications}
clientData={guacamoleClients} providerData={providers}
providerModalOpened={providerModalOpened} clientData={guacamoleClients}
closeProviderModal={closeProviderModal} modalOpened={providerModalOpened}
/> closeProviderModal={closeProviderModal}
<WeatherModal />
current={current} )}
extendedForecast={extendedForecast} {weatherModalOpened && (
weatherModalOpened={weatherModalOpened} <WeatherModal
handleResetSearch={handleResetSearch} current={current}
closeWeatherModal={closeWeatherModal} extendedForecast={extendedForecast}
/> weatherModalOpened={weatherModalOpened}
handleResetSearch={handleResetSearch}
closeWeatherModal={closeWeatherModal}
/>
)}
</> </>
); );
} }

View File

@@ -9,7 +9,7 @@ import SettingsCard from './SettingsCard';
import ThemingCard from './ThemingCard'; import ThemingCard from './ThemingCard';
import WakeOnLan from './WakeOnLanCard'; import WakeOnLan from './WakeOnLanCard';
function SettingsButton({ openProviderModal }: { openProviderModal: () => void }) { export function SettingsButton({ openProviderModal }: { openProviderModal: () => void }) {
const { data: appCategoryData } = useQueryAPI({ const { data: appCategoryData } = useQueryAPI({
endpoint: 'api/appCategories', endpoint: 'api/appCategories',
}); });
@@ -171,5 +171,3 @@ function SettingsButton({ openProviderModal }: { openProviderModal: () => void }
</Menu> </Menu>
); );
} }
export default SettingsButton;

View File

@@ -0,0 +1,6 @@
export { Applications } from './Applications/Applications';
export { Bookmarks } from './Bookmarks/Bookmarks';
export { Header } from './Header/Header';
export { LargeHeader } from './Header/LargeHeader';
export { Search } from './Search/Search';
export { SettingsButton } from './SettingsButton/SettingsButton';

View File

@@ -13,16 +13,18 @@ import {
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { actionIconClasses, scrollAreaClasses, tabClasses, textClasses } from 'styles'; import { actionIconClasses, scrollAreaClasses, tabClasses, textClasses } from 'styles';
import type { PersonalData } from 'types'; import type { PersonalData } from 'types';
import Applications from './components/Applications/Applications'; import {
import Bookmarks from './components/Bookmarks/Bookmarks'; Applications,
import GuacamoleClients from './components/GuacamoleClients/GuacamoleClients'; Bookmarks,
import Messages from './components/Messages/Messages'; GuacamoleClients,
import Personalization from './components/Personalization/Personalization'; Messages,
import SearchHistory from './components/Search/SearchHistory'; Personalization,
import SearchProviders from './components/Search/SearchProviders'; SearchHistory,
import { ThemePlaylists } from './components/Theming/ThemePlaylists'; SearchProviders,
import { Themes } from './components/Theming/Themes'; ThemePlaylists,
import WolClients from './components/WoL/WolClients'; Themes,
WolClients,
} from './components';
export function Settings() { export function Settings() {
const endpoint = 'api/personalData'; const endpoint = 'api/personalData';

View File

@@ -26,7 +26,7 @@ import { buttonClasses, popoverClasses, selectClasses, textInputClasses } from '
import type { Application } from 'types'; import type { Application } from 'types';
import DebouncedApplication from './DebouncedApplication'; import DebouncedApplication from './DebouncedApplication';
const ApplicationSettings = () => { export function Applications() {
const allIcons: any = { ...fa6, ...vsc, ...si }; const allIcons: any = { ...fa6, ...vsc, ...si };
const defaultApplication: Application = { const defaultApplication: Application = {
id: null, id: null,
@@ -141,165 +141,167 @@ const ApplicationSettings = () => {
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
/> />
<AddItemModal {modalOpened && (
modalOpened={modalOpened} <AddItemModal
handleClose={handleClose} modalOpened={modalOpened}
title={'Add a new application'} handleClose={handleClose}
body={ title={'Add a new application'}
<form body={
onSubmit={form.onSubmit((values) => { <form
mutateAsync({ onSubmit={form.onSubmit((values) => {
method: 'POST', mutateAsync({
endpoint: appsEndpoint, method: 'POST',
body: { endpoint: appsEndpoint,
...values, body: {
index: applications.length, ...values,
}, index: applications.length,
}); },
handleClose(); });
})} handleClose();
> })}
<Stack gap={0} align='center'> >
<Group w={'100%'} justify='center'> <Stack gap={0} align='center'>
<TextInput <Group w={'100%'} justify='center'>
key={form.key('name')} <TextInput
{...form.getInputProps('name')} key={form.key('name')}
label={'Name'} {...form.getInputProps('name')}
placeholder='eg. Nextcloud' label={'Name'}
className={textInputClasses.settingsItem} placeholder='eg. Nextcloud'
w={'45%'} className={textInputClasses.settingsItem}
classNames={textInputClasses} w={'45%'}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
<TextInput />
key={form.key('baseUrl')} <TextInput
{...form.getInputProps('baseUrl')} key={form.key('baseUrl')}
label={'Base URL'} {...form.getInputProps('baseUrl')}
placeholder='eg. /nextcloud' label={'Base URL'}
className={textInputClasses.settingsItem} placeholder='eg. /nextcloud'
w={'45%'} className={textInputClasses.settingsItem}
classNames={textInputClasses} w={'45%'}
styles={ classNames={textInputClasses}
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
</Group> />
<Group w={isMobile ? '94%' : '92%'} justify='center'> </Group>
<TextInput <Group w={isMobile ? '94%' : '92%'} justify='center'>
key={form.key('url')} <TextInput
{...form.getInputProps('url')} key={form.key('url')}
label={'URL'} {...form.getInputProps('url')}
placeholder='eg. https://domain.example.com/nextcloud' label={'URL'}
className={textInputClasses.settingsItem} placeholder='eg. https://domain.example.com/nextcloud'
w={'100%'} className={textInputClasses.settingsItem}
classNames={textInputClasses} w={'100%'}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
</Group> />
<Group w={'100%'} justify='center' pos={'relative'}> </Group>
<Select <Group w={'100%'} justify='center' pos={'relative'}>
key={form.key('icon')} <Select
{...form.getInputProps('icon')} key={form.key('icon')}
label={'Icon'} {...form.getInputProps('icon')}
className={selectClasses.settingsItem} label={'Icon'}
classNames={{ className={selectClasses.settingsItem}
wrapper: selectClasses.menuSelectWrapper, classNames={{
input: selectClasses.menuSelectInput, wrapper: selectClasses.menuSelectWrapper,
label: selectClasses.menuSelectLabel, input: selectClasses.menuSelectInput,
options: selectClasses.menuSelectOptions, label: selectClasses.menuSelectLabel,
option: selectClasses.menuSelectOption, options: selectClasses.menuSelectOptions,
dropdown: selectClasses.dropdown, option: selectClasses.menuSelectOption,
}} dropdown: selectClasses.dropdown,
w={'45%'} }}
mt={'.75rem'} w={'45%'}
limit={100} mt={'.75rem'}
placeholder='Search for an icon...' limit={100}
data={Object.keys(allIcons).map((icon) => { placeholder='Search for an icon...'
return { value: icon, label: icon }; data={Object.keys(allIcons).map((icon) => {
})} return { value: icon, label: icon };
comboboxProps={{ })}
zIndex: 3000, comboboxProps={{
}} zIndex: 3000,
styles={ }}
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => { }
const Icon = allIcons[item.option.value]; renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
return ( const Icon = allIcons[item.option.value];
<Group> return (
{item.option.value} <Group>
<Icon /> {item.option.value}
</Group> <Icon />
); </Group>
}} );
searchable }}
maxDropdownHeight={175} searchable
onDropdownOpen={() => setIsOpen(true)} maxDropdownHeight={175}
onDropdownClose={() => setIsOpen(false)} onDropdownOpen={() => setIsOpen(true)}
withAsterisk onDropdownClose={() => setIsOpen(false)}
/> withAsterisk
<TextInput />
key={form.key('filter')} <TextInput
{...form.getInputProps('filter')} key={form.key('filter')}
label={'Filter'} {...form.getInputProps('filter')}
placeholder='eg. Storage' label={'Filter'}
className={textInputClasses.settingsItem} placeholder='eg. Storage'
pt={'1rem'} className={textInputClasses.settingsItem}
w={'45%'} pt={'1rem'}
classNames={textInputClasses} w={'45%'}
defaultValue={defaultApplication.filterName} classNames={textInputClasses}
withAsterisk defaultValue={defaultApplication.filterName}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
</Group> />
<Group mt={'1rem'}> </Group>
<Button <Group mt={'1rem'}>
type='submit' <Button
className={buttonClasses.saveButton} type='submit'
classNames={buttonClasses} className={buttonClasses.saveButton}
> classNames={buttonClasses}
Save >
</Button> Save
<Button </Button>
className={buttonClasses.cancelButton} <Button
classNames={buttonClasses} className={buttonClasses.cancelButton}
onClick={handleClose} classNames={buttonClasses}
> onClick={handleClose}
Cancel >
</Button> Cancel
</Group> </Button>
</Stack> </Group>
</form> </Stack>
} </form>
/> }
/>
)}
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}
@@ -410,5 +412,4 @@ const ApplicationSettings = () => {
</DragDropContext> </DragDropContext>
</Stack> </Stack>
); );
}; }
export default ApplicationSettings;

View File

@@ -30,7 +30,7 @@ function DebouncedApplication({
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [app, setApp] = useState<Application>(application); const [app, setApp] = useState<Application>(application);
const [update, setUpdate] = useState<any>(); const [update, setUpdate] = useState<Application>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
@@ -53,22 +53,24 @@ function DebouncedApplication({
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={handleClose} modalOpened={modalOpened}
handleDelete={() => handleDelete(app, 'api/applications')} handleClose={handleClose}
title='Delete Application' handleDelete={() => handleDelete(app, 'api/applications')}
body={ title='Delete Application'
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this application?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this application?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${app.name}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${app.name}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Box <Box
style={{ style={{
position: 'absolute', position: 'absolute',
@@ -192,7 +194,7 @@ function DebouncedApplication({
onChange={(value: string | null) => { onChange={(value: string | null) => {
setUpdate({ setUpdate({
...app, ...app,
icon: value, icon: value as Application['icon'],
}); });
}} }}
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => { renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {

View File

@@ -18,7 +18,7 @@ import { buttonClasses, textInputClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types'; import type { Bookmark, BookmarkGroup } from 'types';
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem'; import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
const BookmarkSettings = () => { export function Bookmarks() {
const defaultGroup = { const defaultGroup = {
id: null, id: null,
categoryName: '', categoryName: '',
@@ -68,7 +68,7 @@ const BookmarkSettings = () => {
), ),
), ),
]; ];
const foundGroups: BookmarkGroup[] = groupData.filter((a: BookmarkGroup) => const foundGroups: BookmarkGroup[] = groupData?.filter((a: BookmarkGroup) =>
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()), a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
); );
const handleClose = () => { const handleClose = () => {
@@ -121,79 +121,81 @@ const BookmarkSettings = () => {
return ( return (
<Stack> <Stack>
<AddItemModal {modalOpened && (
modalOpened={modalOpened} <AddItemModal
handleClose={handleClose} modalOpened={modalOpened}
title={'Add a new Bookmark Group'} handleClose={handleClose}
body={ title={'Add a new Bookmark Group'}
<form body={
onSubmit={form.onSubmit((values) => { <form
mutateAsync({ onSubmit={form.onSubmit((values) => {
method: 'POST', mutateAsync({
endpoint: categoriesEndpoint, method: 'POST',
body: { endpoint: categoriesEndpoint,
...values, body: {
categoryIndex: groups.length, ...values,
}, categoryIndex: groups.length,
}); },
handleClose(); });
})} handleClose();
> })}
<Stack gap={0} w={'100%'} align='center'> >
<Group w={'100%'} justify='center'> <Stack gap={0} w={'100%'} align='center'>
<TextInput <Group w={'100%'} justify='center'>
key={form.key('categoryName')} <TextInput
{...form.getInputProps('categoryName')} key={form.key('categoryName')}
label={'Category Name'} {...form.getInputProps('categoryName')}
placeholder='eg. Shopping' label={'Category Name'}
className={textInputClasses.settingsItem} placeholder='eg. Shopping'
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
<TextInput />
key={form.key('filterName')} <TextInput
{...form.getInputProps('filterName')} key={form.key('filterName')}
label={'Filter Name'} {...form.getInputProps('filterName')}
placeholder='eg. Personal' label={'Filter Name'}
className={textInputClasses.settingsItem} placeholder='eg. Personal'
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
</Group> />
<Group mt={'1rem'}> </Group>
<Button <Group mt={'1rem'}>
type='submit' <Button
className={buttonClasses.saveButton} type='submit'
classNames={buttonClasses} className={buttonClasses.saveButton}
> classNames={buttonClasses}
Save >
</Button> Save
<Button </Button>
className={buttonClasses.cancelButton} <Button
classNames={buttonClasses} className={buttonClasses.cancelButton}
onClick={handleClose} classNames={buttonClasses}
> onClick={handleClose}
Cancel >
</Button> Cancel
</Group> </Button>
</Stack> </Group>
</form> </Stack>
} </form>
/> }
/>
)}
<SettingsHeader <SettingsHeader
setDragging={groups.length > 0 ? setDragging : undefined} setDragging={groups.length > 0 ? setDragging : undefined}
openModal={openModal} openModal={openModal}
@@ -337,5 +339,4 @@ const BookmarkSettings = () => {
)} )}
</Stack> </Stack>
); );
}; }
export default BookmarkSettings;

View File

@@ -25,20 +25,20 @@ function DebouncedBookmarkItem({
(a: BookmarkGroup, b: BookmarkGroup) => (a: BookmarkGroup, b: BookmarkGroup) =>
a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(), a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(),
) || []; ) || [];
const [update, setUpdate] = useState<any>({}); const [update, setUpdate] = useState<Bookmark>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
closeModal(); closeModal();
}; };
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced }); mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced });
} }
}, [debounced]); }, [debounced]);
useEffect(() => { useEffect(() => {
if (update !== bookmark && Object.keys(update).length) { if (update && update !== bookmark && Object.keys(update).length) {
setBookmark(update); setBookmark(update);
} }
}, [update]); }, [update]);
@@ -49,22 +49,24 @@ function DebouncedBookmarkItem({
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={handleClose} modalOpened={modalOpened}
handleDelete={() => handleDelete(bookmark, 'api/bookmarks')} handleClose={handleClose}
body={ handleDelete={() => handleDelete(bookmark, 'api/bookmarks')}
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this bookmark?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this bookmark?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${bookmark.name}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${bookmark.name}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
title='Delete Bookmark' }
/> title='Delete Bookmark'
/>
)}
<Box <Box
style={{ style={{
position: 'absolute', position: 'absolute',
@@ -142,7 +144,7 @@ function DebouncedBookmarkItem({
onChange={(value: string | null) => { onChange={(value: string | null) => {
setUpdate({ setUpdate({
...bookmark, ...bookmark,
category: value, category: value as BookmarkGroup['categoryName'],
}); });
}} }}
searchable searchable

View File

@@ -48,7 +48,7 @@ function DebouncedGroupSettingsItem({
const [popoverOpened, setPopoverOpened] = useState(false); const [popoverOpened, setPopoverOpened] = useState(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [group, setGroup] = useState<BookmarkGroup>(bookmarkGroup); const [group, setGroup] = useState<BookmarkGroup>(bookmarkGroup);
const [update, setUpdate] = useState<any>({}); const [update, setUpdate] = useState<BookmarkGroup>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const [newBookmark, setNewBookmark] = useState<Bookmark>({ const [newBookmark, setNewBookmark] = useState<Bookmark>({
id: undefined, id: undefined,
@@ -89,18 +89,18 @@ function DebouncedGroupSettingsItem({
} }
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ mutateAsync({
method: 'PATCH', method: 'PATCH',
endpoint: 'api/bookmarkCategories', endpoint: 'api/bookmarkCategories',
body: debounced, body: debounced,
}); });
setUpdate({}); setUpdate(undefined);
} }
}, [debounced]); }, [debounced]);
useEffect(() => { useEffect(() => {
if (update !== group && Object.keys(update).length) { if (update && update !== group && Object.keys(update).length) {
setGroup(update); setGroup(update);
} }
}, [update]); }, [update]);
@@ -113,28 +113,30 @@ function DebouncedGroupSettingsItem({
return ( return (
<Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}> <Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}>
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={closeModal} modalOpened={modalOpened}
handleDelete={() => { handleClose={closeModal}
bookmarks.length > 0 && handleDelete(bookmarks, 'api/bookmarks'); handleDelete={() => {
handleDelete(group, 'api/bookmarkCategories'); bookmarks.length > 0 && handleDelete(bookmarks, 'api/bookmarks');
}} handleDelete(group, 'api/bookmarkCategories');
title='Delete message' }}
body={ title='Delete message'
<Stack gap={0} w={'100%'} h={'175px'} align='center'> body={
<Text>Are you sure you want to delete this bookmark group?</Text> <Stack gap={0} w={'100%'} h={'175px'} align='center'>
<Text <Text>Are you sure you want to delete this bookmark group?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${group.categoryName}"`}</Text> classNames={textClasses}
<Text style={{ fontWeight: 'bold', textTransform: 'uppercase' }}> >{`"${group.categoryName}"`}</Text>
This will also delete all bookmarks in this group! <Text style={{ fontWeight: 'bold', textTransform: 'uppercase' }}>
</Text> This will also delete all bookmarks in this group!
<Text c='red'>This action cannot be undone!</Text> </Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Card.Section <Card.Section
p={'1rem'} p={'1rem'}
pt={0} pt={0}

View File

@@ -38,7 +38,7 @@ function DebouncedGuacamoleClient({
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const types: string[] = ['SSH', 'RDP', 'VNC']; const types: string[] = ['SSH', 'RDP', 'VNC'];
const [client, setClient] = useState<GuacamoleClient>(guacamoleClient); const [client, setClient] = useState<GuacamoleClient>(guacamoleClient);
const [update, setUpdate] = useState<any>({}); const [update, setUpdate] = useState<GuacamoleClient>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
@@ -46,13 +46,13 @@ function DebouncedGuacamoleClient({
}; };
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/guacamoleClients', body: debounced }); mutateAsync({ method: 'PATCH', endpoint: 'api/guacamoleClients', body: debounced });
} }
}, [debounced]); }, [debounced]);
useEffect(() => { useEffect(() => {
if (update !== client && Object.keys(update).length) { if (update && update !== client && Object.keys(update).length) {
setClient(update); setClient(update);
} }
}, [update]); }, [update]);
@@ -62,22 +62,24 @@ function DebouncedGuacamoleClient({
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={handleClose} modalOpened={modalOpened}
handleDelete={() => handleDelete(client, 'api/guacamoleClients')} handleClose={handleClose}
title={'Delete Guacamole Client'} handleDelete={() => handleDelete(client, 'api/guacamoleClients')}
body={ title={'Delete Guacamole Client'}
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this WoL Client?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this WoL Client?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${client.clientName}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${client.clientName}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Tooltip <Tooltip
className={toolTipClasses.default} className={toolTipClasses.default}
classNames={toolTipClasses} classNames={toolTipClasses}
@@ -177,7 +179,7 @@ function DebouncedGuacamoleClient({
onChange={(value: string | null) => { onChange={(value: string | null) => {
setUpdate({ setUpdate({
...guacamoleClient, ...guacamoleClient,
type: value, type: value as GuacamoleClient['type'],
}); });
}} }}
searchable searchable

View File

@@ -1,6 +1,5 @@
import { Card, Stack, Text } from '@mantine/core'; import { Card, Stack, Text } from '@mantine/core';
import { FaDesktop, FaTerminal } from 'react-icons/fa'; import { FaComputer, FaDesktop, FaTerminal } from 'react-icons/fa6';
import { FaComputer } from 'react-icons/fa6';
import { cardClasses } from 'styles'; import { cardClasses } from 'styles';
import type { GuacamoleClient } from 'types'; import type { GuacamoleClient } from 'types';

View File

@@ -16,14 +16,13 @@ import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components'; import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FaTerminal } from 'react-icons/fa'; import { FaComputer, FaDesktop, FaTerminal } from 'react-icons/fa6';
import { FaComputer, FaDesktop } from 'react-icons/fa6';
import { buttonClasses, popoverClasses, selectClasses, textInputClasses } from 'styles'; import { buttonClasses, popoverClasses, selectClasses, textInputClasses } from 'styles';
import type { GuacamoleClient } from 'types'; import type { GuacamoleClient } from 'types';
import DebouncedGuacamoleClient from './DebouncedGuacamoleClient'; import DebouncedGuacamoleClient from './DebouncedGuacamoleClient';
import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem'; import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem';
const GuacamoleClients = () => { export function GuacamoleClients() {
const defaultGuacamoleClient: GuacamoleClient = { const defaultGuacamoleClient: GuacamoleClient = {
id: undefined, id: undefined,
clientName: '', clientName: '',
@@ -73,127 +72,129 @@ const GuacamoleClients = () => {
return ( return (
<Stack> <Stack>
<AddItemModal {modalOpened && (
modalOpened={modalOpened} <AddItemModal
handleClose={handleClose} modalOpened={modalOpened}
title={'Add a new Guacamole Client'} handleClose={handleClose}
body={ title={'Add a new Guacamole Client'}
<form body={
onSubmit={form.onSubmit((values) => { <form
mutateAsync({ onSubmit={form.onSubmit((values) => {
method: 'POST', mutateAsync({
endpoint: endpoint, method: 'POST',
body: { ...values, enabled: 1 }, endpoint: endpoint,
}); body: { ...values, enabled: 1 },
handleClose(); });
})} handleClose();
> })}
<Stack gap={0} w={'100%'} align='center'> >
<Group w={'100%'} justify='center'> <Stack gap={0} w={'100%'} align='center'>
<TextInput <Group w={'100%'} justify='center'>
key={form.key('clientName')} <TextInput
{...form.getInputProps('clientName')} key={form.key('clientName')}
label={'Client Name'} {...form.getInputProps('clientName')}
placeholder='eg. Workstation' label={'Client Name'}
className={textInputClasses.settingsItem} placeholder='eg. Workstation'
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
<TextInput />
key={form.key('clientIdentifier')} <TextInput
{...form.getInputProps('clientIdentifier')} key={form.key('clientIdentifier')}
label={'clientIdentifier'} {...form.getInputProps('clientIdentifier')}
placeholder='eg. MzIAYwBteXNxbA' label={'clientIdentifier'}
className={textInputClasses.settingsItem} placeholder='eg. MzIAYwBteXNxbA'
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
</Group> />
<Group w={'100%'} justify='center' pos={'relative'}> </Group>
<Select <Group w={'100%'} justify='center' pos={'relative'}>
key={form.key('type')} <Select
{...form.getInputProps('type')} key={form.key('type')}
label={'Type'} {...form.getInputProps('type')}
className={selectClasses.settingsItem} label={'Type'}
classNames={{ className={selectClasses.settingsItem}
wrapper: selectClasses.menuSelectWrapper, classNames={{
input: selectClasses.menuSelectInput, wrapper: selectClasses.menuSelectWrapper,
label: selectClasses.menuSelectLabel, input: selectClasses.menuSelectInput,
options: selectClasses.menuSelectOptions, label: selectClasses.menuSelectLabel,
option: selectClasses.menuSelectOption, options: selectClasses.menuSelectOptions,
dropdown: selectClasses.dropdown, option: selectClasses.menuSelectOption,
}} dropdown: selectClasses.dropdown,
w={'45%'} }}
mt={'.75rem'} w={'45%'}
limit={100} mt={'.75rem'}
placeholder='Client Type' limit={100}
data={types.map((type) => { placeholder='Client Type'
return { data={types.map((type) => {
label: type, return {
value: type, label: type,
}; value: type,
})} };
comboboxProps={{ })}
zIndex: 3000, comboboxProps={{
}} zIndex: 3000,
styles={ }}
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => { }
return ( renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
<Group> return (
{item.option.value} <Group>
{item.option.value === 'SSH' && <FaTerminal />} {item.option.value}
{item.option.value === 'RDP' && <FaComputer />} {item.option.value === 'SSH' && <FaTerminal />}
{item.option.value === 'VNC' && <FaDesktop />} {item.option.value === 'RDP' && <FaComputer />}
</Group> {item.option.value === 'VNC' && <FaDesktop />}
); </Group>
}} );
searchable }}
maxDropdownHeight={175} searchable
onDropdownOpen={() => setIsOpen(true)} maxDropdownHeight={175}
onDropdownClose={() => setIsOpen(false)} onDropdownOpen={() => setIsOpen(true)}
withAsterisk onDropdownClose={() => setIsOpen(false)}
/> withAsterisk
</Group> />
<Group mt={'1rem'}> </Group>
<Button <Group mt={'1rem'}>
type='submit' <Button
className={buttonClasses.saveButton} type='submit'
classNames={buttonClasses} className={buttonClasses.saveButton}
> classNames={buttonClasses}
Save >
</Button> Save
<Button </Button>
className={buttonClasses.cancelButton} <Button
classNames={buttonClasses} className={buttonClasses.cancelButton}
onClick={handleClose} classNames={buttonClasses}
> onClick={handleClose}
Cancel >
</Button> Cancel
</Group> </Button>
</Stack> </Group>
</form> </Stack>
} </form>
/> }
/>
)}
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={openModal} openModal={openModal}
@@ -267,5 +268,4 @@ const GuacamoleClients = () => {
</Stack> </Stack>
</Stack> </Stack>
); );
}; }
export default GuacamoleClients;

View File

@@ -23,22 +23,24 @@ function DebouncedMessage({
w={'100%'} w={'100%'}
style={{ alignItems: 'center' }} style={{ alignItems: 'center' }}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={closeModal} modalOpened={modalOpened}
handleDelete={() => handleDelete(message, 'api/messages')} handleClose={closeModal}
title='Delete message' handleDelete={() => handleDelete(message, 'api/messages')}
body={ title='Delete message'
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this message?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this message?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${message.message}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${message.message}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Box <Box
style={{ style={{
position: 'absolute', position: 'absolute',

View File

@@ -16,7 +16,7 @@ import { buttonClasses, textInputClasses } from 'styles';
import type { Message } from 'types'; import type { Message } from 'types';
import DebouncedMessage from './DebouncedMessage'; import DebouncedMessage from './DebouncedMessage';
const MessageSettings = () => { export function Messages() {
const endpoint = 'api/messages'; const endpoint = 'api/messages';
const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({ const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
@@ -83,60 +83,62 @@ const MessageSettings = () => {
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
/> />
<AddItemModal {modalOpened && (
modalOpened={modalOpened} <AddItemModal
handleClose={handleClose} modalOpened={modalOpened}
title={'Add a new message'} handleClose={handleClose}
body={ title={'Add a new message'}
<form body={
onSubmit={form.onSubmit((values) => { <form
mutateAsync({ onSubmit={form.onSubmit((values) => {
method: 'POST', mutateAsync({
endpoint: endpoint, method: 'POST',
body: { message: values.message }, endpoint: endpoint,
}); body: { message: values.message },
handleClose(); });
})} handleClose();
> })}
<Stack gap={0} align='center'> >
<TextInput <Stack gap={0} align='center'>
autoFocus={!isMobile} <TextInput
key={form.key('message')} autoFocus={!isMobile}
{...form.getInputProps('message')} key={form.key('message')}
label={'Message'} {...form.getInputProps('message')}
placeholder='eg. Hello, World!' label={'Message'}
w={'45%'} placeholder='eg. Hello, World!'
className={textInputClasses.settingsItem} w={'45%'}
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
<Group mt={'1rem'}> />
<Button <Group mt={'1rem'}>
type='submit' <Button
className={buttonClasses.saveButton} type='submit'
classNames={buttonClasses} className={buttonClasses.saveButton}
> classNames={buttonClasses}
Save >
</Button> Save
<Button </Button>
className={buttonClasses.cancelButton} <Button
classNames={buttonClasses} className={buttonClasses.cancelButton}
onClick={handleClose} classNames={buttonClasses}
> onClick={handleClose}
Cancel >
</Button> Cancel
</Group> </Button>
</Stack> </Group>
</form> </Stack>
} </form>
/> }
/>
)}
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}
mb={isMobile ? '6rem' : undefined} mb={isMobile ? '6rem' : undefined}
@@ -173,5 +175,4 @@ const MessageSettings = () => {
</Stack> </Stack>
</Stack> </Stack>
); );
}; }
export default MessageSettings;

View File

@@ -16,7 +16,7 @@ import FontPicker from 'react-fontpicker-ts';
import { cardClasses, switchClasses, textClasses, textInputClasses } from 'styles'; import { cardClasses, switchClasses, textClasses, textInputClasses } from 'styles';
import type { PersonalData } from 'types'; import type { PersonalData } from 'types';
const PersonalizationSettings = () => { export function Personalization() {
const endpoint = 'api/personalData'; const endpoint = 'api/personalData';
const { data: personalData, isFetching: personalDataIsFetching } = useQueryAPI({ const { data: personalData, isFetching: personalDataIsFetching } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
@@ -27,14 +27,14 @@ const PersonalizationSettings = () => {
personal?.enableGuacamole === 1, personal?.enableGuacamole === 1,
); );
const [defaultFont, setDefaultFont] = useState<string>(personal?.defaultFont); const [defaultFont, setDefaultFont] = useState<string>(personal?.defaultFont);
const [update, setUpdate] = useState<any>({}); const [update, setUpdate] = useState<PersonalData>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }); mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
setUpdate({}); setUpdate(undefined);
} }
}, [debounced]); }, [debounced]);
@@ -322,5 +322,4 @@ const PersonalizationSettings = () => {
</Stack> </Stack>
</Stack> </Stack>
); );
}; }
export default PersonalizationSettings;

View File

@@ -30,7 +30,7 @@ function DebouncedSearchProvider({
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [provider, setProvider] = useState<Provider>(searchProvider); const [provider, setProvider] = useState<Provider>(searchProvider);
const [update, setUpdate] = useState<any>({}); const [update, setUpdate] = useState<Provider>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const handleClose = () => { const handleClose = () => {
@@ -39,13 +39,13 @@ function DebouncedSearchProvider({
}; };
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced }); mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced });
} }
}, [debounced]); }, [debounced]);
useEffect(() => { useEffect(() => {
if (update !== provider && Object.keys(update).length) { if (update && update !== provider && Object.keys(update).length) {
setProvider(update); setProvider(update);
} }
}, [update]); }, [update]);
@@ -56,22 +56,24 @@ function DebouncedSearchProvider({
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={handleClose} modalOpened={modalOpened}
handleDelete={() => handleDelete(provider, 'api/providers')} handleClose={handleClose}
title={'Delete Search Provider'} handleDelete={() => handleDelete(provider, 'api/providers')}
body={ title={'Delete Search Provider'}
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this Search Provider?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this Search Provider?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${provider.name}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${provider.name}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Box <Box
style={{ style={{
position: 'absolute', position: 'absolute',

View File

@@ -1,13 +1,17 @@
import { Group, Stack, Text } from '@mantine/core'; import { Group, Stack, Text } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { DataTable, DeleteItemModal, SettingsHeader } from 'components'; import {
import { SearchHistoryColumns } from 'components/Columns/SearchHistoryColumns'; DataTable,
DeleteItemModal,
SearchHistoryColumns,
SettingsHeader,
} from 'components';
import { useCommonHooks, useQueryAPI } from 'hooks'; import { useCommonHooks, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { textClasses } from 'styles'; import { textClasses } from 'styles';
import type { SearchHistoryItem } from 'types'; import type { SearchHistoryItem } from 'types';
const SearchHistory = () => { export function SearchHistory() {
const searchEndpoint = 'api/searchHistory'; const searchEndpoint = 'api/searchHistory';
const { data: historyData, isFetching: historyDataIsFetching } = useQueryAPI({ const { data: historyData, isFetching: historyDataIsFetching } = useQueryAPI({
endpoint: searchEndpoint, endpoint: searchEndpoint,
@@ -41,28 +45,30 @@ const SearchHistory = () => {
return ( return (
<Stack w={'100%'}> <Stack w={'100%'}>
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={handleClose} modalOpened={modalOpened}
handleDelete={() => handleDelete(searchHistoryItem, 'api/searchHistory')} handleClose={handleClose}
title={'Delete Search History Item'} handleDelete={() => handleDelete(searchHistoryItem, 'api/searchHistory')}
body={ title={'Delete Search History Item'}
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this Search History Item?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Group> <Text>Are you sure you want to delete this Search History Item?</Text>
<Text <Group>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`${searchHistoryItem?.provider}`}</Text> classNames={textClasses}
<Text >{`${searchHistoryItem?.provider}`}</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${searchHistoryItem?.searchTerm}"`}</Text> classNames={textClasses}
</Group> >{`"${searchHistoryItem?.searchTerm}"`}</Text>
<Text c='red'>This action cannot be undone!</Text> </Group>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={undefined} openModal={undefined}
@@ -107,6 +113,4 @@ const SearchHistory = () => {
</Stack> </Stack>
</Stack> </Stack>
); );
}; }
export default SearchHistory;

View File

@@ -35,7 +35,7 @@ import type { Provider } from 'types';
import DebouncedSearchProvider from './DebouncedSearchProvider'; import DebouncedSearchProvider from './DebouncedSearchProvider';
import SearchProviderSettingsItem from './SearchProviderSettingsItem'; import SearchProviderSettingsItem from './SearchProviderSettingsItem';
const SearchProviderSettings = () => { export function SearchProviders() {
const allIcons: any = { ...fa6, ...vsc, ...si }; const allIcons: any = { ...fa6, ...vsc, ...si };
const defaultProvider: Provider = { const defaultProvider: Provider = {
id: undefined, id: undefined,
@@ -88,30 +88,120 @@ const SearchProviderSettings = () => {
return ( return (
<Stack> <Stack>
<AddItemModal {modalOpened && (
modalOpened={modalOpened} <AddItemModal
handleClose={handleClose} modalOpened={modalOpened}
title={'Add a new Search Provider'} handleClose={handleClose}
body={ title={'Add a new Search Provider'}
<form body={
onSubmit={form.onSubmit((values) => { <form
mutateAsync({ onSubmit={form.onSubmit((values) => {
method: 'POST', mutateAsync({
endpoint: endpoint, method: 'POST',
body: values, endpoint: endpoint,
}); body: values,
handleClose(); });
})} handleClose();
> })}
<Stack gap={0} w={'100%'} align='center'> >
<Stack w={'100%'} align='center'> <Stack gap={0} w={'100%'} align='center'>
<Group w={'100%'} justify='center' pos={'relative'}> <Stack w={'100%'} align='center'>
<Group w={'100%'} justify='center' pos={'relative'}>
<TextInput
key={form.key('name')}
{...form.getInputProps('name')}
label={'Name'}
placeholder='eg. Google'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: {
boxShadow: '2px 2px var(--mantine-color-border-0)',
},
}
: undefined
}
w={'30%'}
/>
<TextInput
key={form.key('prefix')}
{...form.getInputProps('prefix')}
label={'Prefix'}
placeholder='eg. /g'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: {
boxShadow: '2px 2px var(--mantine-color-border-0)',
},
}
: undefined
}
w={'30%'}
/>
<Select
key={form.key('icon')}
{...form.getInputProps('icon')}
label={'Icon'}
className={selectClasses.settingsItem}
classNames={{
wrapper: selectClasses.menuSelectWrapper,
input: selectClasses.menuSelectInput,
label: selectClasses.menuSelectLabel,
options: selectClasses.menuSelectOptions,
option: selectClasses.menuSelectOption,
dropdown: selectClasses.dropdown,
}}
w={'30%'}
mt={'.75rem'}
limit={100}
placeholder='Search for an icon...'
data={Object.keys(allIcons).map((icon) => {
return { value: icon, label: icon };
})}
comboboxProps={{
zIndex: 3000,
}}
styles={
theme.textShadowsEnabled
? {
label: {
boxShadow: '2px 2px var(--mantine-color-border-0)',
},
}
: undefined
}
renderOption={(
item: ComboboxLikeRenderOptionInput<ComboboxItem>,
) => {
const Icon = allIcons[item.option.value];
return (
<Group>
{item.option.value}
<Icon />
</Group>
);
}}
searchable
clearable
maxDropdownHeight={175}
onDropdownOpen={() => setIsOpen(true)}
onDropdownClose={() => setIsOpen(false)}
/>
</Group>
<TextInput <TextInput
key={form.key('name')} key={form.key('url')}
{...form.getInputProps('name')} {...form.getInputProps('url')}
label={'Name'} label={'Url'}
placeholder='eg. Google' placeholder='eg. https://www.google.com/search?q='
className={textInputClasses.settingsItem} className={textInputClasses.settingsItem}
w={'94%'}
classNames={textInputClasses} classNames={textInputClasses}
withAsterisk withAsterisk
styles={ styles={
@@ -121,141 +211,61 @@ const SearchProviderSettings = () => {
} }
: undefined : undefined
} }
w={'30%'}
/>
<TextInput
key={form.key('prefix')}
{...form.getInputProps('prefix')}
label={'Prefix'}
placeholder='eg. /g'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
w={'30%'}
/>
<Select
key={form.key('icon')}
{...form.getInputProps('icon')}
label={'Icon'}
className={selectClasses.settingsItem}
classNames={{
wrapper: selectClasses.menuSelectWrapper,
input: selectClasses.menuSelectInput,
label: selectClasses.menuSelectLabel,
options: selectClasses.menuSelectOptions,
option: selectClasses.menuSelectOption,
dropdown: selectClasses.dropdown,
}}
w={'30%'}
mt={'.75rem'}
limit={100}
placeholder='Search for an icon...'
data={Object.keys(allIcons).map((icon) => {
return { value: icon, label: icon };
})}
comboboxProps={{
zIndex: 3000,
}}
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const Icon = allIcons[item.option.value];
return (
<Group>
{item.option.value}
<Icon />
</Group>
);
}}
searchable
clearable
maxDropdownHeight={175}
onDropdownOpen={() => setIsOpen(true)}
onDropdownClose={() => setIsOpen(false)}
/> />
</Stack>
<Group
style={{
position: 'absolute',
right: '1rem',
top: '.5rem',
width: 'fit-content',
backgroundColor:
'color-mix(in srgb, var(--mantine-color-background-0), transparent 50%)',
padding: '.5rem',
borderRadius: '10px',
color: 'var(--mantine-color-search-0)',
backdropFilter: 'brightness(0.5)',
}}
>
<Tooltip
className={toolTipClasses.default}
classNames={toolTipClasses}
label={'Default'}
refProp='rootRef'
withArrow
>
<Switch
key={form.key('default')}
{...form.getInputProps('default')}
className={switchClasses.enable}
classNames={switchClasses}
defaultChecked={false}
thumbIcon={<FiPower />}
/>
</Tooltip>
</Group> </Group>
<TextInput
key={form.key('url')}
{...form.getInputProps('url')}
label={'Url'}
placeholder='eg. https://www.google.com/search?q='
className={textInputClasses.settingsItem}
w={'94%'}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
</Stack>
<Group
style={{
position: 'absolute',
right: '1rem',
top: '.5rem',
width: 'fit-content',
backgroundColor:
'color-mix(in srgb, var(--mantine-color-background-0), transparent 50%)',
padding: '.5rem',
borderRadius: '10px',
color: 'var(--mantine-color-search-0)',
backdropFilter: 'brightness(0.5)',
}}
>
<Tooltip
className={toolTipClasses.default}
classNames={toolTipClasses}
label={'Default'}
refProp='rootRef'
withArrow
>
<Switch
key={form.key('default')}
{...form.getInputProps('default')}
className={switchClasses.enable}
classNames={switchClasses}
defaultChecked={false}
thumbIcon={<FiPower />}
/>
</Tooltip>
</Group>
<Group mt={'1rem'}> <Group mt={'1rem'}>
<Button <Button
type='submit' type='submit'
className={buttonClasses.saveButton} className={buttonClasses.saveButton}
classNames={buttonClasses} classNames={buttonClasses}
> >
Save Save
</Button> </Button>
<Button <Button
className={buttonClasses.cancelButton} className={buttonClasses.cancelButton}
classNames={buttonClasses} classNames={buttonClasses}
onClick={handleClose} onClick={handleClose}
> >
Cancel Cancel
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</form> </form>
} }
/> />
)}
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={openModal} openModal={openModal}
@@ -333,5 +343,4 @@ const SearchProviderSettings = () => {
</Stack> </Stack>
</Stack> </Stack>
); );
}; }
export default SearchProviderSettings;

View File

@@ -67,16 +67,18 @@ export function ThemePlaylists() {
return ( return (
<Stack> <Stack>
<ThemePlaylistModal {opened && (
opened={opened} <ThemePlaylistModal
handleClose={handleClose} opened={opened}
selectedPlaylist={selectedPlaylist} handleClose={handleClose}
defaultPlaylist={defaultPlaylist} selectedPlaylist={selectedPlaylist}
setSelectedPlaylist={setSelectedPlaylist} defaultPlaylist={defaultPlaylist}
handleSavePlaylist={handleSavePlaylist} setSelectedPlaylist={setSelectedPlaylist}
allThemes={allThemeData} handleSavePlaylist={handleSavePlaylist}
createPlaylist={mutateAsync} allThemes={allThemeData}
/> createPlaylist={mutateAsync}
/>
)}
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={open} openModal={open}

View File

@@ -42,22 +42,24 @@ export function ThemePlaylistCard({
onMouseEnter={() => setHover(true)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)} onMouseLeave={() => setHover(false)}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={closeModal} modalOpened={modalOpened}
handleDelete={() => handleDelete(cardPlaylist, 'api/themePlaylists')} handleClose={closeModal}
title='Delete Theme Playlist' handleDelete={() => handleDelete(cardPlaylist, 'api/themePlaylists')}
body={ title='Delete Theme Playlist'
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this Theme Playlist?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this Theme Playlist?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${cardPlaylist.name}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${cardPlaylist.name}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Text <Text
style={{ style={{
position: 'absolute', position: 'absolute',

View File

@@ -28,7 +28,6 @@ export function ThemePlaylistModal({
}) { }) {
const { isMobile } = useCommonHooks(); const { isMobile } = useCommonHooks();
const [themeList, setThemeList] = useState<any>([]); const [themeList, setThemeList] = useState<any>([]);
useEffect(() => { useEffect(() => {
if (selectedPlaylist?.themeList) { if (selectedPlaylist?.themeList) {
setThemeList(JSON.parse(selectedPlaylist?.themeList)); setThemeList(JSON.parse(selectedPlaylist?.themeList));
@@ -49,7 +48,7 @@ export function ThemePlaylistModal({
centered centered
onClose={() => { onClose={() => {
handleClose(); handleClose();
setThemeList('[]'); setThemeList([]);
}} }}
keepMounted={false} keepMounted={false}
> >

View File

@@ -1,8 +1,7 @@
import { ActionIcon, Group, isLightColor, Stack, Text } from '@mantine/core'; import { ActionIcon, Group, isLightColor, Stack, Text } from '@mantine/core';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useState } from 'react'; import { useState } from 'react';
import { FaTrashAlt } from 'react-icons/fa'; import { FaPencil, FaTrash } from 'react-icons/fa6';
import { FaPencil } from 'react-icons/fa6';
import { FiEdit, FiPower, FiShuffle } from 'react-icons/fi'; import { FiEdit, FiPower, FiShuffle } from 'react-icons/fi';
import { actionIconClasses } from 'styles'; import { actionIconClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types'; import type { Theme, ThemePlaylist } from 'types';
@@ -238,7 +237,7 @@ export function ThemeManagementPill({
onClick={openModal} onClick={openModal}
disabled={cardTheme.name === theme.name} disabled={cardTheme.name === theme.name}
> >
<FaTrashAlt <FaTrash
style={{ padding: '.25rem', filter: 'brightness(1.25)' }} style={{ padding: '.25rem', filter: 'brightness(1.25)' }}
color='Brown' color='Brown'
/> />

View File

@@ -27,7 +27,7 @@ export function NewThemeStarter({
setSelectedTheme: (selectedTheme: Theme) => void; setSelectedTheme: (selectedTheme: Theme) => void;
setDefaultTheme: (selectedTheme: Theme) => void; setDefaultTheme: (selectedTheme: Theme) => void;
}) { }) {
const [pressed, setPressed] = useState<any>(false); const [pressed, setPressed] = useState<boolean>(false);
const handlers = useLongPress(() => { const handlers = useLongPress(() => {
setPressed(true); setPressed(true);
}); });

View File

@@ -10,8 +10,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { FaClipboard } from 'react-icons/fa'; import { FaClipboard, FaX } from 'react-icons/fa6';
import { FiX } from 'react-icons/fi';
import { scrollAreaClasses, toolTipClasses } from 'styles'; import { scrollAreaClasses, toolTipClasses } from 'styles';
import { type Theme, ThemeProperties } from 'types'; import { type Theme, ThemeProperties } from 'types';
@@ -104,7 +103,7 @@ export default function ThemeChanges({
pos={'absolute'} pos={'absolute'}
right={0} right={0}
> >
<FiX /> <FaX />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Group> </Group>

View File

@@ -1,55 +0,0 @@
import { Box, Stack, Text } from '@mantine/core';
import { useState } from 'react';
import { FiCheck } from 'react-icons/fi';
import { boxClasses, textClasses } from 'styles';
import type { Theme } from 'types';
export function ThemeExampleApp({ selectedTheme }: { selectedTheme: Theme }) {
const [textDecoration, setTextDecoration] = useState<string>('none');
return (
<Box
component='a'
onMouseOver={() => setTextDecoration('underline')}
onMouseOut={() => setTextDecoration('none')}
className={boxClasses.appItemBox}
h={'64px'}
>
<Stack
style={{
marginRight: '1rem',
marginLeft: '1rem',
}}
justify='center'
>
<FiCheck style={{ color: selectedTheme?.primaryColor }} />
</Stack>
<Stack
style={{
justifyContent: 'center',
overflow: 'hidden',
textOverflow: 'ellipsis',
gap: 0,
}}
>
<Text
c={selectedTheme?.primaryColor}
className={textClasses.appName}
classNames={textClasses}
>
{'Example App'}
</Text>
<Text
c={selectedTheme?.accentColor}
className={textClasses.appUrl}
classNames={textClasses}
style={{
textDecoration: textDecoration,
}}
>
{'https://www.example.com'}
</Text>
</Stack>
</Box>
);
}

View File

@@ -11,9 +11,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import FontPicker from 'react-fontpicker-ts'; import FontPicker from 'react-fontpicker-ts';
import { FaFont, FaGoogle } from 'react-icons/fa'; import { FaFont, FaGoogle, FaShuffle, FaX } from 'react-icons/fa6';
import { FaShuffle } from 'react-icons/fa6';
import { FiX } from 'react-icons/fi';
import { textClasses, toolTipClasses } from 'styles'; import { textClasses, toolTipClasses } from 'styles';
import type { Theme } from 'types'; import type { Theme } from 'types';
import ThemeChanges from './ThemeChanges'; import ThemeChanges from './ThemeChanges';
@@ -192,7 +190,7 @@ export default function ThemeOptions({
borderRadius: '0px', borderRadius: '0px',
}} }}
> >
<FiX /> <FaX />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Group> </Group>

View File

@@ -17,7 +17,7 @@ export function ThemePreviewMiddleware({
selectedPlaylist: any; selectedPlaylist: any;
setSelectedPlaylist: (selectedPlaylist: any) => void; setSelectedPlaylist: (selectedPlaylist: any) => void;
}) { }) {
const [pressed, setPressed] = useState<any>(false); const [pressed, setPressed] = useState<boolean>(false);
const handlers = useLongPress(() => { const handlers = useLongPress(() => {
setPressed(true); setPressed(true);
}); });

View File

@@ -3,7 +3,7 @@ import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { DeleteItemModal } from 'components'; import { DeleteItemModal } from 'components';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiTrash } from 'react-icons/fi'; import { FaTrash } from 'react-icons/fa';
import { cardClasses, textClasses, textInputClasses, toolTipClasses } from 'styles'; import { cardClasses, textClasses, textInputClasses, toolTipClasses } from 'styles';
import type { WolClient } from 'types'; import type { WolClient } from 'types';
@@ -19,7 +19,7 @@ function DebouncedWolClient({
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [client, setClient] = useState<WolClient>(wolClient); const [client, setClient] = useState<WolClient>(wolClient);
const [update, setUpdate] = useState<any>({}); const [update, setUpdate] = useState<WolClient>();
const [debounced] = useDebouncedValue(update, 1000); const [debounced] = useDebouncedValue(update, 1000);
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
@@ -27,13 +27,13 @@ function DebouncedWolClient({
}; };
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced }); mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced });
} }
}, [debounced]); }, [debounced]);
useEffect(() => { useEffect(() => {
if (update !== client && Object.keys(update).length) { if (update && update !== client && Object.keys(update).length) {
setClient(update); setClient(update);
} }
}, [update]); }, [update]);
@@ -44,22 +44,24 @@ function DebouncedWolClient({
className={cardClasses.settingsItem} className={cardClasses.settingsItem}
classNames={cardClasses} classNames={cardClasses}
> >
<DeleteItemModal {modalOpened && (
modalOpened={modalOpened} <DeleteItemModal
handleClose={handleClose} modalOpened={modalOpened}
handleDelete={() => handleDelete(client, 'api/WolClients')} handleClose={handleClose}
title={'Delete WoL Client'} handleDelete={() => handleDelete(client, 'api/WolClients')}
body={ title={'Delete WoL Client'}
<Stack gap={0} w={'100%'} h={'150px'} align='center'> body={
<Text>Are you sure you want to delete this WoL Client?</Text> <Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text <Text>Are you sure you want to delete this WoL Client?</Text>
className={textClasses.deleteItem} <Text
classNames={textClasses} className={textClasses.deleteItem}
>{`"${client.hostname}"`}</Text> classNames={textClasses}
<Text c='red'>This action cannot be undone!</Text> >{`"${client.hostname}"`}</Text>
</Stack> <Text c='red'>This action cannot be undone!</Text>
} </Stack>
/> }
/>
)}
<Tooltip <Tooltip
className={toolTipClasses.default} className={toolTipClasses.default}
classNames={toolTipClasses} classNames={toolTipClasses}
@@ -73,7 +75,7 @@ function DebouncedWolClient({
openModal(); openModal();
}} }}
> >
<FiTrash color='black' /> <FaTrash color='black' />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Group w={'100%'} justify='center'> <Group w={'100%'} justify='center'>

View File

@@ -18,7 +18,7 @@ import type { WolClient } from 'types';
import DebouncedWolClient from './DebouncedWolClient'; import DebouncedWolClient from './DebouncedWolClient';
import WolClientSettingsItem from './WolClientSettingsItem'; import WolClientSettingsItem from './WolClientSettingsItem';
const WolClientSettings = () => { export function WolClients() {
const defaultWolClient: WolClient = { const defaultWolClient: WolClient = {
id: undefined, id: undefined,
hostname: '', hostname: '',
@@ -65,76 +65,78 @@ const WolClientSettings = () => {
return ( return (
<Stack> <Stack>
<AddItemModal {modalOpened && (
modalOpened={modalOpened} <AddItemModal
handleClose={handleClose} modalOpened={modalOpened}
title={'Add a new WoL Client'} handleClose={handleClose}
body={ title={'Add a new WoL Client'}
<form body={
onSubmit={form.onSubmit((values) => { <form
mutateAsync({ onSubmit={form.onSubmit((values) => {
method: 'POST', mutateAsync({
endpoint: endpoint, method: 'POST',
body: values, endpoint: endpoint,
}); body: values,
handleClose(); });
})} handleClose();
> })}
<Stack gap={0} w={'100%'} align='center'> >
<Group w={'100%'} justify='center'> <Stack gap={0} w={'100%'} align='center'>
<TextInput <Group w={'100%'} justify='center'>
key={form.key('hostname')} <TextInput
{...form.getInputProps('hostname')} key={form.key('hostname')}
label={'Hostname'} {...form.getInputProps('hostname')}
placeholder='eg. Workstation' label={'Hostname'}
className={textInputClasses.settingsItem} placeholder='eg. Workstation'
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
<TextInput />
key={form.key('macAddress')} <TextInput
{...form.getInputProps('macAddress')} key={form.key('macAddress')}
label={'MAC Address'} {...form.getInputProps('macAddress')}
placeholder='eg. 00:00:00:00:00:00' label={'MAC Address'}
className={textInputClasses.settingsItem} placeholder='eg. 00:00:00:00:00:00'
classNames={textInputClasses} className={textInputClasses.settingsItem}
withAsterisk classNames={textInputClasses}
styles={ withAsterisk
theme.textShadowsEnabled styles={
? { theme.textShadowsEnabled
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, ? {
} label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
: undefined }
} : undefined
/> }
</Group> />
<Group mt={'1rem'}> </Group>
<Button <Group mt={'1rem'}>
type='submit' <Button
className={buttonClasses.saveButton} type='submit'
classNames={buttonClasses} className={buttonClasses.saveButton}
> classNames={buttonClasses}
Save >
</Button> Save
<Button </Button>
className={buttonClasses.cancelButton} <Button
classNames={buttonClasses} className={buttonClasses.cancelButton}
onClick={handleClose} classNames={buttonClasses}
> onClick={handleClose}
Cancel >
</Button> Cancel
</Group> </Button>
</Stack> </Group>
</form> </Stack>
} </form>
/> }
/>
)}
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={openModal} openModal={openModal}
@@ -204,5 +206,4 @@ const WolClientSettings = () => {
</Stack> </Stack>
</Stack> </Stack>
); );
}; }
export default WolClientSettings;

View File

@@ -0,0 +1,10 @@
export { Applications } from './Applications/Applications';
export { Bookmarks } from './Bookmarks/Bookmarks';
export { GuacamoleClients } from './GuacamoleClients/GuacamoleClients';
export { Messages } from './Messages/Messages';
export { Personalization } from './Personalization/Personalization';
export { SearchHistory } from './Search/SearchHistory';
export { SearchProviders } from './Search/SearchProviders';
export { ThemePlaylists } from './Theming/ThemePlaylists';
export { Themes } from './Theming/Themes';
export { WolClients } from './WoL/WolClients';