Compare commits

..

2 Commits

Author SHA1 Message Date
yyh
941759e069 refactor(web): use css icons for external API panel 2026-08-09 17:10:57 +08:00
yyh
8989bcd053 fix(web): name external API panel close 2026-08-09 17:10:56 +08:00
5 changed files with 31 additions and 89 deletions

View File

@@ -141,6 +141,11 @@
"count": 1
}
},
"web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx": {
"jsx_a11y/tabindex-no-positive": {
"count": 3

View File

@@ -1,58 +0,0 @@
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import MailAndCodeAuth from '../mail-and-code-auth'
const pushMock = vi.fn()
const replaceMock = vi.fn()
const setCountdownLeftTimeMock = vi.fn()
const sendWebAppEMailLoginCodeMock = vi.fn()
const searchParams = new URLSearchParams({
redirect_url: encodeURIComponent('/chatbot/test-app'),
})
vi.mock('@/next/navigation', () => ({
useRouter: () => ({ push: pushMock, replace: replaceMock }),
useSearchParams: () => searchParams,
}))
vi.mock('@/context/i18n', () => ({
useLocale: () => 'en-US',
}))
vi.mock('@/app/components/signin/storage', () => ({
COUNT_DOWN_TIME_MS: 60_000,
useSetCountdownLeftTime: () => setCountdownLeftTimeMock,
}))
vi.mock('@/service/common', () => ({
sendWebAppEMailLoginCode: (...args: unknown[]) => sendWebAppEMailLoginCodeMock(...args),
}))
describe('MailAndCodeAuth', () => {
beforeEach(() => {
vi.clearAllMocks()
sendWebAppEMailLoginCodeMock.mockResolvedValue({ result: 'success', data: 'token-abc' })
})
it('submits from the email field through the native form', async () => {
const user = userEvent.setup()
render(<MailAndCodeAuth />)
const emailInput = screen.getByRole('textbox', { name: 'login.email' })
const submitButton = screen.getByRole('button', { name: 'login.signup.verifyMail' })
expect(emailInput).toHaveAttribute('name', 'email')
expect(emailInput).toHaveAttribute('autocomplete', 'email')
expect(emailInput).toHaveAttribute('spellcheck', 'false')
expect(submitButton).toHaveAttribute('type', 'submit')
await user.tab()
expect(emailInput).toHaveFocus()
await user.type(emailInput, 'user@example.com{Enter}')
await waitFor(() => {
expect(sendWebAppEMailLoginCodeMock).toHaveBeenCalledTimes(1)
})
expect(pushMock).toHaveBeenCalledWith(expect.stringContaining('/webapp-signin/check-code?'))
})
})

View File

@@ -1,10 +1,10 @@
import { Button } from '@langgenius/dify-ui/button'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast'
import { noop } from 'es-toolkit/function'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { resolveWebAppLoginRedirect } from '@/app/(shareLayout)/webapp-signin/login-redirect'
import Input from '@/app/components/base/input'
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage'
import { emailRegex } from '@/config'
import { useLocale } from '@/context/i18n'
@@ -64,37 +64,33 @@ export default function MailAndCodeAuth() {
}
return (
<Form
onFormSubmit={() => {
void handleGetEMailVerificationCode()
}}
>
<Field name="email" className="mb-2 block">
<FieldLabel className="my-2 py-0 system-md-semibold! text-text-secondary">
<form onSubmit={noop}>
<input type="text" className="hidden" />
<div className="mb-2">
<label htmlFor="email" className="my-2 system-md-semibold text-text-secondary">
{t(($) => $.email, { ns: 'login' })}
</FieldLabel>
</label>
<div className="mt-1">
<FieldControl
<Input
id="email"
type="email"
autoComplete="email"
spellCheck={false}
value={email}
placeholder={t(($) => $.emailPlaceholder, { ns: 'login' }) as string}
onValueChange={setEmail}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mt-3">
<Button
type="submit"
loading={loading}
disabled={loading || !email}
variant="primary"
className="w-full"
onClick={handleGetEMailVerificationCode}
>
{t(($) => $['signup.verifyMail'], { ns: 'login' })}
</Button>
</div>
</Field>
</Form>
</div>
</form>
)
}

View File

@@ -105,10 +105,8 @@ describe('ExternalAPIPanel', () => {
})
it('should render close button', () => {
const { container } = render(<ExternalAPIPanel {...defaultProps} />)
const closeButton =
container.querySelector('[class*="action-button"]') || screen.getAllByRole('button')[0]
expect(closeButton)!.toBeInTheDocument()
render(<ExternalAPIPanel {...defaultProps} />)
expect(screen.getByRole('button', { name: 'common.operation.close' })).toBeInTheDocument()
})
})
@@ -171,11 +169,7 @@ describe('ExternalAPIPanel', () => {
it('should call onClose when close button is clicked', () => {
const onClose = vi.fn()
render(<ExternalAPIPanel canManageExternalKnowledgeApi={true} onClose={onClose} />)
// Find the close button (ActionButton with close icon)
const buttons = screen.getAllByRole('button')
const closeButton =
buttons.find((btn) => btn.querySelector('svg[class*="ri-close"]')) || buttons[0]
fireEvent.click(closeButton!)
fireEvent.click(screen.getByRole('button', { name: 'common.operation.close' }))
expect(onClose).toHaveBeenCalledTimes(1)
})

View File

@@ -1,6 +1,5 @@
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { RiAddLine, RiBookOpenLine, RiCloseLine } from '@remixicon/react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
@@ -64,15 +63,18 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({
href={docLink('/use-dify/knowledge/external-knowledge-api')}
target="_blank"
>
<RiBookOpenLine className="size-3 text-text-accent" />
<span aria-hidden className="i-ri-book-open-line size-3 text-text-accent" />
<div className="grow body-xs-regular text-text-accent">
{t(($) => $.externalAPIPanelDocumentation, { ns: 'dataset' })}
</div>
</a>
</div>
<div className="flex items-center">
<ActionButton onClick={() => onClose()}>
<RiCloseLine className="size-4 text-text-tertiary" />
<ActionButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={() => onClose()}
>
<span aria-hidden className="i-ri-close-line size-4 text-text-tertiary" />
</ActionButton>
</div>
</div>
@@ -83,7 +85,10 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({
className="flex items-center justify-center px-3 py-2"
onClick={handleOpenExternalAPIModal}
>
<RiAddLine className="size-4 text-components-button-primary-text" />
<span
aria-hidden
className="i-ri-add-line size-4 text-components-button-primary-text"
/>
<div className="system-sm-medium text-components-button-primary-text">
{t(($) => $.createExternalAPI, { ns: 'dataset' })}
</div>