[feat] Cloud onboarding flow implementation (#5494)

* feature: cloud onboarding scaffolding

* fix: redirect unknown routes

* feature: cloud onboarding flow

* chore: code review

* test: api mock for test failing

* refactor: Centralize onboarding routing with dedicated check views

- Add UserCheckView to handle all user status routing decisions
- Add InviteCheckView to manage invite code validation flow
- Simplify auth.ts by removing async operations and extra complexity
- Update login/signup to always redirect through UserCheckView
- Remove distributed routing logic from all onboarding components
- Simplify router guards to delegate to check views
- Fix infinite redirect loops for non-whitelisted users
- Use window.location.href for final navigation to bypass router conflicts

Breaking changes:
- Removed claimInvite from auth.ts (moved to CloudClaimInviteView)
- Changed route names to use cloud- prefix consistently
- Simplified getMe() to synchronous function

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

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

* fix: delete unused file

* Revert "test: api mock for test failing"

This reverts commit 06ca56c05e.

* feature: API applied

* feature: survey view

* feature: signup / login view completed

* style: min-h-screen deleted

* feature: completed login flow

* feature: router view added

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jin Yi
2025-09-15 08:36:57 +09:00
committed by GitHub
parent f1fbab6e1f
commit 59a1380f39
26 changed files with 1678 additions and 27 deletions

91
src/api/auth.ts Normal file
View File

@@ -0,0 +1,91 @@
import { isEmpty } from 'es-toolkit/compat'
import { api } from '@/scripts/api'
export interface UserCloudStatus {
status: 'active' | 'waitlisted'
}
const ONBOARDING_SURVEY_KEY = 'onboarding_survey'
export async function getUserCloudStatus(): Promise<UserCloudStatus> {
const response = await api.fetchApi('/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
if (!response.ok) {
throw new Error(`Failed to get user: ${response.statusText}`)
}
return response.json()
}
export async function getInviteCodeStatus(
inviteCode: string
): Promise<{ expired: boolean }> {
const response = await api.fetchApi(
`/invite/${encodeURIComponent(inviteCode)}/status`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
)
if (!response.ok) {
throw new Error(`Failed to get invite code status: ${response.statusText}`)
}
return response.json()
}
export async function getSurveyCompletedStatus(): Promise<boolean> {
const response = await api.fetchApi(`/settings/${ONBOARDING_SURVEY_KEY}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
if (!response.ok) {
return false
}
const data = await response.json()
// Check if data exists and is not empty
return !isEmpty(data.value)
}
export async function postSurveyStatus(): Promise<void> {
await api.fetchApi(`/settings/${ONBOARDING_SURVEY_KEY}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ [ONBOARDING_SURVEY_KEY]: undefined })
})
}
export async function submitSurvey(
survey: Record<string, unknown>
): Promise<void> {
const response = await api.fetchApi('/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ [ONBOARDING_SURVEY_KEY]: survey })
})
if (!response.ok) {
throw new Error(`Failed to submit survey: ${response.statusText}`)
}
}
export async function claimInvite(code: string): Promise<void> {
const res = await api.fetchApi(`/invite/${encodeURIComponent(code)}/claim`, {
method: 'POST'
})
if (!res.ok) {
throw new Error(`Failed to claim invite: ${res.status} ${res.statusText}`)
}
}