Application adding bugfix; consolidated Settings headers

This commit is contained in:
Sean Edmunds
2025-04-03 13:37:22 -05:00
parent f5e193853f
commit 6493a458ff
19 changed files with 902 additions and 1081 deletions

25
package-lock.json generated
View File

@@ -12,7 +12,6 @@
"@iconify/react": "^5.2.0", "@iconify/react": "^5.2.0",
"@mantine/core": "^7.17.3", "@mantine/core": "^7.17.3",
"@mantine/hooks": "^7.17.3", "@mantine/hooks": "^7.17.3",
"@tabler/icons-react": "^3.31.0",
"@tanstack/react-query": "^5.69.3", "@tanstack/react-query": "^5.69.3",
"@tanstack/react-query-devtools": "^5.69.3", "@tanstack/react-query-devtools": "^5.69.3",
"@tanstack/react-query-persist-client": "^5.69.3", "@tanstack/react-query-persist-client": "^5.69.3",
@@ -1588,30 +1587,6 @@
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"node_modules/@tabler/icons": {
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz",
"integrity": "sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
}
},
"node_modules/@tabler/icons-react": {
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz",
"integrity": "sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==",
"dependencies": {
"@tabler/icons": "3.31.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
},
"peerDependencies": {
"react": ">= 16"
}
},
"node_modules/@tanstack/query-core": { "node_modules/@tanstack/query-core": {
"version": "5.69.2", "version": "5.69.2",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.69.2.tgz", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.69.2.tgz",

View File

@@ -14,7 +14,6 @@
"@iconify/react": "^5.2.0", "@iconify/react": "^5.2.0",
"@mantine/core": "^7.17.3", "@mantine/core": "^7.17.3",
"@mantine/hooks": "^7.17.3", "@mantine/hooks": "^7.17.3",
"@tabler/icons-react": "^3.31.0",
"@tanstack/react-query": "^5.69.3", "@tanstack/react-query": "^5.69.3",
"@tanstack/react-query-devtools": "^5.69.3", "@tanstack/react-query-devtools": "^5.69.3",
"@tanstack/react-query-persist-client": "^5.69.3", "@tanstack/react-query-persist-client": "^5.69.3",

View File

@@ -7,7 +7,6 @@ import {
Grid, Grid,
GridCol, GridCol,
Group, Group,
Modal,
Popover, Popover,
Stack, Stack,
Text, Text,
@@ -16,21 +15,15 @@ import {
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { textInputClasses } from 'styles';
import type { Application } from 'types'; import type { Application } from 'types';
import AddItemModal from '../Shared/AddItemModal';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import AppSettingsItem from './AppSettingsItem'; import AppSettingsItem from './AppSettingsItem';
import DebouncedApplication from './DebouncedApplication'; import DebouncedApplication from './DebouncedApplication';
const ApplicationSettings = () => { const ApplicationSettings = () => {
const endpoint = 'api/apps'; const defaultApplication: Application = {
const { data: apps, isFetching: isAppsFetching } = useQueryAPI({
endpoint: endpoint,
});
const [applicationData, setApplicationData] = useState<Application[]>([]);
const [applicationList, setApplicationList] = useState<Application[]>([]);
const { theme, isMobile, modalStyle } = useCommonHooks();
const [newApplication, setNewApplication] = useState<Application>({
id: null, id: null,
name: '', name: '',
url: '', url: '',
@@ -39,7 +32,16 @@ const ApplicationSettings = () => {
filterName: '', filterName: '',
index: 0, index: 0,
enabled: 1, enabled: 1,
};
const appsEndpoint = 'api/apps';
const appsOrderEndpoint = 'api/appsOrder';
const { data: apps, isFetching: isAppsFetching } = useQueryAPI({
endpoint: appsEndpoint,
}); });
const [applicationData, setApplicationData] = useState<Application[]>([]);
const [applicationList, setApplicationList] = useState<Application[]>([]);
const { theme, isMobile } = useCommonHooks();
const [newApplication, setNewApplication] = useState<Application>(defaultApplication);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
@@ -74,6 +76,11 @@ const ApplicationSettings = () => {
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const handleClose = () => {
closeModal();
setNewApplication(defaultApplication);
};
useEffect(() => { useEffect(() => {
if (apps && !isAppsFetching) { if (apps && !isAppsFetching) {
setApplicationData( setApplicationData(
@@ -90,7 +97,7 @@ const ApplicationSettings = () => {
if (applicationList.length > 0 && applicationList !== applicationData) { if (applicationList.length > 0 && applicationList !== applicationData) {
mutateAsync({ mutateAsync({
method: 'PATCH', method: 'PATCH',
endpoint: 'api/applicationOrder', endpoint: appsOrderEndpoint,
body: applicationList, body: applicationList,
}); });
} }
@@ -98,134 +105,116 @@ const ApplicationSettings = () => {
return ( return (
<Stack w={'100%'}> <Stack w={'100%'}>
<Modal.Root <AddItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={handleClose}
classNames={modalClasses} title={'Add a new application'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} align='center'>
onClose={() => { <Group justify='center'>
closeModal(); <TextInput
}} label={'Name'}
style={modalStyle} className={textInputClasses.settingsItem}
closeOnEscape pt={16}
closeOnClickOutside classNames={textInputClasses}
> defaultValue={newApplication.name}
<Modal.Overlay /> onChange={(event) => {
<Modal.Content> setNewApplication({
<Modal.Header> ...newApplication,
<Modal.Title>Add a new application</Modal.Title> name: event.target.value,
<Modal.CloseButton /> });
</Modal.Header> }}
<Modal.Body pb={'2rem'}> />
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <TextInput
<Stack gap={0} align='center'> label={'Base URL'}
<Group justify='center'> className={textInputClasses.settingsItem}
<TextInput pt={16}
label={'Name'} classNames={textInputClasses}
className={textInputClasses.settingsItem} defaultValue={newApplication.baseUrl}
pt={16} onChange={(event) => {
classNames={textInputClasses} setNewApplication({
defaultValue={newApplication.name} ...newApplication,
onChange={(event) => { baseUrl: event.target.value,
setNewApplication({ });
...newApplication, }}
name: event.target.value, />
}); </Group>
}} <Group w={isMobile ? undefined : '60%'} justify='center'>
/> <TextInput
<TextInput label={'URL'}
label={'Base URL'} className={textInputClasses.settingsItem}
className={textInputClasses.settingsItem} pt={16}
pt={16} w={isMobile ? undefined : '91%'}
classNames={textInputClasses} classNames={textInputClasses}
defaultValue={newApplication.baseUrl} defaultValue={newApplication.url}
onChange={(event) => { onChange={(event) => {
setNewApplication({ setNewApplication({
...newApplication, ...newApplication,
baseUrl: event.target.value, url: event.target.value,
}); });
}} }}
/> />
</Group> </Group>
<Group w={isMobile ? undefined : '60%'} justify='center'> <Group justify='center'>
<TextInput <TextInput
label={'URL'} label={'Icon'}
className={textInputClasses.settingsItem} className={textInputClasses.settingsItem}
pt={16} pt={16}
w={isMobile ? undefined : '91%'} classNames={textInputClasses}
classNames={textInputClasses} defaultValue={newApplication.icon}
defaultValue={newApplication.url} onChange={(event) => {
onChange={(event) => { setNewApplication({
setNewApplication({ ...newApplication,
...newApplication, icon: event.target.value,
url: event.target.value, });
}); }}
}} />
/> <TextInput
</Group> label={'Filter'}
<Group justify='center'> className={textInputClasses.settingsItem}
<TextInput pt={16}
label={'Icon'} classNames={textInputClasses}
className={textInputClasses.settingsItem} defaultValue={newApplication.filterName}
pt={16} onChange={(event) => {
classNames={textInputClasses} setNewApplication({
defaultValue={newApplication.icon} ...newApplication,
onChange={(event) => { filterName: event.target.value,
setNewApplication({ });
...newApplication, }}
icon: event.target.value, />
}); </Group>
}}
/>
<TextInput
label={'Filter'}
className={textInputClasses.settingsItem}
pt={16}
classNames={textInputClasses}
defaultValue={newApplication.filterName}
onChange={(event) => {
setNewApplication({
...newApplication,
filterName: event.target.value,
});
}}
/>
</Group>
<Divider color='black' my='lg' /> <Divider color='black' my='lg' />
<Group pos={'absolute'} bottom={8}> <Group pos={'absolute'} bottom={8}>
<Button <Button
color={'green'} color={'green'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
if (newApplication) { if (newApplication) {
mutateAsync({ mutateAsync({
method: 'POST', method: 'POST',
endpoint: endpoint, endpoint: appsEndpoint,
body: newApplication, body: {
}); ...newApplication,
closeModal(); index: applicationData.length,
} },
}} }).then(handleClose);
> }
Save }}
</Button> >
<Button Save
color={'red'} </Button>
styles={{ label: { color: 'black' } }} <Button
onClick={() => { color={'red'}
closeModal(); styles={{ label: { color: 'black' } }}
}} onClick={handleClose}
> >
Cancel Cancel
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</Card> }
</Modal.Body> />
</Modal.Content>
</Modal.Root>
<SettingsHeader <SettingsHeader
setDragging={setDragging} setDragging={setDragging}
openModal={openModal} openModal={openModal}

View File

@@ -3,7 +3,6 @@ import {
Button, Button,
Card, Card,
Group, Group,
Modal,
Stack, Stack,
Switch, Switch,
Text, Text,
@@ -11,14 +10,15 @@ import {
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons-react';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { FiTrash } from 'react-icons/fi';
import { cardClasses, textInputClasses } from 'styles';
import type { Application } from 'types'; import type { Application } from 'types';
import DeleteItemModal from '../Shared/DeleteItemModal';
function DebouncedApplication({ application }: { application: Application }) { function DebouncedApplication({ application }: { application: Application }) {
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks(); const { theme, handleDelete } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
@@ -39,65 +39,49 @@ function DebouncedApplication({ application }: { application: Application }) {
}, [update]); }, [update]);
return ( return (
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <Card className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
fullScreen={isMobile} handleClose={closeModal}
className={modalClasses.helpModal} title='Delete Application'
classNames={modalClasses} body={
opened={modalOpened} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this application?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete Application</Modal.Title> }}
<Modal.CloseButton /> >{`"${app.name}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this application?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(app, 'api/apps');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${app.name}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(app, 'api/apps'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} />
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Tooltip <Tooltip
color={theme.primaryColor} color={theme.primaryColor}
bd={`solid 1px ${theme.borderColor}`} bd={`solid 1px ${theme.borderColor}`}
@@ -111,7 +95,7 @@ function DebouncedApplication({ application }: { application: Application }) {
openModal(); openModal();
}} }}
> >
<IconTrash /> <FiTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip <Tooltip

View File

@@ -1,40 +1,34 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { import { Box, Button, Card, Divider, Group, Stack, Text, TextInput } from '@mantine/core';
Box,
Button,
Card,
Divider,
Group,
Modal,
Stack,
Text,
TextInput,
} from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { textInputClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types'; import type { Bookmark, BookmarkGroup } from 'types';
import AddItemModal from '../Shared/AddItemModal';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem'; import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
const BookmarkSettings = () => { const BookmarkSettings = () => {
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({ const defaultGroup = {
endpoint: 'api/links',
});
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: 'api/categories',
});
const [dragging, setDragging] = useState(false);
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
const [newGroup, setNewGroup] = useState<BookmarkGroup>({
id: undefined, id: undefined,
categoryName: '', categoryName: '',
categoryIndex: 0, categoryIndex: 0,
filterName: '', filterName: '',
};
const linksEndpoint = 'api/links';
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
endpoint: linksEndpoint,
}); });
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const categoriesEndpoint = 'api/categories';
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: categoriesEndpoint,
});
const [dragging, setDragging] = useState(false);
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
const [newGroup, setNewGroup] = useState<BookmarkGroup>(defaultGroup);
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]); const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
const { theme, isMobile, modalStyle } = useCommonHooks(); const { theme, isMobile } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500); const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
@@ -47,6 +41,11 @@ const BookmarkSettings = () => {
return result; return result;
}; };
const handleClose = () => {
setNewGroup(defaultGroup);
closeModal();
};
function onDragEnd(result: any) { function onDragEnd(result: any) {
if (!result.destination) { if (!result.destination) {
return; return;
@@ -95,90 +94,70 @@ const BookmarkSettings = () => {
return ( return (
<Stack> <Stack>
<Modal.Root <AddItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={handleClose}
classNames={modalClasses} title={'Add a new Bookmark Group'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} align='center'>
onClose={() => { <Group w={'100%'} justify='center'>
closeModal(); <TextInput
}} label={'Category Name'}
style={modalStyle} className={textInputClasses.settingsItem}
closeOnEscape pt={16}
closeOnClickOutside classNames={textInputClasses}
> defaultValue={newGroup.categoryName}
<Modal.Overlay /> onChange={(event) => {
<Modal.Content> setNewGroup({
<Modal.Header> ...newGroup,
<Modal.Title>Add a new Bookmark Group</Modal.Title> categoryName: event.target.value,
<Modal.CloseButton /> });
</Modal.Header> }}
<Modal.Body pb={'2rem'}> />
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <TextInput
<Stack gap={0} w={'100%'} align='center'> label={'Filter Name'}
<Group w={'100%'} justify='center'> className={textInputClasses.settingsItem}
<TextInput pt={16}
label={'Category Name'} classNames={textInputClasses}
className={textInputClasses.settingsItem} defaultValue={newGroup.filterName}
pt={16} onChange={(event) => {
classNames={textInputClasses} setNewGroup({
defaultValue={newGroup.categoryName} ...newGroup,
onChange={(event) => { filterName: event.target.value,
setNewGroup({ });
...newGroup, }}
categoryName: event.target.value, />
}); </Group>
}}
/>
<TextInput
label={'Filter Name'}
className={textInputClasses.settingsItem}
pt={16}
classNames={textInputClasses}
defaultValue={newGroup.filterName}
onChange={(event) => {
setNewGroup({
...newGroup,
filterName: event.target.value,
});
}}
/>
</Group>
<Divider color='black' my='lg' /> <Divider color='black' my='lg' />
<Group pos={'absolute'} bottom={8}> <Group pos={'absolute'} bottom={8}>
<Button <Button
color={'green'} color={'green'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
if (newGroup) { if (newGroup) {
mutateAsync({ mutateAsync({
method: 'POST', method: 'POST',
endpoint: 'api/categories', endpoint: categoriesEndpoint,
body: { ...newGroup, categoryIndex: groups.length }, body: { ...newGroup, categoryIndex: groups.length },
}); });
closeModal(); handleClose();
} }
}} }}
> >
Save Save
</Button> </Button>
<Button <Button
color={'red'} color={'red'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={handleClose}
closeModal(); >
}} Cancel
> </Button>
Cancel </Group>
</Button> </Stack>
</Group> }
</Stack> />
</Card>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<SettingsHeader <SettingsHeader
setDragging={setDragging} setDragging={setDragging}
openModal={openModal} openModal={openModal}

View File

@@ -3,21 +3,24 @@ import {
Button, Button,
Card, Card,
Group, Group,
Modal,
Stack, Stack,
Text, Text,
TextInput, TextInput,
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons-react';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { FiTrash } from 'react-icons/fi';
import { cardClasses, textInputClasses } from 'styles';
import type { Bookmark } from 'types'; import type { Bookmark } from 'types';
import DeleteItemModal from '../Shared/DeleteItemModal';
function DebouncedBookmarkItem({ bookmarkItem }: { bookmarkItem: Bookmark }) { function DebouncedBookmarkItem({
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks(); bookmarkItem,
setIsOpen,
}: { bookmarkItem: Bookmark; setIsOpen: any }) {
const { theme, handleDelete } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
@@ -39,65 +42,49 @@ function DebouncedBookmarkItem({ bookmarkItem }: { bookmarkItem: Bookmark }) {
return ( return (
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <Card className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={closeModal}
classNames={modalClasses} body={
opened={modalOpened} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
fullScreen={isMobile} <Text>Are you sure you want to delete this bookmark?</Text>
onClose={() => { <Text
closeModal(); style={{
}} fontWeight: 'bold',
style={modalStyle} fontSize: '20px',
closeOnEscape padding: '8px',
closeOnClickOutside margin: '8px',
> borderRadius: '10px',
<Modal.Overlay style={{ zIndex: 999 }} /> backgroundColor: 'var(--color-text-acc)',
<Modal.Content style={{ zIndex: 1000 }}> color: 'var(--color-background)',
<Modal.Header> }}
<Modal.Title>Delete Bookmark</Modal.Title> >{`"${bookmark.name}"`}</Text>
<Modal.CloseButton /> <Text c='red'>This action cannot be undone!</Text>
</Modal.Header> <Group pos={'absolute'} bottom={8}>
<Modal.Body pb={'2rem'}> <Button
<Stack gap={0} w={'100%'} h={'150px'} align='center'> color={'red'}
<Text>Are you sure you want to delete this bookmark?</Text> styles={{ label: { color: 'black' } }}
<Text onClick={() => {
style={{ handleDelete(bookmark, 'api/links');
fontWeight: 'bold', closeModal();
fontSize: '20px',
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${bookmark.name}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(bookmark, 'api/links'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} title='Delete Bookmark'
onClick={() => { />
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Tooltip <Tooltip
color={theme.primaryColor} color={theme.primaryColor}
bd={`solid 1px ${theme.borderColor}`} bd={`solid 1px ${theme.borderColor}`}
@@ -108,10 +95,11 @@ function DebouncedBookmarkItem({ bookmarkItem }: { bookmarkItem: Bookmark }) {
right={16} right={16}
color='red' color='red'
onClick={() => { onClick={() => {
setIsOpen(true);
openModal(); openModal();
}} }}
> >
<IconTrash /> <FiTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Group w={'100%'} justify='center'> <Group w={'100%'} justify='center'>

View File

@@ -5,7 +5,6 @@ import {
Button, Button,
Card, Card,
Group, Group,
Modal,
Popover, Popover,
Stack, Stack,
Text, Text,
@@ -13,12 +12,12 @@ import {
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons-react';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiPlus } from 'react-icons/fi'; import { FiPlus, FiTrash } from 'react-icons/fi';
import { buttonClasses, cardClasses, modalClasses, textInputClasses } from 'styles'; import { buttonClasses, cardClasses, textInputClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types'; import type { Bookmark, BookmarkGroup } from 'types';
import DeleteItemModal from '../Shared/DeleteItemModal';
import BookmarkSettingsItem from './BookmarkSettingsItem'; import BookmarkSettingsItem from './BookmarkSettingsItem';
import DebouncedBookmarkItem from './DebouncedBookmarkItem'; import DebouncedBookmarkItem from './DebouncedBookmarkItem';
@@ -26,7 +25,7 @@ function DebouncedGroupSettingsItem({
bookmarkGroup, bookmarkGroup,
bookmarks, bookmarks,
}: { bookmarkGroup: BookmarkGroup; bookmarks: Bookmark[] }) { }: { bookmarkGroup: BookmarkGroup; bookmarks: Bookmark[] }) {
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks(); const { theme, isMobile, handleDelete } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [popoverOpened, setPopoverOpened] = useState(false); const [popoverOpened, setPopoverOpened] = useState(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
@@ -41,6 +40,7 @@ function DebouncedGroupSettingsItem({
index: 0, index: 0,
}); });
const [bookmarkList, setBookmarkList] = useState<Bookmark[]>(bookmarks); const [bookmarkList, setBookmarkList] = useState<Bookmark[]>(bookmarks);
const [isOpen, setIsOpen] = useState(false);
const reorder = (list: any, startIndex: any, endIndex: any) => { const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list); const result = Array.from(list);
@@ -90,65 +90,49 @@ function DebouncedGroupSettingsItem({
return ( return (
<Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}> <Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={closeModal}
classNames={modalClasses} title='Delete message'
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this bookmark group?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete Group</Modal.Title> }}
<Modal.CloseButton /> >{`"${group.categoryName}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this bookmark group?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(group, 'api/categories');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${group.categoryName}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(group, 'api/categories'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} />
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Card.Section p={16} bg={theme.searchColor} c={theme.backgroundColor}> <Card.Section p={16} bg={theme.searchColor} c={theme.backgroundColor}>
<Stack gap={0}> <Stack gap={0}>
<Group gap={0} align='center' justify='space-between'> <Group gap={0} align='center' justify='space-between'>
@@ -201,7 +185,7 @@ function DebouncedGroupSettingsItem({
openModal(); openModal();
}} }}
> >
<IconTrash /> <FiTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Group> </Group>
@@ -243,7 +227,7 @@ function DebouncedGroupSettingsItem({
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} {...provided.dragHandleProps}
> >
<Popover withArrow withOverlay> <Popover withArrow withOverlay closeOnClickOutside={!isOpen}>
<Popover.Target> <Popover.Target>
<Box> <Box>
<BookmarkSettingsItem bookmark={bookmark} /> <BookmarkSettingsItem bookmark={bookmark} />
@@ -259,7 +243,10 @@ function DebouncedGroupSettingsItem({
'--image-background': `url(${theme.backgroundImage})`, '--image-background': `url(${theme.backgroundImage})`,
}} }}
> >
<DebouncedBookmarkItem bookmarkItem={bookmark} /> <DebouncedBookmarkItem
bookmarkItem={bookmark}
setIsOpen={setIsOpen}
/>
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>
{provided.placeholder} {provided.placeholder}
@@ -284,6 +271,7 @@ function DebouncedGroupSettingsItem({
opened={popoverOpened} opened={popoverOpened}
onChange={setPopoverOpened} onChange={setPopoverOpened}
closeOnEscape closeOnEscape
// closeOnClickOutside={}
> >
<Popover.Target> <Popover.Target>
<Button <Button

View File

@@ -3,81 +3,63 @@ import {
Button, Button,
Card, Card,
Group, Group,
Modal,
Stack, Stack,
Text, Text,
TextInput, TextInput,
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons-react';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { FiTrash } from 'react-icons/fi';
import { cardClasses, textInputClasses } from 'styles';
import DeleteItemModal from '../Shared/DeleteItemModal';
function DebouncedMessage({ label, message, setMessage }: any) { function DebouncedMessage({ label, message, setMessage }: any) {
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks(); const { theme, handleDelete } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
return ( return (
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <Card className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={closeModal}
classNames={modalClasses} title='Delete message'
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this message?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete message</Modal.Title> }}
<Modal.CloseButton /> >{`"${message.message}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this message?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(message, 'api/messages');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${message.message}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={closeModal}
handleDelete(message, 'api/messages'); >
closeModal(); Cancel
}} </Button>
> </Group>
Delete </Stack>
</Button> }
<Button />
color={'grey'}
styles={{ label: { color: 'black' } }}
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Tooltip <Tooltip
color={theme.primaryColor} color={theme.primaryColor}
bd={`solid 1px ${theme.borderColor}`} bd={`solid 1px ${theme.borderColor}`}
@@ -91,7 +73,7 @@ function DebouncedMessage({ label, message, setMessage }: any) {
openModal(); openModal();
}} }}
> >
<IconTrash /> <FiTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<TextInput <TextInput

View File

@@ -1,27 +1,18 @@
import { import { Button, Divider, Grid, GridCol, Group, Stack, TextInput } from '@mantine/core';
Button,
Card,
Divider,
Grid,
GridCol,
Group,
Modal,
Stack,
TextInput,
} from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import DebouncedMessage from 'pages/settings/components/Messages/DebouncedMessage'; import DebouncedMessage from 'pages/settings/components/Messages/DebouncedMessage';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { textInputClasses } from 'styles';
import type { Message } from 'types'; import type { Message } from 'types';
import AddItemModal from '../Shared/AddItemModal';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
const MessageSettings = () => { const MessageSettings = () => {
const endpoint = 'api/messages'; const endpoint = 'api/messages';
const { data: messageData, isFetched: isMessagesFetched } = useQueryAPI({ const { data: messageData, isFetched: isMessagesFetched } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
}); });
const { isMobile, modalStyle } = useCommonHooks(); const { isMobile } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
const [newMessage, setNewMessage] = useState(''); const [newMessage, setNewMessage] = useState('');
@@ -32,6 +23,12 @@ const MessageSettings = () => {
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const handleClose = () => {
setNewMessage('');
setNewMessageError('');
closeModal();
};
useEffect(() => { useEffect(() => {
if (messageData && isMessagesFetched) { if (messageData && isMessagesFetched) {
setMessages( setMessages(
@@ -50,74 +47,53 @@ const MessageSettings = () => {
return ( return (
<Stack w={'100%'}> <Stack w={'100%'}>
<Modal.Root <AddItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={handleClose}
classNames={modalClasses} title={'Add a new message'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} align='center'>
onClose={() => { <TextInput
closeModal(); label='Message text'
}} autoFocus={!isMobile}
style={modalStyle} w={'75%'}
closeOnEscape className={textInputClasses.settingsItem}
closeOnClickOutside classNames={textInputClasses}
> onChange={(event) => {
<Modal.Overlay /> setNewMessage(event.target.value);
<Modal.Content> }}
<Modal.Header> error={newMessageError}
<Modal.Title>Add a new message</Modal.Title> />
<Modal.CloseButton /> <Divider color='black' my='lg' />
</Modal.Header> <Group pos={'absolute'} bottom={8}>
<Modal.Body pb={'2rem'}> <Button
<Card className={cardClasses.settingsItem} classNames={cardClasses}> color={'green'}
<Stack gap={0} w={'100%'} align='center'> styles={{ label: { color: 'black' } }}
<TextInput onClick={() => {
label='Message text' if (newMessage) {
autoFocus={!isMobile} mutateAsync({
w={'75%'} method: 'POST',
className={textInputClasses.settingsItem} endpoint: endpoint,
classNames={textInputClasses} body: { message: newMessage },
onChange={(event) => { }).then(handleClose);
setNewMessage(event.target.value); } else {
}} setNewMessageError('Please enter a message');
error={newMessageError} }
/> }}
<Divider color='black' my='lg' /> >
<Group pos={'absolute'} bottom={8}> Save
<Button </Button>
color={'green'} <Button
styles={{ label: { color: 'black' } }} color={'red'}
onClick={() => { styles={{ label: { color: 'black' } }}
if (newMessage) { onClick={handleClose}
mutateAsync({ >
method: 'POST', Cancel
endpoint: endpoint, </Button>
body: { message: newMessage }, </Group>
}); </Stack>
closeModal(); }
} else { />
setNewMessageError('Please enter a message');
}
}}
>
Save
</Button>
<Button
color={'red'}
styles={{ label: { color: 'black' } }}
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Card>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={openModal} openModal={openModal}

View File

@@ -3,21 +3,21 @@ import {
Button, Button,
Card, Card,
Group, Group,
Modal,
Stack, Stack,
Text, Text,
TextInput, TextInput,
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons-react';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { FiTrash } from 'react-icons/fi';
import { cardClasses, textInputClasses } from 'styles';
import type { Provider } from 'types'; import type { Provider } from 'types';
import DeleteItemModal from '../Shared/DeleteItemModal';
function DebouncedSearchProvider({ searchProvider }: any) { function DebouncedSearchProvider({ searchProvider }: any) {
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks(); const { theme, handleDelete } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
@@ -39,65 +39,49 @@ function DebouncedSearchProvider({ searchProvider }: any) {
return ( return (
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <Card className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={closeModal}
classNames={modalClasses} title={'Delete Search Provider'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this Search Provider?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete Search Provider</Modal.Title> }}
<Modal.CloseButton /> >{`"${provider.name}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this Search Provider?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(provider, 'api/providers');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${provider.name}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(provider, 'api/providers'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} />
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Tooltip <Tooltip
color={theme.primaryColor} color={theme.primaryColor}
bd={`solid 1px ${theme.borderColor}`} bd={`solid 1px ${theme.borderColor}`}
@@ -111,7 +95,7 @@ function DebouncedSearchProvider({ searchProvider }: any) {
openModal(); openModal();
}} }}
> >
<IconTrash /> <FiTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Stack> <Stack>

View File

@@ -1,12 +1,10 @@
import { import {
Box, Box,
Button, Button,
Card,
Divider, Divider,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Modal,
Popover, Popover,
Stack, Stack,
TextInput, TextInput,
@@ -14,30 +12,37 @@ import {
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { textInputClasses } from 'styles';
import type { Provider } from 'types'; import type { Provider } from 'types';
import AddItemModal from '../Shared/AddItemModal';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedSearchProvider from './DebouncedSearchProvider'; import DebouncedSearchProvider from './DebouncedSearchProvider';
import SearchProviderSettingsItem from './SearchProviderSettingsItem'; import SearchProviderSettingsItem from './SearchProviderSettingsItem';
const SearchProviderSettings = () => { const SearchProviderSettings = () => {
const defaultProvider = {
id: undefined,
name: '',
url: '',
prefix: '',
};
const endpoint = 'api/providers'; const endpoint = 'api/providers';
const { data: providers, isFetched: providersIsFetched } = useQueryAPI({ const { data: providers, isFetched: providersIsFetched } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
}); });
const [providerData, setProviderData] = useState<Provider[]>([]); const [providerData, setProviderData] = useState<Provider[]>([]);
const { isMobile, modalStyle } = useCommonHooks(); const { isMobile } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [newProvider, setNewProvider] = useState<Provider>({ const [newProvider, setNewProvider] = useState<Provider>(defaultProvider);
id: undefined,
name: '',
url: '',
prefix: '',
});
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const handleClose = () => {
setNewProvider(defaultProvider);
closeModal();
};
useEffect(() => { useEffect(() => {
if (providers && providersIsFetched) { if (providers && providersIsFetched) {
setProviderData( setProviderData(
@@ -52,104 +57,83 @@ const SearchProviderSettings = () => {
return ( return (
<Stack> <Stack>
<Modal.Root <AddItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={handleClose}
classNames={modalClasses} title={'Add a new Search Provider'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} align='center'>
onClose={() => { <Stack>
closeModal(); <Group w={'100%'} justify='center'>
}} <TextInput
style={modalStyle} label={'Name'}
closeOnEscape className={textInputClasses.settingsItem}
closeOnClickOutside pt={16}
> classNames={textInputClasses}
<Modal.Overlay /> defaultValue={newProvider.name}
<Modal.Content> onChange={(event) => {
<Modal.Header> setNewProvider({
<Modal.Title>Add a new Search Provider</Modal.Title> ...newProvider,
<Modal.CloseButton /> name: event.target.value,
</Modal.Header> });
<Modal.Body pb={'2rem'}> }}
<Card className={cardClasses.settingsItem} classNames={cardClasses}> />
<Stack gap={0} w={'100%'} align='center'> <TextInput
<Stack> label={'Prefix'}
<Group w={'100%'} justify='center'> className={textInputClasses.settingsItem}
<TextInput pt={16}
label={'Name'} classNames={textInputClasses}
className={textInputClasses.settingsItem} defaultValue={newProvider.prefix}
pt={16} onChange={(event) => {
classNames={textInputClasses} setNewProvider({
defaultValue={newProvider.name} ...newProvider,
onChange={(event) => { prefix: event.target.value,
setNewProvider({ });
...newProvider, }}
name: event.target.value, />
}); </Group>
}} <TextInput
/> label={'Url'}
<TextInput className={textInputClasses.settingsItem}
label={'Prefix'} pt={16}
className={textInputClasses.settingsItem} classNames={textInputClasses}
pt={16} defaultValue={newProvider.url}
classNames={textInputClasses} onChange={(event) => {
defaultValue={newProvider.prefix} setNewProvider({
onChange={(event) => { ...newProvider,
setNewProvider({ url: event.target.value,
...newProvider, });
prefix: event.target.value, }}
}); />
}} </Stack>
/> <Divider color='black' my='lg' />
</Group> <Group pos={'absolute'} bottom={8}>
<TextInput <Button
label={'Url'} color={'green'}
className={textInputClasses.settingsItem} styles={{ label: { color: 'black' } }}
pt={16} onClick={() => {
classNames={textInputClasses} if (newProvider) {
defaultValue={newProvider.url} mutateAsync({
onChange={(event) => { method: 'POST',
setNewProvider({ endpoint: endpoint,
...newProvider, body: newProvider,
url: event.target.value, }).then(handleClose);
}); }
}} }}
/> >
</Stack> Save
<Divider color='black' my='lg' /> </Button>
<Group pos={'absolute'} bottom={8}> <Button
<Button color={'red'}
color={'green'} styles={{ label: { color: 'black' } }}
styles={{ label: { color: 'black' } }} onClick={handleClose}
onClick={() => { >
if (newProvider) { Cancel
mutateAsync({ </Button>
method: 'POST', </Group>
endpoint: endpoint, </Stack>
body: newProvider, }
}); />
closeModal();
}
}}
>
Save
</Button>
<Button
color={'red'}
styles={{ label: { color: 'black' } }}
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Card>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={openModal} openModal={openModal}

View File

@@ -0,0 +1,35 @@
import { Card, Modal } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { cardClasses, modalClasses } from 'styles';
function AddItemModal({ modalOpened, handleClose, body, title }: any) {
const { isMobile, modalStyle } = useCommonHooks();
return (
<Modal.Root
size={'60%'}
className={modalClasses.helpModal}
classNames={modalClasses}
opened={modalOpened}
fullScreen={isMobile}
onClose={handleClose}
style={modalStyle}
closeOnEscape
closeOnClickOutside
>
<Modal.Overlay style={{ zIndex: 999 }} />
<Modal.Content style={{ zIndex: 1000 }}>
<Modal.Header>
<Modal.Title>{title}</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body pb={'2rem'}>
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
{body}
</Card>
</Modal.Body>
</Modal.Content>
</Modal.Root>
);
}
export default AddItemModal;

View File

@@ -0,0 +1,35 @@
import { Card, Modal } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { cardClasses, modalClasses } from 'styles';
function DeleteItemModal({ modalOpened, handleClose, body, title }: any) {
const { isMobile, modalStyle } = useCommonHooks();
return (
<Modal.Root
size={'60%'}
className={modalClasses.helpModal}
classNames={modalClasses}
opened={modalOpened}
fullScreen={isMobile}
onClose={handleClose}
style={modalStyle}
closeOnEscape
closeOnClickOutside
>
<Modal.Overlay style={{ zIndex: 999 }} />
<Modal.Content style={{ zIndex: 1000 }}>
<Modal.Header>
<Modal.Title>{title}</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body pb={'2rem'}>
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
{body}
</Card>
</Modal.Body>
</Modal.Content>
</Modal.Root>
);
}
export default DeleteItemModal;

View File

@@ -1,19 +1,11 @@
import { import { ActionIcon, Box, Button, Group, Overlay, Stack, Text } from '@mantine/core';
ActionIcon,
Box,
Button,
Group,
Modal,
Overlay,
Stack,
Text,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useState } from 'react'; import { useState } from 'react';
import { FiEdit, FiShuffle, FiTrash, FiX } from 'react-icons/fi'; import { FiEdit, FiShuffle, FiTrash, FiX } from 'react-icons/fi';
import { actionIconClasses, modalClasses } from 'styles'; import { actionIconClasses } from 'styles';
import type { ThemePlaylist } from 'types'; import type { ThemePlaylist } from 'types';
import DeleteItemModal from '../Shared/DeleteItemModal';
function ThemeCard({ cardTheme, handleOpen, allThemes }: any) { function ThemeCard({ cardTheme, handleOpen, allThemes }: any) {
const { data: themePlaylists } = useQueryAPI({ const { data: themePlaylists } = useQueryAPI({
@@ -29,10 +21,8 @@ function ThemeCard({ cardTheme, handleOpen, allThemes }: any) {
currentTheme, currentTheme,
setCurrentTheme, setCurrentTheme,
theme, theme,
isMobile,
currentThemePlaylist, currentThemePlaylist,
setCurrentThemePlaylist, setCurrentThemePlaylist,
modalStyle,
handleDelete, handleDelete,
} = useCommonHooks(); } = useCommonHooks();
@@ -46,65 +36,49 @@ function ThemeCard({ cardTheme, handleOpen, allThemes }: any) {
onMouseEnter={() => setHover(true)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)} onMouseLeave={() => setHover(false)}
> >
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
fullScreen={isMobile} handleClose={closeModal}
className={modalClasses.helpModal} title={'Delete Theme'}
classNames={modalClasses} body={
opened={modalOpened} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this theme?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete Theme</Modal.Title> }}
<Modal.CloseButton /> >{`"${cardTheme.name}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this theme?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(cardTheme, 'api/themes');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${cardTheme.name}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(cardTheme, 'api/themes'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} />
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Text <Text
style={{ style={{
position: 'absolute', position: 'absolute',

View File

@@ -2,7 +2,6 @@ import {
ActionIcon, ActionIcon,
Button, Button,
Group, Group,
Modal,
Overlay, Overlay,
ScrollArea, ScrollArea,
Stack, Stack,
@@ -12,19 +11,12 @@ import { useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useState } from 'react'; import { useState } from 'react';
import { FiEdit, FiRefreshCcw, FiTrash } from 'react-icons/fi'; import { FiEdit, FiRefreshCcw, FiTrash } from 'react-icons/fi';
import { modalClasses } from 'styles'; import DeleteItemModal from '../Shared/DeleteItemModal';
import ThemeMiniCard from './ThemeMiniCard'; import ThemeMiniCard from './ThemeMiniCard';
function ThemePlaylistCard({ cardPlaylist, handleOpen, allThemes }: any) { function ThemePlaylistCard({ cardPlaylist, handleOpen, allThemes }: any) {
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const { const { setThemeName, setCurrentThemePlaylist, theme, handleDelete } = useCommonHooks();
setThemeName,
setCurrentThemePlaylist,
theme,
isMobile,
modalStyle,
handleDelete,
} = useCommonHooks();
const syncableLists = ['All', 'Backgrounds', 'No Backgrounds']; const syncableLists = ['All', 'Backgrounds', 'No Backgrounds'];
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
@@ -42,65 +34,49 @@ function ThemePlaylistCard({ cardPlaylist, handleOpen, allThemes }: any) {
onMouseEnter={() => setHover(true)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)} onMouseLeave={() => setHover(false)}
> >
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
fullScreen={isMobile} closeModal={closeModal}
className={modalClasses.helpModal} title='Delete Theme Playlist'
classNames={modalClasses} body={
opened={modalOpened} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this Theme Playlist?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete Theme Playlist</Modal.Title> }}
<Modal.CloseButton /> >{`"${cardPlaylist.name}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this Theme Playlist?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(cardPlaylist, 'api/themePlaylists');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${cardPlaylist.name}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(cardPlaylist, 'api/themePlaylists'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} />
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Text <Text
style={{ style={{
position: 'absolute', position: 'absolute',

View File

@@ -10,11 +10,13 @@ import ThemePlaylistCard from './ThemePlaylistCard';
import ThemePlaylistModal from './ThemePlaylistModal'; import ThemePlaylistModal from './ThemePlaylistModal';
function ThemePlaylists() { function ThemePlaylists() {
const themesEndpoint = 'api/themes';
const { data: allThemes } = useQueryAPI({ const { data: allThemes } = useQueryAPI({
endpoint: 'api/themes', endpoint: themesEndpoint,
}); });
const themePlaylistsEndpoint = 'api/themePlaylists';
const { data: themePlaylists } = useQueryAPI({ const { data: themePlaylists } = useQueryAPI({
endpoint: 'api/themePlaylists', endpoint: themePlaylistsEndpoint,
}); });
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
@@ -36,7 +38,7 @@ function ThemePlaylists() {
const handleSavePlaylist = () => { const handleSavePlaylist = () => {
mutateAsync({ mutateAsync({
method: selectedPlaylist.id === undefined ? 'POST' : 'PATCH', method: selectedPlaylist.id === undefined ? 'POST' : 'PATCH',
endpoint: 'api/themePlaylists', endpoint: themePlaylistsEndpoint,
body: selectedPlaylist, body: selectedPlaylist,
}); });
close(); close();

View File

@@ -2,20 +2,20 @@ import { Box, Group, Overlay, Stack, Switch, Text } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiChevronRight } from 'react-icons/fi';
import '@mantine/core/styles/ModalBase.css';
import '@mantine/core/styles/Modal.css';
import { FaDice } from 'react-icons/fa'; import { FaDice } from 'react-icons/fa';
import { FiChevronRight } from 'react-icons/fi';
import type { Theme } from 'types'; import type { Theme } from 'types';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import ThemeCard from './ThemeCard'; import ThemeCard from './ThemeCard';
import ThemeModal from './ThemeModal'; import ThemeModal from './ThemeModal';
function ThemesSettings() { function ThemesSettings() {
const themesEndpoint = 'api/themes';
const backgroundsEndpoint = 'api/backgrounds';
const { data: allThemes } = useQueryAPI({ const { data: allThemes } = useQueryAPI({
endpoint: 'api/themes', endpoint: 'api/themes',
}); });
const { data: backgroundData, isFetched: isBackgroundFetched } = useQueryAPI({ const { data: backgroundData, isFetched: isBackgroundFetched } = useQueryAPI({
endpoint: 'api/backgrounds', endpoint: backgroundsEndpoint,
}); });
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
@@ -43,7 +43,7 @@ function ThemesSettings() {
const handleSaveTheme = () => { const handleSaveTheme = () => {
mutateAsync({ mutateAsync({
method: selectedTheme.id === undefined ? 'POST' : 'PATCH', method: selectedTheme.id === undefined ? 'POST' : 'PATCH',
endpoint: 'api/themes', endpoint: themesEndpoint,
body: selectedTheme, body: selectedTheme,
}); });
close(); close();

View File

@@ -3,20 +3,20 @@ import {
Button, Button,
Card, Card,
Group, Group,
Modal,
Stack, Stack,
Text, Text,
TextInput, TextInput,
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons-react';
import { useCommonHooks, useMutationAPI } from 'hooks'; import { useCommonHooks, useMutationAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { FiTrash } from 'react-icons/fi';
import { cardClasses, textInputClasses } from 'styles';
import DeleteItemModal from '../Shared/DeleteItemModal';
function DebouncedWolClient({ wolClient }: any) { function DebouncedWolClient({ wolClient }: any) {
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks(); const { theme, handleDelete } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
@@ -38,65 +38,49 @@ function DebouncedWolClient({ wolClient }: any) {
return ( return (
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <Card className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root <DeleteItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={closeModal}
classNames={modalClasses} title={'Delete WoL Client'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} h={'150px'} align='center'>
onClose={() => { <Text>Are you sure you want to delete this WoL Client?</Text>
closeModal(); <Text
}} style={{
style={modalStyle} fontWeight: 'bold',
closeOnEscape fontSize: '20px',
closeOnClickOutside padding: '8px',
> margin: '8px',
<Modal.Overlay /> borderRadius: '10px',
<Modal.Content> backgroundColor: 'var(--color-text-acc)',
<Modal.Header> color: 'var(--color-background)',
<Modal.Title>Delete WoL Client</Modal.Title> }}
<Modal.CloseButton /> >{`"${client.hostname}"`}</Text>
</Modal.Header> <Text c='red'>This action cannot be undone!</Text>
<Modal.Body pb={'2rem'}> <Group pos={'absolute'} bottom={8}>
<Stack gap={0} w={'100%'} h={'150px'} align='center'> <Button
<Text>Are you sure you want to delete this WoL Client?</Text> color={'red'}
<Text styles={{ label: { color: 'black' } }}
style={{ onClick={() => {
fontWeight: 'bold', handleDelete(client, 'api/WolClients');
fontSize: '20px', closeModal();
padding: '8px',
margin: '8px',
borderRadius: '10px',
backgroundColor: 'var(--color-text-acc)',
color: 'var(--color-background)',
}} }}
>{`"${client.hostname}"`}</Text> >
<Text c='red'>This action cannot be undone!</Text> Delete
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'red'} color={'grey'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
handleDelete(client, 'api/WolClients'); closeModal();
closeModal(); }}
}} >
> Cancel
Delete </Button>
</Button> </Group>
<Button </Stack>
color={'grey'} }
styles={{ label: { color: 'black' } }} />
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<Tooltip <Tooltip
color={theme.primaryColor} color={theme.primaryColor}
bd={`solid 1px ${theme.borderColor}`} bd={`solid 1px ${theme.borderColor}`}
@@ -110,7 +94,7 @@ function DebouncedWolClient({ wolClient }: any) {
openModal(); openModal();
}} }}
> >
<IconTrash /> <FiTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Group w={'100%'} justify='center'> <Group w={'100%'} justify='center'>

View File

@@ -1,11 +1,9 @@
import { import {
Button, Button,
Card,
Divider, Divider,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Modal,
Popover, Popover,
Stack, Stack,
TextInput, TextInput,
@@ -13,29 +11,36 @@ import {
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cardClasses, modalClasses, textInputClasses } from 'styles'; import { textInputClasses } from 'styles';
import type { WolClient } from 'types'; import type { WolClient } from 'types';
import AddItemModal from '../Shared/AddItemModal';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedWolClient from './DebouncedWolClient'; import DebouncedWolClient from './DebouncedWolClient';
import WolClientSettingsItem from './WolClientSettingsItem'; import WolClientSettingsItem from './WolClientSettingsItem';
const WolClientSettings = () => { const WolClientSettings = () => {
const defaultWolClient: WolClient = {
id: undefined,
hostname: '',
macAddress: '',
};
const endpoint = 'api/wolClients'; const endpoint = 'api/wolClients';
const { data: wolClientsData, isFetched: wolClientsIsFetched } = useQueryAPI({ const { data: wolClientsData, isFetched: wolClientsIsFetched } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
}); });
const [wolClients, setWolClients] = useState<WolClient[]>([]); const [wolClients, setWolClients] = useState<WolClient[]>([]);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const { isMobile, modalStyle } = useCommonHooks(); const { isMobile } = useCommonHooks();
const [newWolClient, setNewWolClient] = useState<WolClient>({ const [newWolClient, setNewWolClient] = useState<WolClient>(defaultWolClient);
id: undefined,
hostname: '',
macAddress: '',
});
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const handleClose = () => {
setNewWolClient(defaultWolClient);
closeModal();
};
useEffect(() => { useEffect(() => {
if (wolClientsData && wolClientsIsFetched) { if (wolClientsData && wolClientsIsFetched) {
setWolClients( setWolClients(
@@ -48,93 +53,75 @@ const WolClientSettings = () => {
return ( return (
<Stack> <Stack>
<Modal.Root <AddItemModal
size={'60%'} modalOpened={modalOpened}
className={modalClasses.helpModal} handleClose={handleClose}
classNames={modalClasses} title={'Add a new WoL Client'}
opened={modalOpened} body={
fullScreen={isMobile} <Stack gap={0} w={'100%'} align='center'>
onClose={() => { <Group w={'100%'} justify='center'>
closeModal(); <TextInput
}} label={'Hostname'}
style={modalStyle} className={textInputClasses.settingsItem}
closeOnEscape pt={16}
closeOnClickOutside classNames={textInputClasses}
> defaultValue={newWolClient.hostname}
<Modal.Overlay /> onChange={(event) => {
<Modal.Content> setNewWolClient({
<Modal.Header> ...newWolClient,
<Modal.Title>Add a new WoL Client</Modal.Title> hostname: event.target.value,
<Modal.CloseButton /> });
</Modal.Header> }}
<Modal.Body pb={'2rem'}> />
<Card className={cardClasses.settingsItem} classNames={cardClasses}> <TextInput
<Stack gap={0} w={'100%'} align='center'> label={'MAC Address'}
<Group w={'100%'} justify='center'> className={textInputClasses.settingsItem}
<TextInput pt={16}
label={'Hostname'} classNames={textInputClasses}
className={textInputClasses.settingsItem} defaultValue={newWolClient.macAddress}
pt={16} onChange={(event) => {
classNames={textInputClasses} setNewWolClient({
defaultValue={newWolClient.hostname} ...newWolClient,
onChange={(event) => { macAddress: event.target.value,
setNewWolClient({ });
...newWolClient, }}
hostname: event.target.value, />
}); </Group>
}} <Divider color='black' my='lg' />
/> <Group pos={'absolute'} bottom={8}>
<TextInput <Button
label={'MAC Address'} color={'green'}
className={textInputClasses.settingsItem} styles={{ label: { color: 'black' } }}
pt={16} onClick={() => {
classNames={textInputClasses} if (newWolClient) {
defaultValue={newWolClient.macAddress} mutateAsync({
onChange={(event) => { method: 'POST',
setNewWolClient({ endpoint: endpoint,
...newWolClient, body: newWolClient,
macAddress: event.target.value, });
}); closeModal();
}} }
/> }}
</Group> >
<Divider color='black' my='lg' /> Save
<Group pos={'absolute'} bottom={8}> </Button>
<Button <Button
color={'green'} color={'red'}
styles={{ label: { color: 'black' } }} styles={{ label: { color: 'black' } }}
onClick={() => { onClick={() => {
if (newWolClient) { closeModal();
mutateAsync({ }}
method: 'POST', >
endpoint: endpoint, Cancel
body: newWolClient, </Button>
}); </Group>
closeModal(); </Stack>
} }
}} />
>
Save
</Button>
<Button
color={'red'}
styles={{ label: { color: 'black' } }}
onClick={() => {
closeModal();
}}
>
Cancel
</Button>
</Group>
</Stack>
</Card>
</Modal.Body>
</Modal.Content>
</Modal.Root>
<SettingsHeader <SettingsHeader
setDragging={undefined} setDragging={undefined}
openModal={openModal} openModal={openModal}
title={'Search Providers'} title={'WoL Clients'}
tooltip={{ tooltip={{
title: 'Wake on Lan clients can be configured here.', title: 'Wake on Lan clients can be configured here.',
highlightList: ['Clients', 'homepage'], highlightList: ['Clients', 'homepage'],