Skip to content

Commit fc1ea93

Browse files
author
Lasim
committed
feat(frontend): add loading states and text to buttons in forms
1 parent bfd2bbc commit fc1ea93

File tree

12 files changed

+146
-92
lines changed

12 files changed

+146
-92
lines changed

services/frontend/src/components/admin/UserActionsGroup.vue

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from '@/components/ui/alert-dialog'
1616
import { Alert, AlertDescription } from '@/components/ui/alert'
1717
import { UserService } from '@/services/userService'
18-
import { CheckCircle, AlertCircle, Loader2 } from 'lucide-vue-next'
18+
import { CheckCircle, AlertCircle } from 'lucide-vue-next'
1919
import type { User } from '@/views/admin/users/types'
2020
2121
interface Props {
@@ -130,11 +130,12 @@ const clearMessages = () => {
130130
<AlertDialogTrigger as-child>
131131
<Button
132132
variant="outline"
133-
:disabled="!canResetPassword || isLoading"
133+
:disabled="!canResetPassword"
134+
:loading="isLoading"
135+
:loading-text="t('adminUsers.userDetail.actions.forceResetPassword')"
134136
:title="!canResetPassword ? t('adminUsers.userDetail.actions.resetPasswordDisabled') : undefined"
135137
class="justify-start"
136138
>
137-
<Loader2 v-if="isLoading" class="h-4 w-4 mr-2 animate-spin" />
138139
{{ t('adminUsers.userDetail.actions.forceResetPassword') }}
139140
</Button>
140141
</AlertDialogTrigger>
@@ -155,9 +156,14 @@ const clearMessages = () => {
155156
<AlertDialogCancel>
156157
{{ t('adminUsers.userDetail.actions.resetPasswordConfirm.cancel') }}
157158
</AlertDialogCancel>
158-
<AlertDialogAction @click="handlePasswordReset" :disabled="isLoading">
159-
<Loader2 v-if="isLoading" class="h-4 w-4 mr-2 animate-spin" />
160-
{{ t('adminUsers.userDetail.actions.resetPasswordConfirm.confirm') }}
159+
<AlertDialogAction as-child>
160+
<Button
161+
@click="handlePasswordReset"
162+
:loading="isLoading"
163+
:loading-text="t('adminUsers.userDetail.actions.resetPasswordConfirm.confirm')"
164+
>
165+
{{ t('adminUsers.userDetail.actions.resetPasswordConfirm.confirm') }}
166+
</Button>
161167
</AlertDialogAction>
162168
</AlertDialogFooter>
163169
</AlertDialogContent>

services/frontend/src/components/admin/mcp-catalog/McpServerAddFormWizard.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
44
import { Button } from '@/components/ui/button'
55
import { Alert, AlertDescription } from '@/components/ui/alert'
66
import { ProgressBars } from '@/components/ui/progress-bars'
7-
import { FileText, Github, Settings, Loader2 } from 'lucide-vue-next'
7+
import { FileText, Github, Settings } from 'lucide-vue-next'
88
import { McpCatalogService } from '@/services/mcpCatalogService'
99
import { useEventBus } from '@/composables/useEventBus'
1010
import GitHubRepositoryStep from '@/components/admin/mcp-catalog/GitHubRepositoryStep.vue'
@@ -454,10 +454,11 @@ onUnmounted(() => {
454454
v-if="currentStep === 0"
455455
@click="handleGitHubStepNext"
456456
:disabled="!canProceedFromGitHub"
457+
:loading="isFetchingGitHub"
458+
:loading-text="t('mcpCatalog.form.navigation.fetching')"
457459
class="min-w-[120px]"
458460
>
459-
<Loader2 v-if="isFetchingGitHub" class="h-4 w-4 animate-spin mr-2" />
460-
{{ isFetchingGitHub ? t('mcpCatalog.form.navigation.fetching') : t('mcpCatalog.form.navigation.next') }}
461+
{{ t('mcpCatalog.form.navigation.next') }}
461462
</Button>
462463

463464
<!-- Normal next button for Claude config step -->

services/frontend/src/components/admin/mcp-catalog/McpServerEditFormWizard.vue

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
44
import { Button } from '@/components/ui/button'
55
import { Alert, AlertDescription } from '@/components/ui/alert'
66
import { ProgressBars } from '@/components/ui/progress-bars'
7-
import { FileText, Github, Code, Zap, CheckCircle, Loader2 } from 'lucide-vue-next'
7+
import { FileText, Github, Code, Zap, CheckCircle } from 'lucide-vue-next'
88
import { McpCatalogService } from '@/services/mcpCatalogService'
99
import { useEventBus } from '@/composables/useEventBus'
1010
import BasicInfoStep from '@/components/admin/mcp-catalog/BasicInfoStep.vue'
@@ -221,14 +221,6 @@ const canProceedFromGitHub = computed(() => {
221221
})
222222
223223
// Dynamic button text based on props or defaults
224-
const submitText = computed(() => {
225-
if (props.submitButtonText) return props.submitButtonText
226-
if (isSubmitting.value) {
227-
return props.mode === 'edit' ? t('mcpCatalog.form.navigation.updating') : t('mcpCatalog.form.navigation.creating')
228-
}
229-
return props.mode === 'edit' ? t('mcpCatalog.form.navigation.update') : t('mcpCatalog.form.navigation.submit')
230-
})
231-
232224
const cancelText = computed(() => {
233225
return props.cancelButtonText || t('mcpCatalog.form.navigation.cancel')
234226
})
@@ -513,10 +505,11 @@ onUnmounted(() => {
513505
v-if="currentStep === 0"
514506
@click="handleGitHubStepNext"
515507
:disabled="!canProceedFromGitHub"
508+
:loading="isFetchingGitHub"
509+
:loading-text="t('mcpCatalog.form.navigation.fetching')"
516510
class="min-w-[120px]"
517511
>
518-
<Loader2 v-if="isFetchingGitHub" class="h-4 w-4 animate-spin mr-2" />
519-
{{ isFetchingGitHub ? t('mcpCatalog.form.navigation.fetching') : t('mcpCatalog.form.navigation.next') }}
512+
{{ t('mcpCatalog.form.navigation.next') }}
520513
</Button>
521514

522515
<!-- Normal next button for other steps -->
@@ -531,9 +524,10 @@ onUnmounted(() => {
531524
<Button
532525
v-else
533526
@click="submitForm"
534-
:disabled="isSubmitting"
527+
:loading="isSubmitting"
528+
:loading-text="props.mode === 'edit' ? t('mcpCatalog.form.navigation.updating') : t('mcpCatalog.form.navigation.creating')"
535529
>
536-
{{ submitText }}
530+
{{ props.mode === 'edit' ? t('mcpCatalog.form.navigation.update') : t('mcpCatalog.form.navigation.submit') }}
537531
</Button>
538532
</div>
539533
</div>

services/frontend/src/components/credentials/AddCredentialDialog.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,12 @@ onMounted(() => {
389389
</Button>
390390
<Button
391391
type="submit"
392-
:disabled="!isFormValid || isSaving"
392+
:disabled="!isFormValid"
393+
:loading="isSaving"
394+
:loading-text="t('credentials.form.buttons.saving')"
393395
class="min-w-[120px]"
394396
>
395-
<Loader2 v-if="isSaving" class="h-4 w-4 animate-spin mr-2" />
396-
{{ isSaving ? t('credentials.form.buttons.saving') : t('credentials.form.buttons.save') }}
397+
{{ t('credentials.form.buttons.save') }}
397398
</Button>
398399
</div>
399400
</form>

services/frontend/src/components/mcp-server/wizard/McpServerInstallWizard.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useRoute } from 'vue-router'
66
import { Button } from '@/components/ui/button'
77
import { ProgressBars } from '@/components/ui/progress-bars'
88
import { toast } from 'vue-sonner'
9-
import { Server, Settings, Cloud, Loader2 } from 'lucide-vue-next'
9+
import { Server, Settings, Cloud } from 'lucide-vue-next'
1010
import { McpInstallationService } from '@/services/mcpInstallationService'
1111
import { TeamService } from '@/services/teamService'
1212
import { McpCatalogService } from '@/services/mcpCatalogService'
@@ -448,10 +448,11 @@ onMounted(async () => {
448448
<Button
449449
v-else
450450
@click="submitInstallation"
451-
:disabled="!canSubmit || isSubmitting"
451+
:disabled="!canSubmit"
452+
:loading="isSubmitting"
453+
:loading-text="t('mcpInstallations.wizard.installing')"
452454
>
453-
<Loader2 v-if="isSubmitting" class="h-4 w-4 animate-spin mr-2" />
454-
{{ isSubmitting ? t('mcpInstallations.wizard.installing') : t('mcpInstallations.wizard.install') }}
455+
{{ t('mcpInstallations.wizard.install') }}
455456
</Button>
456457
</div>
457458
</div>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default {
66
search: {
77
placeholder: 'Search credentials',
88
button: 'Search',
9+
searching: 'Searching...',
910
noResults: 'No credentials found',
1011
results: 'Found {count} credential{count, plural, one {} other {s}} for "{query}"'
1112
},
@@ -79,7 +80,8 @@ export default {
7980
view: 'View Details',
8081
editCredential: 'Edit Credential',
8182
editName: 'Edit Name',
82-
updateSecrets: 'Update Secrets'
83+
updateSecrets: 'Update Secrets',
84+
creating: 'Creating...'
8385
},
8486
editName: {
8587
title: 'Edit Name',

services/frontend/src/i18n/locales/en/mcp-catalog.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,11 @@ export default {
514514
cancel: 'Cancel',
515515
confirm: 'Delete Server',
516516
deleting: 'Deleting...'
517+
},
518+
errorActions: {
519+
tryAgain: 'Try Again',
520+
backToCatalog: 'Back to Catalog',
521+
loading: 'Loading...'
517522
}
518523
},
519524

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ export default {
242242
cancel: 'Cancel',
243243
confirm: 'Delete Team',
244244
deleting: 'Deleting...'
245+
},
246+
errorActions: {
247+
tryAgain: 'Try Again',
248+
backToTeams: 'Back to Teams',
249+
loading: 'Loading...'
245250
}
246251
}
247252
}

services/frontend/src/views/VerifyEmail.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { ref, onMounted } from 'vue'
33
import { useRouter, useRoute } from 'vue-router'
4-
import { CheckCircle, XCircle, Mail, Loader2, AlertTriangle } from 'lucide-vue-next'
4+
import { CheckCircle, XCircle, Mail, AlertTriangle, Loader2 } from 'lucide-vue-next'
55
import { useI18n } from 'vue-i18n'
66
import { getEnv } from '@/utils/env'
77
@@ -303,12 +303,13 @@ onMounted(() => {
303303

304304
<Button
305305
@click="resendVerification"
306-
:disabled="isResending || !resendEmail"
306+
:disabled="!resendEmail"
307+
:loading="isResending"
308+
:loading-text="$t('verifyEmail.error.resendSection.buttonSending')"
307309
class="w-full"
308310
variant="outline"
309311
>
310-
<Loader2 v-if="isResending" class="h-4 w-4 animate-spin mr-2" />
311-
{{ isResending ? $t('verifyEmail.error.resendSection.buttonSending') : $t('verifyEmail.error.resendSection.button') }}
312+
{{ $t('verifyEmail.error.resendSection.button') }}
312313
</Button>
313314

314315
<div class="flex space-x-3">

services/frontend/src/views/admin/mcp-server-catalog/edit/[id].vue

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const eventBus = useEventBus()
2222
2323
// State
2424
const isLoading = ref(true)
25+
const isRetrying = ref(false)
2526
const loadError = ref<string | null>(null)
2627
const serverData = ref<McpServer | null>(null)
2728
const initialFormData = ref<Partial<McpServerFormData> | null>(null)
@@ -42,6 +43,7 @@ const goToCatalog = () => {
4243
const loadServerData = async () => {
4344
try {
4445
isLoading.value = true
46+
isRetrying.value = true
4547
loadError.value = null
4648
4749
const server = await McpCatalogService.getServerById(serverId)
@@ -55,6 +57,7 @@ const loadServerData = async () => {
5557
console.error('Failed to load server:', error)
5658
} finally {
5759
isLoading.value = false
60+
isRetrying.value = false
5861
}
5962
}
6063
@@ -194,56 +197,50 @@ const convertServerToFormData = (server: McpServer): Partial<McpServerFormData>
194197
195198
// Handle form submission
196199
const handleSubmit = async (formData: McpServerFormData) => {
197-
try {
198-
// Convert form data to API request format
199-
const requestData: UpdateMcpServerRequest = {
200-
// Basic info
201-
name: formData.basic.name,
202-
description: formData.basic.description,
203-
long_description: formData.basic.long_description || undefined,
204-
category_id: formData.basic.category_id || undefined,
205-
author_name: formData.basic.author_name || undefined,
206-
author_contact: formData.basic.author_contact || undefined,
207-
organization: formData.basic.organization || undefined,
208-
license: formData.basic.license || undefined,
209-
tags: formData.basic.tags.length > 0 ? formData.basic.tags : undefined,
210-
211-
// Repository (use GitHub data if available, fallback to repository data)
212-
github_url: formData.github.github_url || formData.repository.github_url || undefined,
213-
git_branch: formData.github.git_branch || formData.repository.git_branch || 'main',
214-
homepage_url: formData.repository.homepage_url || undefined,
215-
216-
// Technical
217-
language: formData.technical.language,
218-
runtime: formData.technical.runtime,
219-
runtime_min_version: formData.technical.runtime_min_version || undefined,
220-
installation_methods: formData.technical.installation_methods,
221-
dependencies: formData.technical.dependencies ? JSON.parse(formData.technical.dependencies) : undefined,
222-
223-
// Capabilities
224-
tools: formData.capabilities.tools,
225-
resources: formData.capabilities.resources.length > 0 ? formData.capabilities.resources : undefined,
226-
prompts: formData.capabilities.prompts.length > 0 ? formData.capabilities.prompts : undefined,
227-
environment_variables: formData.capabilities.environment_variables.length > 0 ? formData.capabilities.environment_variables : undefined,
228-
default_config: formData.capabilities.default_config ? JSON.parse(formData.capabilities.default_config) : undefined
229-
}
230-
231-
// Submit to API
232-
await McpCatalogService.updateGlobalServer(serverId, requestData)
200+
// Convert form data to API request format
201+
const requestData: UpdateMcpServerRequest = {
202+
// Basic info
203+
name: formData.basic.name,
204+
description: formData.basic.description,
205+
long_description: formData.basic.long_description || undefined,
206+
category_id: formData.basic.category_id || undefined,
207+
author_name: formData.basic.author_name || undefined,
208+
author_contact: formData.basic.author_contact || undefined,
209+
organization: formData.basic.organization || undefined,
210+
license: formData.basic.license || undefined,
211+
tags: formData.basic.tags.length > 0 ? formData.basic.tags : undefined,
212+
213+
// Repository (use GitHub data if available, fallback to repository data)
214+
github_url: formData.github.github_url || formData.repository.github_url || undefined,
215+
git_branch: formData.github.git_branch || formData.repository.git_branch || 'main',
216+
homepage_url: formData.repository.homepage_url || undefined,
217+
218+
// Technical
219+
language: formData.technical.language,
220+
runtime: formData.technical.runtime,
221+
runtime_min_version: formData.technical.runtime_min_version || undefined,
222+
installation_methods: formData.technical.installation_methods,
223+
dependencies: formData.technical.dependencies ? JSON.parse(formData.technical.dependencies) : undefined,
224+
225+
// Capabilities
226+
tools: formData.capabilities.tools,
227+
resources: formData.capabilities.resources.length > 0 ? formData.capabilities.resources : undefined,
228+
prompts: formData.capabilities.prompts.length > 0 ? formData.capabilities.prompts : undefined,
229+
environment_variables: formData.capabilities.environment_variables.length > 0 ? formData.capabilities.environment_variables : undefined,
230+
default_config: formData.capabilities.default_config ? JSON.parse(formData.capabilities.default_config) : undefined
231+
}
233232
234-
// Emit success event
235-
eventBus.emit('mcp-server-updated', { serverId })
233+
// Submit to API
234+
await McpCatalogService.updateGlobalServer(serverId, requestData)
236235
237-
// Navigate back to view page with success parameter
238-
router.push({
239-
path: `/admin/mcp-server-catalog/view/${serverId}`,
240-
query: { updated: 'true' }
241-
})
236+
// Emit success event
237+
eventBus.emit('mcp-server-updated', { serverId })
242238
243-
} catch (error) {
244-
// Re-throw error to let the wizard handle it
245-
throw error
246-
}
239+
// Navigate back to view page with success parameter
240+
router.push({
241+
path: `/admin/mcp-server-catalog/view/${serverId}`,
242+
query: { updated: 'true' }
243+
})
247244
}
248245
249246
const handleCancel = () => {
@@ -281,11 +278,17 @@ onMounted(() => {
281278
{{ t('mcpCatalog.edit.errorLoading', { error: loadError }) }}
282279
</AlertDescription>
283280
<div class="mt-4 flex gap-2">
284-
<Button variant="outline" size="sm" @click="loadServerData">
285-
Try Again
281+
<Button
282+
variant="outline"
283+
size="sm"
284+
:loading="isRetrying"
285+
:loading-text="t('mcpCatalog.edit.errorActions.loading')"
286+
@click="loadServerData"
287+
>
288+
{{ t('mcpCatalog.edit.errorActions.tryAgain') }}
286289
</Button>
287290
<Button variant="ghost" size="sm" @click="goToCatalog">
288-
Back to Catalog
291+
{{ t('mcpCatalog.edit.errorActions.backToCatalog') }}
289292
</Button>
290293
</div>
291294
</Alert>

0 commit comments

Comments
 (0)