1+ import { useEffect , useState } from "react" ;
2+ import { Save , TextIndentLeft , X } from "react-bootstrap-icons" ;
3+ import { toast } from "sonner" ;
4+ import { t } from "../../utils/helper" ;
5+ import { SettingItem } from "../settings/common" ;
6+
7+ const UA_OPTIONS = [
8+ {
9+ key : "default" ,
10+ label : t ( "ua_option_default" , "default" ) ,
11+ value : "default"
12+ } ,
13+ {
14+ key : "sfm_1_12" ,
15+ label : t ( "ua_option_sfm_1_12" , "sfm 1.12" ) ,
16+ value : "SFM/1.12.9 (Build 1; sing-box 1.12.12; language zh_CN)"
17+ } ,
18+ {
19+ key : "sfa_1_12" ,
20+ label : t ( "ua_option_sfa_1_12" , "sfa 1.12" ) ,
21+ value : "SFA/1.12.9 (Build 1; sing-box 1.12.12; language zh_CN)"
22+ } ,
23+ {
24+ key : "sfi_1_12" ,
25+ label : t ( "ua_option_sfi_1_12" , "sfi 1.12" ) ,
26+ value : "SFI/1.12.9 (Build 1; sing-box 1.12.12; language zh_CN)"
27+ } ,
28+ {
29+ key : "custom" ,
30+ label : t ( "ua_option_custom" , "custom" ) ,
31+ value : ""
32+ }
33+ ] ;
34+
35+ import { getUserAgent , setUserAgent } from "../../single/store" ;
36+
37+ export default function UASettingsItem ( ) {
38+ const [ isOpen , setIsOpen ] = useState ( false ) ;
39+ const [ selectedUA , setSelectedUA ] = useState ( "default" ) ;
40+ const [ customUA , setCustomUA ] = useState ( "" ) ;
41+ const [ isLoading , setIsLoading ] = useState ( false ) ;
42+
43+ useEffect ( ( ) => {
44+ if ( isOpen ) {
45+ loadUA ( ) ;
46+ }
47+ } , [ isOpen ] ) ;
48+
49+ const loadUA = async ( ) => {
50+ const ua = await getUserAgent ( ) ;
51+ const option = UA_OPTIONS . find ( opt => opt . value === ua ) ;
52+ if ( option ) {
53+ setSelectedUA ( option . key ) ;
54+ } else {
55+ setSelectedUA ( "custom" ) ;
56+ setCustomUA ( ua ) ;
57+ }
58+ } ;
59+
60+ const handleOpen = ( ) => {
61+ setIsOpen ( true ) ;
62+ } ;
63+
64+ const handleClose = ( ) => {
65+ setIsOpen ( false ) ;
66+ } ;
67+
68+ const handleSave = async ( ) => {
69+ setIsLoading ( true ) ;
70+ try {
71+ const uaValue = selectedUA === "custom"
72+ ? customUA
73+ : UA_OPTIONS . find ( opt => opt . key === selectedUA ) ?. value || "default" ;
74+
75+ if ( selectedUA === "custom" && ! customUA . trim ( ) ) {
76+ toast . error ( t ( "ua_cannot_empty" , "User Agent cannot be empty" ) ) ;
77+ return ;
78+ }
79+
80+ await setUserAgent ( uaValue ) ;
81+ toast . success ( t ( "ua_saved" , "User Agent settings saved successfully" ) ) ;
82+ handleClose ( ) ;
83+ } catch ( error ) {
84+ toast . error ( t ( "ua_save_failed" , "Failed to save User Agent settings" ) ) ;
85+ } finally {
86+ setIsLoading ( false ) ;
87+ }
88+ } ;
89+
90+ return (
91+ < >
92+ < SettingItem
93+ icon = { < TextIndentLeft className = "w-5 h-5 text-gray-500" /> }
94+ title = { t ( "user_agent_settings" , "User Agent Settings" ) }
95+ subTitle = { t ( "open_user_agent" , "Open user agent settings" ) }
96+ disabled = { false }
97+ onPress = { handleOpen }
98+ />
99+
100+ { isOpen && (
101+ < div className = "fixed inset-0 z-50 flex items-center justify-center px-4" >
102+ < div
103+ className = "absolute inset-0 bg-gray-400/60"
104+ onClick = { handleClose }
105+ />
106+
107+ < div className = "relative bg-white rounded-lg p-3 w-80 max-w-full" >
108+ < div className = "flex items-center justify-between mb-4" >
109+ < div className = "flex items-center gap-2" >
110+ < TextIndentLeft size = { 14 } className = "text-gray-500" />
111+ < h3 className = "text-xs font-medium text-gray-700" >
112+ { t ( "user_agent_settings" , "User Agent Settings" ) }
113+ </ h3 >
114+ </ div >
115+ < button
116+ onClick = { handleClose }
117+ className = "hover:bg-gray-100 rounded p-1 transition-colors"
118+ >
119+ < X size = { 14 } className = "text-gray-500" />
120+ </ button >
121+ </ div >
122+
123+ < div className = "flex flex-col gap-6" >
124+ < div >
125+ < select
126+ className = "select select-sm select-ghost border-[0.8px] border-gray-200 "
127+
128+ value = { selectedUA }
129+ onChange = { ( e ) => setSelectedUA ( e . target . value ) }
130+ >
131+ { UA_OPTIONS . map ( option => (
132+ < option key = { option . key } value = { option . key } >
133+ { option . label }
134+ </ option >
135+ ) ) }
136+ </ select >
137+
138+ { selectedUA === "custom" && (
139+ < input
140+ type = "text"
141+ className = "w-full px-3 py-2 mt-6 text-xs rounded border border-gray-300 focus:border-gray-400 focus:ring-1 focus:ring-gray-400 outline-none transition-colors "
142+ placeholder = { t ( "custom_ua_placeholder" , "Enter custom User Agent" ) }
143+ value = { customUA }
144+ onChange = { ( e ) => setCustomUA ( e . target . value ) }
145+ />
146+ ) }
147+
148+ < p className = "text-xs text-gray-500 mt-2" >
149+ { t ( "ua_hint" , "Select or enter a custom User Agent for requests" ) }
150+ </ p >
151+ </ div >
152+ </ div >
153+
154+ < div className = "flex justify-end gap-2 mt-6" >
155+ < button
156+ className = "px-3 py-1 text-xs rounded bg-transparent hover:bg-gray-100 text-gray-600 transition-colors"
157+ onClick = { handleClose }
158+ >
159+ { t ( "cancel" , "Cancel" ) }
160+ </ button >
161+ < button
162+ className = "flex items-center gap-1.5 px-3 py-1 text-xs bg-gray-600 text-white hover:bg-gray-700 rounded transition-colors disabled:opacity-50"
163+ onClick = { handleSave }
164+ disabled = { isLoading }
165+ >
166+ < Save size = { 14 } />
167+ { isLoading ? t ( "saving" , "Saving..." ) : t ( "save" , "Save" ) }
168+ </ button >
169+ </ div >
170+ </ div >
171+ </ div >
172+ ) }
173+ </ >
174+ ) ;
175+ }
0 commit comments