Form updates
This commit is contained in:
26
package-lock.json
generated
26
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/form": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/notifications": "^8.0.2",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
@@ -943,6 +944,19 @@
|
||||
"react-dom": "^18.x || ^19.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@mantine/form": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.1.0.tgz",
|
||||
"integrity": "sha512-bCLdeWRbuYfG7l5b7nSKYEdWEWLOl55g/dyifRjFEr9pOK9ZNCj7oCOtXp0Lk1QJGqopZoaKxb5mvajEwDFXoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"klona": "^2.0.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.x || ^19.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@mantine/hooks": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.0.2.tgz",
|
||||
@@ -2713,8 +2727,7 @@
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "5.2.2",
|
||||
@@ -3105,6 +3118,15 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/klona": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
|
||||
"integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/kolorist": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/form": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/notifications": "^8.0.2",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
|
||||
@@ -71,36 +71,29 @@ export function useMutationAPI() {
|
||||
const response = await fetch(`${url}/${endpoint}`, options);
|
||||
if (!response.ok) {
|
||||
const responseData = await response.json();
|
||||
if (typeof responseData.detail !== 'string') {
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail[0]?.msg,
|
||||
loc: responseData.detail[0]?.loc,
|
||||
type: responseData.detail[0]?.type,
|
||||
});
|
||||
throw new Error(errorResponse);
|
||||
if (typeof responseData === 'object') {
|
||||
if (responseData.error.code === 'ER_DUP_ENTRY') {
|
||||
setTimeout(() => {
|
||||
notifications.update({
|
||||
id: id,
|
||||
loading: false,
|
||||
title: (
|
||||
<Text fw={600} c={'var(--mantine-color-accent-0)'}>
|
||||
{method} Failure!
|
||||
</Text>
|
||||
),
|
||||
message: (
|
||||
<Text c={'var(--mantine-color-primary-0)'}>
|
||||
Entry with that name already exists.
|
||||
</Text>
|
||||
),
|
||||
autoClose: 2500,
|
||||
icon: <FaCheck size={24} />,
|
||||
});
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
if (typeof responseData.detail === 'object') {
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail.message,
|
||||
});
|
||||
throw new Error(errorResponse);
|
||||
}
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail,
|
||||
});
|
||||
throw new Error(errorResponse);
|
||||
return await responseData;
|
||||
}
|
||||
setTimeout(() => {
|
||||
notifications.update({
|
||||
|
||||
@@ -247,8 +247,15 @@ function SettingsButton() {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
}}
|
||||
onClick={() => {
|
||||
setCurrentThemePlaylist('All');
|
||||
handleThemeChange(allThemes, true);
|
||||
const themeList = JSON.parse(
|
||||
themePlaylistData?.find(
|
||||
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
|
||||
)?.themeList ?? '[]',
|
||||
);
|
||||
const filteredThemes = allThemes.filter((t: Theme) =>
|
||||
themeList.includes(t.id),
|
||||
);
|
||||
handleThemeChange(filteredThemes, false);
|
||||
}}
|
||||
>
|
||||
<FaDice />
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { isNotEmpty, useForm } from '@mantine/form';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -36,10 +37,19 @@ const ApplicationSettings = () => {
|
||||
url: '',
|
||||
icon: '',
|
||||
baseUrl: '',
|
||||
filterName: '',
|
||||
filterName: 'Default',
|
||||
index: 0,
|
||||
enabled: 1,
|
||||
};
|
||||
const form = useForm({
|
||||
initialValues: defaultApplication,
|
||||
validate: {
|
||||
name: isNotEmpty('Name is required'),
|
||||
url: isNotEmpty('URL is required'),
|
||||
icon: isNotEmpty('Icon is required'),
|
||||
filterName: isNotEmpty('Filter name is required'),
|
||||
},
|
||||
});
|
||||
const appsEndpoint = 'api/applications';
|
||||
const appsOrderEndpoint = 'api/applicationOrder';
|
||||
const { data: applicationData, isFetching: isAppsFetching } = useQueryAPI({
|
||||
@@ -48,7 +58,6 @@ const ApplicationSettings = () => {
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
const [applicationList, setApplicationList] = useState<Application[]>([]);
|
||||
const { isMobile, reorder } = useCommonHooks();
|
||||
const [newApplication, setNewApplication] = useState<Application>(defaultApplication);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
@@ -80,7 +89,7 @@ const ApplicationSettings = () => {
|
||||
|
||||
const handleClose = () => {
|
||||
closeModal();
|
||||
setNewApplication(defaultApplication);
|
||||
form.reset();
|
||||
};
|
||||
useEffect(() => {
|
||||
if (applicationData && !isAppsFetching) {
|
||||
@@ -116,137 +125,124 @@ const ApplicationSettings = () => {
|
||||
handleClose={handleClose}
|
||||
title={'Add a new application'}
|
||||
body={
|
||||
<Stack gap={0} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.name}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Base URL'}
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.baseUrl}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
baseUrl: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={isMobile ? '94%' : '92%'} justify='center'>
|
||||
<TextInput
|
||||
label={'URL'}
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'100%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.url}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
url: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<Select
|
||||
label={'Icon'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'45%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Search for an icon...'
|
||||
data={Object.keys(allIcons).map((icon) => {
|
||||
return { value: icon, label: icon };
|
||||
})}
|
||||
defaultValue={newApplication.icon}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
onChange={(value: string | null) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
icon: value ?? '',
|
||||
});
|
||||
}}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const Icon = allIcons[item.option.value];
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
<Icon />
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Filter'}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newApplication.filterName}
|
||||
onChange={(event) => {
|
||||
setNewApplication({
|
||||
...newApplication,
|
||||
filterName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: appsEndpoint,
|
||||
body: {
|
||||
...values,
|
||||
index: applications.length,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='Nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('baseUrl')}
|
||||
{...form.getInputProps('baseUrl')}
|
||||
label={'Base URL'}
|
||||
placeholder='/nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={isMobile ? '94%' : '92%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'URL'}
|
||||
placeholder='https://domain.example.com/nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'100%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<Select
|
||||
key={form.key('icon')}
|
||||
{...form.getInputProps('icon')}
|
||||
label={'Icon'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'45%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Search for an icon...'
|
||||
data={Object.keys(allIcons).map((icon) => {
|
||||
return { value: icon, label: icon };
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const Icon = allIcons[item.option.value];
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
<Icon />
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('filter')}
|
||||
{...form.getInputProps('filter')}
|
||||
label={'Filter'}
|
||||
placeholder='Storage'
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={defaultApplication.filterName}
|
||||
withAsterisk
|
||||
/>
|
||||
</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: appsEndpoint,
|
||||
body: {
|
||||
...newApplication,
|
||||
index: applications.length,
|
||||
},
|
||||
}).then(handleClose);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
type='submit'
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
|
||||
@@ -129,6 +129,7 @@ function DebouncedApplication({
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
disabled
|
||||
/>
|
||||
<TextInput
|
||||
label={'Base URL'}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import { Box, Button, Card, Divider, Group, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { isNotEmpty, useForm } from '@mantine/form';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -16,6 +17,13 @@ const BookmarkSettings = () => {
|
||||
categoryIndex: 0,
|
||||
filterName: '',
|
||||
};
|
||||
const form = useForm({
|
||||
initialValues: defaultGroup,
|
||||
validate: {
|
||||
categoryName: isNotEmpty('Category name is required'),
|
||||
filterName: isNotEmpty('Filter name is required'),
|
||||
},
|
||||
});
|
||||
const bookmarksEndpoint = 'api/bookmarks';
|
||||
const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({
|
||||
endpoint: bookmarksEndpoint,
|
||||
@@ -27,7 +35,6 @@ const BookmarkSettings = () => {
|
||||
});
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
|
||||
const [newGroup, setNewGroup] = useState<BookmarkGroup>(defaultGroup);
|
||||
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
|
||||
const { isMobile, reorder } = useCommonHooks();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
@@ -37,8 +44,8 @@ const BookmarkSettings = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewGroup(defaultGroup);
|
||||
closeModal();
|
||||
form.reset();
|
||||
};
|
||||
|
||||
function onDragEnd(result: any) {
|
||||
@@ -118,61 +125,60 @@ const BookmarkSettings = () => {
|
||||
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}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newGroup.categoryName}
|
||||
onChange={(event) => {
|
||||
setNewGroup({
|
||||
...newGroup,
|
||||
categoryName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Filter Name'}
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newGroup.filterName}
|
||||
onChange={(event) => {
|
||||
setNewGroup({
|
||||
...newGroup,
|
||||
filterName: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: categoriesEndpoint,
|
||||
body: {
|
||||
...values,
|
||||
categoryIndex: groups.length,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('categoryName')}
|
||||
{...form.getInputProps('categoryName')}
|
||||
label={'Category Name'}
|
||||
placeholder='Shopping'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('filterName')}
|
||||
{...form.getInputProps('filterName')}
|
||||
label={'Filter Name'}
|
||||
placeholder='Personal'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
</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: categoriesEndpoint,
|
||||
body: { ...newGroup, categoryIndex: groups.length },
|
||||
});
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
type='submit'
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { isNotEmpty, useForm } from '@mantine/form';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -47,6 +48,13 @@ function DebouncedGroupSettingsItem({
|
||||
category: group.categoryName,
|
||||
index: 0,
|
||||
});
|
||||
const form = useForm({
|
||||
initialValues: newBookmark,
|
||||
validate: {
|
||||
name: isNotEmpty('Name is required'),
|
||||
url: isNotEmpty('URL is required'),
|
||||
},
|
||||
});
|
||||
const [bookmarkList, setBookmarkList] = useState<Bookmark[]>(bookmarks);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -193,7 +201,7 @@ function DebouncedGroupSettingsItem({
|
||||
borderRadius: '100%',
|
||||
backgroundColor: 'var(--mantine-color-accent-0)',
|
||||
position: 'absolute',
|
||||
top: isMobile ? 140 : 78,
|
||||
top: isMobile ? 140 : 92,
|
||||
right: 18,
|
||||
zIndex: 10,
|
||||
fontSize: '1.25rem',
|
||||
@@ -307,99 +315,95 @@ function DebouncedGroupSettingsItem({
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '90%' : undefined}>
|
||||
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<Stack w={'100%'} display={'flex'} pos={'relative'}>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
labelProps={{
|
||||
style: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
classNames={textInputClasses}
|
||||
onChange={(event) => {
|
||||
setNewBookmark({
|
||||
...newBookmark,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Category'}
|
||||
labelProps={{
|
||||
style: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
classNames={textInputClasses}
|
||||
disabled
|
||||
defaultValue={group.categoryName}
|
||||
onChange={(event) => {
|
||||
setNewBookmark({
|
||||
...newBookmark,
|
||||
category: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center' mb={60}>
|
||||
<TextInput
|
||||
label={'URL'}
|
||||
labelProps={{
|
||||
style: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
className={textInputClasses.settingsItem}
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: 'api/bookmarks',
|
||||
body: { ...values, index: bookmarks.length },
|
||||
});
|
||||
setPopoverOpened(false);
|
||||
form.reset();
|
||||
})}
|
||||
>
|
||||
<Stack w={'100%'} display={'flex'} pos={'relative'}>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='Google'
|
||||
labelProps={{
|
||||
style: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('category')}
|
||||
{...form.getInputProps('category')}
|
||||
label={'Category'}
|
||||
labelProps={{
|
||||
style: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
classNames={textInputClasses}
|
||||
disabled
|
||||
defaultValue={group.categoryName}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center' mb={60}>
|
||||
<TextInput
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'URL'}
|
||||
placeholder='https://google.com'
|
||||
labelProps={{
|
||||
style: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'100%'}
|
||||
pt={'1rem'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
</Group>
|
||||
<Group
|
||||
w={'100%'}
|
||||
pt={'1rem'}
|
||||
classNames={textInputClasses}
|
||||
onChange={(event) => {
|
||||
setNewBookmark({
|
||||
...newBookmark,
|
||||
url: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group
|
||||
w={'100%'}
|
||||
pos={'absolute'}
|
||||
bottom={8}
|
||||
align='center'
|
||||
justify='center'
|
||||
>
|
||||
<Button
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
if (newBookmark) {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: 'api/bookmarks',
|
||||
body: { ...newBookmark, index: bookmarks.length },
|
||||
});
|
||||
pos={'absolute'}
|
||||
bottom={8}
|
||||
align='center'
|
||||
justify='center'
|
||||
>
|
||||
<Button
|
||||
type='submit'
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
setPopoverOpened(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
setPopoverOpened(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Card>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Stack,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { isNotEmpty, useForm } from '@mantine/form';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -27,6 +28,14 @@ const SearchProviderSettings = () => {
|
||||
url: '',
|
||||
prefix: '',
|
||||
};
|
||||
const form = useForm({
|
||||
initialValues: defaultProvider,
|
||||
validate: {
|
||||
name: isNotEmpty('Name is required'),
|
||||
url: isNotEmpty('URL is required'),
|
||||
prefix: isNotEmpty('Prefix is required'),
|
||||
},
|
||||
});
|
||||
const endpoint = 'api/providers';
|
||||
const { data: providerData, isFetched: providerDataIsFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
@@ -34,7 +43,6 @@ const SearchProviderSettings = () => {
|
||||
const [providers, setProviders] = useState<Provider[]>([]);
|
||||
const { isMobile } = useCommonHooks();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [newProvider, setNewProvider] = useState<Provider>(defaultProvider);
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
@@ -42,8 +50,8 @@ const SearchProviderSettings = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewProvider(defaultProvider);
|
||||
closeModal();
|
||||
form.reset();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -70,74 +78,68 @@ const SearchProviderSettings = () => {
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Search Provider'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Stack w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: values,
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Stack w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='Google'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('prefix')}
|
||||
{...form.getInputProps('prefix')}
|
||||
label={'Prefix'}
|
||||
placeholder='/g'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
label={'Name'}
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'Url'}
|
||||
placeholder='https://www.google.com/search?q='
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'64%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.name}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
name: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'Prefix'}
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newProvider.prefix}
|
||||
onChange={(event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
prefix: event.target.value,
|
||||
});
|
||||
}}
|
||||
withAsterisk
|
||||
/>
|
||||
</Stack>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
type='submit'
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
<TextInput
|
||||
label={'Url'}
|
||||
className={textInputClasses.settingsItem}
|
||||
w={isMobile ? '64%' : '100%'}
|
||||
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>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Stack,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { isNotEmpty, matches, useForm } from '@mantine/form';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -25,6 +26,16 @@ const WolClientSettings = () => {
|
||||
hostname: '',
|
||||
macAddress: '',
|
||||
};
|
||||
const form = useForm({
|
||||
initialValues: defaultWolClient,
|
||||
validate: {
|
||||
hostname: isNotEmpty('Hostname is required'),
|
||||
macAddress: matches(
|
||||
/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/,
|
||||
'Invalid MAC Address',
|
||||
),
|
||||
},
|
||||
});
|
||||
const endpoint = 'api/wolClients';
|
||||
const { data: wolClientsData, isFetched: wolClientsIsFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
@@ -32,7 +43,6 @@ const WolClientSettings = () => {
|
||||
const [wolClients, setWolClients] = useState<WolClient[]>([]);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
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);
|
||||
@@ -40,8 +50,8 @@ const WolClientSettings = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewWolClient(defaultWolClient);
|
||||
closeModal();
|
||||
form.reset();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,62 +74,56 @@ const WolClientSettings = () => {
|
||||
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}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={newWolClient.hostname}
|
||||
onChange={(event) => {
|
||||
setNewWolClient({
|
||||
...newWolClient,
|
||||
hostname: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={'MAC Address'}
|
||||
className={textInputClasses.settingsItem}
|
||||
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>
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: values,
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('hostname')}
|
||||
{...form.getInputProps('hostname')}
|
||||
label={'Hostname'}
|
||||
placeholder='Workstation'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('macAddress')}
|
||||
{...form.getInputProps('macAddress')}
|
||||
label={'MAC Address'}
|
||||
placeholder='00:00:00:00:00:00'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
/>
|
||||
</Group>
|
||||
<Divider color='black' my='lg' />
|
||||
<Group pos={'absolute'} bottom={8}>
|
||||
<Button
|
||||
type='submit'
|
||||
color={'green'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
<SettingsHeader
|
||||
|
||||
Reference in New Issue
Block a user