refactor(web): update frontend toast call sites to use the new shortcut API (#33808)

Signed-off-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh 2026-03-20 16:02:22 +08:00 committed by GitHub
parent ac87704685
commit 27ed40225d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
75 changed files with 391 additions and 706 deletions

View File

@ -95,7 +95,7 @@ describe('Cloud Plan Payment Flow', () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
toast.close()
toast.dismiss()
setupAppContext()
mockFetchSubscriptionUrls.mockResolvedValue({ url: 'https://pay.example.com/checkout' })
mockInvoices.mockResolvedValue({ url: 'https://billing.example.com/invoices' })

View File

@ -66,7 +66,7 @@ describe('Self-Hosted Plan Flow', () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
toast.close()
toast.dismiss()
setupAppContext()
// Mock window.location with minimal getter/setter (Location props are non-enumerable)

View File

@ -11,8 +11,8 @@ import SideBar from '@/app/components/explore/sidebar'
import { MediaType } from '@/hooks/use-breakpoints'
import { AppModeEnum } from '@/types/app'
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastSuccess } = vi.hoisted(() => ({
mockToastSuccess: vi.fn(),
}))
let mockMediaType: string = MediaType.pc
@ -53,14 +53,16 @@ vi.mock('@/service/use-explore', () => ({
}),
}))
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockToastAdd,
close: vi.fn(),
update: vi.fn(),
promise: vi.fn(),
},
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/ui/toast')>()
return {
...actual,
toast: {
...actual.toast,
success: mockToastSuccess,
},
}
})
const createInstalledApp = (overrides: Partial<InstalledApp> = {}): InstalledApp => ({
id: overrides.id ?? 'app-1',
@ -105,9 +107,7 @@ describe('Sidebar Lifecycle Flow', () => {
await waitFor(() => {
expect(mockUpdatePinStatus).toHaveBeenCalledWith({ appId: 'app-1', isPinned: true })
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({
type: 'success',
}))
expect(mockToastSuccess).toHaveBeenCalled()
})
// Step 2: Simulate refetch returning pinned state, then unpin
@ -124,9 +124,7 @@ describe('Sidebar Lifecycle Flow', () => {
await waitFor(() => {
expect(mockUpdatePinStatus).toHaveBeenCalledWith({ appId: 'app-1', isPinned: false })
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({
type: 'success',
}))
expect(mockToastSuccess).toHaveBeenCalled()
})
})
@ -150,10 +148,7 @@ describe('Sidebar Lifecycle Flow', () => {
// Step 4: Uninstall API called and success toast shown
await waitFor(() => {
expect(mockUninstall).toHaveBeenCalledWith('app-1')
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({
type: 'success',
title: 'common.api.remove',
}))
expect(mockToastSuccess).toHaveBeenCalledWith('common.api.remove')
})
})

View File

@ -24,17 +24,11 @@ export default function CheckCode() {
const verify = async () => {
try {
if (!code.trim()) {
toast.add({
type: 'error',
title: t('checkCode.emptyCode', { ns: 'login' }),
})
toast.error(t('checkCode.emptyCode', { ns: 'login' }))
return
}
if (!/\d{6}/.test(code)) {
toast.add({
type: 'error',
title: t('checkCode.invalidCode', { ns: 'login' }),
})
toast.error(t('checkCode.invalidCode', { ns: 'login' }))
return
}
setIsLoading(true)

View File

@ -27,15 +27,12 @@ export default function CheckCode() {
const handleGetEMailVerificationCode = async () => {
try {
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
setIsLoading(true)
@ -48,16 +45,10 @@ export default function CheckCode() {
router.push(`/webapp-reset-password/check-code?${params.toString()}`)
}
else if (res.code === 'account_not_found') {
toast.add({
type: 'error',
title: t('error.registrationNotAllowed', { ns: 'login' }),
})
toast.error(t('error.registrationNotAllowed', { ns: 'login' }))
}
else {
toast.add({
type: 'error',
title: res.data,
})
toast.error(res.data)
}
}
catch (error) {

View File

@ -24,10 +24,7 @@ const ChangePasswordForm = () => {
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const showErrorMessage = useCallback((message: string) => {
toast.add({
type: 'error',
title: message,
})
toast.error(message)
}, [])
const getSignInUrl = () => {

View File

@ -43,24 +43,15 @@ export default function CheckCode() {
try {
const appCode = getAppCodeFromRedirectUrl()
if (!code.trim()) {
toast.add({
type: 'error',
title: t('checkCode.emptyCode', { ns: 'login' }),
})
toast.error(t('checkCode.emptyCode', { ns: 'login' }))
return
}
if (!/\d{6}/.test(code)) {
toast.add({
type: 'error',
title: t('checkCode.invalidCode', { ns: 'login' }),
})
toast.error(t('checkCode.invalidCode', { ns: 'login' }))
return
}
if (!redirectUrl || !appCode) {
toast.add({
type: 'error',
title: t('error.redirectUrlMissing', { ns: 'login' }),
})
toast.error(t('error.redirectUrlMissing', { ns: 'login' }))
return
}
setIsLoading(true)

View File

@ -17,10 +17,7 @@ const ExternalMemberSSOAuth = () => {
const redirectUrl = searchParams.get('redirect_url')
const showErrorToast = (message: string) => {
toast.add({
type: 'error',
title: message,
})
toast.error(message)
}
const getAppCodeFromRedirectUrl = useCallback(() => {

View File

@ -22,15 +22,12 @@ export default function MailAndCodeAuth() {
const handleGetEMailVerificationCode = async () => {
try {
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
setIsLoading(true)

View File

@ -46,26 +46,20 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
const appCode = getAppCodeFromRedirectUrl()
const handleEmailPasswordLogin = async () => {
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
if (!password?.trim()) {
toast.add({ type: 'error', title: t('error.passwordEmpty', { ns: 'login' }) })
toast.error(t('error.passwordEmpty', { ns: 'login' }))
return
}
if (!redirectUrl || !appCode) {
toast.add({
type: 'error',
title: t('error.redirectUrlMissing', { ns: 'login' }),
})
toast.error(t('error.redirectUrlMissing', { ns: 'login' }))
return
}
try {
@ -94,15 +88,12 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
router.replace(decodeURIComponent(redirectUrl))
}
else {
toast.add({
type: 'error',
title: res.data,
})
toast.error(res.data)
}
}
catch (e: any) {
if (e.code === 'authentication_failed')
toast.add({ type: 'error', title: e.message })
toast.error(e.message)
}
finally {
setIsLoading(false)

View File

@ -37,10 +37,7 @@ const SSOAuth: FC<SSOAuthProps> = ({
const handleSSOLogin = () => {
const appCode = getAppCodeFromRedirectUrl()
if (!redirectUrl || !appCode) {
toast.add({
type: 'error',
title: t('error.invalidRedirectUrlOrAppCode', { ns: 'login' }),
})
toast.error(t('error.invalidRedirectUrlOrAppCode', { ns: 'login' }))
return
}
setIsLoading(true)
@ -66,10 +63,7 @@ const SSOAuth: FC<SSOAuthProps> = ({
})
}
else {
toast.add({
type: 'error',
title: t('error.invalidSSOProtocol', { ns: 'login' }),
})
toast.error(t('error.invalidSSOProtocol', { ns: 'login' }))
setIsLoading(false)
}
}

View File

@ -91,10 +91,7 @@ export default function OAuthAuthorize() {
globalThis.location.href = url.toString()
}
catch (err: any) {
toast.add({
type: 'error',
title: `${t('error.authorizeFailed', { ns: 'oauth' })}: ${err.message}`,
})
toast.error(`${t('error.authorizeFailed', { ns: 'oauth' })}: ${err.message}`)
}
}
@ -102,11 +99,10 @@ export default function OAuthAuthorize() {
const invalidParams = !client_id || !redirect_uri
if ((invalidParams || isError) && !hasNotifiedRef.current) {
hasNotifiedRef.current = true
toast.add({
type: 'error',
title: invalidParams ? t('error.invalidParams', { ns: 'oauth' }) : t('error.authAppInfoFetchFailed', { ns: 'oauth' }),
timeout: 0,
})
toast.error(
invalidParams ? t('error.invalidParams', { ns: 'oauth' }) : t('error.authAppInfoFetchFailed', { ns: 'oauth' }),
{ timeout: 0 },
)
}
}, [client_id, redirect_uri, isError])

View File

@ -137,10 +137,7 @@ const Apps = ({
})
setIsShowCreateModal(false)
toast.add({
type: 'success',
title: t('newApp.appCreated', { ns: 'app' }),
})
toast.success(t('newApp.appCreated', { ns: 'app' }))
if (onSuccess)
onSuccess()
if (app.app_id)
@ -149,7 +146,7 @@ const Apps = ({
getRedirection(isCurrentWorkspaceEditor, { id: app.app_id!, mode }, push)
}
catch {
toast.add({ type: 'error', title: t('newApp.appCreateFailed', { ns: 'app' }) })
toast.error(t('newApp.appCreateFailed', { ns: 'app' }))
}
}

View File

@ -7,27 +7,25 @@ describe('base/ui/toast', () => {
vi.clearAllMocks()
vi.useFakeTimers({ shouldAdvanceTime: true })
act(() => {
toast.close()
toast.dismiss()
})
})
afterEach(() => {
act(() => {
toast.close()
toast.dismiss()
vi.runOnlyPendingTimers()
})
vi.useRealTimers()
})
// Core host and manager integration.
it('should render a toast when add is called', async () => {
it('should render a success toast when called through the typed shortcut', async () => {
render(<ToastHost />)
act(() => {
toast.add({
title: 'Saved',
toast.success('Saved', {
description: 'Your changes are available now.',
type: 'success',
})
})
@ -47,20 +45,14 @@ describe('base/ui/toast', () => {
render(<ToastHost />)
act(() => {
toast.add({
title: 'First toast',
})
toast('First toast')
})
expect(await screen.findByText('First toast')).toBeInTheDocument()
act(() => {
toast.add({
title: 'Second toast',
})
toast.add({
title: 'Third toast',
})
toast('Second toast')
toast('Third toast')
})
expect(await screen.findByText('Third toast')).toBeInTheDocument()
@ -74,13 +66,25 @@ describe('base/ui/toast', () => {
})
})
// Neutral calls should map directly to a toast with only a title.
it('should render a neutral toast when called directly', async () => {
render(<ToastHost />)
act(() => {
toast('Neutral toast')
})
expect(await screen.findByText('Neutral toast')).toBeInTheDocument()
expect(document.body.querySelector('[aria-hidden="true"].i-ri-information-2-fill')).not.toBeInTheDocument()
})
// Base UI limit should cap the visible stack and mark overflow toasts as limited.
it('should mark overflow toasts as limited when the stack exceeds the configured limit', async () => {
render(<ToastHost limit={1} />)
act(() => {
toast.add({ title: 'First toast' })
toast.add({ title: 'Second toast' })
toast('First toast')
toast('Second toast')
})
expect(await screen.findByText('Second toast')).toBeInTheDocument()
@ -88,13 +92,12 @@ describe('base/ui/toast', () => {
})
// Closing should work through the public manager API.
it('should close a toast when close(id) is called', async () => {
it('should dismiss a toast when dismiss(id) is called', async () => {
render(<ToastHost />)
let toastId = ''
act(() => {
toastId = toast.add({
title: 'Closable',
toastId = toast('Closable', {
description: 'This toast can be removed.',
})
})
@ -102,7 +105,7 @@ describe('base/ui/toast', () => {
expect(await screen.findByText('Closable')).toBeInTheDocument()
act(() => {
toast.close(toastId)
toast.dismiss(toastId)
})
await waitFor(() => {
@ -117,8 +120,7 @@ describe('base/ui/toast', () => {
render(<ToastHost />)
act(() => {
toast.add({
title: 'Dismiss me',
toast('Dismiss me', {
description: 'Manual dismissal path.',
onClose,
})
@ -143,9 +145,7 @@ describe('base/ui/toast', () => {
render(<ToastHost />)
act(() => {
toast.add({
title: 'Default timeout',
})
toast('Default timeout')
})
expect(await screen.findByText('Default timeout')).toBeInTheDocument()
@ -170,9 +170,7 @@ describe('base/ui/toast', () => {
render(<ToastHost timeout={3000} />)
act(() => {
toast.add({
title: 'Configured timeout',
})
toast('Configured timeout')
})
expect(await screen.findByText('Configured timeout')).toBeInTheDocument()
@ -197,8 +195,7 @@ describe('base/ui/toast', () => {
render(<ToastHost />)
act(() => {
toast.add({
title: 'Custom timeout',
toast('Custom timeout', {
timeout: 1000,
})
})
@ -214,8 +211,7 @@ describe('base/ui/toast', () => {
})
act(() => {
toast.add({
title: 'Persistent',
toast('Persistent', {
timeout: 0,
})
})
@ -235,10 +231,8 @@ describe('base/ui/toast', () => {
let toastId = ''
act(() => {
toastId = toast.add({
title: 'Loading',
toastId = toast.info('Loading', {
description: 'Preparing your data…',
type: 'info',
})
})
@ -264,8 +258,7 @@ describe('base/ui/toast', () => {
render(<ToastHost />)
act(() => {
toast.add({
title: 'Action toast',
toast('Action toast', {
actionProps: {
children: 'Undo',
onClick: onAction,

View File

@ -57,9 +57,8 @@ const VariantExamples = () => {
},
} as const
toast.add({
type,
...copy[type],
toast[type](copy[type].title, {
description: copy[type].description,
})
}
@ -103,14 +102,16 @@ const StackExamples = () => {
title: 'Ready to publish',
description: 'The newest toast stays frontmost while older items tuck behind it.',
},
].forEach(item => toast.add(item))
].forEach((item) => {
toast[item.type](item.title, {
description: item.description,
})
})
}
const createBurst = () => {
Array.from({ length: 5 }).forEach((_, index) => {
toast.add({
type: index % 2 === 0 ? 'info' : 'success',
title: `Background task ${index + 1}`,
toast[index % 2 === 0 ? 'info' : 'success'](`Background task ${index + 1}`, {
description: 'Use this to inspect how the stack behaves near the host limit.',
})
})
@ -191,16 +192,12 @@ const PromiseExamples = () => {
const ActionExamples = () => {
const createActionToast = () => {
toast.add({
type: 'warning',
title: 'Project archived',
toast.warning('Project archived', {
description: 'You can restore it from workspace settings for the next 30 days.',
actionProps: {
children: 'Undo',
onClick: () => {
toast.add({
type: 'success',
title: 'Project restored',
toast.success('Project restored', {
description: 'The workspace is active again.',
})
},
@ -209,17 +206,12 @@ const ActionExamples = () => {
}
const createLongCopyToast = () => {
toast.add({
type: 'info',
title: 'Knowledge ingestion in progress',
toast.info('Knowledge ingestion in progress', {
description: 'This longer example helps validate line wrapping, close button alignment, and action button placement when the content spans multiple rows.',
actionProps: {
children: 'View details',
onClick: () => {
toast.add({
type: 'info',
title: 'Job details opened',
})
toast.info('Job details opened')
},
},
})
@ -243,9 +235,7 @@ const ActionExamples = () => {
const UpdateExamples = () => {
const createUpdatableToast = () => {
const toastId = toast.add({
type: 'info',
title: 'Import started',
const toastId = toast.info('Import started', {
description: 'Preparing assets and metadata for processing.',
timeout: 0,
})
@ -261,7 +251,7 @@ const UpdateExamples = () => {
}
const clearAll = () => {
toast.close()
toast.dismiss()
}
return (

View File

@ -5,6 +5,7 @@ import type {
ToastManagerUpdateOptions,
ToastObject,
} from '@base-ui/react/toast'
import type { ReactNode } from 'react'
import { Toast as BaseToast } from '@base-ui/react/toast'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
@ -44,6 +45,9 @@ export type ToastUpdateOptions = Omit<ToastManagerUpdateOptions<ToastData>, 'dat
type?: ToastType
}
export type ToastOptions = Omit<ToastAddOptions, 'title'>
export type TypedToastOptions = Omit<ToastOptions, 'type'>
type ToastPromiseResultOption<Value> = string | ToastUpdateOptions | ((value: Value) => string | ToastUpdateOptions)
export type ToastPromiseOptions<Value> = {
@ -57,6 +61,21 @@ export type ToastHostProps = {
limit?: number
}
type ToastDismiss = (toastId?: string) => void
type ToastCall = (title: ReactNode, options?: ToastOptions) => string
type TypedToastCall = (title: ReactNode, options?: TypedToastOptions) => string
export type ToastApi = {
(title: ReactNode, options?: ToastOptions): string
success: TypedToastCall
error: TypedToastCall
warning: TypedToastCall
info: TypedToastCall
dismiss: ToastDismiss
update: (toastId: string, options: ToastUpdateOptions) => void
promise: <Value>(promiseValue: Promise<Value>, options: ToastPromiseOptions<Value>) => Promise<Value>
}
const toastManager = BaseToast.createToastManager<ToastData>()
function isToastType(type: string): type is ToastType {
@ -67,21 +86,48 @@ function getToastType(type?: string): ToastType | undefined {
return type && isToastType(type) ? type : undefined
}
export const toast = {
add(options: ToastAddOptions) {
return toastManager.add(options)
},
close(toastId?: string) {
toastManager.close(toastId)
},
update(toastId: string, options: ToastUpdateOptions) {
toastManager.update(toastId, options)
},
promise<Value>(promiseValue: Promise<Value>, options: ToastPromiseOptions<Value>) {
return toastManager.promise(promiseValue, options)
},
function addToast(options: ToastAddOptions) {
return toastManager.add(options)
}
const showToast: ToastCall = (title, options) => addToast({
...options,
title,
})
const dismissToast: ToastDismiss = (toastId) => {
toastManager.close(toastId)
}
function createTypedToast(type: ToastType): TypedToastCall {
return (title, options) => addToast({
...options,
title,
type,
})
}
function updateToast(toastId: string, options: ToastUpdateOptions) {
toastManager.update(toastId, options)
}
function promiseToast<Value>(promiseValue: Promise<Value>, options: ToastPromiseOptions<Value>) {
return toastManager.promise(promiseValue, options)
}
export const toast: ToastApi = Object.assign(
showToast,
{
success: createTypedToast('success'),
error: createTypedToast('error'),
warning: createTypedToast('warning'),
info: createTypedToast('info'),
dismiss: dismissToast,
update: updateToast,
promise: promiseToast,
},
)
function ToastIcon({ type }: { type?: ToastType }) {
return type
? <span aria-hidden="true" className={cn('h-5 w-5', TOAST_TONE_STYLES[type].iconClassName)} />

View File

@ -70,7 +70,7 @@ beforeAll(() => {
beforeEach(() => {
vi.clearAllMocks()
toast.close()
toast.dismiss()
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: true })
mockUseAsyncWindowOpen.mockReturnValue(vi.fn(async open => await open()))
mockBillingInvoices.mockResolvedValue({ url: 'https://billing.example' })

View File

@ -66,10 +66,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
return
if (!isCurrentWorkspaceManager) {
toast.add({
type: 'error',
title: t('buyPermissionDeniedTip', { ns: 'billing' }),
})
toast.error(t('buyPermissionDeniedTip', { ns: 'billing' }))
return
}
setLoading(true)
@ -82,7 +79,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
throw new Error('Failed to open billing page')
}, {
onError: (err) => {
toast.add({ type: 'error', title: err.message || String(err) })
toast.error(err.message || String(err))
},
})
return

View File

@ -58,7 +58,7 @@ beforeAll(() => {
beforeEach(() => {
vi.clearAllMocks()
toast.close()
toast.dismiss()
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: true })
assignedHref = ''
})

View File

@ -56,10 +56,7 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
const handleGetPayUrl = useCallback(() => {
// Only workspace manager can buy plan
if (!isCurrentWorkspaceManager) {
toast.add({
type: 'error',
title: t('buyPermissionDeniedTip', { ns: 'billing' }),
})
toast.error(t('buyPermissionDeniedTip', { ns: 'billing' }))
return
}
if (isFreePlan) {

View File

@ -13,17 +13,20 @@ vi.mock('@/app/components/base/amplitude', () => ({
trackEvent: vi.fn(),
}))
const { mockToastNotify } = vi.hoisted(() => ({
mockToastNotify: vi.fn(),
const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({
mockToastSuccess: vi.fn(),
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/toast')>()
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/ui/toast')>()
return {
...actual,
default: Object.assign(actual.default, {
notify: mockToastNotify,
}),
toast: {
...actual.toast,
success: mockToastSuccess,
error: mockToastError,
},
}
})
@ -45,8 +48,8 @@ vi.mock('@/service/knowledge/use-dataset', () => ({
describe('CreateCard', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastNotify.mockReset()
mockToastNotify.mockImplementation(() => ({ clear: vi.fn() }))
mockToastSuccess.mockReset()
mockToastError.mockReset()
})
describe('Rendering', () => {

View File

@ -20,10 +20,7 @@ const CreateCard = () => {
onSuccess: (data) => {
if (data) {
const { id } = data
toast.add({
type: 'success',
title: t('creation.successTip', { ns: 'datasetPipeline' }),
})
toast.success(t('creation.successTip', { ns: 'datasetPipeline' }))
invalidDatasetList()
trackEvent('create_datasets_from_scratch', {
dataset_id: id,
@ -32,10 +29,7 @@ const CreateCard = () => {
}
},
onError: () => {
toast.add({
type: 'error',
title: t('creation.errorTip', { ns: 'datasetPipeline' }),
})
toast.error(t('creation.errorTip', { ns: 'datasetPipeline' }))
},
})
}, [createEmptyDataset, push, invalidDatasetList, t])

View File

@ -14,8 +14,8 @@ vi.mock('@/service/use-pipeline', () => ({
useInvalidCustomizedTemplateList: () => mockInvalidCustomizedTemplateList,
}))
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -24,7 +24,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -95,7 +95,7 @@ describe('EditPipelineInfo', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
_mockOnSelect = undefined
_mockOnClose = undefined
})
@ -243,10 +243,7 @@ describe('EditPipelineInfo', () => {
fireEvent.click(saveButton)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'datasetPipeline.editPipelineInfoNameRequired',
})
expect(mockToastError).toHaveBeenCalledWith('datasetPipeline.editPipelineInfoNameRequired')
})
})

View File

@ -14,8 +14,9 @@ vi.mock('@/app/components/base/amplitude', () => ({
trackEvent: vi.fn(),
}))
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({
mockToastSuccess: vi.fn(),
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -24,7 +25,8 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
success: mockToastSuccess,
error: mockToastError,
},
}
})
@ -182,7 +184,8 @@ describe('TemplateCard', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastSuccess.mockReset()
mockToastError.mockReset()
mockIsExporting = false
_capturedOnConfirm = undefined
_capturedOnCancel = undefined
@ -237,10 +240,7 @@ describe('TemplateCard', () => {
fireEvent.click(chooseButton)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: expect.any(String),
})
expect(mockToastError).toHaveBeenCalledWith(expect.any(String))
})
})
@ -300,10 +300,7 @@ describe('TemplateCard', () => {
fireEvent.click(chooseButton)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'success',
title: expect.any(String),
})
expect(mockToastSuccess).toHaveBeenCalledWith(expect.any(String))
})
})
@ -318,10 +315,7 @@ describe('TemplateCard', () => {
fireEvent.click(chooseButton)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: expect.any(String),
})
expect(mockToastError).toHaveBeenCalledWith(expect.any(String))
})
})
})
@ -467,10 +461,7 @@ describe('TemplateCard', () => {
fireEvent.click(exportButton)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'success',
title: expect.any(String),
})
expect(mockToastSuccess).toHaveBeenCalledWith(expect.any(String))
})
})
@ -485,10 +476,7 @@ describe('TemplateCard', () => {
fireEvent.click(exportButton)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: expect.any(String),
})
expect(mockToastError).toHaveBeenCalledWith(expect.any(String))
})
})

View File

@ -67,10 +67,7 @@ const EditPipelineInfo = ({
const handleSave = useCallback(async () => {
if (!name) {
toast.add({
type: 'error',
title: t('editPipelineInfoNameRequired', { ns: 'datasetPipeline' }),
})
toast.error(t('editPipelineInfoNameRequired', { ns: 'datasetPipeline' }))
return
}
const request = {

View File

@ -50,10 +50,7 @@ const TemplateCard = ({
const handleUseTemplate = useCallback(async () => {
const { data: pipelineTemplateInfo } = await getPipelineTemplateInfo()
if (!pipelineTemplateInfo) {
toast.add({
type: 'error',
title: t('creation.errorTip', { ns: 'datasetPipeline' }),
})
toast.error(t('creation.errorTip', { ns: 'datasetPipeline' }))
return
}
const request = {
@ -61,10 +58,7 @@ const TemplateCard = ({
}
await createDataset(request, {
onSuccess: async (newDataset) => {
toast.add({
type: 'success',
title: t('creation.successTip', { ns: 'datasetPipeline' }),
})
toast.success(t('creation.successTip', { ns: 'datasetPipeline' }))
invalidDatasetList()
if (newDataset.pipeline_id)
await handleCheckPluginDependencies(newDataset.pipeline_id, true)
@ -76,10 +70,7 @@ const TemplateCard = ({
push(`/datasets/${newDataset.dataset_id}/pipeline`)
},
onError: () => {
toast.add({
type: 'error',
title: t('creation.errorTip', { ns: 'datasetPipeline' }),
})
toast.error(t('creation.errorTip', { ns: 'datasetPipeline' }))
},
})
}, [getPipelineTemplateInfo, createDataset, t, handleCheckPluginDependencies, push, invalidDatasetList, pipeline.name, pipeline.id, type])
@ -109,16 +100,10 @@ const TemplateCard = ({
onSuccess: (res) => {
const blob = new Blob([res.data], { type: 'application/yaml' })
downloadBlob({ data: blob, fileName: `${pipeline.name}.pipeline` })
toast.add({
type: 'success',
title: t('exportDSL.successTip', { ns: 'datasetPipeline' }),
})
toast.success(t('exportDSL.successTip', { ns: 'datasetPipeline' }))
},
onError: () => {
toast.add({
type: 'error',
title: t('exportDSL.errorTip', { ns: 'datasetPipeline' }),
})
toast.error(t('exportDSL.errorTip', { ns: 'datasetPipeline' }))
},
})
}, [t, isExporting, pipeline.id, pipeline.name, exportPipelineDSL])

View File

@ -32,9 +32,9 @@ vi.mock('@/service/base', () => ({
ssePost: mockSsePost,
}))
// Mock toast.add because the component reports errors through the UI toast manager.
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
// Mock toast.error because the component reports errors through the UI toast manager.
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -43,7 +43,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -197,7 +197,7 @@ const createDefaultProps = (overrides?: Partial<OnlineDocumentsProps>): OnlineDo
describe('OnlineDocuments', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
// Reset store state
mockStoreState.documentsData = []
@ -515,10 +515,7 @@ describe('OnlineDocuments', () => {
render(<OnlineDocuments {...props} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'Something went wrong',
})
expect(mockToastError).toHaveBeenCalledWith('Something went wrong')
})
})
@ -780,10 +777,7 @@ describe('OnlineDocuments', () => {
render(<OnlineDocuments {...props} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'API Error Message',
})
expect(mockToastError).toHaveBeenCalledWith('API Error Message')
})
})
@ -1100,10 +1094,7 @@ describe('OnlineDocuments', () => {
render(<OnlineDocuments {...props} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'Failed to fetch documents',
})
expect(mockToastError).toHaveBeenCalledWith('Failed to fetch documents')
})
// Should still show loading since documentsData is empty

View File

@ -96,10 +96,7 @@ const OnlineDocuments = ({
setDocumentsData(documentsData.data as DataSourceNotionWorkspace[])
},
onDataSourceNodeError: (error: DataSourceNodeErrorResponse) => {
toast.add({
type: 'error',
title: error.error,
})
toast.error(error.error)
},
},
)

View File

@ -45,8 +45,8 @@ vi.mock('@/service/use-datasource', () => ({
useGetDataSourceAuth: mockUseGetDataSourceAuth,
}))
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -55,7 +55,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -236,7 +236,7 @@ const resetMockStoreState = () => {
describe('OnlineDrive', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
// Reset store state
resetMockStoreState()
@ -547,10 +547,7 @@ describe('OnlineDrive', () => {
render(<OnlineDrive {...props} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: errorMessage,
})
expect(mockToastError).toHaveBeenCalledWith(errorMessage)
})
})
})
@ -921,10 +918,7 @@ describe('OnlineDrive', () => {
render(<OnlineDrive {...props} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: errorMessage,
})
expect(mockToastError).toHaveBeenCalledWith(errorMessage)
})
})
})

View File

@ -105,10 +105,7 @@ const OnlineDrive = ({
isLoadingRef.current = false
},
onDataSourceNodeError: (error: DataSourceNodeErrorResponse) => {
toast.add({
type: 'error',
title: error.error,
})
toast.error(error.error)
setIsLoading(false)
isLoadingRef.current = false
},

View File

@ -6,8 +6,8 @@ import { CrawlStep } from '@/models/datasets'
import { PipelineInputVarType } from '@/models/pipeline'
import Options from '../index'
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -16,7 +16,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -131,7 +131,7 @@ const createDefaultProps = (overrides?: Partial<OptionsProps>): OptionsProps =>
describe('Options', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
// Reset mock form values
Object.keys(mockFormValues).forEach(key => delete mockFormValues[key])
@ -643,11 +643,7 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
// Assert - Toast should be called with error message
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
}),
)
expect(mockToastError).toHaveBeenCalled()
})
it('should handle validation error and display field name in message', () => {
@ -665,12 +661,7 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
// Assert - Toast message should contain field path
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
title: expect.stringContaining('email_address'),
}),
)
expect(mockToastError).toHaveBeenCalledWith(expect.stringContaining('email_address'))
})
it('should handle empty variables gracefully', () => {
@ -719,12 +710,8 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
// Assert - Toast should be called once (only first error)
expect(mockToastAdd).toHaveBeenCalledTimes(1)
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
}),
)
expect(mockToastError).toHaveBeenCalledTimes(1)
expect(mockToastError).toHaveBeenCalled()
})
it('should handle validation pass when all required fields have values', () => {
@ -743,7 +730,7 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
// Assert - No toast error, onSubmit called
expect(mockToastAdd).not.toHaveBeenCalled()
expect(mockToastError).not.toHaveBeenCalled()
expect(mockOnSubmit).toHaveBeenCalled()
})
@ -840,7 +827,7 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
expect(mockOnSubmit).toHaveBeenCalled()
expect(mockToastAdd).not.toHaveBeenCalled()
expect(mockToastError).not.toHaveBeenCalled()
})
it('should fail validation with invalid data', () => {
@ -859,7 +846,7 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
expect(mockOnSubmit).not.toHaveBeenCalled()
expect(mockToastAdd).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
})
it('should show error toast message when validation fails', () => {
@ -876,12 +863,7 @@ describe('Options', () => {
fireEvent.click(screen.getByRole('button'))
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
title: expect.any(String),
}),
)
expect(mockToastError).toHaveBeenCalledWith(expect.any(String))
})
})

View File

@ -44,10 +44,7 @@ const Options = ({
const issues = result.error.issues
const firstIssue = issues[0]
const errorMessage = `"${firstIssue.path.join('.')}" ${firstIssue.message}`
toast.add({
type: 'error',
title: errorMessage,
})
toast.error(errorMessage)
return errorMessage
}
return undefined

View File

@ -5,8 +5,8 @@ import OnlineDocumentPreview from '../online-document-preview'
// Uses global react-i18next mock from web/vitest.setup.ts
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -15,7 +15,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -67,7 +67,7 @@ const defaultProps = {
describe('OnlineDocumentPreview', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
mockPipelineId.mockReturnValue('pipeline-123')
mockUsePreviewOnlineDocument.mockReturnValue({
mutateAsync: mockMutateAsync,
@ -270,10 +270,7 @@ describe('OnlineDocumentPreview', () => {
render(<OnlineDocumentPreview {...defaultProps} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: errorMessage,
})
expect(mockToastError).toHaveBeenCalledWith(errorMessage)
})
})
@ -288,10 +285,7 @@ describe('OnlineDocumentPreview', () => {
render(<OnlineDocumentPreview {...defaultProps} />)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'Network Error',
})
expect(mockToastError).toHaveBeenCalledWith('Network Error')
})
})
})

View File

@ -44,10 +44,7 @@ const OnlineDocumentPreview = ({
setContent(data.content)
},
onError(error) {
toast.add({
type: 'error',
title: error.message,
})
toast.error(error.message)
},
})
}, [currentPage.page_id])

View File

@ -7,8 +7,8 @@ import Actions from '../actions'
import Form from '../form'
import Header from '../header'
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -17,7 +17,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -346,7 +346,7 @@ describe('Form', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
})
describe('Rendering', () => {
@ -455,10 +455,7 @@ describe('Form', () => {
// Assert - validation error should be shown
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: '"field1" is required',
})
expect(mockToastError).toHaveBeenCalledWith('"field1" is required')
})
})
})
@ -577,10 +574,7 @@ describe('Form', () => {
fireEvent.submit(form)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: '"field1" is required',
})
expect(mockToastError).toHaveBeenCalledWith('"field1" is required')
})
})
@ -594,7 +588,7 @@ describe('Form', () => {
// Assert - wait a bit and verify onSubmit was not called
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
})
expect(onSubmit).not.toHaveBeenCalled()
})

View File

@ -4,8 +4,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { z } from 'zod'
import Form from '../form'
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastError } = vi.hoisted(() => ({
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
@ -14,7 +14,7 @@ vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
...actual,
toast: {
...actual.toast,
add: mockToastAdd,
error: mockToastError,
},
}
})
@ -57,7 +57,7 @@ const defaultProps = {
describe('Form (process-documents)', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastAdd.mockReset()
mockToastError.mockReset()
})
// Verify basic rendering of form structure
@ -119,12 +119,7 @@ describe('Form (process-documents)', () => {
fireEvent.submit(form)
await waitFor(() => {
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
title: '"name" Name is required',
}),
)
expect(mockToastError).toHaveBeenCalledWith('"name" Name is required')
})
})
@ -137,7 +132,7 @@ describe('Form (process-documents)', () => {
await waitFor(() => {
expect(defaultProps.onSubmit).toHaveBeenCalled()
})
expect(mockToastAdd).not.toHaveBeenCalled()
expect(mockToastError).not.toHaveBeenCalled()
})
})

View File

@ -34,10 +34,7 @@ const Form = ({
const issues = result.error.issues
const firstIssue = issues[0]
const errorMessage = `"${firstIssue.path.join('.')}" ${firstIssue.message}`
toast.add({
type: 'error',
title: errorMessage,
})
toast.error(errorMessage)
return errorMessage
}
return undefined

View File

@ -13,7 +13,8 @@ vi.mock('@/next/navigation', () => ({
}),
}))
const toastAddSpy = vi.spyOn(toast, 'add')
const toastErrorSpy = vi.spyOn(toast, 'error')
const toastSuccessSpy = vi.spyOn(toast, 'success')
// Mock dataset detail context
let mockIndexingTechnique = IndexingType.QUALIFIED
@ -128,7 +129,7 @@ describe('NewSegmentModal', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useRealTimers()
toast.close()
toast.dismiss()
mockFullScreen = false
mockIndexingTechnique = IndexingType.QUALIFIED
})
@ -248,11 +249,7 @@ describe('NewSegmentModal', () => {
fireEvent.click(screen.getByTestId('save-btn'))
await waitFor(() => {
expect(toastAddSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
}),
)
expect(toastErrorSpy).toHaveBeenCalledTimes(1)
})
})
@ -262,11 +259,7 @@ describe('NewSegmentModal', () => {
fireEvent.click(screen.getByTestId('save-btn'))
await waitFor(() => {
expect(toastAddSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
}),
)
expect(toastErrorSpy).toHaveBeenCalledTimes(1)
})
})
@ -277,11 +270,7 @@ describe('NewSegmentModal', () => {
fireEvent.click(screen.getByTestId('save-btn'))
await waitFor(() => {
expect(toastAddSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
}),
)
expect(toastErrorSpy).toHaveBeenCalledTimes(1)
})
})
})
@ -327,11 +316,7 @@ describe('NewSegmentModal', () => {
fireEvent.click(screen.getByTestId('save-btn'))
await waitFor(() => {
expect(toastAddSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'success',
}),
)
expect(toastSuccessSpy).toHaveBeenCalledTimes(1)
})
})
})

View File

@ -11,7 +11,8 @@ vi.mock('@/next/navigation', () => ({
}),
}))
const toastAddSpy = vi.spyOn(toast, 'add')
const toastErrorSpy = vi.spyOn(toast, 'error')
const toastSuccessSpy = vi.spyOn(toast, 'success')
// Mock document context
let mockParentMode = 'paragraph'
@ -93,7 +94,7 @@ describe('NewChildSegmentModal', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useRealTimers()
toast.close()
toast.dismiss()
mockFullScreen = false
mockParentMode = 'paragraph'
})
@ -189,11 +190,7 @@ describe('NewChildSegmentModal', () => {
fireEvent.click(screen.getByTestId('save-btn'))
await waitFor(() => {
expect(toastAddSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
}),
)
expect(toastErrorSpy).toHaveBeenCalledTimes(1)
})
})
})
@ -244,11 +241,7 @@ describe('NewChildSegmentModal', () => {
fireEvent.click(screen.getByTestId('save-btn'))
await waitFor(() => {
expect(toastAddSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'success',
}),
)
expect(toastSuccessSpy).toHaveBeenCalledTimes(1)
})
})
})

