Application adding bugfix; consolidated Settings headers
This commit is contained in:
25
package-lock.json
generated
25
package-lock.json
generated
@@ -12,7 +12,6 @@
|
||||
"@iconify/react": "^5.2.0",
|
||||
"@mantine/core": "^7.17.3",
|
||||
"@mantine/hooks": "^7.17.3",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tanstack/react-query": "^5.69.3",
|
||||
"@tanstack/react-query-devtools": "^5.69.3",
|
||||
"@tanstack/react-query-persist-client": "^5.69.3",
|
||||
@@ -1588,30 +1587,6 @@
|
||||
"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": {
|
||||
"version": "5.69.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.69.2.tgz",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"@iconify/react": "^5.2.0",
|
||||
"@mantine/core": "^7.17.3",
|
||||
"@mantine/hooks": "^7.17.3",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tanstack/react-query": "^5.69.3",
|
||||
"@tanstack/react-query-devtools": "^5.69.3",
|
||||
"@tanstack/react-query-persist-client": "^5.69.3",
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Modal,
|
||||
Popover,
|
||||
Stack,
|
||||
Text,
|
||||
@@ -16,21 +15,15 @@ import {
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cardClasses, modalClasses, textInputClasses } from 'styles';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { Application } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import AppSettingsItem from './AppSettingsItem';
|
||||
import DebouncedApplication from './DebouncedApplication';
|
||||
|
||||
const ApplicationSettings = () => {
|
||||
const endpoint = 'api/apps';
|
||||
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>({
|
||||
const defaultApplication: Application = {
|
||||
id: null,
|
||||
name: '',
|
||||
url: '',
|
||||
@@ -39,7 +32,16 @@ const ApplicationSettings = () => {
|
||||
filterName: '',
|
||||
index: 0,
|
||||
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 [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
@@ -74,6 +76,11 @@ const ApplicationSettings = () => {
|
||||
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
|
||||
const handleClose = () => {
|
||||
closeModal();
|
||||
setNewApplication(defaultApplication);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (apps && !isAppsFetching) {
|
||||
setApplicationData(
|
||||
@@ -90,7 +97,7 @@ const ApplicationSettings = () => {
|
||||
if (applicationList.length > 0 && applicationList !== applicationData) {
|
||||
mutateAsync({
|
||||
method: 'PATCH',
|
||||
endpoint: 'api/applicationOrder',
|
||||
endpoint: appsOrderEndpoint,
|
||||
body: applicationList,
|
||||
});
|
||||
}
|
||||
@@ -98,134 +105,116 @@ const ApplicationSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack w={'100%'}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Add a new application</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Stack gap={0} align='center'>
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.name}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Base URL'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.baseUrl}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
baseUrl: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={isMobile ? undefined : '60%'} justify='center'>
|
||||
<TextInput
|
||||
label={'URL'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
w={isMobile ? undefined : '91%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.url}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
url: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
label={'Icon'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.icon}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
icon: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Filter'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.filterName}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
filterName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new application'}
|
||||
body={
|
||||
<Stack gap={0} align='center'>
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.name}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Base URL'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.baseUrl}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
baseUrl: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={isMobile ? undefined : '60%'} justify='center'>
|
||||
<TextInput
|
||||
label={'URL'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
w={isMobile ? undefined : '91%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.url}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
url: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
label={'Icon'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.icon}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
icon: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<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' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newApplication) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: newApplication,
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newApplication) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: appsEndpoint,
|
||||
body: {
|
||||
...newApplication,
|
||||
index: applicationData.length,
|
||||
},
|
||||
}).then(handleClose);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
setDragging={setDragging}
|
||||
openModal={openModal}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Switch,
|
||||
Text,
|
||||
@@ -11,14 +10,15 @@ import {
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
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 DeleteItemModal from '../Shared/DeleteItemModal';
|
||||
|
||||
function DebouncedApplication({ application }: { application: Application }) {
|
||||
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks();
|
||||
const { theme, handleDelete } = useCommonHooks();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
@@ -39,65 +39,49 @@ function DebouncedApplication({ application }: { application: Application }) {
|
||||
}, [update]);
|
||||
return (
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
fullScreen={isMobile}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete Application</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this application?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
title='Delete Application'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this application?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(app, 'api/apps');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${app.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(app, 'api/apps');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
color={theme.primaryColor}
|
||||
bd={`solid 1px ${theme.borderColor}`}
|
||||
@@ -111,7 +95,7 @@ function DebouncedApplication({ application }: { application: Application }) {
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<IconTrash />
|
||||
<FiTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
|
||||
@@ -1,40 +1,34 @@
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { Box, Button, Card, Divider, Group, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cardClasses, modalClasses, textInputClasses } from 'styles';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { Bookmark, BookmarkGroup } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
|
||||
const BookmarkSettings = () => {
|
||||
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
|
||||
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>({
|
||||
const defaultGroup = {
|
||||
id: undefined,
|
||||
categoryName: '',
|
||||
categoryIndex: 0,
|
||||
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 { theme, isMobile, modalStyle } = useCommonHooks();
|
||||
const { theme, isMobile } = useCommonHooks();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
|
||||
@@ -47,6 +41,11 @@ const BookmarkSettings = () => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setNewGroup(defaultGroup);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
function onDragEnd(result: any) {
|
||||
if (!result.destination) {
|
||||
return;
|
||||
@@ -95,90 +94,70 @@ const BookmarkSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Add a new Bookmark Group</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Category Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newGroup.categoryName}
|
||||
onChange={(event) => {
|
||||
setNewGroup({
|
||||
...newGroup,
|
||||
categoryName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Filter Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newGroup.filterName}
|
||||
onChange={(event) => {
|
||||
setNewGroup({
|
||||
...newGroup,
|
||||
filterName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Bookmark Group'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Category Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newGroup.categoryName}
|
||||
onChange={(event) => {
|
||||
setNewGroup({
|
||||
...newGroup,
|
||||
categoryName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<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' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newGroup) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: 'api/categories',
|
||||
body: { ...newGroup, categoryIndex: groups.length },
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newGroup) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: categoriesEndpoint,
|
||||
body: { ...newGroup, categoryIndex: groups.length },
|
||||
});
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
setDragging={setDragging}
|
||||
openModal={openModal}
|
||||
|
||||
@@ -3,21 +3,24 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
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 DeleteItemModal from '../Shared/DeleteItemModal';
|
||||
|
||||
function DebouncedBookmarkItem({ bookmarkItem }: { bookmarkItem: Bookmark }) {
|
||||
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks();
|
||||
function DebouncedBookmarkItem({
|
||||
bookmarkItem,
|
||||
setIsOpen,
|
||||
}: { bookmarkItem: Bookmark; setIsOpen: any }) {
|
||||
const { theme, handleDelete } = useCommonHooks();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
@@ -39,65 +42,49 @@ function DebouncedBookmarkItem({ bookmarkItem }: { bookmarkItem: Bookmark }) {
|
||||
|
||||
return (
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay style={{ zIndex: 999 }} />
|
||||
<Modal.Content style={{ zIndex: 1000 }}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete Bookmark</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(bookmark, 'api/links');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${bookmark.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(bookmark, 'api/links');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
title='Delete Bookmark'
|
||||
/>
|
||||
<Tooltip
|
||||
color={theme.primaryColor}
|
||||
bd={`solid 1px ${theme.borderColor}`}
|
||||
@@ -108,10 +95,11 @@ function DebouncedBookmarkItem({ bookmarkItem }: { bookmarkItem: Bookmark }) {
|
||||
right={16}
|
||||
color='red'
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<IconTrash />
|
||||
<FiTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Group w={'100%'} justify='center'>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
Popover,
|
||||
Stack,
|
||||
Text,
|
||||
@@ -13,12 +12,12 @@ import {
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FiPlus } from 'react-icons/fi';
|
||||
import { buttonClasses, cardClasses, modalClasses, textInputClasses } from 'styles';
|
||||
import { FiPlus, FiTrash } from 'react-icons/fi';
|
||||
import { buttonClasses, cardClasses, textInputClasses } from 'styles';
|
||||
import type { Bookmark, BookmarkGroup } from 'types';
|
||||
import DeleteItemModal from '../Shared/DeleteItemModal';
|
||||
import BookmarkSettingsItem from './BookmarkSettingsItem';
|
||||
import DebouncedBookmarkItem from './DebouncedBookmarkItem';
|
||||
|
||||
@@ -26,7 +25,7 @@ function DebouncedGroupSettingsItem({
|
||||
bookmarkGroup,
|
||||
bookmarks,
|
||||
}: { bookmarkGroup: BookmarkGroup; bookmarks: Bookmark[] }) {
|
||||
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks();
|
||||
const { theme, isMobile, handleDelete } = useCommonHooks();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const [popoverOpened, setPopoverOpened] = useState(false);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
@@ -41,6 +40,7 @@ function DebouncedGroupSettingsItem({
|
||||
index: 0,
|
||||
});
|
||||
const [bookmarkList, setBookmarkList] = useState<Bookmark[]>(bookmarks);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const reorder = (list: any, startIndex: any, endIndex: any) => {
|
||||
const result = Array.from(list);
|
||||
@@ -90,65 +90,49 @@ function DebouncedGroupSettingsItem({
|
||||
|
||||
return (
|
||||
<Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete Group</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark group?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
title='Delete message'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark group?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(group, 'api/categories');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${group.categoryName}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(group, 'api/categories');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Card.Section p={16} bg={theme.searchColor} c={theme.backgroundColor}>
|
||||
<Stack gap={0}>
|
||||
<Group gap={0} align='center' justify='space-between'>
|
||||
@@ -201,7 +185,7 @@ function DebouncedGroupSettingsItem({
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<IconTrash />
|
||||
<FiTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
@@ -243,7 +227,7 @@ function DebouncedGroupSettingsItem({
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
<Popover withArrow withOverlay>
|
||||
<Popover withArrow withOverlay closeOnClickOutside={!isOpen}>
|
||||
<Popover.Target>
|
||||
<Box>
|
||||
<BookmarkSettingsItem bookmark={bookmark} />
|
||||
@@ -259,7 +243,10 @@ function DebouncedGroupSettingsItem({
|
||||
'--image-background': `url(${theme.backgroundImage})`,
|
||||
}}
|
||||
>
|
||||
<DebouncedBookmarkItem bookmarkItem={bookmark} />
|
||||
<DebouncedBookmarkItem
|
||||
bookmarkItem={bookmark}
|
||||
setIsOpen={setIsOpen}
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
{provided.placeholder}
|
||||
@@ -284,6 +271,7 @@ function DebouncedGroupSettingsItem({
|
||||
opened={popoverOpened}
|
||||
onChange={setPopoverOpened}
|
||||
closeOnEscape
|
||||
// closeOnClickOutside={}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Button
|
||||
|
||||
@@ -3,81 +3,63 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
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) {
|
||||
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks();
|
||||
const { theme, handleDelete } = useCommonHooks();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
return (
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete message</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this message?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
title='Delete message'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this message?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(message, 'api/messages');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${message.message}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(message, 'api/messages');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
color={theme.primaryColor}
|
||||
bd={`solid 1px ${theme.borderColor}`}
|
||||
@@ -91,7 +73,7 @@ function DebouncedMessage({ label, message, setMessage }: any) {
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<IconTrash />
|
||||
<FiTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<TextInput
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { Button, Divider, Grid, GridCol, Group, Stack, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import DebouncedMessage from 'pages/settings/components/Messages/DebouncedMessage';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cardClasses, modalClasses, textInputClasses } from 'styles';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { Message } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
const MessageSettings = () => {
|
||||
const endpoint = 'api/messages';
|
||||
const { data: messageData, isFetched: isMessagesFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const { isMobile, modalStyle } = useCommonHooks();
|
||||
const { isMobile } = useCommonHooks();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
@@ -32,6 +23,12 @@ const MessageSettings = () => {
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewMessage('');
|
||||
setNewMessageError('');
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (messageData && isMessagesFetched) {
|
||||
setMessages(
|
||||
@@ -50,74 +47,53 @@ const MessageSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack w={'100%'}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Add a new message</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<TextInput
|
||||
label='Message text'
|
||||
autoFocus={!isMobile}
|
||||
w={'75%'}
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
onChange={(event) => {
|
||||
setNewMessage(event.target.value);
|
||||
}}
|
||||
error={newMessageError}
|
||||
/>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newMessage) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: { message: newMessage },
|
||||
});
|
||||
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>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new message'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<TextInput
|
||||
label='Message text'
|
||||
autoFocus={!isMobile}
|
||||
w={'75%'}
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
onChange={(event) => {
|
||||
setNewMessage(event.target.value);
|
||||
}}
|
||||
error={newMessageError}
|
||||
/>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newMessage) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: { message: newMessage },
|
||||
}).then(handleClose);
|
||||
} else {
|
||||
setNewMessageError('Please enter a message');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
|
||||
@@ -3,21 +3,21 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
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 DeleteItemModal from '../Shared/DeleteItemModal';
|
||||
|
||||
function DebouncedSearchProvider({ searchProvider }: any) {
|
||||
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks();
|
||||
const { theme, handleDelete } = useCommonHooks();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
@@ -39,65 +39,49 @@ function DebouncedSearchProvider({ searchProvider }: any) {
|
||||
|
||||
return (
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete Search Provider</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Search Provider?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
title={'Delete Search Provider'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Search Provider?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(provider, 'api/providers');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${provider.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(provider, 'api/providers');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
color={theme.primaryColor}
|
||||
bd={`solid 1px ${theme.borderColor}`}
|
||||
@@ -111,7 +95,7 @@ function DebouncedSearchProvider({ searchProvider }: any) {
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<IconTrash />
|
||||
<FiTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Stack>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Modal,
|
||||
Popover,
|
||||
Stack,
|
||||
TextInput,
|
||||
@@ -14,30 +12,37 @@ import {
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cardClasses, modalClasses, textInputClasses } from 'styles';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { Provider } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedSearchProvider from './DebouncedSearchProvider';
|
||||
import SearchProviderSettingsItem from './SearchProviderSettingsItem';
|
||||
|
||||
const SearchProviderSettings = () => {
|
||||
const defaultProvider = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
url: '',
|
||||
prefix: '',
|
||||
};
|
||||
const endpoint = 'api/providers';
|
||||
const { data: providers, isFetched: providersIsFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const [providerData, setProviderData] = useState<Provider[]>([]);
|
||||
const { isMobile, modalStyle } = useCommonHooks();
|
||||
const { isMobile } = useCommonHooks();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [newProvider, setNewProvider] = useState<Provider>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
url: '',
|
||||
prefix: '',
|
||||
});
|
||||
const [newProvider, setNewProvider] = useState<Provider>(defaultProvider);
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewProvider(defaultProvider);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (providers && providersIsFetched) {
|
||||
setProviderData(
|
||||
@@ -52,104 +57,83 @@ const SearchProviderSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Add a new Search Provider</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Stack>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.name}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Prefix'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.prefix}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
prefix: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
label={'Url'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.url}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
url: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newProvider) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
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>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Search Provider'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Stack>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.name}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Prefix'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.prefix}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
prefix: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
label={'Url'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.url}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
url: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newProvider) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: newProvider,
|
||||
}).then(handleClose);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
|
||||
35
src/pages/settings/components/Shared/AddItemModal.tsx
Normal file
35
src/pages/settings/components/Shared/AddItemModal.tsx
Normal 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;
|
||||
35
src/pages/settings/components/Shared/DeleteItemModal.tsx
Normal file
35
src/pages/settings/components/Shared/DeleteItemModal.tsx
Normal 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;
|
||||
@@ -1,19 +1,11 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Modal,
|
||||
Overlay,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { ActionIcon, Box, Button, Group, Overlay, Stack, Text } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useState } from 'react';
|
||||
import { FiEdit, FiShuffle, FiTrash, FiX } from 'react-icons/fi';
|
||||
import { actionIconClasses, modalClasses } from 'styles';
|
||||
import { actionIconClasses } from 'styles';
|
||||
import type { ThemePlaylist } from 'types';
|
||||
import DeleteItemModal from '../Shared/DeleteItemModal';
|
||||
|
||||
function ThemeCard({ cardTheme, handleOpen, allThemes }: any) {
|
||||
const { data: themePlaylists } = useQueryAPI({
|
||||
@@ -29,10 +21,8 @@ function ThemeCard({ cardTheme, handleOpen, allThemes }: any) {
|
||||
currentTheme,
|
||||
setCurrentTheme,
|
||||
theme,
|
||||
isMobile,
|
||||
currentThemePlaylist,
|
||||
setCurrentThemePlaylist,
|
||||
modalStyle,
|
||||
handleDelete,
|
||||
} = useCommonHooks();
|
||||
|
||||
@@ -46,65 +36,49 @@ function ThemeCard({ cardTheme, handleOpen, allThemes }: any) {
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
fullScreen={isMobile}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete Theme</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this theme?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
title={'Delete Theme'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this theme?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(cardTheme, 'api/themes');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${cardTheme.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(cardTheme, 'api/themes');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Group,
|
||||
Modal,
|
||||
Overlay,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
@@ -12,19 +11,12 @@ import { useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
import { useState } from 'react';
|
||||
import { FiEdit, FiRefreshCcw, FiTrash } from 'react-icons/fi';
|
||||
import { modalClasses } from 'styles';
|
||||
import DeleteItemModal from '../Shared/DeleteItemModal';
|
||||
import ThemeMiniCard from './ThemeMiniCard';
|
||||
|
||||
function ThemePlaylistCard({ cardPlaylist, handleOpen, allThemes }: any) {
|
||||
const [hover, setHover] = useState(false);
|
||||
const {
|
||||
setThemeName,
|
||||
setCurrentThemePlaylist,
|
||||
theme,
|
||||
isMobile,
|
||||
modalStyle,
|
||||
handleDelete,
|
||||
} = useCommonHooks();
|
||||
const { setThemeName, setCurrentThemePlaylist, theme, handleDelete } = useCommonHooks();
|
||||
const syncableLists = ['All', 'Backgrounds', 'No Backgrounds'];
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
@@ -42,65 +34,49 @@ function ThemePlaylistCard({ cardPlaylist, handleOpen, allThemes }: any) {
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
fullScreen={isMobile}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete Theme Playlist</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Theme Playlist?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
closeModal={closeModal}
|
||||
title='Delete Theme Playlist'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Theme Playlist?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(cardPlaylist, 'api/themePlaylists');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${cardPlaylist.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(cardPlaylist, 'api/themePlaylists');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -10,11 +10,13 @@ import ThemePlaylistCard from './ThemePlaylistCard';
|
||||
import ThemePlaylistModal from './ThemePlaylistModal';
|
||||
|
||||
function ThemePlaylists() {
|
||||
const themesEndpoint = 'api/themes';
|
||||
const { data: allThemes } = useQueryAPI({
|
||||
endpoint: 'api/themes',
|
||||
endpoint: themesEndpoint,
|
||||
});
|
||||
const themePlaylistsEndpoint = 'api/themePlaylists';
|
||||
const { data: themePlaylists } = useQueryAPI({
|
||||
endpoint: 'api/themePlaylists',
|
||||
endpoint: themePlaylistsEndpoint,
|
||||
});
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
@@ -36,7 +38,7 @@ function ThemePlaylists() {
|
||||
const handleSavePlaylist = () => {
|
||||
mutateAsync({
|
||||
method: selectedPlaylist.id === undefined ? 'POST' : 'PATCH',
|
||||
endpoint: 'api/themePlaylists',
|
||||
endpoint: themePlaylistsEndpoint,
|
||||
body: selectedPlaylist,
|
||||
});
|
||||
close();
|
||||
|
||||
@@ -2,20 +2,20 @@ import { Box, Group, Overlay, Stack, Switch, Text } from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
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 { FiChevronRight } from 'react-icons/fi';
|
||||
import type { Theme } from 'types';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import ThemeCard from './ThemeCard';
|
||||
import ThemeModal from './ThemeModal';
|
||||
function ThemesSettings() {
|
||||
const themesEndpoint = 'api/themes';
|
||||
const backgroundsEndpoint = 'api/backgrounds';
|
||||
const { data: allThemes } = useQueryAPI({
|
||||
endpoint: 'api/themes',
|
||||
});
|
||||
const { data: backgroundData, isFetched: isBackgroundFetched } = useQueryAPI({
|
||||
endpoint: 'api/backgrounds',
|
||||
endpoint: backgroundsEndpoint,
|
||||
});
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
@@ -43,7 +43,7 @@ function ThemesSettings() {
|
||||
const handleSaveTheme = () => {
|
||||
mutateAsync({
|
||||
method: selectedTheme.id === undefined ? 'POST' : 'PATCH',
|
||||
endpoint: 'api/themes',
|
||||
endpoint: themesEndpoint,
|
||||
body: selectedTheme,
|
||||
});
|
||||
close();
|
||||
|
||||
@@ -3,20 +3,20 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
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) {
|
||||
const { theme, isMobile, modalStyle, handleDelete } = useCommonHooks();
|
||||
const { theme, handleDelete } = useCommonHooks();
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
@@ -38,65 +38,49 @@ function DebouncedWolClient({ wolClient }: any) {
|
||||
|
||||
return (
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Delete WoL Client</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this WoL Client?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
padding: '8px',
|
||||
margin: '8px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: 'var(--color-text-acc)',
|
||||
color: 'var(--color-background)',
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
title={'Delete WoL Client'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this WoL Client?</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '20px',
|
||||
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>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(client, 'api/WolClients');
|
||||
closeModal();
|
||||
}}
|
||||
>{`"${client.hostname}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleDelete(client, 'api/WolClients');
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
color={'grey'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
color={theme.primaryColor}
|
||||
bd={`solid 1px ${theme.borderColor}`}
|
||||
@@ -110,7 +94,7 @@ function DebouncedWolClient({ wolClient }: any) {
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<IconTrash />
|
||||
<FiTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Group w={'100%'} justify='center'>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Modal,
|
||||
Popover,
|
||||
Stack,
|
||||
TextInput,
|
||||
@@ -13,29 +11,36 @@ import {
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cardClasses, modalClasses, textInputClasses } from 'styles';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { WolClient } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedWolClient from './DebouncedWolClient';
|
||||
import WolClientSettingsItem from './WolClientSettingsItem';
|
||||
|
||||
const WolClientSettings = () => {
|
||||
const defaultWolClient: WolClient = {
|
||||
id: undefined,
|
||||
hostname: '',
|
||||
macAddress: '',
|
||||
};
|
||||
const endpoint = 'api/wolClients';
|
||||
const { data: wolClientsData, isFetched: wolClientsIsFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const [wolClients, setWolClients] = useState<WolClient[]>([]);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const { isMobile, modalStyle } = useCommonHooks();
|
||||
const [newWolClient, setNewWolClient] = useState<WolClient>({
|
||||
id: undefined,
|
||||
hostname: '',
|
||||
macAddress: '',
|
||||
});
|
||||
const { isMobile } = useCommonHooks();
|
||||
const [newWolClient, setNewWolClient] = useState<WolClient>(defaultWolClient);
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewWolClient(defaultWolClient);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (wolClientsData && wolClientsIsFetched) {
|
||||
setWolClients(
|
||||
@@ -48,93 +53,75 @@ const WolClientSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Modal.Root
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
style={modalStyle}
|
||||
closeOnEscape
|
||||
closeOnClickOutside
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Add a new WoL Client</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body pb={'2rem'}>
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Hostname'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newWolClient.hostname}
|
||||
onChange={(event) => {
|
||||
setNewWolClient({
|
||||
...newWolClient,
|
||||
hostname: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'MAC Address'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newWolClient.macAddress}
|
||||
onChange={(event) => {
|
||||
setNewWolClient({
|
||||
...newWolClient,
|
||||
macAddress: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newWolClient) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: newWolClient,
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new WoL Client'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Hostname'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newWolClient.hostname}
|
||||
onChange={(event) => {
|
||||
setNewWolClient({
|
||||
...newWolClient,
|
||||
hostname: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'MAC Address'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={16}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newWolClient.macAddress}
|
||||
onChange={(event) => {
|
||||
setNewWolClient({
|
||||
...newWolClient,
|
||||
macAddress: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newWolClient) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: newWolClient,
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
title={'Search Providers'}
|
||||
title={'WoL Clients'}
|
||||
tooltip={{
|
||||
title: 'Wake on Lan clients can be configured here.',
|
||||
highlightList: ['Clients', 'homepage'],
|
||||
|
||||
Reference in New Issue
Block a user