refactor: optimize ui of error toast (#766)

This commit is contained in:
Kaffi Y. 2024-10-06 00:19:02 +08:00 committed by GitHub
parent c0d49e5436
commit 5285ea4ad9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 56 additions and 41 deletions

View File

@ -41,6 +41,7 @@ export const CopyButton: Component<{
<MotionButtonBase
type="button"
onClick={handleCopy}
aria-label={copied ? "Copied!" : "Copy"}
className={cn(
"center pointer-events-auto flex text-xs",
"rounded-md border border-accent/5 bg-accent/80 p-1.5 text-white backdrop-blur duration-200",

View File

@ -1,10 +1,12 @@
import { t } from "i18next"
import { FetchError } from "ofetch"
import { createElement } from "react"
import type { ExternalToast } from "sonner"
import { toast } from "sonner"
import { CopyButton } from "~/components/ui/code-highlighter"
import { Markdown } from "~/components/ui/markdown"
import { cn } from "~/lib/utils"
export const getFetchErrorMessage = (error: Error) => {
if (error instanceof FetchError) {
@ -22,7 +24,17 @@ export const getFetchErrorMessage = (error: Error) => {
return error.message
}
export const toastFetchError = (error: Error) => {
/**
* Just a wrapper around `toastFetchError` to create a function that can be used as a callback.
*/
export const createErrorToaster = (title?: string, toastOptions?: ExternalToast) => (err: Error) =>
toastFetchError(err, { title, ...toastOptions })
export const toastFetchError = (
error: Error,
{ title: _title, ..._toastOptions }: ExternalToast & { title?: string } = {},
) => {
let message = ""
let _reason = ""
if (error instanceof FetchError) {
@ -43,30 +55,40 @@ export const toastFetchError = (error: Error) => {
}
}
if (!_reason) {
return toast.error(message)
} else {
return toast.error(message, {
duration: 5000,
const toastOptions: ExternalToast = {
..._toastOptions,
classNames: {
toast: "items-start bg-theme-background",
title: "-mt-1 mb-1", // to align with the icon (actually cut the top space from line-height)
content: "w-full",
..._toastOptions.classNames,
},
}
description: createElement(
"div",
{
className: "min-w-0 flex relative",
},
[
createElement(CopyButton, {
className: "absolute right-0 top-0 z-[1]",
key: "copy",
value: _reason,
}),
createElement(Markdown, {
key: "reason",
className: "text-sm opacity-70 min-w-0",
children: _reason,
}),
],
),
if (!_reason) {
const title = _title || message
toastOptions.description = _title ? message : undefined
return toast.error(title, toastOptions)
} else {
return toast.error(message || _title, {
duration: 5000,
...toastOptions,
description: createElement("div", {}, [
createElement(CopyButton, {
className: cn(
"relative z-[1] float-end -mt-1",
"border-transparent bg-theme-background text-theme-foreground opacity-60 transition-opacity",
"hover:bg-theme-button-hover hover:opacity-100 focus:border-theme-foreground/80",
),
key: "copy",
value: _reason,
}),
createElement(Markdown, {
key: "reason",
className: "text-sm opacity-70 min-w-0 flex-1",
children: _reason,
}),
]),
})
}
}

View File

@ -21,6 +21,7 @@ import {
import { Input } from "~/components/ui/input"
import { apiClient } from "~/lib/api-fetch"
import { FeedViewType } from "~/lib/enum"
import { createErrorToaster } from "~/lib/error-parser"
import { cn } from "~/lib/utils"
import type { InboxModel } from "~/models"
import { useInbox } from "~/queries/inboxes"
@ -107,9 +108,7 @@ const InboxInnerForm = ({
subscriptionActions.fetchByView(FeedViewType.Articles)
toast.success(t("discover.inbox_create_success"))
},
onError: () => {
toast.error(t("discover.inbox_create_error"))
},
onError: createErrorToaster(t("discover.inbox_create_error")),
})
const mutationChange = useMutation({
@ -126,9 +125,7 @@ const InboxInnerForm = ({
subscriptionActions.fetchByView(FeedViewType.Articles)
toast.success(t("discover.inbox_update_success"))
},
onError: () => {
toast.error(t("discover.inbox_update_error"))
},
onError: createErrorToaster(t("discover.inbox_update_error")),
})
function onSubmit(values: z.infer<typeof formSchema>) {

View File

@ -16,6 +16,7 @@ import {
} from "~/components/ui/table"
import { apiClient } from "~/lib/api-fetch"
import { FeedViewType } from "~/lib/enum"
import { createErrorToaster } from "~/lib/error-parser"
import { useInboxList } from "~/queries/inboxes"
import { subscriptionActions } from "~/store/subscription"
@ -156,9 +157,7 @@ const ConfirmDestroyModalContent = ({ id, onSuccess }: { id: string; onSuccess:
toast.success(t("discover.inbox_destroy_success"))
onSuccess()
},
onError: () => {
toast.error(t("discover.inbox_destroy_error"))
},
onError: createErrorToaster(t("discover.inbox_destroy_error")),
})
return (

View File

@ -197,10 +197,7 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam
<div className="relative flex size-full overflow-hidden" ref={carouselRef}>
<SwipeWrapper active={active}>
{views.map((item, index) => (
<section
key={item.name}
className="h-full w-[var(--fo-feed-col-w)] shrink-0 snap-center"
>
<section key={item.name} className="h-full w-feed-col shrink-0 snap-center">
<FeedList className="flex size-full flex-col text-sm" view={index} />
</section>
))}

View File

@ -39,6 +39,7 @@ import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "~/compon
import { views } from "~/constants"
import { useAuthQuery, useI18n } from "~/hooks/common"
import { apiClient } from "~/lib/api-fetch"
import { createErrorToaster } from "~/lib/error-parser"
import { cn, isBizId } from "~/lib/utils"
import type { FeedModel } from "~/models"
import { ViewSelectorRadioGroup } from "~/modules/shared/ViewSelectorRadioGroup"
@ -241,9 +242,7 @@ const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: s
if (!list) return
if (id) subscriptionActions.changeListView(id, views[list.view].view, views[values.view].view)
},
async onError() {
toast.error(t(id ? "lists.edit.error" : "lists.created.error"))
},
onError: createErrorToaster(id ? t("lists.edit.error") : t("lists.created.error")),
})
function onSubmit(values: z.infer<typeof formSchema>) {

View File

@ -47,6 +47,7 @@ export const RootProviders: FC<PropsWithChildren> = ({ children }) => (
<FeatureFlagDebugger />
{import.meta.env.DEV && <Devtools />}
{children}
<Toaster />
</I18nProvider>
</Provider>
</HotkeysProvider>
@ -54,7 +55,6 @@ export const RootProviders: FC<PropsWithChildren> = ({ children }) => (
<InvalidateQueryProvider />
</PersistQueryClientProvider>
</MotionConfig>
<Toaster />
</LazyMotion>
)