View File

@ -53,16 +53,14 @@ const NewChildSegmentModal: FC<NewChildSegmentModalProps> = ({
const params: SegmentUpdater = { content: '' }
if (!content.trim())
return toast.add({ type: 'error', title: t('segment.contentEmpty', { ns: 'datasetDocuments' }) })
return toast.error(t('segment.contentEmpty', { ns: 'datasetDocuments' }))
params.content = content
setLoading(true)
await addChildSegment({ datasetId, documentId, segmentId: chunkId, body: params }, {
onSuccess(res) {
toast.add({
type: 'success',
title: t('segment.childChunkAdded', { ns: 'datasetDocuments' }),
toast.success(t('segment.childChunkAdded', { ns: 'datasetDocuments' }), {
actionProps: isFullDocMode
? {
children: t('operation.view', { ns: 'common' }),

View File

@ -63,16 +63,10 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
const params: SegmentUpdater = { content: '', attachment_ids: [] }
if (docForm === ChunkingMode.qa) {
if (!question.trim()) {
return toast.add({
type: 'error',
title: t('segment.questionEmpty', { ns: 'datasetDocuments' }),
})
return toast.error(t('segment.questionEmpty', { ns: 'datasetDocuments' }))
}
if (!answer.trim()) {
return toast.add({
type: 'error',
title: t('segment.answerEmpty', { ns: 'datasetDocuments' }),
})
return toast.error(t('segment.answerEmpty', { ns: 'datasetDocuments' }))
}
params.content = question
@ -80,10 +74,7 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
}
else {
if (!question.trim()) {
return toast.add({
type: 'error',
title: t('segment.contentEmpty', { ns: 'datasetDocuments' }),
})
return toast.error(t('segment.contentEmpty', { ns: 'datasetDocuments' }))
}
params.content = question
@ -98,9 +89,7 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
setLoading(true)
await addSegment({ datasetId, documentId, body: params }, {
onSuccess() {
toast.add({
type: 'success',
title: t('segment.chunkAdded', { ns: 'datasetDocuments' }),
toast.success(t('segment.chunkAdded', { ns: 'datasetDocuments' }), {
actionProps: {
children: t('operation.view', { ns: 'common' }),
onClick: viewNewlyAddedChunk,

View File

@ -21,12 +21,19 @@ vi.mock('@/context/i18n', () => ({
useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path || ''}`,
}))
const mockNotify = vi.hoisted(() => vi.fn())
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockNotify,
},
}))
const mockToastSuccess = vi.hoisted(() => vi.fn())
const mockToastError = vi.hoisted(() => vi.fn())
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/ui/toast')>()
return {
...actual,
toast: {
...actual.toast,
success: mockToastSuccess,
error: mockToastError,
},
}
})
// Mock modal context
vi.mock('@/context/modal-context', () => ({
@ -162,10 +169,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
})
// Verify success notification
expect(mockNotify).toHaveBeenCalledWith({
type: 'success',
title: 'dataset.externalKnowledgeForm.connectedSuccess',
})
expect(mockToastSuccess).toHaveBeenCalledWith('dataset.externalKnowledgeForm.connectedSuccess')
// Verify navigation back
expect(mockRouterBack).toHaveBeenCalledTimes(1)
@ -204,10 +208,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
// Verify error notification
await waitFor(() => {
expect(mockNotify).toHaveBeenCalledWith({
type: 'error',
title: 'dataset.externalKnowledgeForm.connectedFailed',
})
expect(mockToastError).toHaveBeenCalledWith('dataset.externalKnowledgeForm.connectedFailed')
})
// Verify no navigation
@ -226,10 +227,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
await fillFormAndSubmit(user)
await waitFor(() => {
expect(mockNotify).toHaveBeenCalledWith({
type: 'error',
title: 'dataset.externalKnowledgeForm.connectedFailed',
})
expect(mockToastError).toHaveBeenCalledWith('dataset.externalKnowledgeForm.connectedFailed')
})
expect(mockRouterBack).not.toHaveBeenCalled()
@ -272,10 +270,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
resolvePromise({ id: 'new-id' })
await waitFor(() => {
expect(mockNotify).toHaveBeenCalledWith({
type: 'success',
title: 'dataset.externalKnowledgeForm.connectedSuccess',
})
expect(mockToastSuccess).toHaveBeenCalledWith('dataset.externalKnowledgeForm.connectedSuccess')
})
})
})

View File

@ -20,7 +20,7 @@ const ExternalKnowledgeBaseConnector = () => {
setLoading(true)
const result = await createExternalKnowledgeBase({ body: formValue })
if (result && result.id) {
toast.add({ type: 'success', title: t('externalKnowledgeForm.connectedSuccess', { ns: 'dataset' }) })
toast.success(t('externalKnowledgeForm.connectedSuccess', { ns: 'dataset' }))
trackEvent('create_external_knowledge_base', {
provider: formValue.provider,
name: formValue.name,
@ -31,7 +31,7 @@ const ExternalKnowledgeBaseConnector = () => {
}
catch (error) {
console.error('Error creating external knowledge base:', error)
toast.add({ type: 'error', title: t('externalKnowledgeForm.connectedFailed', { ns: 'dataset' }) })
toast.error(t('externalKnowledgeForm.connectedFailed', { ns: 'dataset' }))
}
setLoading(false)
}

View File

@ -4,8 +4,8 @@ import { MediaType } from '@/hooks/use-breakpoints'
import { AppModeEnum } from '@/types/app'
import SideBar from '../index'
const { mockToastAdd } = vi.hoisted(() => ({
mockToastAdd: vi.fn(),
const { mockToastSuccess } = vi.hoisted(() => ({
mockToastSuccess: vi.fn(),
}))
const mockSegments = ['apps']
@ -47,14 +47,16 @@ vi.mock('@/service/use-explore', () => ({
}),
}))
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockToastAdd,
close: vi.fn(),
update: vi.fn(),
promise: vi.fn(),
},
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/ui/toast')>()
return {
...actual,
toast: {
...actual.toast,
success: mockToastSuccess,
},
}
})
const createInstalledApp = (overrides: Partial<InstalledApp> = {}): InstalledApp => ({
id: overrides.id ?? 'app-123',
@ -166,10 +168,7 @@ describe('SideBar', () => {
await waitFor(() => {
expect(mockUninstall).toHaveBeenCalledWith('app-123')
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({
type: 'success',
title: 'common.api.remove',
}))
expect(mockToastSuccess).toHaveBeenCalledWith('common.api.remove')
})
})
@ -183,10 +182,7 @@ describe('SideBar', () => {
await waitFor(() => {
expect(mockUpdatePinStatus).toHaveBeenCalledWith({ appId: 'app-123', isPinned: true })
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({
type: 'success',
title: 'common.api.success',
}))
expect(mockToastSuccess).toHaveBeenCalledWith('common.api.success')
})
})

View File

@ -51,18 +51,12 @@ const SideBar = () => {
const id = currId
await uninstallApp(id)
setShowConfirm(false)
toast.add({
type: 'success',
title: t('api.remove', { ns: 'common' }),
})
toast.success(t('api.remove', { ns: 'common' }))
}
const handleUpdatePinStatus = async (id: string, isPinned: boolean) => {
await updatePinStatus({ appId: id, isPinned })
toast.add({
type: 'success',
title: t('api.success', { ns: 'common' }),
})
toast.success(t('api.success', { ns: 'common' }))
}
const pinnedAppsCount = installedApps.filter(({ is_pinned }) => is_pinned).length

View File

@ -24,7 +24,7 @@ vi.mock('react-i18next', async () => {
})
})
const mockNotify = vi.hoisted(() => vi.fn())
const mockToastSuccess = vi.hoisted(() => vi.fn())
const mockUpdateModelList = vi.hoisted(() => vi.fn())
const mockInvalidateDefaultModel = vi.hoisted(() => vi.fn())
const mockUpdateDefaultModel = vi.hoisted(() => vi.fn(() => Promise.resolve({ result: 'success' })))
@ -43,11 +43,16 @@ vi.mock('@/context/provider-context', () => ({
}),
}))
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockNotify,
},
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/ui/toast')>()
return {
...actual,
toast: {
...actual.toast,
success: mockToastSuccess,
},
}
})
vi.mock('../../hooks', () => ({
useModelList: () => ({
@ -148,10 +153,7 @@ describe('SystemModel', () => {
await waitFor(() => {
expect(mockUpdateDefaultModel).toHaveBeenCalledTimes(1)
expect(mockNotify).toHaveBeenCalledWith({
type: 'success',
title: 'Modified successfully',
})
expect(mockToastSuccess).toHaveBeenCalledWith('Modified successfully')
expect(mockInvalidateDefaultModel).toHaveBeenCalledTimes(5)
expect(mockUpdateModelList).toHaveBeenCalledTimes(5)
})
@ -173,7 +175,7 @@ describe('SystemModel', () => {
expect(mockUpdateDefaultModel).toHaveBeenCalledTimes(1)
})
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument()
expect(mockNotify).not.toHaveBeenCalled()
expect(mockToastSuccess).not.toHaveBeenCalled()
expect(mockInvalidateDefaultModel).not.toHaveBeenCalled()
expect(mockUpdateModelList).not.toHaveBeenCalled()
})

View File

@ -123,7 +123,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
},
})
if (res.result === 'success') {
toast.add({ type: 'success', title: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) })
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
setOpen(false)
const allModelTypes = [ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank, ModelTypeEnum.speech2text, ModelTypeEnum.tts]

View File

@ -4,7 +4,8 @@ import { DeleteConfirm } from '../delete-confirm'
const mockRefetch = vi.fn()
const mockDelete = vi.fn()
const mockToastAdd = vi.hoisted(() => vi.fn())
const mockToastSuccess = vi.hoisted(() => vi.fn())
const mockToastError = vi.hoisted(() => vi.fn())
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => ({ refetch: mockRefetch }),
@ -14,11 +15,17 @@ vi.mock('@/service/use-triggers', () => ({
useDeleteTriggerSubscription: () => ({ mutate: mockDelete, isPending: false }),
}))
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockToastAdd,
},
}))
vi.mock('@/app/components/base/ui/toast', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/app/components/base/ui/toast')>()
return {
...actual,
toast: {
...actual.toast,
success: mockToastSuccess,
error: mockToastError,
},
}
})
beforeEach(() => {
vi.clearAllMocks()
@ -42,7 +49,7 @@ describe('DeleteConfirm', () => {
fireEvent.click(screen.getByRole('button', { name: /pluginTrigger\.subscription\.list\.item\.actions\.deleteConfirm\.confirm/ }))
expect(mockDelete).not.toHaveBeenCalled()
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' }))
expect(mockToastError).toHaveBeenCalledWith('pluginTrigger.subscription.list.item.actions.deleteConfirm.confirmInputWarning')
})
it('should allow deletion after matching input name', () => {
@ -87,6 +94,6 @@ describe('DeleteConfirm', () => {
fireEvent.click(screen.getByRole('button', { name: /pluginTrigger\.subscription\.list\.item\.actions\.deleteConfirm\.confirm/ }))
expect(mockToastAdd).toHaveBeenCalledWith(expect.objectContaining({ type: 'error', title: 'network error' }))
expect(mockToastError).toHaveBeenCalledWith('network error')
})
})

View File

@ -41,26 +41,17 @@ export const DeleteConfirm = (props: Props) => {
const onConfirm = () => {
if (workflowsInUse > 0 && inputName !== currentName) {
toast.add({
type: 'error',
title: t(`${tPrefix}.confirmInputWarning`, { ns: 'pluginTrigger' }),
})
toast.error(t(`${tPrefix}.confirmInputWarning`, { ns: 'pluginTrigger' }))
return
}
deleteSubscription(currentId, {
onSuccess: () => {
toast.add({
type: 'success',
title: t(`${tPrefix}.success`, { ns: 'pluginTrigger', name: currentName }),
})
toast.success(t(`${tPrefix}.success`, { ns: 'pluginTrigger', name: currentName }))
refetch?.()
onClose(true)
},
onError: (error: unknown) => {
toast.add({
type: 'error',
title: error instanceof Error ? error.message : t(`${tPrefix}.error`, { ns: 'pluginTrigger', name: currentName }),
})
toast.error(error instanceof Error ? error.message : t(`${tPrefix}.error`, { ns: 'pluginTrigger', name: currentName }))
},
})
}

View File

@ -6,10 +6,10 @@ import GetSchema from '../get-schema'
vi.mock('@/service/tools', () => ({
importSchemaFromURL: vi.fn(),
}))
const mockToastAdd = vi.hoisted(() => vi.fn())
const mockToastError = vi.hoisted(() => vi.fn())
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockToastAdd,
error: mockToastError,
},
}))
const importSchemaFromURLMock = vi.mocked(importSchemaFromURL)
@ -30,10 +30,7 @@ describe('GetSchema', () => {
fireEvent.change(input, { target: { value: 'ftp://invalid' } })
fireEvent.click(screen.getByText('common.operation.ok'))
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'tools.createTool.urlError',
})
expect(mockToastError).toHaveBeenCalledWith('tools.createTool.urlError')
})
it('imports schema from url when valid', async () => {

View File

@ -27,10 +27,7 @@ const GetSchema: FC<Props> = ({
const [isParsing, setIsParsing] = useState(false)
const handleImportFromUrl = async () => {
if (!importUrl.startsWith('http://') && !importUrl.startsWith('https://')) {
toast.add({
type: 'error',
title: t('createTool.urlError', { ns: 'tools' }),
})
toast.error(t('createTool.urlError', { ns: 'tools' }))
return
}
setIsParsing(true)

View File

@ -48,10 +48,10 @@ vi.mock('@/service/use-plugins', () => ({
}),
}))
const mockToastAdd = vi.hoisted(() => vi.fn())
const mockToastError = vi.hoisted(() => vi.fn())
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: mockToastAdd,
error: mockToastError,
},
}))
@ -310,10 +310,7 @@ describe('MCPModal', () => {
// Wait a bit and verify onConfirm was not called
await new Promise(resolve => setTimeout(resolve, 100))
expect(onConfirm).not.toHaveBeenCalled()
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'tools.mcp.modal.invalidServerUrl',
})
expect(mockToastError).toHaveBeenCalledWith('tools.mcp.modal.invalidServerUrl')
})
it('should not call onConfirm with invalid server identifier', async () => {
@ -335,10 +332,7 @@ describe('MCPModal', () => {
// Wait a bit and verify onConfirm was not called
await new Promise(resolve => setTimeout(resolve, 100))
expect(onConfirm).not.toHaveBeenCalled()
expect(mockToastAdd).toHaveBeenCalledWith({
type: 'error',
title: 'tools.mcp.modal.invalidServerIdentifier',
})
expect(mockToastError).toHaveBeenCalledWith('tools.mcp.modal.invalidServerIdentifier')
})
})

View File

@ -82,11 +82,11 @@ const MCPModalContent: FC<MCPModalContentProps> = ({
const submit = async () => {
if (!isValidUrl(state.url)) {
toast.add({ type: 'error', title: t('mcp.modal.invalidServerUrl', { ns: 'tools' }) })
toast.error(t('mcp.modal.invalidServerUrl', { ns: 'tools' }))
return
}
if (!isValidServerID(state.serverIdentifier.trim())) {
toast.add({ type: 'error', title: t('mcp.modal.invalidServerIdentifier', { ns: 'tools' }) })
toast.error(t('mcp.modal.invalidServerIdentifier', { ns: 'tools' }))
return
}
const formattedHeaders = state.headers.reduce((acc, item) => {

View File

@ -71,10 +71,10 @@ vi.mock('@/app/components/tools/edit-custom-collection-modal', () => ({
}))
// Mock toast
const mockToastNotify = vi.fn()
const mockToastSuccess = vi.fn()
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: (options: { type: string, title: string }) => mockToastNotify(options),
success: (title: string) => mockToastSuccess(title),
},
}))
@ -198,10 +198,7 @@ describe('CustomCreateCard', () => {
fireEvent.click(screen.getByTestId('submit-modal'))
await waitFor(() => {
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'success',
title: expect.any(String),
})
expect(mockToastSuccess).toHaveBeenCalledWith(expect.any(String))
})
})

View File

@ -92,9 +92,13 @@ vi.mock('@/app/components/base/confirm', () => ({
: null,
}))
const mockToastAdd = vi.hoisted(() => vi.fn())
const mockToastSuccess = vi.hoisted(() => vi.fn())
const mockToastError = vi.hoisted(() => vi.fn())
vi.mock('@/app/components/base/ui/toast', () => ({
toast: { add: mockToastAdd },
toast: {
success: mockToastSuccess,
error: mockToastError,
},
}))
vi.mock('@/app/components/header/indicator', () => ({

View File

@ -21,10 +21,7 @@ const Contribute = ({ onRefreshData }: Props) => {
const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)
const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
await createCustomCollection(data)
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setIsShowEditCustomCollectionModal(false)
onRefreshData()
}

View File

@ -122,19 +122,13 @@ const ProviderDetail = ({
await getCustomProvider()
// Use fresh data from form submission to avoid race condition with collection.labels
setCustomCollection(prev => prev ? { ...prev, labels: data.labels } : null)
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setIsShowEditCustomCollectionModal(false)
}
const doRemoveCustomToolCollection = async () => {
await removeCustomCollection(collection?.name as string)
onRefreshData()
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setIsShowEditCustomCollectionModal(false)
}
// workflow provider
@ -161,10 +155,7 @@ const ProviderDetail = ({
const removeWorkflowToolProvider = async () => {
await deleteWorkflowTool(collection.id)
onRefreshData()
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setIsShowEditWorkflowToolModal(false)
}
const updateWorkflowToolProvider = async (data: WorkflowToolProviderRequest & Partial<{
@ -175,10 +166,7 @@ const ProviderDetail = ({
invalidateAllWorkflowTools()
onRefreshData()
getWorkflowToolProvider()
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setIsShowEditWorkflowToolModal(false)
}
const onClickCustomToolDelete = () => {
@ -385,19 +373,13 @@ const ProviderDetail = ({
onCancel={() => setShowSettingAuth(false)}
onSaved={async (value) => {
await updateBuiltInToolCredential(collection.name, value)
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
await onRefreshData()
setShowSettingAuth(false)
}}
onRemove={async () => {
await removeBuiltInToolCredential(collection.name)
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
await onRefreshData()
setShowSettingAuth(false)
}}

View File

@ -40,17 +40,11 @@ const OutputVarList: FC<Props> = ({
const { run: validateVarInput } = useDebounceFn((existingVariables: typeof list, newKey: string) => {
const result = checkKeys([newKey], true)
if (!result.isValid) {
toast.add({
type: 'error',
title: t(`varKeyError.${result.errorMessageKey}`, { ns: 'appDebug', key: result.errorKey }),
})
toast.error(t(`varKeyError.${result.errorMessageKey}`, { ns: 'appDebug', key: result.errorKey }))
return
}
if (existingVariables.some(key => key.variable?.trim() === newKey.trim())) {
toast.add({
type: 'error',
title: t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: newKey }),
})
toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: newKey }))
}
}, { wait: 500 })

View File

@ -53,17 +53,11 @@ const VarList: FC<Props> = ({
const { run: validateVarInput } = useDebounceFn((list: Variable[], newKey: string) => {
const result = checkKeys([newKey], true)
if (!result.isValid) {
toast.add({
type: 'error',
title: t(`varKeyError.${result.errorMessageKey}`, { ns: 'appDebug', key: result.errorKey }),
})
toast.error(t(`varKeyError.${result.errorMessageKey}`, { ns: 'appDebug', key: result.errorKey }))
return
}
if (list.some(item => item.variable?.trim() === newKey.trim())) {
toast.add({
type: 'error',
title: t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: newKey }),
})
toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: newKey }))
}
}, { wait: 500 })

View File

@ -120,10 +120,7 @@ export const VersionHistoryPanel = ({
break
case VersionHistoryContextMenuOptions.copyId:
copy(item.id)
toast.add({
type: 'success',
title: t('versionHistory.action.copyIdSuccess', { ns: 'workflow' }),
})
toast.success(t('versionHistory.action.copyIdSuccess', { ns: 'workflow' }))
break
case VersionHistoryContextMenuOptions.exportDSL:
handleExportDSL?.(false, item.id)
@ -156,18 +153,12 @@ export const VersionHistoryPanel = ({
workflowStore.setState({ isRestoring: false })
workflowStore.setState({ backupDraft: undefined })
handleRefreshWorkflowDraft()
toast.add({
type: 'success',
title: t('versionHistory.action.restoreSuccess', { ns: 'workflow' }),
})
toast.success(t('versionHistory.action.restoreSuccess', { ns: 'workflow' }))
deleteAllInspectVars()
invalidAllLastRun()
}
catch {
toast.add({
type: 'error',
title: t('versionHistory.action.restoreFailure', { ns: 'workflow' }),
})
toast.error(t('versionHistory.action.restoreFailure', { ns: 'workflow' }))
}
finally {
resetWorkflowVersionHistory()
@ -180,19 +171,13 @@ export const VersionHistoryPanel = ({
await deleteWorkflow(deleteVersionUrl?.(id) || '', {
onSuccess: () => {
setDeleteConfirmOpen(false)
toast.add({
type: 'success',
title: t('versionHistory.action.deleteSuccess', { ns: 'workflow' }),
})
toast.success(t('versionHistory.action.deleteSuccess', { ns: 'workflow' }))
resetWorkflowVersionHistory()
deleteAllInspectVars()
invalidAllLastRun()
},
onError: () => {
toast.add({
type: 'error',
title: t('versionHistory.action.deleteFailure', { ns: 'workflow' }),
})
toast.error(t('versionHistory.action.deleteFailure', { ns: 'workflow' }))
},
onSettled: () => {
setDeleteConfirmOpen(false)
@ -210,17 +195,11 @@ export const VersionHistoryPanel = ({
}, {
onSuccess: () => {
setEditModalOpen(false)
toast.add({
type: 'success',
title: t('versionHistory.action.updateSuccess', { ns: 'workflow' }),
})
toast.success(t('versionHistory.action.updateSuccess', { ns: 'workflow' }))
resetWorkflowVersionHistory()
},
onError: () => {
toast.add({
type: 'error',
title: t('versionHistory.action.updateFailure', { ns: 'workflow' }),
})
toast.error(t('versionHistory.action.updateFailure', { ns: 'workflow' }))
},
onSettled: () => {
setEditModalOpen(false)

View File

@ -210,7 +210,7 @@ const WorkflowPreview = () => {
copy(content)
else
copy(JSON.stringify(content))
toast.add({ type: 'success', title: t('actionMsg.copySuccessfully', { ns: 'common' }) })
toast.success(t('actionMsg.copySuccessfully', { ns: 'common' }))
}}
>
<span className="i-ri-clipboard-line h-3.5 w-3.5" />

View File

@ -29,10 +29,7 @@ const ChangePasswordForm = () => {
const [showSuccess, setShowSuccess] = useState(false)
const showErrorMessage = useCallback((message: string) => {
toast.add({
type: 'error',
title: message,
})
toast.error(message)
}, [])
const valid = useCallback(() => {

View File

@ -23,17 +23,11 @@ export default function CheckCode() {
const verify = async () => {
try {
if (!code.trim()) {
toast.add({
type: 'error',
title: t('checkCode.emptyCode', { ns: 'login' }),
})
toast.error(t('checkCode.emptyCode', { ns: 'login' }))
return
}
if (!/\d{6}/.test(code)) {
toast.add({
type: 'error',
title: t('checkCode.invalidCode', { ns: 'login' }),
})
toast.error(t('checkCode.invalidCode', { ns: 'login' }))
return
}
setIsLoading(true)

View File

@ -26,15 +26,12 @@ export default function CheckCode() {
const handleGetEMailVerificationCode = async () => {
try {
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
setIsLoading(true)
@ -47,10 +44,7 @@ export default function CheckCode() {
router.push(`/reset-password/check-code?${params.toString()}`)
}
else {
toast.add({
type: 'error',
title: res.data,
})
toast.error(res.data)
}
}
catch (error) {

View File

@ -24,10 +24,7 @@ const ChangePasswordForm = () => {
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const showErrorMessage = useCallback((message: string) => {
toast.add({
type: 'error',
title: message,
})
toast.error(message)
}, [])
const getSignInUrl = () => {

View File

@ -31,17 +31,11 @@ export default function CheckCode() {
const verify = async () => {
try {
if (!code.trim()) {
toast.add({
type: 'error',
title: t('checkCode.emptyCode', { ns: 'login' }),
})
toast.error(t('checkCode.emptyCode', { ns: 'login' }))
return
}
if (!/\d{6}/.test(code)) {
toast.add({
type: 'error',
title: t('checkCode.invalidCode', { ns: 'login' }),
})
toast.error(t('checkCode.invalidCode', { ns: 'login' }))
return
}
setIsLoading(true)

View File

@ -26,15 +26,12 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
const handleGetEMailVerificationCode = async () => {
try {
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
setIsLoading(true)

View File

@ -35,18 +35,15 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
const handleEmailPasswordLogin = async () => {
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
if (!password?.trim()) {
toast.add({ type: 'error', title: t('error.passwordEmpty', { ns: 'login' }) })
toast.error(t('error.passwordEmpty', { ns: 'login' }))
return
}
@ -83,18 +80,12 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
}
}
else {
toast.add({
type: 'error',
title: res.data,
})
toast.error(res.data)
}
}
catch (error) {
if ((error as ResponseError).code === 'authentication_failed') {
toast.add({
type: 'error',
title: t('error.invalidEmailOrPassword', { ns: 'login' }),
})
toast.error(t('error.invalidEmailOrPassword', { ns: 'login' }))
}
}
finally {

View File

@ -49,10 +49,7 @@ const SSOAuth: FC<SSOAuthProps> = ({
})
}
else {
toast.add({
type: 'error',
title: t('error.invalidSSOProtocol', { ns: 'login' }),
})
toast.error(t('error.invalidSSOProtocol', { ns: 'login' }))
setIsLoading(false)
}
}

View File

@ -48,10 +48,7 @@ const NormalForm = () => {
}
if (message) {
toast.add({
type: 'error',
title: message,
})
toast.error(message)
}
setAllMethodsAreDisabled(!systemFeatures.enable_social_oauth_login && !systemFeatures.enable_email_code_login && !systemFeatures.enable_email_password_login && !systemFeatures.sso_enforced_for_signin)
setShowORLine((systemFeatures.enable_social_oauth_login || systemFeatures.sso_enforced_for_signin) && (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login))

View File

@ -26,17 +26,11 @@ export default function CheckCode() {
const verify = async () => {
try {
if (!code.trim()) {
toast.add({
type: 'error',
title: t('checkCode.emptyCode', { ns: 'login' }),
})
toast.error(t('checkCode.emptyCode', { ns: 'login' }))
return
}
if (!/\d{6}/.test(code)) {
toast.add({
type: 'error',
title: t('checkCode.invalidCode', { ns: 'login' }),
})
toast.error(t('checkCode.invalidCode', { ns: 'login' }))
return
}
setIsLoading(true)
@ -47,10 +41,7 @@ export default function CheckCode() {
router.push(`/signup/set-password?${params.toString()}`)
}
else {
toast.add({
type: 'error',
title: t('checkCode.invalidCode', { ns: 'login' }),
})
toast.error(t('checkCode.invalidCode', { ns: 'login' }))
}
}
catch (error) { console.error(error) }

View File

@ -30,14 +30,11 @@ export default function Form({
return
if (!email) {
toast.add({ type: 'error', title: t('error.emailEmpty', { ns: 'login' }) })
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.add({
type: 'error',
title: t('error.emailInValid', { ns: 'login' }),
})
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
const res = await submitMail({ email, language: locale })

View File

@ -37,10 +37,7 @@ const ChangePasswordForm = () => {
const { mutateAsync: register, isPending } = useMailRegister()
const showErrorMessage = useCallback((message: string) => {
toast.add({
type: 'error',
title: message,
})
toast.error(message)
}, [])
const valid = useCallback(() => {
@ -82,10 +79,7 @@ const ChangePasswordForm = () => {
})
Cookies.remove('utm_info') // Clean up: remove utm_info cookie
toast.add({
type: 'success',
title: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
router.replace('/apps')
}
}

View File

@ -133,9 +133,7 @@ export const ProviderContextProvider = ({
const quota = anthropic.system_configuration.quota_configurations.find(item => item.quota_type === anthropic.system_configuration.current_quota_type)
if (quota && quota.is_valid && quota.quota_used < quota.quota_limit) {
localStorage.setItem('anthropic_quota_notice', 'true')
toast.add({
type: 'info',
title: t('provider.anthropicHosted.trialQuotaTip', { ns: 'common' }),
toast.info(t('provider.anthropicHosted.trialQuotaTip', { ns: 'common' }), {
timeout: 60000,
})
}

View File

@ -48,7 +48,7 @@ const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook
const shouldNotifyError = response.status !== 401 && errorData && !otherOptions.silent
if (shouldNotifyError)
toast.add({ type: 'error', title: errorData.message })
toast.error(errorData.message)
if (response.status === 403 && errorData?.code === 'already_setup')
globalThis.location.href = `${globalThis.location.origin}/signin`