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",
"@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",

View File

@@ -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",

View File

@@ -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}

View File

@@ -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

View File

@@ -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}

View File

@@ -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'>

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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>

View File

@@ -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}

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 {
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',

View File

@@ -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',

View File

@@ -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();

View File

@@ -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();

View File

@@ -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'>

View File

@@ -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'],