11import { useState } from 'react' ;
2+ import { useMutation } from '@tanstack/react-query' ;
23import { Button } from '@/components/ui/button' ;
34import { Input } from '@/components/ui/input' ;
45import {
@@ -20,58 +21,70 @@ import {
2021
2122export const ServerSettings = ( ) => {
2223 const [ baseUrl , setBaseUrlState ] = useState < string > ( ( ) => getBaseUrl ( ) ) ;
23- const [ baseUrlError , setBaseUrlError ] = useState < string | null > ( null ) ;
24- const [ isTestingConnection , setIsTestingConnection ] = useState ( false ) ;
2524 const [ isOpen , setIsOpen ] = useState ( false ) ;
2625
26+ const connectionMutation = useMutation ( {
27+ mutationFn : testConnection ,
28+ onSuccess : ( result , url ) => {
29+ if ( result . reachable ) {
30+ const normalizedUrl = normalizeBaseUrl ( url ) ;
31+ setBaseUrl ( normalizedUrl ) ;
32+ setBaseUrlState ( normalizedUrl ) ;
33+ }
34+ } ,
35+ } ) ;
36+
37+ const getErrorMessage = ( ) : string | null => {
38+ if ( connectionMutation . error ) {
39+ return connectionMutation . error . message || 'Connection failed' ;
40+ }
41+ if ( connectionMutation . data && ! connectionMutation . data . reachable ) {
42+ return connectionMutation . data . error || 'Server unreachable' ;
43+ }
44+ return null ;
45+ } ;
46+
47+ const errorMessage = getErrorMessage ( ) ;
48+
2749 const handleBaseUrlChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
2850 setBaseUrlState ( e . target . value ) ;
29- setBaseUrlError ( null ) ;
51+ connectionMutation . reset ( ) ;
3052 } ;
3153
32- const handleBaseUrlBlur = async ( ) => {
54+ const handleBaseUrlBlur = ( ) => {
3355 const currentStoredUrl = getBaseUrl ( ) ;
3456 const trimmedUrl = baseUrl . trim ( ) ;
3557
3658 if ( trimmedUrl === currentStoredUrl ) {
3759 return ;
3860 }
3961
62+ const lastTestedUrl = connectionMutation . variables ;
63+ if ( lastTestedUrl === trimmedUrl && errorMessage ) {
64+ return ;
65+ }
66+
4067 if ( trimmedUrl === '' || trimmedUrl === getDefaultBaseUrl ( ) ) {
4168 setBaseUrl ( trimmedUrl ) ;
4269 setBaseUrlState ( trimmedUrl || getDefaultBaseUrl ( ) ) ;
43- setBaseUrlError ( null ) ;
70+ connectionMutation . reset ( ) ;
4471 return ;
4572 }
4673
4774 const validation = validateBaseUrl ( trimmedUrl ) ;
4875 if ( ! validation . valid ) {
49- setBaseUrlError ( validation . error || 'Invalid URL' ) ;
76+ connectionMutation . mutate ( trimmedUrl ) ;
5077 return ;
5178 }
5279
53- setIsTestingConnection ( true ) ;
54- setBaseUrlError ( null ) ;
55-
56- const result = await testConnection ( trimmedUrl ) ;
57-
58- setIsTestingConnection ( false ) ;
59-
60- if ( result . reachable ) {
61- const normalizedUrl = normalizeBaseUrl ( trimmedUrl ) ;
62- setBaseUrl ( normalizedUrl ) ;
63- setBaseUrlState ( normalizedUrl ) ;
64- setBaseUrlError ( null ) ;
65- } else {
66- setBaseUrlError ( result . error || 'Server unreachable' ) ;
67- }
80+ connectionMutation . mutate ( trimmedUrl ) ;
6881 } ;
6982
7083 const handleResetBaseUrl = ( ) => {
7184 const defaultUrl = getDefaultBaseUrl ( ) ;
7285 setBaseUrlState ( defaultUrl ) ;
7386 setBaseUrl ( defaultUrl ) ;
74- setBaseUrlError ( null ) ;
87+ connectionMutation . reset ( ) ;
7588 } ;
7689
7790 return (
@@ -82,7 +95,7 @@ export const ServerSettings = () => {
8295 onOpenChange = { setIsOpen }
8396 >
8497 < div className = "space-y-2" >
85- < Field data-invalid = { ! ! baseUrlError } >
98+ < Field data-invalid = { ! ! errorMessage } >
8699 < FieldLabel htmlFor = "base-url-input" > Base URL</ FieldLabel >
87100 < FieldDescription >
88101 The Valhalla server URL for routing and isochrone requests
@@ -96,29 +109,29 @@ export const ServerSettings = () => {
96109 value = { baseUrl }
97110 onChange = { handleBaseUrlChange }
98111 onBlur = { handleBaseUrlBlur }
99- disabled = { isTestingConnection }
100- aria-invalid = { ! ! baseUrlError }
112+ disabled = { connectionMutation . isPending }
113+ aria-invalid = { ! ! errorMessage }
101114 className = {
102- baseUrlError
115+ errorMessage
103116 ? 'border-destructive focus-visible:ring-destructive/50'
104117 : ''
105118 }
106119 />
107- { isTestingConnection && (
120+ { connectionMutation . isPending && (
108121 < div className = "absolute right-3 top-1/2 -translate-y-1/2" >
109122 < Loader2 className = "size-4 animate-spin text-muted-foreground" />
110123 </ div >
111124 ) }
112125 </ div >
113126 </ div >
114- < FieldError > { baseUrlError } </ FieldError >
127+ < FieldError > { errorMessage } </ FieldError >
115128 </ Field >
116129 < Button
117130 variant = "outline"
118131 size = "sm"
119132 onClick = { handleResetBaseUrl }
120133 disabled = {
121- isTestingConnection ||
134+ connectionMutation . isPending ||
122135 normalizeBaseUrl ( baseUrl ) === getDefaultBaseUrl ( )
123136 }
124137 className = "w-full"
0 commit comments