refactor: optimize ui of error toast (#766)
This commit is contained in:
parent
c0d49e5436
commit
5285ea4ad9
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue