mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(web): require hub URL without clearing input
This commit is contained in:
@@ -16,6 +16,7 @@ import { fetchLatestMessages } from '@/lib/message-window-store'
|
||||
import { useAppGoBack } from '@/hooks/useAppGoBack'
|
||||
import { useTranslation } from '@/lib/use-translation'
|
||||
import { VoiceProvider } from '@/lib/voice-context'
|
||||
import { requireHubUrlForLogin } from '@/lib/runtime-config'
|
||||
import { LoginPrompt } from '@/components/LoginPrompt'
|
||||
import { InstallPrompt } from '@/components/InstallPrompt'
|
||||
import { OfflineBanner } from '@/components/OfflineBanner'
|
||||
@@ -29,6 +30,8 @@ import type { SyncEvent } from '@/types/api'
|
||||
|
||||
type ToastEvent = Extract<SyncEvent, { type: 'toast' }>
|
||||
|
||||
const REQUIRE_SERVER_URL = requireHubUrlForLogin()
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<ToastProvider>
|
||||
@@ -271,6 +274,7 @@ function AppInner() {
|
||||
serverUrl={serverUrl}
|
||||
setServerUrl={setServerUrl}
|
||||
clearServerUrl={clearServerUrl}
|
||||
requireServerUrl={REQUIRE_SERVER_URL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -284,6 +288,7 @@ function AppInner() {
|
||||
serverUrl={serverUrl}
|
||||
setServerUrl={setServerUrl}
|
||||
clearServerUrl={clearServerUrl}
|
||||
requireServerUrl={REQUIRE_SERVER_URL}
|
||||
error={authError ?? undefined}
|
||||
/>
|
||||
)
|
||||
@@ -309,6 +314,7 @@ function AppInner() {
|
||||
serverUrl={serverUrl}
|
||||
setServerUrl={setServerUrl}
|
||||
clearServerUrl={clearServerUrl}
|
||||
requireServerUrl={REQUIRE_SERVER_URL}
|
||||
error={authError ?? t('login.error.authFailed')}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { I18nProvider } from '@/lib/i18n-context'
|
||||
import { LoginPrompt } from './LoginPrompt'
|
||||
|
||||
function renderWithProviders(ui: React.ReactElement) {
|
||||
return render(
|
||||
<I18nProvider>
|
||||
{ui}
|
||||
</I18nProvider>
|
||||
)
|
||||
}
|
||||
|
||||
describe('LoginPrompt', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
const localStorageMock = {
|
||||
getItem: vi.fn(() => 'en'),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
}
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock })
|
||||
})
|
||||
|
||||
it('does not clear first hub URL edit when hub URL required', async () => {
|
||||
renderWithProviders(
|
||||
<LoginPrompt
|
||||
baseUrl="https://app.example.com"
|
||||
serverUrl={null}
|
||||
setServerUrl={vi.fn((value: string) => ({ ok: true as const, value }))}
|
||||
clearServerUrl={vi.fn()}
|
||||
requireServerUrl={true}
|
||||
onLogin={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText('Access token'), { target: { value: 'token' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Sign In' }))
|
||||
|
||||
const hubInput = await screen.findByPlaceholderText('https://hapi.example.com')
|
||||
expect(screen.getByText('Hub URL required. Please set it before signing in.')).toBeInTheDocument()
|
||||
|
||||
fireEvent.change(hubInput, { target: { value: 'https://hub.example.com' } })
|
||||
|
||||
expect(hubInput).toHaveValue('https://hub.example.com')
|
||||
expect(screen.queryByText('Hub URL required. Please set it before signing in.')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -15,6 +15,7 @@ type LoginPromptProps = {
|
||||
serverUrl: string | null
|
||||
setServerUrl: (input: string) => ServerUrlResult
|
||||
clearServerUrl: () => void
|
||||
requireServerUrl?: boolean
|
||||
error?: string | null
|
||||
}
|
||||
|
||||
@@ -37,6 +38,12 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isBindMode && props.requireServerUrl && !props.serverUrl) {
|
||||
setServerError(t('login.server.required'))
|
||||
setIsServerDialogOpen(true)
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
@@ -71,7 +78,6 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
return
|
||||
}
|
||||
setServerInput(props.serverUrl ?? '')
|
||||
setServerError(null)
|
||||
}, [isServerDialogOpen, props.serverUrl])
|
||||
|
||||
const handleSaveServer = useCallback((e: React.FormEvent) => {
|
||||
@@ -93,6 +99,13 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
setIsServerDialogOpen(false)
|
||||
}, [props])
|
||||
|
||||
const handleServerDialogOpenChange = useCallback((open: boolean) => {
|
||||
setIsServerDialogOpen(open)
|
||||
if (!open) {
|
||||
setServerError(null)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const displayError = error || props.error
|
||||
const serverSummary = props.serverUrl ?? `${props.baseUrl} ${t('login.server.default')}`
|
||||
const title = isBindMode ? t('login.bind.title') : t('login.title')
|
||||
@@ -158,7 +171,7 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
<a href="https://hapi.run/docs" target="_blank" rel="noopener noreferrer" className="underline hover:text-[var(--app-fg)]">
|
||||
{t('login.help')}
|
||||
</a>
|
||||
<Dialog open={isServerDialogOpen} onOpenChange={setIsServerDialogOpen}>
|
||||
<Dialog open={isServerDialogOpen} onOpenChange={handleServerDialogOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
<button type="button" className="underline hover:text-[var(--app-fg)]">
|
||||
Hub {props.serverUrl ? `${t('login.server.custom')}` : `${t('login.server.default')}`}
|
||||
|
||||
@@ -31,6 +31,7 @@ export default {
|
||||
'login.server.origin': 'Hub origin',
|
||||
'login.server.placeholder': 'https://hapi.example.com',
|
||||
'login.server.hint': 'Use http(s) only. Any path is ignored.',
|
||||
'login.server.required': 'Hub URL required. Please set it before signing in.',
|
||||
'login.server.useSameOrigin': 'Use same origin',
|
||||
'login.server.save': 'Save hub',
|
||||
'login.footer': 'Designed with',
|
||||
|
||||
@@ -31,6 +31,7 @@ export default {
|
||||
'login.server.origin': '服务器地址',
|
||||
'login.server.placeholder': 'https://hapi.example.com',
|
||||
'login.server.hint': '仅使用 http(s) 协议。任何路径将被忽略。',
|
||||
'login.server.required': '必须先填写服务器地址才能登录。',
|
||||
'login.server.useSameOrigin': '使用同源',
|
||||
'login.server.save': '保存',
|
||||
'login.footer': '用',
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
const TRUE_VALUES = new Set(['1', 'true', 'yes', 'on'])
|
||||
|
||||
function parseBooleanFlag(value: string | undefined): boolean {
|
||||
if (!value) {
|
||||
return false
|
||||
}
|
||||
return TRUE_VALUES.has(value.trim().toLowerCase())
|
||||
}
|
||||
|
||||
export function requireHubUrlForLogin(): boolean {
|
||||
return parseBooleanFlag(import.meta.env.VITE_REQUIRE_HUB_URL)
|
||||
}
|
||||
Reference in New Issue
Block a user