Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions messages/en.context.json
Original file line number Diff line number Diff line change
Expand Up @@ -2050,6 +2050,10 @@
"onboarding-source-manual": "Used in Capgo web console areas: pages/admin/dashboard. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-source-mcp": "Used in Capgo web console areas: pages/admin/dashboard. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-trend": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-welcome-cta": "Used in Capgo web console areas: components/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-welcome-description": "Used in Capgo web console areas: components/dashboard. Role: helper or description text. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-welcome-eyebrow": "Used in Capgo web console areas: components/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-welcome-title": "Used in Capgo web console areas: components/dashboard. Role: section or dialog title. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-wizard-abandoned": "Used in Capgo web console areas: pages/admin/dashboard. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-wizard-completed": "Used in Capgo web console areas: pages/admin/dashboard. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"onboarding-wizard-dropoff": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
Expand Down
4 changes: 4 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,10 @@
"onboarding-resume-restart": "Restart",
"onboarding-resume-title": "Continue where you left off?",
"onboarding-trend": "Onboarding Trend",
"onboarding-welcome-cta": "Set up my first update",
"onboarding-welcome-description": "You’re a few steps away from the magic of over-the-air updates — and so much more.",
"onboarding-welcome-eyebrow": "Good to have you here",
"onboarding-welcome-title": "Welcome to Capgo",
"onboarding-wizard-abandoned": "Abandoned",
"onboarding-wizard-completed": "Completed",
"onboarding-wizard-dropoff": "Onboarding wizard drop-off",
Expand Down
9 changes: 9 additions & 0 deletions playwright/e2e/register.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ async function loginToOnboarding(page: Page, email: string, password: string) {
await page.login(email, password, /\/onboarding\/app/)
}

async function continuePastWelcome(page: Page) {
const continueButton = page.locator('[data-test="onboarding-welcome-continue"]')
await expect(continueButton).toBeVisible()
await continueButton.click()
}

