From cec6dc51975a2d206bc600789d0c5aff0c9a88cd Mon Sep 17 00:00:00 2001 From: gitstart-omnivore Date: Tue, 7 Jun 2022 08:40:29 +0000 Subject: [PATCH] Rebase --- packages/web/components/patterns/CardMenu.tsx | 5 + .../patterns/LibraryCards/CardTypes.tsx | 2 + .../templates/homeFeed/EditTitleModal.tsx | 157 ++++++++++++++++++ .../templates/homeFeed/HomeFeedContainer.tsx | 39 ++++- .../mutations/updatePageMutation.ts | 48 ++++++ .../queries/useGetLibraryItemsQuery.tsx | 12 +- .../web/stories/EditTitleModal.stories.tsx | 40 +++++ 7 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 packages/web/components/templates/homeFeed/EditTitleModal.tsx create mode 100644 packages/web/lib/networking/mutations/updatePageMutation.ts create mode 100644 packages/web/stories/EditTitleModal.stories.tsx diff --git a/packages/web/components/patterns/CardMenu.tsx b/packages/web/components/patterns/CardMenu.tsx index 229da3c98..357de8d8b 100644 --- a/packages/web/components/patterns/CardMenu.tsx +++ b/packages/web/components/patterns/CardMenu.tsx @@ -15,6 +15,7 @@ export type CardMenuDropdownAction = | 'set-labels' | 'showOriginal' | 'unsubscribe' + | 'editTitle' type CardMenuProps = { item: LibraryItemNode @@ -47,6 +48,10 @@ export function CardMenu(props: CardMenuProps): JSX.Element { onSelect={() => props.actionHandler('showOriginal')} title="Open Original" /> + props.actionHandler('editTitle')} + title="Edit Title" + /> {isVipUser(props.viewer) && ( { diff --git a/packages/web/components/patterns/LibraryCards/CardTypes.tsx b/packages/web/components/patterns/LibraryCards/CardTypes.tsx index 160439ee2..cb45a6119 100644 --- a/packages/web/components/patterns/LibraryCards/CardTypes.tsx +++ b/packages/web/components/patterns/LibraryCards/CardTypes.tsx @@ -5,6 +5,7 @@ import type { LibraryItemNode } from '../../../lib/networking/queries/useGetLibr export type LinkedItemCardAction = | 'showDetail' | 'showOriginal' + | 'editTitle' | 'archive' | 'unarchive' | 'delete' @@ -14,6 +15,7 @@ export type LinkedItemCardAction = | 'snooze' | 'set-labels' | 'unsubscribe' + | 'update-item' export type LinkedItemCardProps = { item: LibraryItemNode diff --git a/packages/web/components/templates/homeFeed/EditTitleModal.tsx b/packages/web/components/templates/homeFeed/EditTitleModal.tsx new file mode 100644 index 000000000..c5d795f86 --- /dev/null +++ b/packages/web/components/templates/homeFeed/EditTitleModal.tsx @@ -0,0 +1,157 @@ +import { + ModalRoot, + ModalContent, + ModalOverlay, +} from '../../elements/ModalPrimitives' +import { VStack, HStack, Box } from '../../elements/LayoutPrimitives' +import { Button } from '../../elements/Button' +import { StyledText } from '../../elements/StyledText' +import { CrossIcon } from '../../elements/images/CrossIcon' +import { theme } from '../../tokens/stitches.config' +import { FormInput } from '../../elements/FormElements' +import { useState, useCallback } from 'react' +import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery' +import { StyledTextArea } from '../../elements/StyledTextArea' +import { updatePageMutation } from '../../../lib/networking/mutations/updatePageMutation' +import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' + +type EditTitleModalProps = { + onOpenChange: (open: boolean) => void + item: LibraryItem + updateItem: (item: LibraryItem) => Promise, +} + +export function EditTitleModal(props: EditTitleModalProps): JSX.Element { + const [title, setTitle] = useState(props.item.node.title) + const [description, setDescription] = useState(props.item.node.description) + + const handleUpdateTitle = async () => { + if (title !== '') { + const res = await updatePageMutation({ + pageId: props.item.node.id, + title, + description, + }) + + if (res) { + await props.updateItem({ + cursor: props.item.cursor, + node: { + ...props.item.node, + title: title, + description: description, + }, + }) + showSuccessToast('Link updated succesfully', { + position: 'bottom-right', + }) + props.onOpenChange(false) + } else { + showErrorToast('There was an error updating your link', { + position: 'bottom-right', + }) + } + } else { + showErrorToast('Title can\'t be empty', { + position: 'bottom-right', + }) + } + } + + return ( + + + { + // remove focus from modal + (document.activeElement as HTMLElement).blur() + }} + > + + + + Edit Title or Description + + + + Title + +
{ + event.preventDefault() + }} + > + setTitle(event.target.value)} + css={{ + borderRadius: '8px', + border: '1px solid $grayTextContrast', + width: '100%', + p: '$2', + }} + /> + + Description + + + setDescription(event.target.value)} + maxLength={4000} + /> + + + + + + +
+
+
+
+ ) +} diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 92f132a26..dc8de6f77 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -39,7 +39,11 @@ import { SetLabelsModal } from '../article/SetLabelsModal' import { Label } from '../../../lib/networking/fragments/labelFragment' import { EmptyLibrary } from './EmptyLibrary' import TopBarProgress from 'react-topbar-progress-indicator' -import { State, PageType } from '../../../lib/networking/fragments/articleFragment' +import { + State, + PageType, +} from '../../../lib/networking/fragments/articleFragment' +import { EditTitleModal } from './EditTitleModal' export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT' @@ -85,6 +89,7 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { ) const [showAddLinkModal, setShowAddLinkModal] = useState(false) + const [showEditTitleModal, setShowEditTitleModal] = useState(false) const [queryInputs, setQueryInputs] = useState(defaultQuery) @@ -97,6 +102,9 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { }) ) + const { itemsPages, size, setSize, isValidating, performActionOnItem } = + useGetLibraryItemsQuery(queryInputs) + useEffect(() => { if (!router.isReady) return const q = router.query['q'] @@ -106,13 +114,11 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { } if (qs !== (queryInputs.searchQuery || '')) { setQueryInputs({ ...queryInputs, searchQuery: qs }) + performActionOnItem('refresh', undefined as unknown as any) } // intentionally not watching queryInputs here to prevent infinite looping // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setQueryInputs, router.isReady, router.query]) - - const { itemsPages, size, setSize, isValidating, performActionOnItem } = - useGetLibraryItemsQuery(queryInputs) + }, [setQueryInputs, router.isReady, router.query, performActionOnItem]) const hasMore = useMemo(() => { if (!itemsPages) { @@ -261,7 +267,10 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { if (item.node.state === State.PROCESSING) { router.push(`/${username}/links/${item.node.id}`) } else { - const dl = item.node.pageType === PageType.HIGHLIGHTS ? `#${item.node.id}` : '' + const dl = + item.node.pageType === PageType.HIGHLIGHTS + ? `#${item.node.id}` + : '' router.push(`/${username}/${item.node.slug}` + dl) } } @@ -298,6 +307,8 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { break case 'unsubscribe': performActionOnItem('unsubscribe', item) + case 'update-item': + performActionOnItem('update-item', item) break } } @@ -438,6 +449,7 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { } const href = `${window.location.pathname}?${qp.toString()}` router.push(href, href, { shallow: true }) + performActionOnItem('refresh', undefined as unknown as any) }} loadMore={() => { if (isValidating) { @@ -457,6 +469,8 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { setLabelsTarget={setLabelsTarget} showAddLinkModal={showAddLinkModal} setShowAddLinkModal={setShowAddLinkModal} + showEditTitleModal={showEditTitleModal} + setShowEditTitleModal={setShowEditTitleModal} setActiveItem={(item: LibraryItem) => { activateCard(item.node.id) }} @@ -482,6 +496,8 @@ type HomeFeedContentProps = { setLabelsTarget: (target: LibraryItem | undefined) => void showAddLinkModal: boolean setShowAddLinkModal: (show: boolean) => void + showEditTitleModal: boolean + setShowEditTitleModal: (show: boolean) => void setActiveItem: (item: LibraryItem) => void actionHandler: ( action: LinkedItemCardAction, @@ -500,6 +516,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { const [showRemoveLinkConfirmation, setShowRemoveLinkConfirmation] = useState(false) const [linkToRemove, setLinkToRemove] = useState() + const [linkToEdit, setLinkToEdit] = useState() const updateLayout = useCallback( async (newLayout: LayoutType) => { @@ -723,6 +740,9 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { if (action === 'delete') { setShowRemoveLinkConfirmation(true) setLinkToRemove(linkedItem) + } else if (action === 'editTitle') { + props.setShowEditTitleModal(true) + setLinkToEdit(linkedItem) } else { props.actionHandler(action, linkedItem) } @@ -756,6 +776,13 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { {props.showAddLinkModal && ( props.setShowAddLinkModal(false)} /> )} + {props.showEditTitleModal && ( + props.actionHandler('update-item', item)} + onOpenChange={() => props.setShowEditTitleModal(false)} + item={linkToEdit as LibraryItem} + /> + )} {props.shareTarget && viewerData?.me?.profile.username && ( { + const mutation = gql` + mutation { + updatePage( + input: { + pageId: "${input.pageId}" + title: "${input.title}" + description: "${input.description}" + } + ) { + ... on UpdatePageSuccess { + updatedPage { + id + title + url + createdAt + author + image + description + publishedAt + } + } + ... on UpdatePageError { + errorCodes + } + } + } + ` + + try { + const data = await gqlFetcher(mutation) + const output = data as any + return output.updatePage + } catch (err) { + return undefined + } +} diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx index bdbfe4a9a..b729bebc5 100644 --- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx @@ -37,6 +37,7 @@ type LibraryItemAction = | 'mark-unread' | 'refresh' | 'unsubscribe' + | 'update-item' export type LibraryItemsData = { search: LibraryItems @@ -195,6 +196,10 @@ export function useGetLibraryItemsQuery({ } } + const getIndexOf = (page: LibraryItems, item: LibraryItem) => { + return page.edges.findIndex(i => i.node.id === item.node.id) + } + const performActionOnItem = async ( action: LibraryItemAction, item: LibraryItem @@ -207,13 +212,14 @@ export function useGetLibraryItemsQuery({ if (!responsePages) { return } + for (const searchResults of responsePages) { - const itemIndex = searchResults.search.edges.indexOf(item) + const itemIndex = getIndexOf(searchResults.search, item) if (itemIndex !== -1) { if (typeof mutatedItem === 'undefined') { searchResults.search.edges.splice(itemIndex, 1) } else { - searchResults.search.edges[itemIndex] = mutatedItem + searchResults.search.edges.splice(itemIndex, 1, mutatedItem) } break } @@ -330,6 +336,8 @@ export function useGetLibraryItemsQuery({ } }) } + case 'update-item': + updateData(item) break case 'refresh': await mutate() diff --git a/packages/web/stories/EditTitleModal.stories.tsx b/packages/web/stories/EditTitleModal.stories.tsx new file mode 100644 index 000000000..53a7b41f9 --- /dev/null +++ b/packages/web/stories/EditTitleModal.stories.tsx @@ -0,0 +1,40 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react' +import { EditTitleModal } from '../components/templates/homeFeed/EditTitleModal' +import { LibraryItem } from '../lib/networking/queries/useGetLibraryItemsQuery' + +export default { + title: 'Components/EditTitleModal', + component: EditTitleModal, + argTypes: { + onOpenChange: { + description: + 'This is the function that changes the open and closed state of the modal', + }, + item: { + description: 'The article whose title or description is to be changed.', + }, + }, + parameters: { + docs: { + page: null, + }, + previewTabs: { + 'storybook/docs/panel': { hidden: true }, + }, + viewMode: 'canvas', + }, +} as ComponentMeta + +export const EditTitleModalStory: ComponentStory = ( + args +) => ( + {}} + item={{ + cursor: '', + node: { title: '', description: '' } as LibraryItem['node'], + }} + updateItem={async () => console.log('update item')} + + /> +)