@@ -85,79 +85,114 @@ const selectedGroup = computed(() => {
8585import { type Setting } from ' @/components/settings/GlobalSettingsSidebarNav.vue' // Import Setting interface
8686import { Button } from ' @/components/ui/button'
8787import { Input } from ' @/components/ui/input'
88- import { Label } from ' @/components/ui/label' // Using Label directly for now, can switch to Form components later
89- // import { Switch } from '@/components/ui/switch' // For boolean settings later
90- // VeeValidate and Zod for later, more complex validation
91- // import { toTypedSchema } from '@vee-validate/zod'
92- // import { useForm } from 'vee-validate'
93- // import * as z from 'zod'
94-
95- const editableSettings = ref <Setting []>([])
96-
97- watch (() => selectedGroup .value , (newGroup ) => { // Removed explicit type for newGroup
98- if (newGroup && newGroup .settings ) {
99- editableSettings .value = JSON .parse (JSON .stringify (newGroup .settings ))
100- } else {
101- editableSettings .value = []
102- }
103- }, { immediate: true , deep: true })
88+ import { Switch } from ' @/components/ui/switch'
89+ import {
90+ FormControl ,
91+ FormDescription ,
92+ FormField ,
93+ FormItem ,
94+ FormLabel ,
95+ FormMessage ,
96+ } from ' @/components/ui/form'
97+ // VeeValidate and Zod for form validation
98+ import { toTypedSchema } from ' @vee-validate/zod'
99+ import { useForm } from ' vee-validate'
100+ import * as z from ' zod'
104101
105- function getSettingInputType(setting : Setting ): string {
106- if (setting .is_encrypted ) {
107- return ' password'
108- }
109- // Add more logic here if type information is available (e.g., for numbers, booleans using Switch)
110- // For now, default to text.
111- // Example: if (setting.value_type === 'boolean') return 'checkbox'; (would need Switch component)
112- // if (typeof setting.value === 'number') return 'number';
113- return ' text'
114- }
102+ // Create dynamic Zod schema based on settings
103+ function createSettingsSchema(settings : Setting []) {
104+ const schemaObject: Record <string , z .ZodTypeAny > = {}
115105
116- async function handleSaveChanges() {
117- if (! selectedGroup .value ) return // selectedGroup itself could be null
106+ settings .forEach (setting => {
107+ switch (setting .type ) {
108+ case ' string' :
109+ schemaObject [setting .key ] = z .string ()
110+ break
111+ case ' number' :
112+ schemaObject [setting .key ] = z .number ()
113+ break
114+ case ' boolean' :
115+ schemaObject [setting .key ] = z .boolean ()
116+ break
117+ }
118+ })
118119
119- const originalSettings = selectedGroup . value . settings
120- if ( ! originalSettings ) return // No original settings to compare against
120+ return z . object ( schemaObject )
121+ }
121122
122- const changedSettings = editableSettings .value .filter ((editedSetting , index ) => {
123- const originalSetting = originalSettings [index ]
124- return originalSetting && editedSetting .value !== originalSetting .value
123+ // Create initial form values from settings
124+ function createInitialValues(settings : Setting []) {
125+ const values: Record <string , any > = {}
126+ settings .forEach (setting => {
127+ switch (setting .type ) {
128+ case ' number' :
129+ values [setting .key ] = Number (setting .value ) || 0
130+ break
131+ case ' boolean' :
132+ values [setting .key ] = setting .value === ' true' || setting .value === true
133+ break
134+ case ' string' :
135+ default :
136+ values [setting .key ] = setting .value || ' '
137+ break
138+ }
125139 })
140+ return values
141+ }
142+
143+ // Form setup
144+ const formSchema = computed (() => {
145+ if (! selectedGroup .value ?.settings ) return z .object ({})
146+ return createSettingsSchema (selectedGroup .value .settings )
147+ })
148+
149+ const initialValues = computed (() => {
150+ if (! selectedGroup .value ?.settings ) return {}
151+ return createInitialValues (selectedGroup .value .settings )
152+ })
153+
154+ const form = useForm ({
155+ validationSchema: computed (() => toTypedSchema (formSchema .value )),
156+ initialValues: initialValues
157+ })
126158
127- if (changedSettings .length === 0 ) {
128- // console.log('No changes to save.') // Removed console log
129- successAlertMessage .value = t (' globalSettings.alerts.noChanges' ); // New i18n key
130- showSuccessAlert .value = true ;
131- // setTimeout for this specific alert could be added if desired, or rely on manual close
132- return
159+ // Watch for group changes and reset form
160+ watch (() => selectedGroup .value , (newGroup ) => {
161+ if (newGroup ?.settings ) {
162+ const newInitialValues = createInitialValues (newGroup .settings )
163+ form .resetForm ({ values: newInitialValues })
133164 }
165+ }, { immediate: true , deep: true })
166+
167+ const onSubmit = form .handleSubmit (async (values ) => {
168+ if (! selectedGroup .value ) return
134169
135- console .log (' Saving changed settings :' , changedSettings )
170+ console .log (' Form values :' , values )
136171
137- // Prepare settings for bulk update, ensuring all necessary fields are present
138- const settingsToUpdate = changedSettings .map (setting => {
139- // Find the original setting to get all its properties, as changedSettings might only have key/value
140- const originalFullSetting = selectedGroup .value ?.settings ?.find (s => s .key === setting .key )
172+ // Convert form values to API format
173+ const settingsToUpdate = Object .entries (values ).map (([key , value ]) => {
174+ const setting = selectedGroup .value ?.settings ?.find (s => s .key === key )
141175 return {
142- key: setting .key ,
143- value: setting .value ,
144- group_id: selectedGroup .value ?.id , // Add group_id
145- description: originalFullSetting ?.description , // Preserve description
146- is_encrypted: originalFullSetting ?.is_encrypted , // Preserve encryption status
176+ key ,
177+ value: String (value ), // API expects string values
178+ type: setting ?.type ,
179+ group_id: selectedGroup .value ?.id ,
180+ description: setting ?.description ,
181+ encrypted: setting ?.is_encrypted
147182 }
148183 })
149184
150185 try {
151186 if (! apiUrl ) {
152187 throw new Error (' VITE_DEPLOYSTACK_BACKEND_URL is not configured for saving settings.' )
153188 }
189+
154190 const response = await fetch (` ${apiUrl }/api/settings/bulk ` , {
155191 method: ' POST' ,
156192 headers: {
157193 ' Content-Type' : ' application/json' ,
158- // Add Authorization header if needed: 'Authorization': `Bearer ${yourAuthToken}`
159194 },
160- credentials: ' include' , // Added credentials include
195+ credentials: ' include' ,
161196 body: JSON .stringify ({ settings: settingsToUpdate }),
162197 })
163198
@@ -168,44 +203,35 @@ async function handleSaveChanges() {
168203
169204 const result = await response .json ()
170205 if (! result .success ) {
171- // Handle cases where API returns success: false but HTTP status is 200
172206 throw new Error (result .message || ' Failed to save settings due to an API error.' )
173207 }
174208
175- console .log (' API Save Result:' , result )
176- // Successfully saved, now update the local state to reflect changes
177- // This ensures the UI is in sync without needing an immediate refetch.
209+ console .log (' Settings saved successfully via API.' )
210+ successAlertMessage .value = t (' globalSettings.alerts.saveSuccess' )
211+ showSuccessAlert .value = true
212+
213+ // Update local state
178214 const groupIndex = settingGroups .value .findIndex (g => g .id === selectedGroup .value ?.id )
179215 if (groupIndex !== - 1 ) {
180- // Create a new copy of the group with updated settings
216+ const updatedSettings = selectedGroup .value .settings ?.map (setting => ({
217+ ... setting ,
218+ value: String ((values as Record <string , any >)[setting .key ])
219+ }))
220+
181221 const updatedGroup = {
182222 ... settingGroups .value [groupIndex ],
183- settings: JSON . parse ( JSON . stringify ( editableSettings . value )) // Use the edited settings
223+ settings: updatedSettings
184224 }
185- // Replace the old group with the updated one
225+
186226 const newSettingGroups = [... settingGroups .value ]
187227 newSettingGroups [groupIndex ] = updatedGroup
188228 settingGroups .value = newSettingGroups
189229 }
190- console .log (' Settings saved successfully via API.' )
191- successAlertMessage .value = t (' globalSettings.alerts.saveSuccess' ); // Assuming you have i18n keys
192- showSuccessAlert .value = true ;
193- // Removed setTimeout to make the alert persistent until manually closed or next save
194-
195- // Optionally, use a toast notification for success
196- // e.g., toast({ title: 'Settings Saved', description: 'Your changes have been successfully saved.' })
197-
198- // Optionally, refetch all groups to ensure data consistency,
199- // or merge changes carefully if the API returns the updated settings.
200- // For now, the local update above handles immediate UI feedback.
201- // await fetchSettingGroupsApi().then(data => settingGroups.value = data); // Example refetch
202230
203231 } catch (saveError ) {
204232 console .error (' Failed to save settings via API:' , saveError )
205- // Optionally, use a toast notification for error
206- // e.g., toast({ title: 'Error Saving Settings', description: (saveError as Error).message, variant: 'destructive' })
207233 }
208- }
234+ })
209235
210236 </script >
211237
@@ -246,18 +272,43 @@ async function handleSaveChanges() {
246272 {{ selectedGroup.description }}
247273 </p >
248274 </div >
249- <form v-if =" editableSettings.length > 0" class =" space-y-6" @submit.prevent =" handleSaveChanges" >
250- <div v-for =" (setting, index) in editableSettings" :key =" setting.key" class =" space-y-2" >
251- <Label :for =" `setting-${setting.key}`" >{{ setting.description || setting.key }}</Label >
252- <Input
253- :id =" `setting-${setting.key}`"
254- :type =" getSettingInputType(setting)"
255- v-model =" editableSettings[index].value"
256- class =" w-full"
257- />
258- <p v-if =" setting.is_encrypted" class =" text-xs text-muted-foreground" >This value is encrypted.</p >
259- <!-- Add FormDescription and FormMessage here if using full VeeValidate structure -->
260- </div >
275+ <form v-if =" selectedGroup.settings && selectedGroup.settings.length > 0" class =" space-y-6" @submit =" onSubmit" >
276+ <FormField
277+ v-for =" setting in selectedGroup.settings"
278+ :key =" setting.key"
279+ v-slot =" { componentField }"
280+ :name =" setting.key"
281+ >
282+ <FormItem >
283+ <FormLabel >{{ setting.description || setting.key }}</FormLabel >
284+ <FormControl >
285+ <!-- String Input (text or password) -->
286+ <Input
287+ v-if =" setting.type === 'string'"
288+ :type =" setting.is_encrypted ? 'password' : 'text'"
289+ v-bind =" componentField"
290+ />
291+
292+ <!-- Number Input -->
293+ <Input
294+ v-else-if =" setting.type === 'number'"
295+ type =" number"
296+ v-bind =" componentField"
297+ />
298+
299+ <!-- Boolean Toggle Switch -->
300+ <Switch
301+ v-else-if =" setting.type === 'boolean'"
302+ v-bind =" componentField"
303+ />
304+ </FormControl >
305+ <FormDescription v-if =" setting.is_encrypted" >
306+ This value is encrypted.
307+ </FormDescription >
308+ <FormMessage />
309+ </FormItem >
310+ </FormField >
311+
261312 <Button type =" submit" >
262313 Save Changes
263314 </Button >
0 commit comments