[feat] Add skeleton loading states to cloud onboarding flow (#5568)

* [feat] Add skeleton loading states to cloud onboarding flow

- Create dedicated skeleton components matching exact layouts
- CloudLoginViewSkeleton for login page with beta notice, form, social buttons
- CloudSurveyViewSkeleton for multi-step survey with progress bar
- CloudWaitlistViewSkeleton for waitlist page with title and messages
- CloudClaimInviteViewSkeleton for invite claiming page
- Update UserCheckView to show contextual skeleton based on redirect destination
- Update InviteCheckView to show appropriate skeleton during loading
- Use i18n for loading text to maintain consistency

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* [feat] Fix skeleton loading flow to show progressive states

- Start with simple loading text when checking user status
- Show survey skeleton while checking survey completion
- Show waitlist skeleton while checking user activation status
- Show login skeleton when redirecting to login on error
- Preserve all original comments from upstream authors
- Use progressive disclosure based on API response flow

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Byrne
2025-09-14 19:40:41 -07:00
committed by GitHub
parent 59a1380f39
commit a13eeaea7e
6 changed files with 147 additions and 10 deletions

View File

@@ -1,5 +1,10 @@
<template>
<div />
<CloudLoginViewSkeleton v-if="skeletonType === 'login'" />
<CloudSurveyViewSkeleton v-else-if="skeletonType === 'survey'" />
<CloudWaitlistViewSkeleton v-else-if="skeletonType === 'waitlist'" />
<div v-else class="flex items-center justify-center min-h-screen">
<div class="animate-pulse text-gray-500">{{ $t('g.loading') }}</div>
</div>
</template>
<script setup lang="ts">
@@ -8,8 +13,13 @@ import { useRouter } from 'vue-router'
import { getSurveyCompletedStatus, getUserCloudStatus } from '@/api/auth'
import CloudLoginViewSkeleton from './skeletons/CloudLoginViewSkeleton.vue'
import CloudSurveyViewSkeleton from './skeletons/CloudSurveyViewSkeleton.vue'
import CloudWaitlistViewSkeleton from './skeletons/CloudWaitlistViewSkeleton.vue'
const router = useRouter()
const isNavigating = ref(false)
const skeletonType = ref<'login' | 'survey' | 'waitlist' | 'loading'>('loading')
onMounted(async () => {
// Prevent multiple executions
@@ -21,28 +31,37 @@ onMounted(async () => {
// Wait for next tick to ensure component is fully mounted
await nextTick()
const cloudUserStats = await getUserCloudStatus()
const surveyStatus = await getSurveyCompletedStatus()
try {
const cloudUserStats = await getUserCloudStatus()
if (!cloudUserStats) {
skeletonType.value = 'login'
await router.replace({ name: 'cloud-login' })
return
}
// We know user exists, now check survey status - show survey skeleton while loading
skeletonType.value = 'survey'
const surveyStatus = await getSurveyCompletedStatus()
// Check onboarding status and redirect accordingly
if (!surveyStatus) {
// User hasn't completed survey
await router.replace({ name: 'cloud-survey' })
} else if (cloudUserStats.status !== 'active') {
// User completed survey but not whitelisted
await router.replace({ name: 'cloud-waitlist' })
} else {
// User is fully onboarded - just reload the page to bypass router issues
window.location.href = '/'
// Survey is done, now check if waitlisted - show waitlist skeleton while loading
skeletonType.value = 'waitlist'
if (cloudUserStats.status !== 'active') {
// User completed survey but not whitelisted
await router.replace({ name: 'cloud-waitlist' })
} else {
// User is fully onboarded - just reload the page to bypass router issues
window.location.href = '/'
}
}
} catch (error) {
// On error, fallback to page reload
skeletonType.value = 'login'
await router.push({ name: 'cloud-login' })
}
})