Files
TriggerFrontend/src/pages/settings/components/Search/SearchProviders.tsx
Sean Edmunds 9a5af3f4ef Style updates
2025-09-08 16:29:52 -05:00

258 lines
7.1 KiB
TypeScript

import {
Box,
Button,
Grid,
GridCol,
Group,
Popover,
Stack,
Switch,
TextInput,
Tooltip,
} from '@mantine/core';
import { isNotEmpty, useForm } from '@mantine/form';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { FiPower } from 'react-icons/fi';
import { popoverClasses, switchClasses, textInputClasses, toolTipClasses } from 'styles';
import type { Provider } from 'types';
import DebouncedSearchProvider from './DebouncedSearchProvider';
import SearchProviderSettingsItem from './SearchProviderSettingsItem';
const SearchProviderSettings = () => {
const defaultProvider = {
id: undefined,
name: '',
url: '',
prefix: '',
default: 0,
};
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,
});
const [providers, setProviders] = useState<Provider[]>([]);
const { isMobile, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const handleClose = () => {
closeModal();
form.reset();
};
useEffect(() => {
if (providerData && providerDataIsFetched) {
setProviders(
providerData
.filter(
(a: Provider) =>
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
a.url.toLowerCase().includes(debouncedSearch.toLowerCase()),
)
.sort((a: Provider, b: Provider) => (a.prefix > b.prefix ? 1 : -1)),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [providerData, providerDataIsFetched, debouncedSearch]);
return (
<Stack>
<AddItemModal
modalOpened={modalOpened}
handleClose={handleClose}
title={'Add a new Search Provider'}
body={
<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='eg. Google'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
<TextInput
key={form.key('prefix')}
{...form.getInputProps('prefix')}
label={'Prefix'}
placeholder='eg. /g'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
</Group>
<TextInput
key={form.key('url')}
{...form.getInputProps('url')}
label={'Url'}
placeholder='eg. https://www.google.com/search?q='
className={textInputClasses.settingsItem}
w={'64%'}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
</Stack>
<Group
style={{
position: 'absolute',
right: '1rem',
top: '.5rem',
width: 'fit-content',
backgroundColor:
'color-mix(in srgb, var(--mantine-color-background-0), transparent 50%)',
padding: '.5rem',
borderRadius: '10px',
color: 'var(--mantine-color-search-0)',
backdropFilter: 'brightness(0.5)',
}}
>
<Tooltip
className={toolTipClasses.default}
classNames={toolTipClasses}
label={'Default'}
refProp='rootRef'
withArrow
>
<Switch
key={form.key('default')}
{...form.getInputProps('default')}
className={switchClasses.enable}
classNames={switchClasses}
defaultChecked={false}
thumbIcon={<FiPower />}
/>
</Tooltip>
</Group>
<Group mt={'1rem'}>
<Button type='submit' color={'green'}>
Save
</Button>
<Button
color={'red'}
styles={{ label: { color: 'black' } }}
onClick={handleClose}
>
Cancel
</Button>
</Group>
</Stack>
</form>
}
/>
<SettingsHeader
setDragging={undefined}
openModal={openModal}
title={'Search Providers'}
tooltip={{
title: 'Search providers and shortcuts can be configured here.',
highlightList: ['providerData', 'search', 'provider', 'prefix'],
description:
'Providers can be used in the search bar. By starting a search with the prefix, it will be redirected to the url of the provider to complete the search.',
}}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchPlaceholder={'Search Providers...'}
isLoading={isLoading}
setIsLoading={setIsLoading}
/>
<Stack
mt={isMobile ? '10rem' : '6rem'}
mb={isMobile ? '6rem' : '1rem'}
w={'100%'}
align='center'
>
<Grid columns={18} w={'75%'}>
{providers.length > 0 ? (
providers?.map((provider: Provider) => (
<GridCol span={isMobile ? 18 : 6} key={provider.id}>
<Popover
withArrow
withOverlay
trapFocus
closeOnClickOutside={!isOpen}
classNames={popoverClasses}
middlewares={{ shift: { padding: 20 } }}
position='bottom'
>
<Popover.Target>
<Box>
<SearchProviderSettingsItem provider={provider} />
</Box>
</Popover.Target>
<Popover.Dropdown w={isMobile ? '90%' : undefined}>
<DebouncedSearchProvider
searchProvider={provider}
setIsOpen={setIsOpen}
/>
</Popover.Dropdown>
</Popover>
</GridCol>
))
) : (
<GridCol
w={'100%'}
span={18}
style={{ display: 'flex', justifyContent: 'center' }}
>
<NoItemsFound />
</GridCol>
)}
</Grid>
</Stack>
</Stack>
);
};
export default SearchProviderSettings;