[fix] Improve cloud onboarding UX with email verification polling and signup flow (#6030)

## Summary
Improve cloud onboarding flow by adding email verification polling and
fixing signup auto-logout issue.

## Changes
- **What**: 
- Add polling mechanism to automatically check email verification status
every 5 seconds on the verify email page
- Fix auto-logout issue after signup by redirecting to root instead of
login page
- Remove automatic logout on login page mount to preserve user session
after signup
- **Breaking**: None
- **Dependencies**: None

## Review Focus
- Email verification polling implementation uses Firebase Auth's
`reload()` method to refresh user state
- Polling stops after 5 minutes to prevent indefinite resource usage
- Proper cleanup of intervals/timeouts in `onUnmounted` hook to prevent
memory leaks
- Signup flow now maintains user session instead of forcing re-login

## User Experience Improvements
1. **Before**: Users had to manually refresh the page after clicking
email verification link
**After**: Page automatically detects verification and proceeds to next
step

2. **Before**: After signup, users were redirected to login page and
automatically logged out, requiring them to sign in again
**After**: Users stay logged in after signup and are redirected to root,
maintaining their session

## Testing
- Tested email verification polling with both verified and unverified
states
- Verified that invite codes are preserved throughout the flow
- Confirmed no memory leaks from polling intervals
- Tested signup flow with email/password authentication

## Related Issues
- Resolves user complaints about having to sign in twice during signup
flow
- Addresses email verification page not auto-advancing after
verification

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6030-fix-Improve-cloud-onboarding-UX-with-email-verification-polling-and-signup-flow-28a6d73d365081be8020caee6337c3e7)
by [Unito](https://www.unito.io)
This commit is contained in:
Jin Yi
2025-10-15 12:11:06 +09:00
committed by GitHub
parent 0239a83da2
commit 07ce463302
3 changed files with 65 additions and 21 deletions

View File

@@ -104,7 +104,7 @@
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import Message from 'primevue/message'
import { computed, onMounted, ref } from 'vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
@@ -179,8 +179,4 @@ const signInWithEmail = async (values: SignInData) => {
await onSuccess()
}
}
onMounted(async () => {
await authActions.logout()
})
</script>

View File

@@ -117,7 +117,8 @@ const navigateToLogin = () => {
}
const onSuccess = async () => {
await router.push({ name: 'cloud-login', query: route.query })
// The invite code will be handled after the user is logged in
await router.push({ path: '/', query: route.query })
}
// Custom error handler for inline display

View File

@@ -35,19 +35,54 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { useFirebaseAuth } from 'vuefire'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
const authStore = useFirebaseAuthStore()
const auth = useFirebaseAuth()!
const router = useRouter()
const route = useRoute()
const { t } = useI18n()
let intervalId: number | null = null
let timeoutId: number | null = null
const redirectInProgress = ref(false)
function clearPolling(): void {
if (intervalId !== null) {
clearInterval(intervalId)
intervalId = null
}
if (timeoutId !== null) {
clearTimeout(timeoutId)
timeoutId = null
}
}
async function redirectToNextStep(): Promise<void> {
if (redirectInProgress.value) return
redirectInProgress.value = true
clearPolling()
const inviteCode = route.query.inviteCode as string | undefined
if (inviteCode) {
await router.push({
name: 'cloud-invite-check',
query: { inviteCode }
})
} else {
await router.push({ name: 'cloud-user-check' })
}
}
const goBack = async () => {
const inviteCode = route.query.inviteCode as string | undefined
const authStore = useFirebaseAuthStore()
@@ -86,23 +121,35 @@ async function onSend() {
}
onMounted(async () => {
// When this screen loads via invite flow,
// ensure the invite code stays in the URL for the next step.
const inviteCode = route.query.inviteCode as string | undefined
// If the user is already verified (email link already clicked),
// continue to the next step automatically.
if (authStore.isEmailVerified) {
if (inviteCode) {
await router.push({
name: 'cloud-invite-check',
query: inviteCode ? { inviteCode } : {}
})
} else {
await router.push({ name: 'cloud-user-check' })
}
} else {
await onSend()
return redirectToNextStep()
}
// Send initial verification email
await onSend()
// Start polling to check email verification status
intervalId = window.setInterval(async () => {
if (auth.currentUser && !redirectInProgress.value) {
await auth.currentUser.reload()
if (auth.currentUser?.emailVerified) {
void redirectToNextStep()
}
}
}, 5000) // Check every 5 seconds
// Stop polling after 5 minutes
timeoutId = window.setTimeout(
() => {
clearPolling()
},
5 * 60 * 1000
)
})
onUnmounted(() => {
clearPolling()
})
</script>