Implement AI Loading Messages and Enhance Locale Handling in Forms (#737)

- Added a new `AIFormLoadingMessages` component to display dynamic loading messages with emojis during form processing, improving user feedback.
- Enhanced locale handling in `OpenCompleteForm.vue` to default to English if an invalid locale is provided, ensuring better user experience and error handling.

These changes aim to provide clearer loading states and improve localization robustness in form components.

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Chirag Chhatrala 2025-04-01 21:24:10 +05:30 committed by GitHub
parent 7efa8ed9cb
commit efb2aabe0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 114 additions and 2 deletions

View File

@ -274,7 +274,11 @@ export default {
watch: {
'form.language': {
handler(newLanguage) {
this.setLocale(newLanguage)
if (newLanguage && typeof newLanguage === 'string') {
this.setLocale(newLanguage)
} else {
this.setLocale('en') // Default to English if invalid locale
}
},
immediate: true
}

View File

@ -0,0 +1,97 @@
<template>
<div
v-motion
:initial="{ opacity: 0, height: 0 }"
:enter="{ opacity: 1, height: 52, transition: { duration: 300, ease: 'easeOut' } }"
:leave="{ opacity: 0, height: 0, transition: { duration: 300, ease: 'easeIn' } }"
class="overflow-hidden w-full"
>
<div
v-motion
:initial="{ opacity: 0, scale: 0.95 }"
:enter="{ opacity: 1, scale: 1, transition: { duration: 300 } }"
class="flex items-center gap-3 bg-blue-50 px-4 py-3 rounded-lg w-full"
>
<div
v-motion
class="w-7 h-7 rounded-full bg-gradient-to-r from-blue-500 to-blue-300 flex items-center justify-center text-base"
:initial="{ rotate: 0 }"
:enter="{
rotate: [-8, 8, -8, 8, 0],
transition: {
repeat: Infinity,
duration: 1500,
ease: 'easeInOut',
times: [0, 0.25, 0.5, 0.75, 1]
}
}"
>
{{ currentEmoji }}
</div>
<span
:key="currentMessage"
v-motion
:initial="{ opacity: 0, y: 5 }"
:enter="{ opacity: 1, y: 0, transition: { duration: 200 } }"
class="text-sm text-gray-700 font-medium"
>
{{ currentMessage }}
</span>
<span class="text-xs text-gray-500 ml-auto tabular-nums">{{ elapsedTime }}s</span>
</div>
</div>
</template>
<script setup>
const messages = [
{ text: "Starting AI magic", emoji: "✨" },
{ text: "Analyzing requirements", emoji: "🤔" },
{ text: "Designing layout", emoji: "📐" },
{ text: "Adding form fields", emoji: "📝" },
{ text: "Fine-tuning validation", emoji: "🎯" },
{ text: "Optimizing UX", emoji: "💫" },
{ text: "Adding smart features", emoji: "🧠" },
{ text: "Polishing design", emoji: "✨" },
{ text: "Running final checks", emoji: "🔍" },
{ text: "Almost ready", emoji: "🚀" },
]
const currentMessage = ref(messages[0].text)
const currentEmoji = ref(messages[0].emoji)
const messageIndex = ref(0)
const startTime = ref(Date.now())
const elapsedTime = ref(0)
// Update message every 2.5 seconds
const interval = setInterval(() => {
const nextIndex = (messageIndex.value + 1) % messages.length
messageIndex.value = nextIndex
currentMessage.value = messages[nextIndex].text
currentEmoji.value = messages[nextIndex].emoji
}, 2500)
// Update elapsed time every second
const timeInterval = setInterval(() => {
elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000)
}, 1000)
onUnmounted(() => {
clearInterval(interval)
clearInterval(timeInterval)
})
</script>
<style>
.animate-shimmer {
animation: shimmer 2s infinite linear;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
</style>

View File

@ -151,8 +151,14 @@
help="Give us a description of the form you want to build (the more details the better)"
placeholder="A simple contact form, with a name, email and message field"
/>
<div
v-if="loading"
class="my-4"
>
<AIFormLoadingMessages />
</div>
<v-button
class="w-full"
class="w-full mt-4"
:loading="loading"
@click.prevent="generateForm"
>
@ -166,7 +172,12 @@
</template>
<script>
import AIFormLoadingMessages from "~/components/open/forms/components/AIFormLoadingMessages.vue"
export default {
components: {
AIFormLoadingMessages,
},
props: {
show: { type: Boolean, required: true },
},