Skip to content

Commit 3a4504d

Browse files
author
Lasim
committed
feat(frontend): enhance global settings with error handling and alerts
1 parent ceac956 commit 3a4504d

File tree

2 files changed

+53
-45
lines changed

2 files changed

+53
-45
lines changed

services/frontend/src/i18n/locales/en/globalSettings.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,28 @@ export default {
22
globalSettings: {
33
title: 'Global Settings',
44
description: 'Manage global application settings',
5+
loading: 'Loading settings...',
6+
errors: {
7+
loadSettings: 'Error loading settings',
8+
saveSettings: 'Failed to save settings',
9+
configNotSet: 'VITE_DEPLOYSTACK_BACKEND_URL is not configured.',
10+
savingConfigNotSet: 'VITE_DEPLOYSTACK_BACKEND_URL is not configured for saving settings.',
11+
fetchFailed: 'Failed to fetch setting groups',
12+
saveFailed: 'Failed to save settings due to an API error.',
13+
unknownError: 'An unknown error occurred'
14+
},
515
alerts: {
616
successTitle: "Success!",
717
saveSuccess: "Your settings have been saved successfully.",
818
noChanges: "No changes detected. Settings are up to date."
19+
},
20+
form: {
21+
saveChanges: 'Save Changes',
22+
encryptedValue: 'This value is encrypted.',
23+
noSettings: 'No settings in this group.',
24+
groupNotFound: 'Group not found or settings unavailable.',
25+
selectCategory: 'Select a category from the sidebar to view its settings.',
26+
noGroups: 'No setting groups found.'
927
}
1028
},
1129
githubApp: {

services/frontend/src/views/admin/GlobalSettings.vue

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import { ref, onMounted, computed, watch } from 'vue'
33
import { useRoute } from 'vue-router'
44
import { useI18n } from 'vue-i18n'
5+
import { toast } from 'vue-sonner'
56
import { useEventBus } from '@/composables/useEventBus'
67
import GlobalSettingsSidebarNav, { type GlobalSettingGroup } from '@/components/settings/GlobalSettingsSidebarNav.vue'
78
import DashboardLayout from '@/components/DashboardLayout.vue'
89
import { getEnv } from '@/utils/env'
9-
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
10-
import { CheckCircle2Icon, XIcon } from 'lucide-vue-next'
10+
1111
import {
1212
Card,
1313
CardContent,
@@ -23,21 +23,20 @@ const route = useRoute()
2323
const settingGroups = ref<GlobalSettingGroup[]>([])
2424
const isLoading = ref(true)
2525
const error = ref<string | null>(null)
26-
const showSuccessAlert = ref(false)
27-
const successAlertMessage = ref('')
26+
2827
2928
const apiUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL') || '' // Fallback to empty string if not set
3029
3130
// Placeholder for the actual API call
3231
async function fetchSettingGroupsApi(): Promise<GlobalSettingGroup[]> {
3332
if (!apiUrl) {
34-
throw new Error('VITE_DEPLOYSTACK_BACKEND_URL is not configured.')
33+
throw new Error(t('globalSettings.errors.configNotSet'))
3534
}
3635
const response = await fetch(`${apiUrl}/api/settings/groups`, { credentials: 'include' })
3736
3837
if (!response.ok) {
3938
const errorData = await response.json().catch(() => ({}))
40-
throw new Error(errorData.message || `Failed to fetch setting groups: ${response.statusText} (status: ${response.status})`)
39+
throw new Error(errorData.message || `${t('globalSettings.errors.fetchFailed')}: ${response.statusText} (status: ${response.status})`)
4140
}
4241
4342
const result = await response.json()
@@ -62,7 +61,7 @@ onMounted(async () => {
6261
settingGroups.value = fetchedGroups // Direct assignment is fine now
6362
error.value = null
6463
} catch (err) {
65-
error.value = err instanceof Error ? err.message : 'An unknown error occurred'
64+
error.value = err instanceof Error ? err.message : t('globalSettings.errors.unknownError')
6665
settingGroups.value = [] // Clear or set to empty on error
6766
} finally {
6867
isLoading.value = false
@@ -108,9 +107,10 @@ function handleSettingsUpdated(updatedSettings: Setting[]) {
108107
settingGroups.value = newSettingGroups
109108
}
110109
111-
// Show success message
112-
successAlertMessage.value = t('globalSettings.alerts.saveSuccess')
113-
showSuccessAlert.value = true
110+
// Show success message with Sonner toast
111+
toast.success(t('globalSettings.alerts.successTitle'), {
112+
description: t('globalSettings.alerts.saveSuccess')
113+
})
114114
115115
// Emit event for other components
116116
eventBus.emit('settings-updated')
@@ -161,10 +161,7 @@ function createInitialValues(settings: Setting[]) {
161161
return values
162162
}
163163
164-
// Watch for route changes and reset success alert
165-
watch(() => route.params.groupId, () => {
166-
showSuccessAlert.value = false
167-
})
164+
168165
169166
// Watch for group changes and set form values
170167
watch(() => selectedGroup.value, (newGroup) => {
@@ -206,7 +203,7 @@ async function handleSubmit(event: Event) {
206203
207204
try {
208205
if (!apiUrl) {
209-
throw new Error('VITE_DEPLOYSTACK_BACKEND_URL is not configured for saving settings.')
206+
throw new Error(t('globalSettings.errors.savingConfigNotSet'))
210207
}
211208
212209
const requestBody = { settings: settingsToUpdate }
@@ -222,18 +219,21 @@ async function handleSubmit(event: Event) {
222219
223220
if (!response.ok) {
224221
const errorData = await response.json().catch(() => ({}))
225-
throw new Error(errorData.error || errorData.message || `Failed to save settings: ${response.statusText} (status: ${response.status})`)
222+
throw new Error(errorData.error || errorData.message || `${t('globalSettings.errors.saveSettings')}: ${response.statusText} (status: ${response.status})`)
226223
}
227224
228225
229226
230227
const result = await response.json()
231228
232229
if (!result.success) {
233-
throw new Error(result.message || 'Failed to save settings due to an API error.')
230+
throw new Error(result.message || t('globalSettings.errors.saveFailed'))
234231
}
235-
successAlertMessage.value = t('globalSettings.alerts.saveSuccess')
236-
showSuccessAlert.value = true
232+
233+
// Show success message with Sonner toast
234+
toast.success(t('globalSettings.alerts.successTitle'), {
235+
description: t('globalSettings.alerts.saveSuccess')
236+
})
237237
238238
// Update local state
239239
const groupIndex = settingGroups.value.findIndex(g => g.id === selectedGroup.value?.id)
@@ -253,8 +253,15 @@ async function handleSubmit(event: Event) {
253253
settingGroups.value = newSettingGroups
254254
}
255255
256+
// Emit event for other components
257+
eventBus.emit('settings-updated')
258+
256259
} catch (err) {
257-
// Handle save error silently or show user-friendly error message
260+
// Show error toast with meaningful message
261+
const errorMessage = err instanceof Error ? err.message : t('globalSettings.errors.unknownError')
262+
toast.error(t('globalSettings.errors.saveSettings'), {
263+
description: errorMessage
264+
})
258265
console.error('Failed to save settings:', err)
259266
}
260267
}
@@ -271,25 +278,8 @@ async function handleSubmit(event: Event) {
271278
</aside>
272279
<div class="flex-1 lg:max-w-3xl">
273280

274-
<Alert v-if="showSuccessAlert" variant="default" class="mb-8 border-green-500 bg-green-50 text-green-700 relative">
275-
<CheckCircle2Icon class="h-5 w-5 text-green-600" />
276-
<AlertTitle class="font-semibold text-green-800">{{ t('globalSettings.alerts.successTitle') }}</AlertTitle>
277-
<AlertDescription>
278-
{{ successAlertMessage }}
279-
</AlertDescription>
280-
<Button
281-
variant="ghost"
282-
size="sm"
283-
class="absolute top-2 right-2 p-1 h-auto text-green-700 hover:bg-green-100"
284-
@click="showSuccessAlert = false"
285-
aria-label="Dismiss success alert"
286-
>
287-
<XIcon class="h-4 w-4" />
288-
</Button>
289-
</Alert>
290-
291-
<div v-if="isLoading" class="text-muted-foreground">Loading settings...</div>
292-
<div v-else-if="error" class="text-red-500">Error loading settings: {{ error }}</div>
281+
<div v-if="isLoading" class="text-muted-foreground">{{ t('globalSettings.loading') }}</div>
282+
<div v-else-if="error" class="text-red-500">{{ t('globalSettings.errors.loadSettings') }}: {{ error }}</div>
293283

294284

295285
<div v-else-if="selectedGroup" class="space-y-6">
@@ -348,29 +338,29 @@ async function handleSubmit(event: Event) {
348338
/>
349339
</div>
350340

351-
<p v-if="setting.is_encrypted" class="text-xs text-muted-foreground">This value is encrypted.</p>
341+
<p v-if="setting.is_encrypted" class="text-xs text-muted-foreground">{{ t('globalSettings.form.encryptedValue') }}</p>
352342
</div>
353343

354344
<Button type="submit">
355-
Save Changes
345+
{{ t('globalSettings.form.saveChanges') }}
356346
</Button>
357347
</form>
358348
<div v-else-if="selectedGroup && (!selectedGroup.settings || selectedGroup.settings.length === 0)">
359-
<p class="text-sm text-muted-foreground">No settings in this group.</p>
349+
<p class="text-sm text-muted-foreground">{{ t('globalSettings.form.noSettings') }}</p>
360350
</div>
361351
<div v-else>
362-
<p class="text-sm text-muted-foreground">Group not found or settings unavailable.</p>
352+
<p class="text-sm text-muted-foreground">{{ t('globalSettings.form.groupNotFound') }}</p>
363353
</div>
364354
</CardContent>
365355
</Card>
366356
</div>
367357

368358

369359
<div v-else-if="!currentGroupId && settingGroups.length > 0">
370-
<p class="text-muted-foreground">Select a category from the sidebar to view its settings.</p>
360+
<p class="text-muted-foreground">{{ t('globalSettings.form.selectCategory') }}</p>
371361
</div>
372362
<div v-else-if="!currentGroupId && settingGroups.length === 0 && !isLoading">
373-
<p class="text-muted-foreground">No setting groups found.</p>
363+
<p class="text-muted-foreground">{{ t('globalSettings.form.noGroups') }}</p>
374364
</div>
375365
</div>
376366
</div>

0 commit comments

Comments
 (0)