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:
Jin Yi
2025-09-21 12:29:56 +09:00
committed by GitHub
parent d3a5d9e995
commit 8ca541e850
15 changed files with 423 additions and 130 deletions

View File

@@ -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>