diff --git a/ui/app/login/page.tsx b/ui/app/login/page.tsx index 93b911b8..5bc6f095 100644 --- a/ui/app/login/page.tsx +++ b/ui/app/login/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; +import type { ChangeEvent, FormEvent } from 'react'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -15,10 +16,12 @@ import { adminLogin } from '@/lib/api/services/auth'; import { ConfigurationService } from '@/lib/api/services/configuration'; import { toast } from 'sonner'; -export default function AdminLoginPage() { +export default function AdminLoginPage(): JSX.Element { const router = useRouter(); - const [password, setPassword] = useState(''); - const [isLoading, setIsLoading] = useState(false); + const allowCustomBaseUrl = !ConfigurationService.isEnvBaseUrlConfigured(); + const [password, setPassword] = useState(''); + const [baseUrl, setBaseUrl] = useState(''); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (ConfigurationService.isTokenValid()) { @@ -26,8 +29,35 @@ export default function AdminLoginPage() { } }, [router]); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + useEffect(() => { + if (!allowCustomBaseUrl) { + return; + } + + const storedBaseUrl = ConfigurationService.getManualBaseUrl(); + if (storedBaseUrl) { + setBaseUrl(storedBaseUrl); + return; + } + + if (typeof window !== 'undefined') { + setBaseUrl(window.location.origin ?? ''); + } + }, [allowCustomBaseUrl]); + + const handleSubmit = async ( + event: FormEvent + ): Promise => { + event.preventDefault(); + + if (allowCustomBaseUrl) { + const normalizedBaseUrl = baseUrl.trim(); + if (!normalizedBaseUrl) { + toast.error('Please enter the API URL'); + return; + } + ConfigurationService.setManualBaseUrl(normalizedBaseUrl); + } if (!password) { toast.error('Please enter your password'); @@ -60,12 +90,28 @@ export default function AdminLoginPage() {
+ {allowCustomBaseUrl && ( +
+ ) => + setBaseUrl(event.target.value) + } + disabled={isLoading} + required + /> +
+ )}
setPassword(e.target.value)} + onChange={(event: ChangeEvent) => + setPassword(event.target.value) + } disabled={isLoading} autoFocus required diff --git a/ui/lib/api/services/configuration.ts b/ui/lib/api/services/configuration.ts index 413a780b..b3bfd19a 100644 --- a/ui/lib/api/services/configuration.ts +++ b/ui/lib/api/services/configuration.ts @@ -15,6 +15,8 @@ export type ServerConfig = z.infer; * Configuration service to manage external server settings */ export class ConfigurationService { + private static readonly manualBaseUrlStorageKey = 'admin_manual_base_url'; + /** * Get the current server configuration from localStorage */ @@ -59,11 +61,22 @@ export class ConfigurationService { * Get the local base URL (never use external configuration) */ static getLocalBaseUrl(): string { - const apiUrl = process.env.NEXT_PUBLIC_API_URL; + const envBaseUrl = process.env.NEXT_PUBLIC_API_URL; - console.log(apiUrl); - if (apiUrl) { - return apiUrl; + console.log(envBaseUrl); + if (envBaseUrl && envBaseUrl.trim()) { + return envBaseUrl; + } + + if (typeof window !== 'undefined') { + const manualBaseUrl = this.getManualBaseUrl(); + if (manualBaseUrl) { + return manualBaseUrl; + } + + if (window.location.origin) { + return window.location.origin; + } } if (process.env.NODE_ENV === 'production') { @@ -73,6 +86,32 @@ export class ConfigurationService { return 'http://127.0.0.1:8000'; } + static isEnvBaseUrlConfigured(): boolean { + const envBaseUrl = process.env.NEXT_PUBLIC_API_URL ?? ''; + return envBaseUrl.trim().length > 0; + } + + static getManualBaseUrl(): string { + if (typeof window === 'undefined') { + return ''; + } + + return localStorage.getItem(this.manualBaseUrlStorageKey) ?? ''; + } + + static setManualBaseUrl(baseUrl: string): void { + if (typeof window === 'undefined') { + return; + } + + const normalizedBaseUrl = baseUrl.trim(); + if (normalizedBaseUrl) { + localStorage.setItem(this.manualBaseUrlStorageKey, normalizedBaseUrl); + } else { + localStorage.removeItem(this.manualBaseUrlStorageKey); + } + } + static isProduction(): boolean { return process.env.NODE_ENV === 'production'; }