diff --git a/apps/web/src/components/navigation/reader-nav.tsx b/apps/web/src/components/navigation/reader-nav.tsx index 5d858b2..c83f418 100644 --- a/apps/web/src/components/navigation/reader-nav.tsx +++ b/apps/web/src/components/navigation/reader-nav.tsx @@ -107,6 +107,7 @@ function ReaderNav({ linkId, onOpenHighlights }: ReaderNavProps) { const { isMobile } = useMediaQuery(); const actions = useReaderActions(linkId, { + deleteFromNavbar: true, disabled: env.isDemo, formatUpdateMessage: (body) => { if (body.readingProgress !== undefined || body.timeSpentReading !== undefined) return false; @@ -117,7 +118,8 @@ function ReaderNav({ linkId, onOpenHighlights }: ReaderNavProps) { if (body.isArchived !== undefined && body.isRead !== undefined) return t('reader.actions.archivedAndMarkedRead'); return t('reader.actions.linkUpdated'); - } + }, + onDeleteSuccess: () => navigate({ to: '/articles' }) }); const [shouldShowNavbar, setShouldShowNavbar] = useState(true); diff --git a/apps/web/src/features/articles/api/delete-link.ts b/apps/web/src/features/articles/api/delete-link.ts index 58edd91..b0d4b0f 100644 --- a/apps/web/src/features/articles/api/delete-link.ts +++ b/apps/web/src/features/articles/api/delete-link.ts @@ -29,7 +29,7 @@ export const useDeleteLink = ({ return useMutation({ ...restConfig, mutationFn: deleteLink, - meta: { invalidates: [linkKeys.all] }, + meta: { invalidates: [linkKeys.lists(), linkKeys.infinites()] }, onSuccess: (data, variables, ...args) => { if (deleteFromNavbar && (linkId ?? variables.id)) { queryClient.removeQueries({ diff --git a/apps/web/src/features/articles/api/query-keys.ts b/apps/web/src/features/articles/api/query-keys.ts index f40e231..ece70a5 100644 --- a/apps/web/src/features/articles/api/query-keys.ts +++ b/apps/web/src/features/articles/api/query-keys.ts @@ -1,10 +1,10 @@ export const linkKeys = { all: ['links'] as const, + infinites: () => [...linkKeys.all, 'infinite'] as const, lists: () => [...linkKeys.all, 'list'] as const, list: (filters?: Record) => [...linkKeys.lists(), { filters }] as const, details: () => [...linkKeys.all, 'detail'] as const, detail: (id: string) => [...linkKeys.details(), id] as const, - infinite: (filters?: Record) => - [...linkKeys.all, 'infinite', { filters }] as const, + infinite: (filters?: Record) => [...linkKeys.infinites(), { filters }] as const, upcoming: (id: string) => [...linkKeys.all, 'upcoming', id] as const }; diff --git a/apps/web/src/features/articles/components/add-article-dialog.tsx b/apps/web/src/features/articles/components/add-article-dialog.tsx index c42a080..6345bbd 100644 --- a/apps/web/src/features/articles/components/add-article-dialog.tsx +++ b/apps/web/src/features/articles/components/add-article-dialog.tsx @@ -53,7 +53,15 @@ export function AddArticleDialog({ const createLinkMutation = useCreateLink({ mutationConfig: { + onMutate: () => { + toast.loading(t('articles.toasts.saving'), { + position: 'top-right', + richColors: true + }); + }, onSuccess: () => { + toast.dismiss(); + toast.success(t('articles.toasts.linkSaved'), { position, richColors: true @@ -129,7 +137,7 @@ export function AddArticleDialog({ }; return ( - + {t('articles.dialog.saveLaterTitle')} diff --git a/apps/web/src/features/articles/hooks/use-article-actions.ts b/apps/web/src/features/articles/hooks/use-article-actions.ts index 74d806a..2f8028c 100644 --- a/apps/web/src/features/articles/hooks/use-article-actions.ts +++ b/apps/web/src/features/articles/hooks/use-article-actions.ts @@ -1,5 +1,7 @@ import { useCallback } from 'react'; +import { useNotificationsStore } from '@/stores/notifications'; + import type { UpdateLinkBody } from '@/types/links'; import { useDeleteLink } from '../api/delete-link'; @@ -27,14 +29,15 @@ export function useArticleActions(config: ArticleActionsConfig = {}): ArticleAct formatUpdateMessage } = config; + const notifySuccess = useNotificationsStore.useSuccess(); + const updateMutation = useUpdateLink({ mutationConfig: { onSuccess: (_, { body }) => { const message = formatUpdateMessage ? formatUpdateMessage(body) : 'Link updated'; if (message) { - // TODO: Show toast - console.log(message); + notifySuccess(message); } } } diff --git a/apps/web/src/pages/articles.tsx b/apps/web/src/pages/articles.tsx index cf2e1c0..6060bab 100644 --- a/apps/web/src/pages/articles.tsx +++ b/apps/web/src/pages/articles.tsx @@ -133,6 +133,7 @@ function ArticlesPage() { const infiniteLinksQuery = useGetLinks({ filters: activeFilter }); const tagsQuery = useGetTags(); const tagGroupsQuery = useGetTagGroups(); + const deletingLinkIdsRef = useRef(new Set()); const previousLinksRef = useRef>>([]); const updateLinkMutation = useUpdateLink({ @@ -142,8 +143,23 @@ function ArticlesPage() { position: 'top-right', richColors: true }), - onSuccess: () => { + onSuccess: (_, { body }) => { toast.dismiss(); + + if (body.isFavorite !== undefined) { + return body.isFavorite + ? notifySuccess(t('reader.actions.markedAsFavorite')) + : notifySuccess(t('reader.actions.removedFromFavorites')); + } + + if (body.isArchived !== undefined) { + return notifySuccess(t('reader.actions.archivedAndMarkedRead')); + } + + if (body.priority !== undefined) { + return notifySuccess(t('articles.toasts.priorityUpdated')); + } + notifySuccess(t('articles.toasts.linkUpdated')); } } @@ -183,7 +199,10 @@ function ArticlesPage() { ); const handleDeleteLink = useCallback( - (linkId: string) => deleteLinkMutation.mutate({ id: linkId }), + (linkId: string) => { + deletingLinkIdsRef.current.add(linkId); + deleteLinkMutation.mutate({ id: linkId }); + }, [deleteLinkMutation] ); @@ -202,7 +221,18 @@ function ArticlesPage() { return; } - const recentlyCompletedLinkIds = getRecentlyCompletedLinkIds(previousLinksRef.current, links); + const currentLinkIds = new Set(links.map((link) => link.id)); + + for (const deletingLinkId of deletingLinkIdsRef.current) { + if (!currentLinkIds.has(deletingLinkId)) { + deletingLinkIdsRef.current.delete(deletingLinkId); + } + } + + const recentlyCompletedLinkIds = getRecentlyCompletedLinkIds( + previousLinksRef.current, + links + ).filter((id) => !deletingLinkIdsRef.current.has(id)); if (recentlyCompletedLinkIds.length > 0) { void Promise.all( diff --git a/apps/web/src/translations/en.json b/apps/web/src/translations/en.json index a6b6924..d236545 100644 --- a/apps/web/src/translations/en.json +++ b/apps/web/src/translations/en.json @@ -159,6 +159,7 @@ }, "articles": { "toasts": { + "saving": "Saving...", "updating": "Updating...", "linkUpdated": "Link updated", "deletingLink": "Deleting link", @@ -169,7 +170,8 @@ "failedCreateTag": "Failed to create tag", "tagCreated": "Tag \"{{name}}\" created", "failedUpdateTags": "Failed to update tags", - "tagsUpdated": "Tags updated" + "tagsUpdated": "Tags updated", + "priorityUpdated": "Priority updated" }, "toolbar": { "searchPlaceholder": "Search articles...", diff --git a/apps/web/src/translations/id.json b/apps/web/src/translations/id.json index 76c3004..c1a7049 100644 --- a/apps/web/src/translations/id.json +++ b/apps/web/src/translations/id.json @@ -159,6 +159,7 @@ }, "articles": { "toasts": { + "saving": "Menyimpan...", "updating": "Memperbarui...", "linkUpdated": "Tautan diperbarui", "deletingLink": "Menghapus tautan", @@ -169,7 +170,8 @@ "failedCreateTag": "Gagal membuat tag", "tagCreated": "Tag \"{{name}}\" dibuat", "failedUpdateTags": "Gagal memperbarui tag", - "tagsUpdated": "Tag diperbarui" + "tagsUpdated": "Tag diperbarui", + "priorityUpdated": "Prioritas diperbarui" }, "toolbar": { "searchPlaceholder": "Cari artikel...",