import { useEffect, useState } from 'react' import StyledModal from './StyledModal' import StyledButton from './StyledButton' import Input from './inputs/Input' import { getServiceLink, normalizeCustomUrl } from '~/lib/navigation' import { ServiceSlim } from '../../types/services' import api from '~/lib/api' interface AppUrlModalProps { open: boolean /** The app whose launch URL is being configured. Null while closed. */ service: ServiceSlim | null onClose: () => void /** Called after a successful save/clear so the parent can refresh the link. */ onSaved: () => void showError: (msg: string) => void } /** * Set or clear an app's custom launch URL — used when the instance sits behind a reverse proxy or * local DNS (e.g. https://jellyfin.myhomelab.net). Leaving the field empty clears the override and * reverts to the default host + port link. Works for both curated and custom apps. */ export default function AppUrlModal({ open, service, onClose, onSaved, showError }: AppUrlModalProps) { const [value, setValue] = useState('') const [submitting, setSubmitting] = useState(false) // Prefill from the app's stored override each time the modal opens for a service. useEffect(() => { if (open && service) setValue(service.custom_url ?? '') }, [open, service]) const trimmed = value.trim() const normalized = normalizeCustomUrl(value) const isInvalid = trimmed.length > 0 && !normalized // What clicking "Open" will actually resolve to once saved. const previewLink = service ? getServiceLink(service.ui_location || '', value) : '' const usingDefault = !normalized async function handleSave() { if (!service || isInvalid) return setSubmitting(true) // Empty clears the override; the backend re-normalizes/validates the value too. const result = await api.setServiceCustomUrl(service.service_name, trimmed ? trimmed : null) setSubmitting(false) if (!result?.success) { showError('Failed to save custom URL.') return } onSaved() } const appName = service?.friendly_name || service?.service_name || 'this app' return (

Set where {appName} opens from — useful if you reach it through a reverse proxy or local DNS. Leave this empty to use the default address ({service?.ui_location ? `host + port ${service.ui_location}` : 'host + port'}).

setValue(e.target.value)} error={isInvalid} className="flex-1 min-w-0" /> {value.length > 0 && ( setValue('')} className="mb-1.5"> Clear )}
{isInvalid ? (

Enter a valid http(s) address (e.g. https://jellyfin.myhomelab.net). A bare host like "jellyfin.lan" becomes http://jellyfin.lan.

) : ( <>

No scheme? We'll default to http://.

Opens as:{' '} {previewLink} {usingDefault ? ' (default)' : ''}

)}
) }