mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-05 13:10:24 +00:00
feat: add email verification check for cloud onboarding (#5636)
## Summary
- Added email verification flow for new users during onboarding
- Implemented invite code claiming with proper validation
- Updated API endpoints from `/invite/` to `/invite_code/` for
consistency
## Changes
### Email Verification
- Added `CloudVerifyEmailView` component with email verification UI
- Added email verification check after login in `CloudLoginView`
- Added `isEmailVerified` property to Firebase auth store
- Users must verify email before claiming invite codes
### Invite Code Flow
- Enhanced `CloudClaimInviteView` with full claim invite functionality
- Updated `InviteCheckView` to route users based on email verification
status
- Modified API to return both `claimed` and `expired` status for invite
codes
- Added proper error handling and Sentry logging for invite operations
### API Updates
- Changed endpoint paths from `/invite/` to `/invite_code/`
- Updated `getInviteCodeStatus()` to return `{ claimed: boolean;
expired: boolean }`
- Updated `claimInvite()` to return `{ success: boolean; message: string
}`
### UI/UX Improvements
- Added Korean translations for all new strings
- Improved button styling and layout in survey and waitlist views
- Added proper loading states and error handling
## Test Plan
- [ ] Test new user signup flow with email verification
- [ ] Test invite code validation (expired/claimed/valid codes)
- [ ] Test email verification redirect flow
- [ ] Test invite claiming after email verification
- [ ] Verify Korean translations display correctly
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,27 +1,159 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-center items-center h-screen font-mono text-black gap-4"
|
||||
>
|
||||
<h1 class="text-2xl">
|
||||
{{ t('cloudClaimInvite_processingTitle') }}
|
||||
</h1>
|
||||
<button
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 cursor-pointer"
|
||||
@click="onClaim"
|
||||
>
|
||||
{{ t('cloudClaimInvite_claimButton') }}
|
||||
</button>
|
||||
<div class="flex items-center bg-neutral-900 text-neutral-100">
|
||||
<main class="w-full max-w-md px-6 py-12 text-center" role="main">
|
||||
<!-- Title -->
|
||||
<h1
|
||||
class="text-white font-abcrom font-black italic uppercase my-0 text-3xl"
|
||||
>
|
||||
{{ t('cloudInvite_title') }}
|
||||
</h1>
|
||||
|
||||
<!-- Subtitle -->
|
||||
<p v-if="inviteCodeClaimed" class="mt-6 text-amber-500 leading-relaxed">
|
||||
{{ t('cloudInvite_alreadyClaimed_prefix') }}
|
||||
<strong>{{ userEmail }}</strong>
|
||||
</p>
|
||||
<p
|
||||
v-else-if="inviteCodeExpired"
|
||||
class="mt-6 text-amber-500 leading-relaxed"
|
||||
>
|
||||
{{ t('cloudInvite_expired_prefix') }}
|
||||
</p>
|
||||
<p v-else class="mt-6 text-neutral-300 leading-relaxed">
|
||||
{{ t('cloudInvite_subtitle') }}
|
||||
</p>
|
||||
|
||||
<div v-if="inviteCodeClaimed || inviteCodeExpired" class="mb-2">
|
||||
<span
|
||||
class="text-blue-400 no-underline cursor-pointer"
|
||||
@click="onClickSupport"
|
||||
>
|
||||
{{ t('cloudInvite_contactLink') }}</span
|
||||
>
|
||||
<span class="text-neutral-400 ml-2">
|
||||
{{ t('cloudInvite_contactLink_suffix') }}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="text-blue-400 no-underline cursor-pointer"
|
||||
@click="onSwitchAccounts"
|
||||
>
|
||||
{{ t('cloudInvite_switchAccounts') }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Signed in as -->
|
||||
<section class="mt-10">
|
||||
<p class="text-sm">
|
||||
{{ t('cloudInvite_signedInAs') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-4 flex flex-col items-center justify-center gap-4">
|
||||
<!-- Avatar box -->
|
||||
<div
|
||||
class="relative grid place-items-center h-28 w-28 rounded-2xl border border-neutral-700 bg-neutral-800 shadow-inner"
|
||||
>
|
||||
<span class="text-5xl font-semibold select-none">{{
|
||||
userInitial
|
||||
}}</span>
|
||||
<!-- subtle ring to mimic screenshot gradient border -->
|
||||
<span
|
||||
class="pointer-events-none absolute inset-0 rounded-2xl ring-1 ring-inset ring-neutral-600/40"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<div class="text-left">
|
||||
<div class="text-sm break-all">
|
||||
{{ userEmail }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
:label="
|
||||
processing
|
||||
? t('cloudInvite_processing')
|
||||
: t('cloudInvite_acceptButton')
|
||||
"
|
||||
class="w-full h-12 font-medium mt-12 text-white"
|
||||
:disabled="processing || inviteCodeClaimed || inviteCodeExpired"
|
||||
@click="onClaim"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { claimInvite, getInviteCodeStatus } from '@/api/auth'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const onClaim = () => {
|
||||
void router.push({ name: 'cloud-user-check' })
|
||||
const processing = ref(false)
|
||||
const inviteCodeExpired = ref(false)
|
||||
const inviteCodeClaimed = ref(false)
|
||||
|
||||
const { userEmail } = useFirebaseAuthStore()
|
||||
|
||||
const inviteCode = computed(() => route.query.inviteCode as string)
|
||||
const userInitial = computed(() => (userEmail?.[0] || 'U').toUpperCase())
|
||||
|
||||
const onSwitchAccounts = () => {
|
||||
void router.push({
|
||||
name: 'cloud-login',
|
||||
query: { inviteCode: inviteCode.value }
|
||||
})
|
||||
}
|
||||
const onClickSupport = () => {
|
||||
window.open('https://support.comfy.org', '_blank', 'noopener')
|
||||
}
|
||||
|
||||
const onClaim = async () => {
|
||||
try {
|
||||
try {
|
||||
if (inviteCode.value) {
|
||||
processing.value = true
|
||||
const response = await claimInvite(inviteCode.value)
|
||||
if (response.success) {
|
||||
await router.push({ name: 'cloud-user-check' })
|
||||
}
|
||||
} else {
|
||||
await router.push({ name: 'cloud-login' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to claim invite:', err)
|
||||
} finally {
|
||||
processing.value = false
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Unexpected error in onClaim:', e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
try {
|
||||
const response = await getInviteCodeStatus(inviteCode.value)
|
||||
inviteCodeExpired.value = response.expired
|
||||
inviteCodeClaimed.value = response.claimed
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch invite code status:', err)
|
||||
await router.push({ name: 'cloud-login' })
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Unexpected error in onMounted:', e)
|
||||
await router.push({ name: 'cloud-login' })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(async () => {
|
||||
const inviteCode = route.params.inviteCode
|
||||
const inviteCode = route.params.code
|
||||
await router.push({
|
||||
name: 'cloud-login',
|
||||
query: {
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
<template>
|
||||
<div class="h-full flex items-center justify-center p-8">
|
||||
<div class="lg:w-96 max-w-[100vw] p-2">
|
||||
<div class="bg-[#2d2e32] p-4 rounded-lg">
|
||||
<h4 class="m-0 pb-2 text-lg">
|
||||
{{ t('cloudPrivateBeta_title') }}
|
||||
</h4>
|
||||
<p class="m-0 text-base leading-6">
|
||||
{{ t('cloudPrivateBeta_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
<template v-if="!hasInviteCode">
|
||||
<div class="bg-[#2d2e32] p-4 rounded-lg">
|
||||
<h4 class="m-0 pb-2 text-lg">
|
||||
{{ t('cloudPrivateBeta_title') }}
|
||||
</h4>
|
||||
<p class="m-0 text-base leading-6">
|
||||
{{ t('cloudPrivateBeta_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 mt-6 mb-8">
|
||||
<h1 class="text-xl font-medium leading-normal my-0">
|
||||
{{ t('auth.login.title') }}
|
||||
</h1>
|
||||
<p class="text-base my-0">
|
||||
<span class="text-muted">{{ t('auth.login.newUser') }}</span>
|
||||
<span
|
||||
class="ml-1 cursor-pointer text-blue-500"
|
||||
@click="navigateToSignup"
|
||||
>{{ t('auth.login.signUp') }}</span
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 mt-6 mb-8">
|
||||
<h1 class="text-xl font-medium leading-normal my-0">
|
||||
{{ t('auth.login.title') }}
|
||||
</h1>
|
||||
<p class="text-base my-0">
|
||||
<span class="text-muted">{{ t('auth.login.newUser') }}</span>
|
||||
<span
|
||||
class="ml-1 cursor-pointer text-blue-500"
|
||||
@click="navigateToSignup"
|
||||
>{{ t('auth.login.signUp') }}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex flex-col gap-1 mt-6 mb-8">
|
||||
<h1
|
||||
class="text-white font-abcrom font-black italic uppercase my-0 text-2xl"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
{{ t('cloudStart_invited') }}
|
||||
</h1>
|
||||
<p class="text-base my-0">
|
||||
<span class="text-muted">{{ t('cloudStart_invited_signin') }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Message v-if="!isSecureContext" severity="warn" class="mb-4">
|
||||
{{ t('auth.login.insecureContextWarning') }}
|
||||
@@ -61,7 +76,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Terms & Contact -->
|
||||
<p class="mt-5 text-sm text-gray-600">
|
||||
<p v-if="!hasInviteCode" class="mt-5 text-sm text-gray-600">
|
||||
{{ t('cloudWaitlist_questionsText') }}
|
||||
<a
|
||||
href="https://support.comfy.org"
|
||||
@@ -72,6 +87,15 @@
|
||||
{{ t('cloudWaitlist_contactLink') }}</a
|
||||
>.
|
||||
</p>
|
||||
<p v-else class="mt-5 text-sm text-gray-600">
|
||||
{{ t('cloudStart_invited_signup_title') }}
|
||||
<span
|
||||
class="text-blue-400 no-underline cursor-pointer"
|
||||
@click="navigateToSignup"
|
||||
>
|
||||
{{ t('cloudStart_invited_signup_description') }}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -80,13 +104,14 @@
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import Message from 'primevue/message'
|
||||
import { ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import CloudSignInForm from '@/platform/onboarding/cloud/components/CloudSignInForm.vue'
|
||||
import { type SignInData } from '@/schemas/signInSchema'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { translateAuthError } from '@/utils/authErrorTranslation'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -96,23 +121,29 @@ const authActions = useFirebaseAuthActions()
|
||||
const isSecureContext = window.isSecureContext
|
||||
const authError = ref('')
|
||||
|
||||
const hasInviteCode = computed(() => !!route.query.inviteCode)
|
||||
|
||||
const navigateToSignup = () => {
|
||||
void router.push({ name: 'cloud-signup', query: route.query })
|
||||
}
|
||||
|
||||
const onSuccess = async () => {
|
||||
// Check if there's an invite code
|
||||
const inviteCode = route.query.inviteCode as string
|
||||
|
||||
if (inviteCode) {
|
||||
// Handle invite code flow - go to invite check
|
||||
await router.push({
|
||||
name: 'cloud-invite-check',
|
||||
query: { inviteCode }
|
||||
})
|
||||
const inviteCode = route.query.inviteCode as string | undefined
|
||||
const { isEmailVerified } = useFirebaseAuthStore()
|
||||
if (!isEmailVerified) {
|
||||
await router.push({ name: 'cloud-verify-email', query: { inviteCode } })
|
||||
} else {
|
||||
// Normal login flow - go to user check
|
||||
await router.push({ name: 'cloud-user-check' })
|
||||
if (inviteCode) {
|
||||
// Handle invite code flow - go to invite check
|
||||
await router.push({
|
||||
name: 'cloud-invite-check',
|
||||
query: { inviteCode }
|
||||
})
|
||||
} else {
|
||||
// Normal login flow - go to user check
|
||||
await router.push({ name: 'cloud-user-check' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,4 +179,8 @@ const signInWithEmail = async (values: SignInData) => {
|
||||
await onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await authActions.logout()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -117,9 +117,7 @@ const navigateToLogin = () => {
|
||||
}
|
||||
|
||||
const onSuccess = async () => {
|
||||
// After successful signup, always go to user check
|
||||
// The user check will handle routing based on their status
|
||||
await router.push({ name: 'cloud-user-check' })
|
||||
await router.push({ name: 'cloud-login', query: route.query })
|
||||
}
|
||||
|
||||
// Custom error handler for inline display
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<Button
|
||||
label="Next"
|
||||
:disabled="!validStep1"
|
||||
class="w-full h-10 bg-gray-800 border-none text-white"
|
||||
class="w-full h-10 border-none text-white"
|
||||
@click="goTo(2, activateCallback)"
|
||||
/>
|
||||
</div>
|
||||
@@ -93,13 +93,13 @@
|
||||
<Button
|
||||
label="Back"
|
||||
severity="secondary"
|
||||
class="border border-white text-white flex-1"
|
||||
class="text-white flex-1"
|
||||
@click="goTo(1, activateCallback)"
|
||||
/>
|
||||
<Button
|
||||
label="Next"
|
||||
:disabled="!validStep2"
|
||||
class="flex-1 h-10 bg-gray-800 border-none text-white"
|
||||
class="flex-1 h-10 text-white"
|
||||
@click="goTo(3, activateCallback)"
|
||||
/>
|
||||
</div>
|
||||
@@ -146,13 +146,13 @@
|
||||
<Button
|
||||
label="Back"
|
||||
severity="secondary"
|
||||
class="border border-white text-white flex-1"
|
||||
class="text-white flex-1"
|
||||
@click="goTo(2, activateCallback)"
|
||||
/>
|
||||
<Button
|
||||
label="Next"
|
||||
:disabled="!validStep3"
|
||||
class="flex-1 h-10 bg-gray-800 border-none text-white"
|
||||
class="flex-1 h-10 border-none text-white"
|
||||
@click="goTo(4, activateCallback)"
|
||||
/>
|
||||
</div>
|
||||
@@ -191,14 +191,14 @@
|
||||
<Button
|
||||
label="Back"
|
||||
severity="secondary"
|
||||
class="border border-white text-white flex-1"
|
||||
class="text-white flex-1"
|
||||
@click="goTo(3, activateCallback)"
|
||||
/>
|
||||
<Button
|
||||
label="Submit"
|
||||
:disabled="!validStep4 || isSubmitting"
|
||||
:loading="isSubmitting"
|
||||
class="flex-1 h-10 bg-gray-800 border-none text-white"
|
||||
class="flex-1 h-10 border-none text-white"
|
||||
@click="onSubmitSurvey"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>{{ t('cloudVerifyEmail_title') }}</h1>
|
||||
<div class="px-6 py-8 max-w-[640px] mx-auto">
|
||||
<!-- Back button -->
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center size-10 rounded-lg bg-transparent border border-white text-foreground/80"
|
||||
aria-label="{{ t('cloudVerifyEmail_back') }}"
|
||||
@click="goBack"
|
||||
>
|
||||
<i class="pi pi-arrow-left" />
|
||||
</button>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="mt-8 text-2xl font-semibold">
|
||||
{{ t('cloudVerifyEmail_title') }}
|
||||
</h1>
|
||||
|
||||
<!-- Body copy -->
|
||||
<p class="mt-6 text-base text-foreground/80">
|
||||
{{ t('cloudVerifyEmail_sent') }}
|
||||
</p>
|
||||
<p class="mt-3 text-base font-medium">{{ authStore.userEmail }}</p>
|
||||
|
||||
<p class="mt-6 text-base text-foreground/80">
|
||||
{{ t('cloudVerifyEmail_clickToContinue') }}
|
||||
</p>
|
||||
|
||||
<p class="mt-10 text-base text-foreground/80">
|
||||
{{ t('cloudVerifyEmail_didntReceive') }}
|
||||
<span class="text-blue-400 no-underline cursor-pointer" @click="onSend">
|
||||
{{ t('cloudVerifyEmail_resend') }}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
// import { verifyEmail } from '@/api/auth'
|
||||
import router from '@/router'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
|
||||
const authStore = useFirebaseAuthStore()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const goBack = async () => {
|
||||
const inviteCode = route.query.inviteCode as string | undefined
|
||||
const authStore = useFirebaseAuthStore()
|
||||
// If the user is already verified (email link already clicked),
|
||||
// continue to the next step automatically.
|
||||
if (authStore.isEmailVerified) {
|
||||
await router.push({
|
||||
name: 'cloud-invite-check',
|
||||
query: inviteCode ? { inviteCode } : {}
|
||||
})
|
||||
} else {
|
||||
await router.push({
|
||||
name: 'cloud-login',
|
||||
query: {
|
||||
inviteCode
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function onSend() {
|
||||
try {
|
||||
await authStore.verifyEmail()
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: t('cloudVerifyEmail_toast_success', {
|
||||
email: authStore.userEmail
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('cloudVerifyEmail_toast_failed')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// verifyEmail()
|
||||
await router.push({ name: 'cloud-invite-check' })
|
||||
// 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()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -5,21 +5,10 @@
|
||||
{{ t('cloudWaitlist_titleLine1') }}<br />
|
||||
{{ t('cloudWaitlist_titleLine2') }}
|
||||
</h1>
|
||||
<div class="max-w-[320px] text-lg font-light">
|
||||
<div class="max-w-[320px] text-lg font-light m-auto">
|
||||
<p class="text-white">
|
||||
{{ t('cloudWaitlist_message') }}
|
||||
</p>
|
||||
<p class="text-white">
|
||||
{{ t('cloudWaitlist_questionsText') }}
|
||||
<a
|
||||
href="https://support.comfy.org"
|
||||
class="text-blue-400 no-underline cursor-pointer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ t('cloudWaitlist_contactLink') }}</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
import { nextTick, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { getInviteCodeStatus } from '@/api/auth'
|
||||
|
||||
import CloudClaimInviteViewSkeleton from './skeletons/CloudClaimInviteViewSkeleton.vue'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -17,19 +15,17 @@ onMounted(async () => {
|
||||
await nextTick()
|
||||
|
||||
const inviteCode = route.query.inviteCode as string
|
||||
const inviteCodeStatus = await getInviteCodeStatus(inviteCode)
|
||||
|
||||
// TODO: should be deleted when api is ready
|
||||
// if (!status.emailVerified) {
|
||||
// await router.push({ name: 'cloud-verify-email' })
|
||||
// return
|
||||
// }
|
||||
|
||||
if (inviteCodeStatus.expired) {
|
||||
await router.push({ name: 'cloud-sorry-contact-support' })
|
||||
try {
|
||||
// Basic guard: missing invite code -> send to support
|
||||
if (!inviteCode || typeof inviteCode !== 'string') {
|
||||
await router.push({ name: 'cloud-sorry-contact-support' })
|
||||
return
|
||||
}
|
||||
await router.push({ name: 'cloud-claim-invite', query: { inviteCode } })
|
||||
} catch (e) {
|
||||
window.open('https://support.comfy.org', '_blank', 'noopener')
|
||||
return
|
||||
}
|
||||
|
||||
await router.push({ name: 'cloud-claim-invite' })
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center min-h-screen">
|
||||
<div v-else class="flex items-center justify-center">
|
||||
<ProgressSpinner class="w-8 h-8" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user