async function expectProtectedRouteRedirect(page: Page, targetPath: string, expectedUrl: RegExp, expectedSelector: string) {
const redirectedPage = await page.context().newPage()

Expand Down Expand Up @@ -39,6 +45,7 @@ test.describe('Registration', () => {
await page.click('[data-test="submit"]')

await page.waitForURL(/\/onboarding\/app/)
await continuePastWelcome(page)
await page.click('[data-test="onboarding-intent-ota"]')
await page.click('[data-test="app-onboarding-continue-intent"]')

Expand Down Expand Up @@ -88,6 +95,7 @@ test.describe('Registration', () => {
await page.click('[data-test="submit"]')

await page.waitForURL(/\/onboarding\/app/)
await continuePastWelcome(page)
await page.click('[data-test="onboarding-intent-ota"]')
await page.click('[data-test="app-onboarding-continue-intent"]')
await page.fill('[data-test="app-onboarding-name"]', appName)
Expand All @@ -114,6 +122,7 @@ test.describe('Registration', () => {
await loginToOnboarding(page, email, password)
await expect(page.locator('[data-test="onboarding-resume-restart"]')).toBeVisible()
await page.locator('[data-test="onboarding-resume-restart"]').click()
await continuePastWelcome(page)
await expect(page.locator('[data-test="onboarding-intent-ota"]')).toBeVisible()
await expect(page.locator('[data-test="onboarding-org-name"]')).toHaveCount(0)
})
Expand Down
4 changes: 3 additions & 1 deletion src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ declare module 'vue' {
AppOnboardingCliSteps: typeof import('./components/dashboard/AppOnboardingCliSteps.vue')['default']
AppOnboardingFlow: typeof import('./components/dashboard/AppOnboardingFlow.vue')['default']
AppOnboardingIconInput: typeof import('./components/dashboard/AppOnboardingIconInput.vue')['default']
AppOnboardingNextStep: typeof import('./components/dashboard/AppOnboardingNextStep.vue')['default']
AppOnboardingWelcome: typeof import('./components/dashboard/AppOnboardingWelcome.vue')['default']
AppPageFrame: typeof import('./components/dashboard/AppPageFrame.vue')['default']
AppPageNotFound: typeof import('./components/dashboard/AppPageNotFound.vue')['default']
AppSetting: typeof import('./components/dashboard/AppSetting.vue')['default']
Expand Down Expand Up @@ -93,6 +93,7 @@ declare module 'vue' {
Navbar: typeof import('./components/Navbar.vue')['default']
OnboardingExploreBanner: typeof import('./components/dashboard/OnboardingExploreBanner.vue')['default']
OnboardingSupportUsernames: typeof import('./components/dashboard/OnboardingSupportUsernames.vue')['default']
OrganizationOnboardingInvite: typeof import('./components/dashboard/OrganizationOnboardingInvite.vue')['default']
OrgEmailNotificationsPanel: typeof import('./components/dashboard/OrgEmailNotificationsPanel.vue')['default']
OrgNotificationStatsPanel: typeof import('./components/dashboard/OrgNotificationStatsPanel.vue')['default']
PageLoader: typeof import('./components/PageLoader.vue')['default']
Expand Down Expand Up @@ -121,6 +122,7 @@ declare module 'vue' {
TableLog: typeof import('./components/TableLog.vue')['default']
Tabs: typeof import('./components/Tabs.vue')['default']
TabSidebar: typeof import('./components/TabSidebar.vue')['default']
TechnicalTeammateInviteCard: typeof import('./components/dashboard/TechnicalTeammateInviteCard.vue')['default']
Toast: typeof import('./components/Toast.vue')['default']
Toggle: typeof import('./components/Toggle.vue')['default']
TrialBanner: typeof import('./components/dashboard/TrialBanner.vue')['default']
Expand Down
34 changes: 31 additions & 3 deletions src/components/dashboard/AppOnboardingFlow.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Database, Json } from '~/types/supabase.types'
import type {
OnboardingAnalyticsStep,
OnboardingCopyEvent,
OnboardingDetailsEvent,
OnboardingDetailsEventProperties,
Expand Down Expand Up @@ -79,6 +80,7 @@ import {
} from '~/utils/userOnboardingProgress'
import AppOnboardingCliSteps from './AppOnboardingCliSteps.vue'
import AppOnboardingIconInput from './AppOnboardingIconInput.vue'
import AppOnboardingWelcome from './AppOnboardingWelcome.vue'
import OrganizationOnboardingInvite from './OrganizationOnboardingInvite.vue'
import TechnicalTeammateInviteCard from './TechnicalTeammateInviteCard.vue'

Expand All @@ -99,6 +101,7 @@ const onboardingUserId = computed(() => main.user?.id ?? main.auth?.id ?? null)
const config = getLocalConfig()
const onboardingTelemetry = createOnboardingTelemetryIdentity({ flow: props.preOrg ? 'pre_org' : 'existing_org', supaHost: config.supaHost })
const STORE_ICON_FETCH_TIMEOUT_MS = 10_000
const WELCOME_CANVAS_MEDIA_QUERY = '(min-width: 640px) and (min-height: 640px)'
const removeBeforeUnloadWarning = useBeforeUnloadWarning(Boolean(props.preOrg))

type AppRow = Omit<Database['public']['Tables']['apps']['Row'], 'onboarding'> & {
Expand All @@ -124,6 +127,9 @@ interface OrganizationWebsitePreview {

const isLoading = ref(true)
const isHydratingOnboarding = ref(true)
const welcomeCanvasEligible = ref(false)
const welcomePending = ref(false)
const showPreOrgWelcome = computed(() => props.preOrg && welcomeCanvasEligible.value && welcomePending.value)
const isSubmitting = ref(false)
const isImportingStore = ref(false)
const isResumeIconLoading = ref(false)
Expand Down Expand Up @@ -365,7 +371,10 @@ function trackOrganizationEvent(
const detailsFieldTracker = createOnboardingDetailsFieldDebouncer(trackDetailsEvent)

function initializeProgressTracking(resumed: boolean) {
const trackedSteps = appOnboardingSteps.value.map(step => step.id)
const initialStep: OnboardingAnalyticsStep = showPreOrgWelcome.value ? 'welcome' : flowStep.value
const trackedSteps: OnboardingAnalyticsStep[] = appOnboardingSteps.value.map(step => step.id)
if (initialStep === 'welcome')
trackedSteps.unshift('welcome')
if (!props.preOrg && resumed && flowStep.value === 'setup')
trackedSteps.push('setup')

Expand All @@ -377,7 +386,7 @@ function initializeProgressTracking(resumed: boolean) {
onboardingAttemptId: onboardingTelemetry.attemptId,
onboardingRunId: onboardingTelemetry.runId,
})
progressTracker.viewStep(flowStep.value)
progressTracker.viewStep(initialStep)
if (pendingDashboardExplored)
trackDashboardExplored()
}
Expand Down Expand Up @@ -544,6 +553,17 @@ function resetOnboardingForm() {
resetStoreImportState()
}

function showWelcomeOnDesktop() {
welcomePending.value = Boolean(props.preOrg && welcomeCanvasEligible.value)
}

function continueFromWelcome() {
const nextStep = flowStep.value
progressTracker?.completeStep('welcome', { nextStep })
progressTracker?.viewStep(nextStep, 'welcome')
welcomePending.value = false
}

function applyOnboardingProgress(progress: ReturnType<typeof parseUserOnboardingProgress>) {
if (!progress)
return
Expand Down Expand Up @@ -594,6 +614,7 @@ async function maybeResumeSavedOnboarding() {
else {
applyDefaultPreOrgDetails()
}
showWelcomeOnDesktop()
return false
}

Expand Down Expand Up @@ -624,6 +645,7 @@ async function maybeResumeSavedOnboarding() {
resetOnboardingForm()
existingApp.value = true
existingAppSetup.value = 'manual'
showWelcomeOnDesktop()
return false
}

Expand Down Expand Up @@ -1606,6 +1628,7 @@ function trackDashboardExplored() {

onMounted(async () => {
window.addEventListener(ONBOARDING_DASHBOARD_EXPLORED_EVENT, trackDashboardExplored)
welcomeCanvasEligible.value = window.matchMedia(WELCOME_CANVAS_MEDIA_QUERY).matches
let resumedFlow = false
isLoading.value = true
isHydratingOnboarding.value = true
Expand Down Expand Up @@ -1723,7 +1746,12 @@ defineExpose({
</script>

<template>
<section class="min-h-full overflow-y-auto bg-slate-50 px-4 py-6 sm:px-6 lg:px-8 dark:bg-slate-950">
<AppOnboardingWelcome
v-if="showPreOrgWelcome && !isLoading"
@continue="continueFromWelcome"
/>

<section v-else class="h-full min-h-0 overflow-y-auto bg-slate-50 px-4 py-6 sm:px-6 lg:px-8 dark:bg-slate-950">
<div class="mx-auto w-full max-w-3xl">
<div v-if="isLoading" class="flex min-h-[50vh] items-center justify-center">
<Spinner size="w-32 h-32" />
Expand Down
Loading
Loading