@@ -6,17 +6,20 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
66import { Separator } from '@/components/ui/separator'
77import { Skeleton } from '@/components/ui/skeleton'
88import { DEFAULT_SHADOWSOCKS_METHOD } from '@/constants/Proxies'
9- import { ShadowsocksMethods , XTLSFlows , useReconnectAllNode } from '@/service/api'
9+ import { ShadowsocksMethods , XTLSFlows , useGetGeneralSettings , useReconnectAllNode } from '@/service/api'
1010import { queryClient } from '@/utils/query-client'
1111import { zodResolver } from '@hookform/resolvers/zod'
12- import { Loader2 , RefreshCcw , XIcon } from 'lucide-react'
12+ import { Loader2 , RefreshCcw } from 'lucide-react'
1313import { useEffect , useState } from 'react'
1414import { useForm } from 'react-hook-form'
1515import { useTranslation } from 'react-i18next'
1616import { toast } from 'sonner'
1717import { z } from 'zod'
1818import { useSettingsContext } from './_dashboard.settings'
1919
20+ /** Radix Select forbids `SelectItem value=""`; map API empty flow to this UI value. */
21+ const DEFAULT_FLOW_SELECT_NONE = '__pg_default_flow_none__'
22+
2023// general settings validation schema
2124const generalSettingsSchema = z . object ( {
2225 default_flow : z . string ( ) . default ( '' ) ,
@@ -27,7 +30,12 @@ type GeneralSettingsFormInput = z.input<typeof generalSettingsSchema>
2730
2831export default function General ( ) {
2932 const { t } = useTranslation ( )
30- const { settings, isLoading, error, updateSettings, isSaving } = useSettingsContext ( )
33+ const { isLoading, error, updateSettings, isSaving } = useSettingsContext ( )
34+ const {
35+ data : generalSettings ,
36+ isLoading : isGeneralLoading ,
37+ error : generalError ,
38+ } = useGetGeneralSettings ( )
3139 const [ isReconnectAllDialogOpen , setIsReconnectAllDialogOpen ] = useState ( false )
3240 const reconnectAllNodeMutation = useReconnectAllNode ( )
3341
@@ -40,11 +48,12 @@ export default function General() {
4048 } )
4149
4250 useEffect ( ( ) => {
51+ if ( ! generalSettings ) return
4352 form . reset ( {
44- default_flow : settings ?. general ? .default_flow || '' ,
45- default_method : settings ?. general ? .default_method || DEFAULT_SHADOWSOCKS_METHOD ,
53+ default_flow : generalSettings . default_flow || '' ,
54+ default_method : generalSettings . default_method || DEFAULT_SHADOWSOCKS_METHOD ,
4655 } )
47- } , [ settings ] )
56+ } , [ generalSettings , form ] )
4857
4958 const onSubmit = async ( data : GeneralSettingsFormInput ) => {
5059 try {
@@ -64,13 +73,12 @@ export default function General() {
6473 }
6574
6675 const handleCancel = ( ) => {
67- if ( settings ?. general ) {
68- form . reset ( {
69- default_flow : '' ,
70- default_method : DEFAULT_SHADOWSOCKS_METHOD ,
71- } )
72- toast . success ( t ( 'settings.general.cancelSuccess' ) )
73- }
76+ if ( ! generalSettings ) return
77+ form . reset ( {
78+ default_flow : generalSettings . default_flow ?? '' ,
79+ default_method : generalSettings . default_method || DEFAULT_SHADOWSOCKS_METHOD ,
80+ } )
81+ toast . success ( t ( 'settings.general.cancelSuccess' ) )
7482 }
7583
7684 const handleReconnectAll = async ( ) => {
@@ -103,8 +111,10 @@ export default function General() {
103111 }
104112 }
105113
114+ const loadError = error || generalError
115+
106116 // TODO: skeleton needs to be improved
107- if ( isLoading ) {
117+ if ( isLoading || isGeneralLoading ) {
108118 return (
109119 < div className = "w-full p-4 sm:py-6 lg:py-8" >
110120 < div className = "space-y-6 sm:space-y-8 lg:space-y-10" >
@@ -140,7 +150,7 @@ export default function General() {
140150 )
141151 }
142152
143- if ( error ) {
153+ if ( loadError ) {
144154 return (
145155 < div className = "flex min-h-[400px] items-center justify-center p-4 sm:py-6 lg:py-8" >
146156 < div className = "space-y-3 text-center" >
@@ -151,14 +161,6 @@ export default function General() {
151161 )
152162 }
153163
154- const clearField = ( field : keyof GeneralSettingsFormInput ) => {
155- return ( e : React . MouseEvent < HTMLButtonElement > ) => {
156- e . preventDefault ( )
157- e . stopPropagation ( )
158- form . setValue ( field , '' )
159- }
160- }
161-
162164 return (
163165 < div className = "flex min-h-[calc(100vh-200px)] w-full flex-col" >
164166 < Form { ...form } >
@@ -170,28 +172,27 @@ export default function General() {
170172 control = { form . control }
171173 name = "default_flow"
172174 render = { ( { field } ) => (
173- < FormItem className = "relative space-y-2" >
175+ < FormItem className = "space-y-2" >
174176 < FormLabel className = "flex items-center gap-2 text-xs font-medium sm:text-sm" > { t ( 'settings.general.defaultFlow.title' ) } </ FormLabel >
175177 < FormControl >
176- < Select value = { field . value } onValueChange = { field . onChange } >
178+ < Select
179+ value = { field . value ? field . value : DEFAULT_FLOW_SELECT_NONE }
180+ onValueChange = { v => field . onChange ( v === DEFAULT_FLOW_SELECT_NONE ? '' : v ) }
181+ >
177182 < SelectTrigger className = "text-xs sm:text-sm" >
178183 < SelectValue />
179184 </ SelectTrigger >
180- { field . value && (
181- < Button size = "icon" variant = "ghost" className = "absolute right-8 top-6" onClick = { clearField ( 'default_flow' ) } >
182- < XIcon />
183- </ Button >
184- ) }
185185 < SelectContent >
186+ < SelectItem value = { DEFAULT_FLOW_SELECT_NONE } className = "text-xs sm:text-sm" >
187+ { t ( 'settings.general.defaultFlow.none' ) }
188+ </ SelectItem >
186189 { Object . values ( XTLSFlows )
187- . filter ( Boolean )
188- . map ( flow => {
189- return (
190- < SelectItem value = { flow } key = { flow } className = "text-xs sm:text-sm" >
191- { flow }
192- </ SelectItem >
193- )
194- } ) }
190+ . filter ( ( flow ) : flow is Exclude < typeof flow , '' > => flow !== '' )
191+ . map ( flow => (
192+ < SelectItem value = { flow } key = { flow } className = "text-xs sm:text-sm" >
193+ { flow }
194+ </ SelectItem >
195+ ) ) }
195196 </ SelectContent >
196197 </ Select >
197198 </ FormControl >
0 commit comments