Dont require confirmation on delete, show undo action

This commit is contained in:
Jackson Harper
2023-08-04 13:44:31 +08:00
parent 81e54f0be6
commit 76e0b36dfb
6 changed files with 133 additions and 29 deletions

View File

@ -77,9 +77,9 @@ export const Button = styled('button', {
fontFamily: 'Inter',
borderRadius: '8px',
cursor: 'pointer',
color: '$grayTextContrast',
color: 'white',
p: '10px 12px',
bg: 'rgb(125, 125, 125, 0.1)',
bg: 'rgb(125, 125, 125, 0.3)',
'&:hover': {
bg: 'rgb(47, 47, 47, 0.1)',
'.ctaButtonIcon': {

View File

@ -1,14 +1,7 @@
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
import debounce from 'lodash/debounce'
import { useRouter } from 'next/router'
import {
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Toaster } from 'react-hot-toast'
import TopBarProgress from 'react-topbar-progress-indicator'
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
@ -48,10 +41,13 @@ import { LibraryHeader, MultiSelectMode } from './LibraryHeader'
import { UploadModal } from '../UploadModal'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithUndo,
} from '../../../lib/toastHelpers'
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
import { NotebookPresenter } from '../article/NotebookPresenter'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
export type LibraryMode = 'reads' | 'highlights'
@ -110,6 +106,19 @@ export function HomeFeedContainer(): JSX.Element {
mutate,
} = useGetLibraryItemsQuery(queryInputs)
useEffect(() => {
const handleRevalidate = () => {
;(async () => {
console.log('revalidating library')
await mutate()
})()
}
document.addEventListener('revalidateLibrary', handleRevalidate)
return () => {
document.removeEventListener('revalidateLibrary', handleRevalidate)
}
}, [mutate])
useEffect(() => {
if (queryValue.startsWith('#')) {
debouncedFetchSearchResults(
@ -1199,10 +1208,7 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
setIsChecked={props.setIsChecked}
multiSelectMode={props.multiSelectMode}
handleAction={(action: LinkedItemCardAction) => {
if (action === 'delete') {
props.setShowRemoveLinkConfirmation(true)
props.setLinkToRemove(linkedItem)
} else if (action === 'editTitle') {
if (action === 'editTitle') {
props.setShowEditTitleModal(true)
props.setLinkToEdit(linkedItem)
} else if (action == 'unsubscribe') {

View File

@ -1,13 +1,15 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { State } from '../fragments/articleFragment'
export type UpdatePageInput = {
pageId: string
title: string
title?: string
byline?: string | undefined
description: string
description?: string
savedAt?: string
publishedAt?: string
state?: State
}
export async function updatePageMutation(

View File

@ -1,15 +1,20 @@
import { gql } from 'graphql-request'
import useSWRInfinite from 'swr/infinite'
import { gqlFetcher } from '../networkHelpers'
import type { PageType, State } from '../fragments/articleFragment'
import { PageType, State } from '../fragments/articleFragment'
import { ContentReader } from '../fragments/articleFragment'
import { setLinkArchivedMutation } from '../mutations/setLinkArchivedMutation'
import { deleteLinkMutation } from '../mutations/deleteLinkMutation'
import { unsubscribeMutation } from '../mutations/unsubscribeMutation'
import { articleReadingProgressMutation } from '../mutations/articleReadingProgressMutation'
import { Label } from './../fragments/labelFragment'
import { showErrorToast, showSuccessToast } from '../../toastHelpers'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithUndo,
} from '../../toastHelpers'
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
import { updatePageMutation } from '../mutations/updatePageMutation'
export interface ReadableItem {
id: string
@ -343,9 +348,26 @@ export function useGetLibraryItemsQuery({
break
case 'delete':
updateData(undefined)
deleteLinkMutation(item.node.id).then((res) => {
const pageId = item.node.id
deleteLinkMutation(pageId).then((res) => {
if (res) {
showSuccessToast('Link removed', { position: 'bottom-right' })
showSuccessToastWithUndo('Page deleted', async () => {
const result = await updatePageMutation({
pageId: pageId,
state: State.SUCCEEDED,
})
mutate()
if (result) {
showSuccessToast('Page recovered')
} else {
showErrorToast(
'Error recovering page, check your deleted items'
)
}
})
} else {
showErrorToast('Error removing link', { position: 'bottom-right' })
}

View File

@ -2,6 +2,7 @@ import { toast, ToastOptions } from 'react-hot-toast'
import { CheckCircle, WarningCircle, X } from 'phosphor-react'
import { Box, HStack } from '../components/elements/LayoutPrimitives'
import { styled } from '@stitches/react'
import { Button } from '../components/elements/Button'
const toastStyles = {
minWidth: 265,
@ -67,10 +68,64 @@ const showToast = (
)
}
const showToastWithUndo = (
message: string,
background: string,
undoAction: () => Promise<void>,
options?: ToastOptions
) => {
return toast(
({ id }) => (
<FullWidthContainer alignment="center">
<CheckCircle size={24} color="white" />
<MessageContainer>{message}</MessageContainer>
<HStack distribution="end" css={{ marginLeft: 16 }}>
<Button
style="ctaLightGray"
onClick={(event) => {
event.preventDefault()
toast.dismiss(id)
;(async () => {
await undoAction()
})()
}}
>
Undo
</Button>
</HStack>
</FullWidthContainer>
),
{
style: {
...toastStyles,
background: background,
},
duration: 3500,
...options,
}
)
}
export const showSuccessToast = (message: string, options?: ToastOptions) => {
return showToast(message, '#55B938', 'success', options)
return showToast(message, '#55B938', 'success', {
position: 'bottom-right',
...options,
})
}
export const showErrorToast = (message: string, options?: ToastOptions) => {
return showToast(message, '#cc0000', 'error', options)
return showToast(message, '#cc0000', 'error', {
position: 'bottom-right',
...options,
})
}
export const showSuccessToastWithUndo = (
message: string,
undoAction: () => Promise<void>
) => {
return showToastWithUndo(message, '#55B938', undoAction, {
position: 'bottom-right',
})
}

View File

@ -27,7 +27,11 @@ import { ArticleActionsMenu } from '../../../components/templates/article/Articl
import { setLinkArchivedMutation } from '../../../lib/networking/mutations/setLinkArchivedMutation'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { useSWRConfig } from 'swr'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithUndo,
} from '../../../lib/toastHelpers'
import { SetLabelsModal } from '../../../components/templates/article/SetLabelsModal'
import { DisplaySettingsModal } from '../../../components/templates/article/DisplaySettingsModal'
import { useReaderSettings } from '../../../lib/hooks/useReaderSettings'
@ -41,6 +45,8 @@ import { VerticalArticleActionsMenu } from '../../../components/templates/articl
import { PdfHeaderSpacer } from '../../../components/templates/article/PdfHeaderSpacer'
import { EpubContainerProps } from '../../../components/templates/article/EpubContainer'
import { useSetPageLabels } from '../../../lib/hooks/useSetPageLabels'
import { updatePageMutation } from '../../../lib/networking/mutations/updatePageMutation'
import { State } from '../../../lib/networking/fragments/articleFragment'
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
() => import('./../../../components/templates/article/PdfArticleContainer'),
@ -138,7 +144,7 @@ export default function Home(): JSX.Element {
}
break
case 'delete':
readerSettings.setShowDeleteConfirmation(true)
await deleteCurrentItem()
break
case 'openOriginalArticle':
const url = article?.url
@ -206,10 +212,23 @@ export default function Home(): JSX.Element {
const deleteCurrentItem = useCallback(async () => {
if (article) {
removeItemFromCache(cache, mutate, article.id)
await deleteLinkMutation(article.id).then((res) => {
const pageId = article.id
removeItemFromCache(cache, mutate, pageId)
await deleteLinkMutation(pageId).then((res) => {
if (res) {
showSuccessToast('Page deleted', { position: 'bottom-right' })
showSuccessToastWithUndo('Page deleted', async () => {
const result = await updatePageMutation({
pageId: pageId,
state: State.SUCCEEDED,
})
document.dispatchEvent(new Event('revalidateLibrary'))
if (result) {
showSuccessToast('Page recovered')
} else {
showErrorToast('Error recovering page, check your deleted items')
}
})
} else {
// todo: revalidate or put back in cache?
showErrorToast('Error deleting page', { position: 'bottom-right' })