diff --git a/src/webapp/components/AddToCollectionModal.tsx b/src/webapp/components/AddToCollectionModal.tsx index 9f1a1a4c..52296583 100644 --- a/src/webapp/components/AddToCollectionModal.tsx +++ b/src/webapp/components/AddToCollectionModal.tsx @@ -143,7 +143,7 @@ export function AddToCollectionModal({ )} - + - {selectedCollections.length > 0 && ( - - Selected collections:{' '} - - {selectedCollections.map((c, index) => ( - - - {c.name} - - {index < selectedCollections.length - 1 && ', '} - - ))} - - - )} + + }> @@ -168,11 +155,16 @@ export default function AddCardDrawer(props: Props) { /> - - - diff --git a/src/webapp/features/cards/lib/mutations/useAddCard.tsx b/src/webapp/features/cards/lib/mutations/useAddCard.tsx index 3b340910..539a965a 100644 --- a/src/webapp/features/cards/lib/mutations/useAddCard.tsx +++ b/src/webapp/features/cards/lib/mutations/useAddCard.tsx @@ -26,8 +26,15 @@ export default function useAddCard() { // Do things that are absolutely necessary and logic related (like query invalidation) in the useMutation callbacks // Do UI related things like redirects or showing toast notifications in mutate callbacks. If the user navigated away from the current screen before the mutation finished, those will purposefully not fire // https://tkdodo.eu/blog/mastering-mutations-in-react-query#some-callbacks-might-not-fire - onSuccess: () => { + onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['my cards'] }); + queryClient.invalidateQueries({ queryKey: ['library'] }); + queryClient.invalidateQueries({ queryKey: ['collections'] }); + + // invalidate each collection query individually + variables.collectionIds?.forEach((id) => { + queryClient.invalidateQueries({ queryKey: ['collection', id] }); + }); }, }); diff --git a/src/webapp/features/collections/components/collectionSelector/CollectionSelector.tsx b/src/webapp/features/collections/components/collectionSelector/CollectionSelector.tsx index 1ff9bebc..0620d78a 100644 --- a/src/webapp/features/collections/components/collectionSelector/CollectionSelector.tsx +++ b/src/webapp/features/collections/components/collectionSelector/CollectionSelector.tsx @@ -10,20 +10,18 @@ import { Button, Drawer, Container, - SimpleGrid, Group, } from '@mantine/core'; -import { IoSearch } from 'react-icons/io5'; -import { useState } from 'react'; +import { Fragment, useState } from 'react'; +import { useDebouncedValue } from '@mantine/hooks'; import useCollections from '../../lib/queries/useCollections'; import useCollectionSearch from '../../lib/queries/useCollectionSearch'; -import { useDebouncedValue } from '@mantine/hooks'; - +import CreateCollectionDrawer from '../createCollectionDrawer/CreateCollectionDrawer'; import CollectionSelectorError from './Error.CollectionSelector'; import CollectionSelectorItem from '../collectionSelectorItem/CollectionSelectorItem'; -import CollectionSelectorNewCollection from '../collectionSelectorNewCollection/CollectionSelectorNewCollection'; import { BiPlus } from 'react-icons/bi'; -import { useContextDrawers } from '@/providers/drawers'; +import { IoSearch } from 'react-icons/io5'; +import { DEFAULT_OVERLAY_PROPS } from '@/styles/overlays'; interface Props { isOpen: boolean; @@ -39,7 +37,7 @@ export default function CollectionSelector(props: Props) { const [search, setSearch] = useState(''); const [debouncedSearch] = useDebouncedValue(search, 200); const searchedCollections = useCollectionSearch({ query: debouncedSearch }); - const drawers = useContextDrawers(); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); const handleCollectionChange = ( checked: boolean, @@ -76,125 +74,162 @@ export default function CollectionSelector(props: Props) { } const hasCollections = data?.collections?.length > 0; + const hasSelectedCollections = props.selectedCollections.length > 0; return ( - - - - Add to collections - - - - - setSearch(e.currentTarget.value)} - size="md" - variant="filled" - id="search" - leftSection={} - rightSection={ - setSearch('')} - style={{ display: search ? undefined : 'none' }} - /> - } - /> - - - Collections - - Selected ({props.selectedCollections.length}) - - + + + + + Add to collections + + + + + setSearch(e.currentTarget.value)} + size="md" + variant="filled" + id="search" + leftSection={} + rightSection={ + setSearch('')} + style={{ display: search ? undefined : 'none' }} + /> + } + /> + + + Collections + + Selected ({props.selectedCollections.length}) + + - {/* Collections Panel */} - - - - {search && } - {search && searchedCollections.isPending && ( - - - Searching collections... - - - - )} - {search && - searchedCollections.data && - searchedCollections.data.collections.length === 0 && ( - + {/* collections Panel */} + + + + {search ? ( + + + + {searchedCollections.isPending && ( + + + Searching collections... + + + + )} + + {searchedCollections.data && + (searchedCollections.data.collections.length === 0 ? ( + + ) : ( + renderCollectionItems( + searchedCollections.data.collections, + ) + ))} + + ) : hasCollections ? ( + renderCollectionItems(data.collections) + ) : ( + + + No collections + + + )} - {search && - searchedCollections.data && - renderCollectionItems(searchedCollections.data.collections)} - {!search && !hasCollections && ( - - - No collections - - - - )} - {!search && - hasCollections && - renderCollectionItems(data.collections)} - - - + + + - {/* Selected Collections Panel */} - - - - {props.selectedCollections.length > 0 ? ( - renderCollectionItems(props.selectedCollections) - ) : ( - - )} - - - - - - - - - - - + {/* selected Collections Panel */} + + + + {props.selectedCollections.length > 0 ? ( + renderCollectionItems(props.selectedCollections) + ) : ( + + )} + + + + + + + {hasSelectedCollections && ( + + )} + + + + + + setIsDrawerOpen(false)} + initialName={search} + onCreate={(newCollection) => { + props.onSelectedCollectionsChange([ + ...props.selectedCollections, + newCollection, + ]); + }} + /> + ); } diff --git a/src/webapp/features/collections/components/collectionSelectorItem/CollectionSelectorItem.tsx b/src/webapp/features/collections/components/collectionSelectorItem/CollectionSelectorItem.tsx index 59839b70..f46b8a09 100644 --- a/src/webapp/features/collections/components/collectionSelectorItem/CollectionSelectorItem.tsx +++ b/src/webapp/features/collections/components/collectionSelectorItem/CollectionSelectorItem.tsx @@ -39,6 +39,9 @@ export default function CollectionSelectorItem(props: Props) { {props.name} + + {props.cardCount} {props.cardCount === 1 ? 'card' : 'cards'} + diff --git a/src/webapp/features/collections/components/collectionSelectorNewCollection/CollectionSelectorNewCollection.tsx b/src/webapp/features/collections/components/collectionSelectorNewCollection/CollectionSelectorNewCollection.tsx deleted file mode 100644 index d1998a4a..00000000 --- a/src/webapp/features/collections/components/collectionSelectorNewCollection/CollectionSelectorNewCollection.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useContextDrawers } from '@/providers/drawers'; -import { Button } from '@mantine/core'; -import { BiPlus } from 'react-icons/bi'; - -interface Props { - name: string; -} - -export default function CollectionSelectorNewCollection(props: Props) { - const drawers = useContextDrawers(); - - return ( - - ); -} diff --git a/src/webapp/features/collections/components/createCollectionDrawer/CreateCollectionDrawer.tsx b/src/webapp/features/collections/components/createCollectionDrawer/CreateCollectionDrawer.tsx index fe4e4437..847d7291 100644 --- a/src/webapp/features/collections/components/createCollectionDrawer/CreateCollectionDrawer.tsx +++ b/src/webapp/features/collections/components/createCollectionDrawer/CreateCollectionDrawer.tsx @@ -10,11 +10,17 @@ import { import { useForm } from '@mantine/form'; import useCreateCollection from '../../lib/mutations/useCreateCollection'; import { notifications } from '@mantine/notifications'; +import { DEFAULT_OVERLAY_PROPS } from '@/styles/overlays'; interface Props { isOpen: boolean; onClose: () => void; initialName?: string; + onCreate?: (newCollection: { + id: string; + name: string; + cardCount: number; + }) => void; } export default function createCollectionDrawer(props: Props) { @@ -35,11 +41,18 @@ export default function createCollectionDrawer(props: Props) { description: form.getValues().description, }, { - onSuccess: () => { + onSuccess: (newCollection) => { notifications.show({ message: `Created collection "${form.getValues().name}".`, }); + props.onClose(); + props.onCreate && + props.onCreate({ + id: newCollection.collectionId, + name: form.getValues().name, + cardCount: 0, + }); }, onError: () => { notifications.show({ @@ -59,11 +72,8 @@ export default function createCollectionDrawer(props: Props) { onClose={props.onClose} withCloseButton={false} position="bottom" - overlayProps={{ - blur: 3, - gradient: - 'linear-gradient(0deg, rgba(204, 255, 0, 0.5), rgba(255, 255, 255, 0.5))', - }} + size={'30rem'} + overlayProps={DEFAULT_OVERLAY_PROPS} > @@ -72,7 +82,7 @@ export default function createCollectionDrawer(props: Props) { - + - - - diff --git a/src/webapp/features/collections/components/createCollectionShortcut/CreateCollectionShortcut.tsx b/src/webapp/features/collections/components/createCollectionShortcut/CreateCollectionShortcut.tsx index b0f044ff..460ce9b3 100644 --- a/src/webapp/features/collections/components/createCollectionShortcut/CreateCollectionShortcut.tsx +++ b/src/webapp/features/collections/components/createCollectionShortcut/CreateCollectionShortcut.tsx @@ -1,18 +1,25 @@ -import { useContextDrawers } from '@/providers/drawers'; import { NavLink } from '@mantine/core'; +import { Fragment, useState } from 'react'; import { BiPlus } from 'react-icons/bi'; +import CreateCollectionDrawer from '@/features/collections/components/createCollectionDrawer/CreateCollectionDrawer'; export default function CreateCollectionShortcut() { - const drawers = useContextDrawers(); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); return ( - } - onClick={() => drawers.open('createCollection')} - /> + + } + onClick={() => setIsDrawerOpen(true)} + /> + setIsDrawerOpen(false)} + /> + ); } diff --git a/src/webapp/features/collections/components/editCollectionDrawer/EditCollectionDrawer.tsx b/src/webapp/features/collections/components/editCollectionDrawer/EditCollectionDrawer.tsx index 2d7ecaaa..cdc4ffac 100644 --- a/src/webapp/features/collections/components/editCollectionDrawer/EditCollectionDrawer.tsx +++ b/src/webapp/features/collections/components/editCollectionDrawer/EditCollectionDrawer.tsx @@ -10,6 +10,7 @@ import { import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; import useUpdateCollection from '../../lib/mutations/useUpdateCollection'; +import { DEFAULT_OVERLAY_PROPS } from '@/styles/overlays'; interface Props { isOpen: boolean; @@ -65,11 +66,8 @@ export default function EditCollectionDrawer(props: Props) { onClose={props.onClose} withCloseButton={false} position="bottom" - overlayProps={{ - blur: 3, - gradient: - 'linear-gradient(0deg, rgba(204, 255, 0, 0.5), rgba(255, 255, 255, 0.5))', - }} + size={'30rem'} + overlayProps={DEFAULT_OVERLAY_PROPS} > @@ -85,6 +83,7 @@ export default function EditCollectionDrawer(props: Props) { label="Name" placeholder="Collection name" variant="filled" + size="md" required maxLength={100} key={form.key('name')} @@ -96,17 +95,27 @@ export default function EditCollectionDrawer(props: Props) { label="Description" placeholder="Describe what this collection is about" variant="filled" + size="md" rows={8} maxLength={500} key={form.key('description')} {...form.getInputProps('description')} /> - - - diff --git a/src/webapp/features/collections/containers/collectionsContainer/CollectionsContainer.tsx b/src/webapp/features/collections/containers/collectionsContainer/CollectionsContainer.tsx index d60e91c9..019eb402 100644 --- a/src/webapp/features/collections/containers/collectionsContainer/CollectionsContainer.tsx +++ b/src/webapp/features/collections/containers/collectionsContainer/CollectionsContainer.tsx @@ -11,11 +11,12 @@ import { import useCollections from '../../lib/queries/useCollections'; import { BiPlus } from 'react-icons/bi'; import CollectionCard from '../../components/collectionCard/CollectionCard'; -import { useContextDrawers } from '@/providers/drawers'; +import { useState } from 'react'; +import CreateCollectionDrawer from '../../components/createCollectionDrawer/CreateCollectionDrawer'; export default function CollectionsContainer() { const { data } = useCollections(); - const drawers = useContextDrawers(); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); return ( @@ -34,7 +35,7 @@ export default function CollectionsContainer() { No collections + setIsDrawerOpen(false)} + /> )} diff --git a/src/webapp/features/composer/components/composerDrawer/ComposerDrawer.tsx b/src/webapp/features/composer/components/composerDrawer/ComposerDrawer.tsx index 29664b96..66c87eeb 100644 --- a/src/webapp/features/composer/components/composerDrawer/ComposerDrawer.tsx +++ b/src/webapp/features/composer/components/composerDrawer/ComposerDrawer.tsx @@ -2,22 +2,25 @@ import { ActionIcon, Affix, Text, - Box, Menu, Stack, Transition, Card, + Overlay, } from '@mantine/core'; -import { useContextDrawers } from '@/providers/drawers'; import { Fragment, useState } from 'react'; import { FiPlus, FiX } from 'react-icons/fi'; import { FaRegNoteSticky } from 'react-icons/fa6'; import { BiCollection } from 'react-icons/bi'; -import Link from 'next/link'; +import { DEFAULT_OVERLAY_PROPS } from '@/styles/overlays'; +import AddCardDrawer from '@/features/cards/components/addCardDrawer/AddCardDrawer'; +import CreateCollectionDrawer from '@/features/collections/components/createCollectionDrawer/CreateCollectionDrawer'; export default function ComposerDrawer() { const [opened, setOpened] = useState(false); - const drawers = useContextDrawers(); + const [activeDrawer, setActiveDrawer] = useState< + 'addCard' | 'createCollection' | null + >(); return ( @@ -73,7 +76,7 @@ export default function ComposerDrawer() { py={0} > drawers.open('addCard')} + onClick={() => setActiveDrawer('addCard')} p={0} style={{ cursor: 'pointer' }} > @@ -88,7 +91,7 @@ export default function ComposerDrawer() { drawers.open('createCollection')} + onClick={() => setActiveDrawer('createCollection')} p={0} style={{ cursor: 'pointer' }} > @@ -101,6 +104,16 @@ export default function ComposerDrawer() { + + setActiveDrawer(null)} + /> + setActiveDrawer(null)} + /> + {(styles) => ( - )} diff --git a/src/webapp/features/library/containers/libraryContainer/LibraryContainer.tsx b/src/webapp/features/library/containers/libraryContainer/LibraryContainer.tsx index 7232f474..1d016966 100644 --- a/src/webapp/features/library/containers/libraryContainer/LibraryContainer.tsx +++ b/src/webapp/features/library/containers/libraryContainer/LibraryContainer.tsx @@ -4,7 +4,7 @@ import UrlCard from '@/features/cards/components/urlCard/UrlCard'; import useMyCards from '@/features/cards/lib/queries/useMyCards'; import CollectionCard from '@/features/collections/components/collectionCard/CollectionCard'; import useCollections from '@/features/collections/lib/queries/useCollections'; -import { useContextDrawers } from '@/providers/drawers'; +import CreateCollectionDrawer from '@/features/collections/components/createCollectionDrawer/CreateCollectionDrawer'; import { Anchor, Container, @@ -17,13 +17,14 @@ import { Button, } from '@mantine/core'; import Link from 'next/link'; +import { useState } from 'react'; import { BiCollection, BiPlus } from 'react-icons/bi'; import { FaRegNoteSticky } from 'react-icons/fa6'; export default function LibraryContainer() { const { data: CollectionsData } = useCollections({ limit: 4 }); const { data: myCardsData } = useMyCards({ limit: 4 }); - const drawers = useContextDrawers(); + const [isCollectionDrawerOpen, setIsCollectionDrawerOpen] = useState(false); return ( @@ -54,7 +55,7 @@ export default function LibraryContainer() { No collections