From 321d620d3c938240448ddaf53402fff70ebf249f Mon Sep 17 00:00:00 2001 From: Sean Edmunds Date: Thu, 20 Mar 2025 17:09:07 -0500 Subject: [PATCH] New settings pages --- .../columns/PersonalizationColumns.tsx | 61 ------- src/components/HelpModal/HelpModal.tsx | 91 +++-------- src/components/HelpModal/HelpSection.tsx | 42 +++++ src/components/HelpModal/HintItem.tsx | 4 +- src/components/Shared/DebouncedMessage.tsx | 120 ++++++++++++++ src/components/index.ts | 1 - .../homepage/components/Search/Search.tsx | 23 ++- src/pages/settings/Settings.tsx | 18 +- .../{ => Applications}/Applications.tsx | 0 .../components/{ => Bookmarks}/Bookmarks.tsx | 0 .../{ => Categories}/Categories.tsx | 2 +- src/pages/settings/components/Messages.tsx | 40 ----- .../settings/components/Messages/Messages.tsx | 154 ++++++++++++++++++ .../settings/components/Personalization.tsx | 40 ----- .../Personalization/Personalization.tsx | 128 +++++++++++++++ .../{ => Search}/SearchProviders.tsx | 0 .../components/{ => Theming}/ThemeCard.tsx | 2 +- .../{ => Theming}/ThemeMiniCard.tsx | 0 .../components/{ => Theming}/ThemeModal.tsx | 0 .../{ => Theming}/ThemeModalContent.tsx | 0 .../{ => Theming}/ThemePlaylistCard.tsx | 0 .../{ => Theming}/ThemePlaylistModal.tsx | 0 .../{ => Theming}/ThemePlaylists.tsx | 0 .../components/{ => Theming}/Themes.tsx | 0 .../components/{ => WoL}/WolClients.tsx | 0 src/styles/Card.module.css | 7 + src/styles/TextInput.module.css | 22 +++ src/styles/index.ts | 2 + 28 files changed, 529 insertions(+), 228 deletions(-) delete mode 100644 src/components/DataTable/columns/PersonalizationColumns.tsx create mode 100644 src/components/HelpModal/HelpSection.tsx create mode 100644 src/components/Shared/DebouncedMessage.tsx rename src/pages/settings/components/{ => Applications}/Applications.tsx (100%) rename src/pages/settings/components/{ => Bookmarks}/Bookmarks.tsx (100%) rename src/pages/settings/components/{ => Categories}/Categories.tsx (95%) delete mode 100644 src/pages/settings/components/Messages.tsx create mode 100644 src/pages/settings/components/Messages/Messages.tsx delete mode 100644 src/pages/settings/components/Personalization.tsx create mode 100644 src/pages/settings/components/Personalization/Personalization.tsx rename src/pages/settings/components/{ => Search}/SearchProviders.tsx (100%) rename src/pages/settings/components/{ => Theming}/ThemeCard.tsx (98%) rename src/pages/settings/components/{ => Theming}/ThemeMiniCard.tsx (100%) rename src/pages/settings/components/{ => Theming}/ThemeModal.tsx (100%) rename src/pages/settings/components/{ => Theming}/ThemeModalContent.tsx (100%) rename src/pages/settings/components/{ => Theming}/ThemePlaylistCard.tsx (100%) rename src/pages/settings/components/{ => Theming}/ThemePlaylistModal.tsx (100%) rename src/pages/settings/components/{ => Theming}/ThemePlaylists.tsx (100%) rename src/pages/settings/components/{ => Theming}/Themes.tsx (100%) rename src/pages/settings/components/{ => WoL}/WolClients.tsx (100%) create mode 100644 src/styles/Card.module.css diff --git a/src/components/DataTable/columns/PersonalizationColumns.tsx b/src/components/DataTable/columns/PersonalizationColumns.tsx deleted file mode 100644 index 4be82ed..0000000 --- a/src/components/DataTable/columns/PersonalizationColumns.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import type { MRT_ColumnDef } from 'mantine-react-table'; -import { useMemo } from 'react'; - -export function PersonalizationColumns() { - const columns = useMemo[]>( - () => [ - { - accessorFn: (row: any) => { - if (row.firstName) { - return `${row.firstName}`; - } - return ''; - }, - id: 'firstName', - header: 'First Name', - size: 50, - Cell: ({ renderedCellValue }) => renderedCellValue, - Header: ({ column }) => column.columnDef.header, - }, - { - accessorFn: (row: any) => { - if (row.lastName) { - return `${row.lastName}`; - } - return ''; - }, - id: 'lastName', - header: 'Last Name', - Cell: ({ renderedCellValue }) => renderedCellValue, - Header: ({ column }) => column.columnDef.header, - }, - { - accessorFn: (row: any) => { - if (row.email) { - return `${row.email}`; - } - return ''; - }, - id: 'email', - header: 'Email', - Cell: ({ renderedCellValue }) => renderedCellValue, - Header: ({ column }) => column.columnDef.header, - }, - { - accessorFn: (row: any) => { - if (row.physicalAddress) { - return `${row.physicalAddress}`; - } - return ''; - }, - id: 'physicalAddress', - header: 'Address', - Cell: ({ renderedCellValue }) => renderedCellValue, - Header: ({ column }) => column.columnDef.header, - }, - ], - [], - ); - - return columns; -} diff --git a/src/components/HelpModal/HelpModal.tsx b/src/components/HelpModal/HelpModal.tsx index 39f7611..0202e43 100644 --- a/src/components/HelpModal/HelpModal.tsx +++ b/src/components/HelpModal/HelpModal.tsx @@ -1,15 +1,18 @@ -import { Divider, Group, Modal, Stack } from '@mantine/core'; +import { Divider, Modal, Stack } from '@mantine/core'; import { useCommonHooks } from 'hooks'; import { modalClasses } from 'styles'; -import HintItem from './HintItem'; +import HelpSection from './HelpSection'; export function HelpModal({ + setSearchValue, + setProviderLabel, + setProviderURL, providerModalOpened, closeProviderModal, applicationData, providerData, }: any) { - const { theme, setSearchValue, setProviderLabel, setProviderURL } = useCommonHooks(); + const { theme } = useCommonHooks(); return ( { - closeProviderModal(); setSearchValue(''); setProviderLabel('Google Search'); setProviderURL('https://www.google.com/search?q='); + closeProviderModal(); }} style={{ '--color-text-pri': theme.primaryColor, @@ -41,67 +44,25 @@ export function HelpModal({ -

- Search Providers -

- - - {providerData?.map((provider: any) => ( - - ))} - -

- Available Applications -

- - - {applicationData?.map((application: any) => ( - - ))} - + { + return { + name: provider.name, + command: provider.prefix, + }; + })} + /> + + { + return { + name: application.url, + command: `!${application.baseUrl.substring(1).toLowerCase()}`, + }; + })} + />
diff --git a/src/components/HelpModal/HelpSection.tsx b/src/components/HelpModal/HelpSection.tsx new file mode 100644 index 0000000..38497a2 --- /dev/null +++ b/src/components/HelpModal/HelpSection.tsx @@ -0,0 +1,42 @@ +import { Divider, Group } from '@mantine/core'; +import { useCommonHooks } from 'hooks'; +import HintItem from './HintItem'; + +function HelpSection({ name, items }: any) { + const { theme } = useCommonHooks(); + return ( + <> +

+ {name} +

+ + + {items + ?.sort((a: any, b: any) => (a.command > b.command ? 1 : -1)) + .map((item: any) => ( + + ))} + + + ); +} + +export default HelpSection; diff --git a/src/components/HelpModal/HintItem.tsx b/src/components/HelpModal/HintItem.tsx index 4b6b56b..dfc0b1d 100644 --- a/src/components/HelpModal/HintItem.tsx +++ b/src/components/HelpModal/HintItem.tsx @@ -1,6 +1,6 @@ import { Group, Text } from '@mantine/core'; -function HintItem({ command, item }: any) { +function HintItem({ command, name }: any) { return ( - {item} + {name} ); diff --git a/src/components/Shared/DebouncedMessage.tsx b/src/components/Shared/DebouncedMessage.tsx new file mode 100644 index 0000000..30afa62 --- /dev/null +++ b/src/components/Shared/DebouncedMessage.tsx @@ -0,0 +1,120 @@ +import { + ActionIcon, + 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'; + +function DebouncedMessage({ label, message, setMessage, handleDelete }: any) { + const { theme } = useCommonHooks(); + const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); + return ( + + { + closeModal(); + }} + style={{ + '--color-text-pri': theme.primaryColor, + '--color-text-acc': theme.accentColor, + '--color-background': theme.backgroundColor, + '--color-border': theme.borderColor, + '--color-search-background': theme.searchColor, + '--image-background': `url(${theme.backgroundImage})`, + }} + closeOnEscape + closeOnClickOutside + > + + + + Add a new message + + + + + Are you sure you want to delete this message? + {`"${message.message}"`} + This action cannot be undone! + + + + + + + + + + { + openModal(); + }} + > + + + + { + setMessage({ + ...message, + message: event.target.value, + }); + }} + /> + + ); +} + +export default DebouncedMessage; diff --git a/src/components/index.ts b/src/components/index.ts index 4231ba9..e276d9e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -7,6 +7,5 @@ export { ApplicationColumns } from './DataTable/columns/ApplicationColumns'; export { BookmarkColumns } from './DataTable/columns/BookmarkColumns'; export { CategoryColumns } from './DataTable/columns/CategoryColumns'; export { MessagesColumns } from './DataTable/columns/MessagesColumns'; -export { PersonalizationColumns } from './DataTable/columns/PersonalizationColumns'; export { SearchProviderColumns } from './DataTable/columns/SearchProviderColumns'; export { WolClientColumns } from './DataTable/columns/WolClientColumns'; diff --git a/src/pages/homepage/components/Search/Search.tsx b/src/pages/homepage/components/Search/Search.tsx index a7603f1..cb3beaa 100644 --- a/src/pages/homepage/components/Search/Search.tsx +++ b/src/pages/homepage/components/Search/Search.tsx @@ -240,14 +240,18 @@ export default function Search({ const optionsFilter: OptionsFilter = ({ options, search }) => { const splittedSearch = search.toLowerCase().trim().split(' '); - if (splittedSearch[0] === '!') { - return apps.map((app: any) => { - return { - label: app.name, - value: app.baseUrl, - ...app, - }; - }); + if (splittedSearch[0].startsWith('!')) { + return apps + .filter((app: any) => + app.name.toLowerCase().includes(splittedSearch[0].substring(1).toLowerCase()), + ) + .map((app: any) => { + return { + label: app.name, + value: app.baseUrl, + ...app, + }; + }); } return options .filter((option: any) => { @@ -469,6 +473,9 @@ export default function Search({ onKeyUp={handleKeyUp} /> { const endpoint = 'api/categories'; const { diff --git a/src/pages/settings/components/Messages.tsx b/src/pages/settings/components/Messages.tsx deleted file mode 100644 index eafb26d..0000000 --- a/src/pages/settings/components/Messages.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Stack, Text } from '@mantine/core'; -import { DataTable, MessagesColumns } from 'components'; -import { useQueryAPI } from 'hooks'; -import { useEffect, useState } from 'react'; -import { textClasses } from 'styles'; -const MessageSettings = () => { - const endpoint = 'api/messages'; - const { - data: messageData, - isFetching: isMessagesFetching, - isFetched: isMessagesFetched, - isLoading: isMessagesLoading, - } = useQueryAPI({ - endpoint: 'api/messages', - }); - const [messages, setMessages] = useState([]); - - useEffect(() => { - if (messageData && isMessagesFetched) { - setMessages(messageData); - } - }, [messageData, isMessagesFetched]); - - return ( - - - Messages - - - - ); -}; -export default MessageSettings; diff --git a/src/pages/settings/components/Messages/Messages.tsx b/src/pages/settings/components/Messages/Messages.tsx new file mode 100644 index 0000000..cb6527f --- /dev/null +++ b/src/pages/settings/components/Messages/Messages.tsx @@ -0,0 +1,154 @@ +import { + Button, + Divider, + Grid, + GridCol, + Group, + Modal, + Stack, + Text, + TextInput, +} from '@mantine/core'; +import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; +import DebouncedMessage from 'components/Shared/DebouncedMessage'; +import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; +import { useEffect, useState } from 'react'; +import { FiPlus } from 'react-icons/fi'; +import { buttonClasses, modalClasses, textClasses, textInputClasses } from 'styles'; +const MessageSettings = () => { + const endpoint = 'api/messages'; + const { + data: messageData, + isFetching: isMessagesFetching, + isFetched: isMessagesFetched, + isLoading: isMessagesLoading, + } = useQueryAPI({ + endpoint: 'api/messages', + }); + const { theme } = useCommonHooks(); + const { mutateAsync } = useMutationAPI(); + const [messages, setMessages] = useState([]); + const [newMessage, setNewMessage] = useState(''); + const [newMessageError, setNewMessageError] = useState(''); + const [message, setMessage] = useState({}); + const [debounced] = useDebouncedValue(message, 1000); + const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); + + const handleDelete = (message: any) => { + mutateAsync({ method: 'DELETE', endpoint: endpoint, body: message }); + }; + + useEffect(() => { + if (messageData && isMessagesFetched) { + setMessages(messageData); + } + }, [messageData, isMessagesFetched]); + + useEffect(() => { + if (Object.keys(debounced).length) { + mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }); + } + }, [debounced]); + + return ( + + { + closeModal(); + }} + style={{ + '--color-text-pri': theme.primaryColor, + '--color-text-acc': theme.accentColor, + '--color-background': theme.backgroundColor, + '--color-border': theme.borderColor, + '--color-search-background': theme.searchColor, + '--image-background': `url(${theme.backgroundImage})`, + }} + closeOnEscape + closeOnClickOutside + > + + + + Add a new message + + + + + { + setNewMessage(event.target.value); + }} + error={newMessageError} + /> + + + + + + + + + + + + Messages + + + + + {messages?.map((message: any) => ( + + + + ))} + + + ); +}; +export default MessageSettings; diff --git a/src/pages/settings/components/Personalization.tsx b/src/pages/settings/components/Personalization.tsx deleted file mode 100644 index 7918808..0000000 --- a/src/pages/settings/components/Personalization.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Text } from '@mantine/core'; -import { DataTable, PersonalizationColumns } from 'components'; -import { useQueryAPI } from 'hooks'; -import { useEffect, useState } from 'react'; -import { textClasses } from 'styles'; -const PersonalizationSettings = () => { - const endpoint = 'api/personalData'; - const { - data: personalData, - isFetched: personalIsFetched, - isFetching: personalIsFetching, - isLoading: personalIsLoading, - } = useQueryAPI({ - endpoint: endpoint, - }); - const [personal, setPersonal] = useState([]); - - useEffect(() => { - if (personalData && personalIsFetched) { - setPersonal(personalData); - } - }, [personalData, personalIsFetched]); - - return ( - <> - - Personalization - - - - ); -}; -export default PersonalizationSettings; diff --git a/src/pages/settings/components/Personalization/Personalization.tsx b/src/pages/settings/components/Personalization/Personalization.tsx new file mode 100644 index 0000000..753e850 --- /dev/null +++ b/src/pages/settings/components/Personalization/Personalization.tsx @@ -0,0 +1,128 @@ +import { Card, Grid, GridCol, Overlay, Stack, Text, TextInput } from '@mantine/core'; +import { useDebouncedValue } from '@mantine/hooks'; +import { useMutationAPI, useQueryAPI } from 'hooks'; +import { useEffect, useState } from 'react'; +import { cardClasses, textClasses, textInputClasses } from 'styles'; +const PersonalizationSettings = () => { + const endpoint = 'api/personalData'; + const { + data: personalData, + isFetched: personalIsFetched, + isFetching: personalIsFetching, + } = useQueryAPI({ + endpoint: endpoint, + }); + const [personal, setPersonal] = useState({}); + const [update, setUpdate] = useState({}); + const [debounced] = useDebouncedValue(update, 1000); + const { mutateAsync: editRow } = useMutationAPI(); + useEffect(() => { + if (personalData && personalIsFetched) { + setPersonal(personalData[0]); + } + }, [personalData, personalIsFetched]); + + useEffect(() => { + if (Object.keys(debounced).length) { + setPersonal(update); + editRow({ method: 'PATCH', endpoint: endpoint, body: debounced }); + setUpdate({}); + } + }, [debounced]); + + useEffect(() => { + if (update !== personal && Object.keys(update).length) { + setPersonal(update); + } + }, [update]); + + return ( + <> + + Personalization + + + + {personalIsFetching && ( + + )} + + + { + const updatedPersonal = { + ...personal, + firstName: event.currentTarget.value, + }; + setUpdate(updatedPersonal); + }} + /> + + + + + { + const updatedPersonal = { + ...personal, + lastName: event.currentTarget.value, + }; + setUpdate(updatedPersonal); + }} + /> + + + + + { + const updatedPersonal = { + ...personal, + email: event.currentTarget.value, + }; + setUpdate(updatedPersonal); + }} + /> + + + + + { + const updatedPersonal = { + ...personal, + physicalAddress: event.currentTarget.value, + }; + setUpdate(updatedPersonal); + }} + /> + + + + + + ); +}; +export default PersonalizationSettings; diff --git a/src/pages/settings/components/SearchProviders.tsx b/src/pages/settings/components/Search/SearchProviders.tsx similarity index 100% rename from src/pages/settings/components/SearchProviders.tsx rename to src/pages/settings/components/Search/SearchProviders.tsx diff --git a/src/pages/settings/components/ThemeCard.tsx b/src/pages/settings/components/Theming/ThemeCard.tsx similarity index 98% rename from src/pages/settings/components/ThemeCard.tsx rename to src/pages/settings/components/Theming/ThemeCard.tsx index 493cce2..4cef9bd 100644 --- a/src/pages/settings/components/ThemeCard.tsx +++ b/src/pages/settings/components/Theming/ThemeCard.tsx @@ -11,7 +11,7 @@ function ThemeCard({ createTheme, allThemes, }: any) { - const { data: themePlaylists, isFetched: isThemePlaylistsFetched } = useQueryAPI({ + const { data: themePlaylists } = useQueryAPI({ endpoint: 'api/themePlaylists', }); const [hover, setHover] = useState(false); diff --git a/src/pages/settings/components/ThemeMiniCard.tsx b/src/pages/settings/components/Theming/ThemeMiniCard.tsx similarity index 100% rename from src/pages/settings/components/ThemeMiniCard.tsx rename to src/pages/settings/components/Theming/ThemeMiniCard.tsx diff --git a/src/pages/settings/components/ThemeModal.tsx b/src/pages/settings/components/Theming/ThemeModal.tsx similarity index 100% rename from src/pages/settings/components/ThemeModal.tsx rename to src/pages/settings/components/Theming/ThemeModal.tsx diff --git a/src/pages/settings/components/ThemeModalContent.tsx b/src/pages/settings/components/Theming/ThemeModalContent.tsx similarity index 100% rename from src/pages/settings/components/ThemeModalContent.tsx rename to src/pages/settings/components/Theming/ThemeModalContent.tsx diff --git a/src/pages/settings/components/ThemePlaylistCard.tsx b/src/pages/settings/components/Theming/ThemePlaylistCard.tsx similarity index 100% rename from src/pages/settings/components/ThemePlaylistCard.tsx rename to src/pages/settings/components/Theming/ThemePlaylistCard.tsx diff --git a/src/pages/settings/components/ThemePlaylistModal.tsx b/src/pages/settings/components/Theming/ThemePlaylistModal.tsx similarity index 100% rename from src/pages/settings/components/ThemePlaylistModal.tsx rename to src/pages/settings/components/Theming/ThemePlaylistModal.tsx diff --git a/src/pages/settings/components/ThemePlaylists.tsx b/src/pages/settings/components/Theming/ThemePlaylists.tsx similarity index 100% rename from src/pages/settings/components/ThemePlaylists.tsx rename to src/pages/settings/components/Theming/ThemePlaylists.tsx diff --git a/src/pages/settings/components/Themes.tsx b/src/pages/settings/components/Theming/Themes.tsx similarity index 100% rename from src/pages/settings/components/Themes.tsx rename to src/pages/settings/components/Theming/Themes.tsx diff --git a/src/pages/settings/components/WolClients.tsx b/src/pages/settings/components/WoL/WolClients.tsx similarity index 100% rename from src/pages/settings/components/WolClients.tsx rename to src/pages/settings/components/WoL/WolClients.tsx diff --git a/src/styles/Card.module.css b/src/styles/Card.module.css new file mode 100644 index 0000000..a87d114 --- /dev/null +++ b/src/styles/Card.module.css @@ -0,0 +1,7 @@ +.settingsItem { + &.root { + color: var(--color-text-pri); + background-color: transparent; + backdrop-filter: blur(10px) brightness(.75) !important; + } +} \ No newline at end of file diff --git a/src/styles/TextInput.module.css b/src/styles/TextInput.module.css index a7596f5..1d2f81e 100644 --- a/src/styles/TextInput.module.css +++ b/src/styles/TextInput.module.css @@ -20,6 +20,28 @@ } } +.settingsItem { + &.root { + .wrapper { + display: flex; + justify-content: center; + .input { + width: 100%; + border: none; + background-color: var(--color-search-background); + color: var(--color-text-pri); + font-size: 18px; + } + .section { + color: var(--color-border); + width: auto; + padding-left: .5rem; + opacity: 1; + } + } + } +} + .search { &.root { .wrapper { diff --git a/src/styles/index.ts b/src/styles/index.ts index fbe708d..30d41dd 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1,5 +1,6 @@ import actionIconClasses from './ActionIcon.module.css'; import buttonClasses from './Button.module.css'; +import cardClasses from './Card.module.css'; import menuClasses from './Menu.module.css'; import modalClasses from './Modal.module.css'; import multiSelectClasses from './MultiSelect.module.css'; @@ -13,6 +14,7 @@ import textInputClasses from './TextInput.module.css'; export { actionIconClasses, + cardClasses, buttonClasses, menuClasses, modalClasses,