@@ -24,7 +24,6 @@ async function fetchSettingGroupsApi(): Promise<GlobalSettingGroup[]> {
2424 if (! apiUrl ) {
2525 throw new Error (' VITE_DEPLOYSTACK_BACKEND_URL is not configured.' )
2626 }
27- console .log (` Fetching all global setting groups with settings from ${apiUrl }/api/settings/groups... ` )
2827 const response = await fetch (` ${apiUrl }/api/settings/groups ` , { credentials: ' include' })
2928
3029 if (! response .ok ) {
@@ -54,7 +53,6 @@ onMounted(async () => {
5453 settingGroups .value = fetchedGroups // Direct assignment is fine now
5554 error .value = null
5655 } catch (err ) {
57- console .error (' Failed to fetch setting groups:' , err )
5856 error .value = err instanceof Error ? err .message : ' An unknown error occurred'
5957 settingGroups .value = [] // Clear or set to empty on error
6058 } finally {
@@ -80,100 +78,58 @@ import { type Setting } from '@/components/settings/GlobalSettingsSidebarNav.vue
8078import { Button } from ' @/components/ui/button'
8179import { Input } from ' @/components/ui/input'
8280import { Switch } from ' @/components/ui/switch'
83- import {
84- FormControl ,
85- FormDescription ,
86- FormField ,
87- FormItem ,
88- FormLabel ,
89- FormMessage ,
90- } from ' @/components/ui/form'
91- // VeeValidate and Zod for form validation
92- import { toTypedSchema } from ' @vee-validate/zod'
93- import { useForm } from ' vee-validate'
94- import * as z from ' zod'
95-
96- // Create dynamic Zod schema based on settings
97- function createSettingsSchema(settings : Setting []) {
98- const schemaObject: Record <string , z .ZodTypeAny > = {}
81+ import { Label } from ' @/components/ui/label'
9982
100- settings .forEach (setting => {
101- switch (setting .type ) {
102- case ' string' :
103- schemaObject [setting .key ] = z .string ()
104- break
105- case ' number' :
106- schemaObject [setting .key ] = z .number ()
107- break
108- case ' boolean' :
109- schemaObject [setting .key ] = z .boolean ()
110- break
111- }
112- })
113-
114- return z .object (schemaObject )
115- }
83+ // Reactive form values
84+ const formValues = ref <Record <string , any >>({})
11685
11786// Create initial form values from settings
11887function createInitialValues(settings : Setting []) {
119- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12088 const values: Record <string , any > = {}
12189 settings .forEach (setting => {
90+ // Handle cases where setting.value might be undefined
91+ const settingValue = setting .value ?? ' '
92+
12293 switch (setting .type ) {
12394 case ' number' :
124- values [setting .key ] = Number (setting . value ) || 0
95+ values [setting .key ] = settingValue ? Number (settingValue ) : 0
12596 break
12697 case ' boolean' :
127- values [setting .key ] = setting . value === ' true' || setting . value === true
98+ values [setting .key ] = settingValue === ' true' || settingValue === true
12899 break
129100 case ' string' :
130101 default :
131- values [setting .key ] = setting . value || ' '
102+ values [setting .key ] = settingValue
132103 break
133104 }
134105 })
135106 return values
136107}
137108
138- // Form setup
139- const formSchema = computed (() => {
140- if (! selectedGroup .value ?.settings ) return z .object ({})
141- return createSettingsSchema (selectedGroup .value .settings )
142- })
143-
144- const initialValues = computed (() => {
145- if (! selectedGroup .value ?.settings ) return {}
146- return createInitialValues (selectedGroup .value .settings )
147- })
148-
149- const form = useForm ({
150- validationSchema: computed (() => toTypedSchema (formSchema .value )),
151- initialValues: initialValues
152- })
153-
154- // Watch for group changes and reset form
109+ // Watch for group changes and set form values
155110watch (() => selectedGroup .value , (newGroup ) => {
156111 if (newGroup ?.settings ) {
157112 const newInitialValues = createInitialValues (newGroup .settings )
158- form . resetForm ({ values: newInitialValues })
113+ formValues . value = newInitialValues
159114 }
160115}, { immediate: true , deep: true })
161116
162- const onSubmit = form .handleSubmit (async (values ) => {
163- if (! selectedGroup .value ) return
117+ // Form submission
118+ async function handleSubmit(event : Event ) {
119+ event .preventDefault ()
164120
165- console . log ( ' Form values: ' , values )
121+ if ( ! selectedGroup . value ) return
166122
167123 // Convert form values to API format
168- const settingsToUpdate = Object .entries (values ).map (([key , value ]) => {
124+ const settingsToUpdate = Object .entries (formValues . value ).map (([key , value ]) => {
169125 const setting = selectedGroup .value ?.settings ?.find (s => s .key === key )
170126 return {
171127 key ,
172- value: String ( value ) , // API expects string values
128+ value: value , // API expects typed values (string, number, boolean)
173129 type: setting ?.type ,
174130 group_id: selectedGroup .value ?.id ,
175131 description: setting ?.description ,
176- encrypted: setting ?.is_encrypted
132+ encrypted: setting ?.is_encrypted || false
177133 }
178134 })
179135
@@ -182,26 +138,27 @@ const onSubmit = form.handleSubmit(async (values) => {
182138 throw new Error (' VITE_DEPLOYSTACK_BACKEND_URL is not configured for saving settings.' )
183139 }
184140
141+ const requestBody = { settings: settingsToUpdate }
142+
185143 const response = await fetch (` ${apiUrl }/api/settings/bulk ` , {
186144 method: ' POST' ,
187145 headers: {
188146 ' Content-Type' : ' application/json' ,
189147 },
190148 credentials: ' include' ,
191- body: JSON .stringify ({ settings: settingsToUpdate } ),
149+ body: JSON .stringify (requestBody ),
192150 })
193151
194152 if (! response .ok ) {
195153 const errorData = await response .json ().catch (() => ({}))
196- throw new Error (errorData .message || ` Failed to save settings: ${response .statusText } (status: ${response .status }) ` )
154+ throw new Error (errorData .error || errorData . message || ` Failed to save settings: ${response .statusText } (status: ${response .status }) ` )
197155 }
198156
199157 const result = await response .json ()
158+
200159 if (! result .success ) {
201160 throw new Error (result .message || ' Failed to save settings due to an API error.' )
202161 }
203-
204- console .log (' Settings saved successfully via API.' )
205162 successAlertMessage .value = t (' globalSettings.alerts.saveSuccess' )
206163 showSuccessAlert .value = true
207164
@@ -210,8 +167,7 @@ const onSubmit = form.handleSubmit(async (values) => {
210167 if (groupIndex !== - 1 ) {
211168 const updatedSettings = selectedGroup .value .settings ?.map (setting => ({
212169 ... setting ,
213- // eslint-disable-next-line @typescript-eslint/no-explicit-any
214- value: String ((values as Record <string , any >)[setting .key ])
170+ value: String (formValues .value [setting .key ])
215171 }))
216172
217173 const updatedGroup = {
@@ -225,9 +181,9 @@ const onSubmit = form.handleSubmit(async (values) => {
225181 }
226182
227183 } catch (saveError ) {
228- console . error ( ' Failed to save settings via API: ' , saveError )
184+ // Handle save error silently or show user-friendly error message
229185 }
230- })
186+ }
231187
232188 </script >
233189
@@ -251,79 +207,74 @@ const onSubmit = form.handleSubmit(async (values) => {
251207 </Button >
252208 </Alert >
253209
254-
255- <div class =" flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0" >
256- <aside class =" -mx-4 lg:w-1/5" >
257- <GlobalSettingsSidebarNav :groups =" settingGroups" />
258- </aside >
259- <div class =" flex-1 lg:max-w-2xl" >
260- <div v-if =" isLoading" class =" text-muted-foreground" >Loading settings...</div >
261- <div v-else-if =" error" class =" text-red-500" >Error loading settings: {{ error }}</div >
262- <div v-else-if =" selectedGroup" class =" space-y-6" >
263- <div >
264- <h3 class =" text-lg font-medium" >
265- {{ selectedGroup.name }}
266- </h3 >
267- <p v-if =" selectedGroup.description" class =" text-sm text-muted-foreground" >
268- {{ selectedGroup.description }}
269- </p >
210+ <div class =" flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0" >
211+ <aside class =" -mx-4 lg:w-1/5" >
212+ <GlobalSettingsSidebarNav :groups =" settingGroups" />
213+ </aside >
214+ <div class =" flex-1 lg:max-w-2xl" >
215+ <div v-if =" isLoading" class =" text-muted-foreground" >Loading settings...</div >
216+ <div v-else-if =" error" class =" text-red-500" >Error loading settings: {{ error }}</div >
217+ <div v-else-if =" selectedGroup" class =" space-y-6" >
218+ <div >
219+ <h3 class =" text-lg font-medium" >
220+ {{ selectedGroup.name }}
221+ </h3 >
222+ <p v-if =" selectedGroup.description" class =" text-sm text-muted-foreground" >
223+ {{ selectedGroup.description }}
224+ </p >
225+ </div >
226+ <form v-if =" selectedGroup.settings && selectedGroup.settings.length > 0" class =" space-y-6" @submit =" handleSubmit" >
227+ <div v-for =" setting in selectedGroup.settings" :key =" setting.key" class =" space-y-2" >
228+ <Label :for =" `setting-${setting.key}`" >{{ setting.description || setting.key }}</Label >
229+
230+
231+ <!-- String Input (text or password) -->
232+ <Input
233+ v-if =" setting.type === 'string'"
234+ :id =" `setting-${setting.key}`"
235+ :type =" setting.is_encrypted ? 'password' : 'text'"
236+ v-model =" formValues[setting.key]"
237+ class =" w-full"
238+ />
239+
240+ <!-- Number Input -->
241+ <Input
242+ v-else-if =" setting.type === 'number'"
243+ :id =" `setting-${setting.key}`"
244+ type =" number"
245+ v-model.number =" formValues[setting.key]"
246+ class =" w-full"
247+ />
248+
249+ <!-- Boolean Toggle Switch -->
250+ <Switch
251+ v-else-if =" setting.type === 'boolean'"
252+ :id =" `setting-${setting.key}`"
253+ v-model:checked =" formValues[setting.key]"
254+ />
255+
256+ <p v-if =" setting.is_encrypted" class =" text-xs text-muted-foreground" >This value is encrypted.</p >
257+ </div >
258+
259+ <Button type =" submit" >
260+ Save Changes
261+ </Button >
262+ </form >
263+ <div v-else-if =" selectedGroup && (!selectedGroup.settings || selectedGroup.settings.length === 0)" >
264+ <p class =" text-sm text-muted-foreground" >No settings in this group.</p >
265+ </div >
266+ <div v-else >
267+ <p class =" text-sm text-muted-foreground" >Group not found or settings unavailable.</p >
268+ </div >
270269 </div >
271- <form v-if =" selectedGroup.settings && selectedGroup.settings.length > 0" class =" space-y-6" @submit =" onSubmit" >
272- <FormField
273- v-for =" setting in selectedGroup.settings"
274- :key =" setting.key"
275- v-slot =" { componentField }"
276- :name =" setting.key"
277- >
278- <FormItem >
279- <FormLabel >{{ setting.description || setting.key }}</FormLabel >
280- <FormControl >
281- <!-- String Input (text or password) -->
282- <Input
283- v-if =" setting.type === 'string'"
284- :type =" setting.is_encrypted ? 'password' : 'text'"
285- v-bind =" componentField"
286- />
287-
288- <!-- Number Input -->
289- <Input
290- v-else-if =" setting.type === 'number'"
291- type =" number"
292- v-bind =" componentField"
293- />
294-
295- <!-- Boolean Toggle Switch -->
296- <Switch
297- v-else-if =" setting.type === 'boolean'"
298- v-bind =" componentField"
299- />
300- </FormControl >
301- <FormDescription v-if =" setting.is_encrypted" >
302- This value is encrypted.
303- </FormDescription >
304- <FormMessage />
305- </FormItem >
306- </FormField >
307-
308- <Button type =" submit" >
309- Save Changes
310- </Button >
311- </form >
312- <div v-else-if =" selectedGroup && (!selectedGroup.settings || selectedGroup.settings.length === 0)" >
313- <p class =" text-sm text-muted-foreground" >No settings in this group.</p >
270+ <div v-else-if =" !currentGroupId && settingGroups.length > 0" >
271+ <p class =" text-muted-foreground" >Select a category from the sidebar to view its settings.</p >
314272 </div >
315- <div v-else >
316- <p class =" text-sm text- muted-foreground" >Group not found or settings unavailable .</p >
273+ <div v-else-if = " !currentGroupId && settingGroups.length === 0 && !isLoading " >
274+ <p class =" text-muted-foreground" >No setting groups found .</p >
317275 </div >
318276 </div >
319- <div v-else-if =" !currentGroupId && settingGroups.length > 0" >
320- <p class =" text-muted-foreground" >Select a category from the sidebar to view its settings.</p >
321- </div >
322- <div v-else-if =" !currentGroupId && settingGroups.length === 0 && !isLoading" >
323- <p class =" text-muted-foreground" >No setting groups found.</p >
324- </div >
325277 </div >
326278 </div >
327- </div >
328279 </DashboardLayout >
329280</template >
0 commit comments