mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 06:47:33 +00:00
[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:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div />
|
||||
<CloudClaimInviteViewSkeleton />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -8,6 +8,8 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { getInviteCodeStatus } from '@/api/auth'
|
||||
|
||||
import CloudClaimInviteViewSkeleton from './skeletons/CloudClaimInviteViewSkeleton.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
@@ -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' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-center items-center h-screen font-mono text-black gap-4"
|
||||
>
|
||||
<Skeleton width="60%" height="2rem" />
|
||||
<Skeleton width="30%" height="2.5rem" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="h-full flex items-center justify-center p-8">
|
||||
<div class="w-96 p-2">
|
||||
<div class="bg-[#2d2e32] p-4 rounded-lg">
|
||||
<Skeleton width="60%" height="1.125rem" class="mb-2" />
|
||||
<Skeleton width="90%" height="1rem" class="mb-2" />
|
||||
<Skeleton width="80%" height="1rem" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 mt-6 mb-8">
|
||||
<Skeleton width="45%" height="1.5rem" class="my-0" />
|
||||
<div class="flex items-center">
|
||||
<Skeleton width="25%" height="1rem" class="mr-1" />
|
||||
<Skeleton width="20%" height="1rem" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<Skeleton width="20%" height="1rem" class="mb-2" />
|
||||
<Skeleton width="100%" height="2.5rem" class="mb-4" />
|
||||
<Skeleton width="25%" height="1rem" class="mb-4" />
|
||||
<Skeleton width="100%" height="2.5rem" class="mb-6" />
|
||||
<Skeleton width="80%" height="1rem" class="mb-4" />
|
||||
<Skeleton width="100%" height="2.5rem" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-8">
|
||||
<div class="flex-1 border-t border-gray-300"></div>
|
||||
<Skeleton width="30%" height="1rem" class="mx-4" />
|
||||
<div class="flex-1 border-t border-gray-300"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<Skeleton width="100%" height="2.5rem" />
|
||||
<Skeleton width="100%" height="2.5rem" />
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<Skeleton width="70%" height="0.875rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col min-h-[638px] min-w-[320px]">
|
||||
<Skeleton width="100%" height="0.5rem" class="mb-8" />
|
||||
|
||||
<div class="p-0 flex-1 flex flex-col">
|
||||
<div class="flex-1 min-h-full flex flex-col justify-between">
|
||||
<div>
|
||||
<Skeleton width="70%" height="1.75rem" class="mb-8" />
|
||||
<div class="flex flex-col gap-6">
|
||||
<div v-for="i in 5" :key="i" class="flex items-center gap-3">
|
||||
<Skeleton width="1.25rem" height="1.25rem" shape="circle" />
|
||||
<Skeleton width="85%" height="0.875rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between pt-4">
|
||||
<span />
|
||||
<Skeleton width="100%" height="2.5rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center p-8">
|
||||
<div class="w-full max-w-md text-center">
|
||||
<div class="mb-8">
|
||||
<Skeleton width="80%" height="3rem" class="mb-2 mx-auto" />
|
||||
<Skeleton width="60%" height="3rem" class="mx-auto" />
|
||||
</div>
|
||||
<div class="max-w-[320px] mx-auto">
|
||||
<div class="mb-4">
|
||||
<Skeleton width="90%" height="1.5rem" class="mb-2 mx-auto" />
|
||||
<Skeleton width="70%" height="1.5rem" class="mx-auto" />
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton width="80%" height="1.5rem" class="mb-2 mx-auto" />
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<Skeleton width="20%" height="1.5rem" />
|
||||
<Skeleton width="15%" height="1.5rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
</script>
|
||||
Reference in New Issue
Block a user