diff --git a/eslint-suppressions.json b/eslint-suppressions.json index b4876dcf45..3692f9cc6a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2579,11 +2579,8 @@ "erasable-syntax-only/enums": { "count": 1 }, - "no-restricted-imports": { - "count": 1 - }, "ts/no-explicit-any": { - "count": 3 + "count": 2 } }, "web/app/components/header/account-setting/model-provider-page/declarations.ts": { diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx index 9e05a93c7a..56a237d741 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx @@ -183,18 +183,20 @@ describe('TransferOwnershipModal', () => { }) }) - it('should show error when sending verification email fails', async () => { + it('should not show a modal-level toast and should stay on start step when sending verification email fails', async () => { const user = userEvent.setup() vi.mocked(sendOwnerEmail).mockRejectedValue(new Error('network error')) renderModal() await user.click(screen.getByTestId('transfer-modal-send-code')) + // The base service layer surfaces the real backend error. The modal itself + // must NOT show an additional toast (e.g. "Error sending verification code: undefined"). await waitFor(() => { - expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ - type: 'error', - message: expect.stringContaining('network error'), - })) + expect(sendOwnerEmail).toHaveBeenCalled() }) + expect(mockNotify).not.toHaveBeenCalled() + // Should remain on the start step instead of advancing to the verify step. + expect(screen.getByTestId('transfer-modal-send-code')).toBeInTheDocument() }) it('should show error when ownership transfer fails', async () => { @@ -229,7 +231,7 @@ describe('TransferOwnershipModal', () => { }) }) - it('should show fallback error prefix when sendOwnerEmail throws null', async () => { + it('should swallow null rejection from sendOwnerEmail without showing a modal-level toast', async () => { const user = userEvent.setup() vi.mocked(sendOwnerEmail).mockRejectedValue(null) @@ -237,11 +239,10 @@ describe('TransferOwnershipModal', () => { await user.click(screen.getByTestId('transfer-modal-send-code')) await waitFor(() => { - expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ - type: 'error', - message: expect.stringContaining('Error sending verification code:'), - })) + expect(sendOwnerEmail).toHaveBeenCalled() }) + expect(mockNotify).not.toHaveBeenCalled() + expect(screen.getByTestId('transfer-modal-send-code')).toBeInTheDocument() }) it('should show fallback error prefix when verifyOwnerEmail throws null', async () => { diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx index 85a3ac3b22..cadc2dc967 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx @@ -1,11 +1,10 @@ import { Button } from '@langgenius/dify-ui/button' +import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' -import { noop } from 'es-toolkit/function' import * as React from 'react' import { useCallback, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import Modal from '@/app/components/base/modal' import { useAppContext } from '@/context/app-context' import { ownershipTransfer, sendOwnerEmail, verifyOwnerEmail } from '@/service/common' import MemberSelector from './member-selector' @@ -52,15 +51,10 @@ const TransferOwnershipModal = ({ onClose, show }: Props) => { }, 1000)) } const sendEmail = async () => { - try { - const res = await sendOwnerEmail({}) - startCount() - if (res.data) - setStepToken(res.data) - } - catch (error) { - toast.error(`Error sending verification code: ${error ? (error as any).message : ''}`) - } + const res = await sendOwnerEmail({}) + startCount() + if (res.data) + setStepToken(res.data) } const verifyEmailAddress = async (code: string, token: string, callback?: () => void) => { try { @@ -81,8 +75,13 @@ const TransferOwnershipModal = ({ onClose, show }: Props) => { } } const sendCodeToOriginEmail = async () => { - await sendEmail() - setStep(STEP.verify) + try { + await sendEmail() + setStep(STEP.verify) + } + catch { + // The base service layer already surfaces the backend error (e.g. rate-limit) as a toast. + } } const handleVerifyOriginEmail = async () => { await verifyEmailAddress(code, stepToken, () => setStep(STEP.transfer)) @@ -104,85 +103,87 @@ const TransferOwnershipModal = ({ onClose, show }: Props) => { } } return ( - -
-
-
- {step === STEP.start && ( - <> -
{t('members.transferModal.title', { ns: 'common' })}
-
-
{t('members.transferModal.warning', { ns: 'common', workspace: currentWorkspace.name.replace(/'/g, '’') })}
-
{t('members.transferModal.warningTip', { ns: 'common' })}
-
- }} values={{ email: userProfile.email }} /> + + +
+
+
+ {step === STEP.start && ( + <> +
{t('members.transferModal.title', { ns: 'common' })}
+
+
{t('members.transferModal.warning', { ns: 'common', workspace: currentWorkspace.name.replace(/'/g, '’') })}
+
{t('members.transferModal.warningTip', { ns: 'common' })}
+
+ }} values={{ email: userProfile.email }} /> +
-
-
-
- - -
- - )} - {step === STEP.verify && ( - <> -
{t('members.transferModal.verifyEmail', { ns: 'common' })}
-
-
- }} values={{ email: userProfile.email }} /> +
+
+ +
-
{t('members.transferModal.verifyContent2', { ns: 'common' })}
-
-
-
{t('members.transferModal.codeLabel', { ns: 'common' })}
- setCode(e.target.value)} maxLength={6} /> -
-
- - -
-
- {t('members.transferModal.resendTip', { ns: 'common' })} - {time > 0 && ({t('members.transferModal.resendCount', { ns: 'common', count: time })})} - {!time && ( - - {t('members.transferModal.resend', { ns: 'common' })} - - )} -
- - )} - {step === STEP.transfer && ( - <> -
{t('members.transferModal.title', { ns: 'common' })}
-
-
{t('members.transferModal.warning', { ns: 'common', workspace: currentWorkspace.name.replace(/'/g, '’') })}
-
{t('members.transferModal.warningTip', { ns: 'common' })}
-
-
-
{t('members.transferModal.transferLabel', { ns: 'common' })}
- -
-
- - -
- - )} - + + )} + {step === STEP.verify && ( + <> +
{t('members.transferModal.verifyEmail', { ns: 'common' })}
+
+
+ }} values={{ email: userProfile.email }} /> +
+
{t('members.transferModal.verifyContent2', { ns: 'common' })}
+
+
+
{t('members.transferModal.codeLabel', { ns: 'common' })}
+ setCode(e.target.value)} maxLength={6} /> +
+
+ + +
+
+ {t('members.transferModal.resendTip', { ns: 'common' })} + {time > 0 && ({t('members.transferModal.resendCount', { ns: 'common', count: time })})} + {!time && ( + + {t('members.transferModal.resend', { ns: 'common' })} + + )} +
+ + )} + {step === STEP.transfer && ( + <> +
{t('members.transferModal.title', { ns: 'common' })}
+
+
{t('members.transferModal.warning', { ns: 'common', workspace: currentWorkspace.name.replace(/'/g, '’') })}
+
{t('members.transferModal.warningTip', { ns: 'common' })}
+
+
+
{t('members.transferModal.transferLabel', { ns: 'common' })}
+ +
+
+ + +
+ + )} + +
) } export default TransferOwnershipModal