Skip to content

Commit

Permalink
Use FF to show old onboarding flow
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewwallacespeckle committed Feb 26, 2025
1 parent 1287a92 commit 4613521
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 7 deletions.
101 changes: 101 additions & 0 deletions packages/frontend-2/components/onboarding/JoinTeammates.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<!-- FF-CLEANUP: Remove when workspaces plans released -->
<template>
<div class="flex flex-col items-center gap-2 w-full">
<CommonCard
v-for="workspace in workspaces"
:key="workspace.id"
class="w-full bg-foundation"
>
<div class="flex gap-4">
<div>
<WorkspaceAvatar :name="workspace.name" :logo="workspace.logo" size="xl" />
</div>
<div class="flex flex-col sm:flex-row gap-4 justify-between flex-1">
<div class="flex flex-col flex-1">
<h6 class="text-heading-sm">{{ workspace.name }}</h6>
<p class="text-body-2xs text-foreground-2">{{ workspace.description }}</p>
</div>
<FormButton
color="outline"
size="sm"
:loading="loadingStates[workspace.id]"
:disabled="requestedWorkspaces.includes(workspace.id)"
@click="() => processRequest(true, workspace.id)"
>
{{
requestedWorkspaces.includes(workspace.id)
? 'Requested'
: 'Request to join'
}}
</FormButton>
</div>
</div>
</CommonCard>
<div class="mt-2 w-full">
<FormButton size="lg" full-width @click="$emit('next')">Continue</FormButton>
</div>
</div>
</template>

<script setup lang="ts">
import type { LimitedWorkspace } from '~/lib/common/generated/gql/graphql'
import { dashboardRequestToJoinWorkspaceMutation } from '~~/lib/dashboard/graphql/mutations'
import {
convertThrowIntoFetchResult,
getFirstErrorMessage
} from '~~/lib/common/helpers/graphql'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useMutation } from '@vue/apollo-composable'

defineProps<{
workspaces: LimitedWorkspace[]
}>()

defineEmits(['next'])

const mixpanel = useMixpanel()
const { triggerNotification } = useGlobalToast()

const loadingStates = ref<Record<string, boolean>>({})

const { mutate: requestToJoin } = useMutation(dashboardRequestToJoinWorkspaceMutation)

const requestedWorkspaces = ref<string[]>([])

const processRequest = async (accept: boolean, workspaceId: string) => {
if (accept) {
loadingStates.value[workspaceId] = true

try {
const result = await requestToJoin({
input: { workspaceId }
}).catch(convertThrowIntoFetchResult)

if (result?.data) {
requestedWorkspaces.value.push(workspaceId)
mixpanel.track('Workspace Join Request Sent', {
workspaceId,
location: 'onboarding',
// eslint-disable-next-line camelcase
workspace_id: workspaceId
})

triggerNotification({
title: 'Request sent',
description: 'Your request to join the workspace has been sent.',
type: ToastNotificationType.Success
})
} else {
const errorMessage = getFirstErrorMessage(result?.errors)
triggerNotification({
title: 'Failed to send request',
description: errorMessage,
type: ToastNotificationType.Danger
})
}
} finally {
loadingStates.value[workspaceId] = false
}
}
}
</script>
6 changes: 5 additions & 1 deletion packages/frontend-2/lib/common/helpers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const loginRoute = '/authn/login'
export const registerRoute = '/authn/register'
export const ssoLoginRoute = '/authn/sso'
export const forgottenPasswordRoute = '/authn/forgotten-password'
export const onboardingRoute = '/onboarding'
export const verifyEmailRoute = '/verify-email'
export const verifyEmailCountdownRoute = '/verify-email?source=registration'
export const serverManagementRoute = '/server-management'
Expand All @@ -24,6 +23,11 @@ export const defaultZapierWebhookUrl =
'https://hooks.zapier.com/hooks/catch/12120532/2m4okri/'
export const guideBillingUrl = 'https://speckle.guide/workspaces/billing.html'

// FF-CLEANUP: Remove when workspaces plans released
// Onboarding routes - To be updated when new billing FF is live
export const onboardingJoinRoute = '/onboarding-join'
export const onboardingRoute = '/onboarding'

export const settingsUserRoutes = {
profile: '/settings/user/profile',
notifications: '/settings/user/notifications',
Expand Down
24 changes: 18 additions & 6 deletions packages/frontend-2/middleware/005-onboarding.global.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,49 @@
import { activeUserQuery } from '~~/lib/auth/composables/activeUser'
import { useApolloClientFromNuxt } from '~~/lib/common/composables/graphql'
import { convertThrowIntoFetchResult } from '~~/lib/common/helpers/graphql'
import { homeRoute, onboardingRoute } from '~~/lib/common/helpers/route'
import {
homeRoute,
onboardingRoute,
onboardingJoinRoute
} from '~~/lib/common/helpers/route'
import { useWorkspaceNewPlansEnabled } from '~/composables/globals'

/**
* Redirect user to /onboarding, if they haven't done it yet
*/
export default defineNuxtRouteMiddleware(async (to) => {
const isAuthPage = to.path.startsWith('/authn/')
if (isAuthPage) return

const client = useApolloClientFromNuxt()
const { data } = await client
.query({
query: activeUserQuery
})
.catch(convertThrowIntoFetchResult)

const isAuthPage = to.path.startsWith('/authn/')
if (isAuthPage) return

// Ignore if not logged in
if (!data?.activeUser?.id) return

// Ignore if user has not verified their email yet
if (!data?.activeUser?.verified) return

// FF-CLEANUP: Remove when workspaces plans released
// Determine which onboarding route to use based on feature flag
const isWorkspaceNewPlansEnabled = useWorkspaceNewPlansEnabled()
const actualOnboardingRoute = isWorkspaceNewPlansEnabled.value
? onboardingRoute
: onboardingJoinRoute

const isOnboardingFinished = data?.activeUser?.isOnboardingFinished
const isGoingToOnboarding = to.path === onboardingRoute
const isGoingToOnboarding = to.path === actualOnboardingRoute
const shouldRedirectToOnboarding =
!isOnboardingFinished &&
!isGoingToOnboarding &&
to.query['skiponboarding'] !== 'true'

if (shouldRedirectToOnboarding) {
return navigateTo(onboardingRoute)
return navigateTo(actualOnboardingRoute)
}

if (isGoingToOnboarding && isOnboardingFinished && to.query['force'] !== 'true') {
Expand Down
92 changes: 92 additions & 0 deletions packages/frontend-2/pages/onboarding-join.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!-- FF-CLEANUP: Remove when workspaces plans released -->
<template>
<HeaderWithEmptyPage empty-header>
<template #header-left>
<HeaderLogoBlock no-link />
</template>
<template #header-right>
<div class="flex gap-2 items-center">
<FormButton
v-if="!isOnboardingForced"
class="opacity-70 hover:opacity-100 p-1"
size="sm"
color="subtle"
@click="setUserOnboardingComplete()"
>
Skip
</FormButton>
<FormButton color="outline" @click="() => logout({ skipRedirect: false })">
Sign out
</FormButton>
</div>
</template>
<template v-if="isLoading">
<div class="py-12 flex flex-col items-center gap-2">
<CommonLoadingIcon />
</div>
</template>
<div v-else class="flex flex-col items-center justify-center p-4 max-w-lg mx-auto">
<h1 class="text-heading-xl text-forefround mb-2 font-normal">
{{ currentStage === 'join' ? 'Join your teammates' : 'Tell us about yourself' }}
</h1>
<p class="text-center text-body-sm text-foreground-2 mb-8">
{{
currentStage === 'join'
? 'We found a workspace that matches your email domain'
: 'Your answers will help us improve'
}}
</p>

<OnboardingJoinTeammates
v-if="currentStage === 'join' && discoverableWorkspaces.length > 0"
:workspaces="discoverableWorkspaces"
@next="currentStage = 'questions'"
/>
<OnboardingQuestionsForm v-else />
</div>
</HeaderWithEmptyPage>
</template>

<script setup lang="ts">
import { useProcessOnboarding } from '~~/lib/auth/composables/onboarding'
import { useAuthManager } from '~/lib/auth/composables/auth'
import { CommonLoadingIcon } from '@speckle/ui-components'
import { useDiscoverableWorkspaces } from '~/lib/workspaces/composables/discoverableWorkspaces'

useHead({
title: 'Welcome to Speckle'
})

definePageMeta({
middleware: ['auth'],
layout: 'empty'
})

const isOnboardingForced = useIsOnboardingForced()

const { setUserOnboardingComplete } = useProcessOnboarding()
const { logout } = useAuthManager()

const isLoading = ref(true)
const currentStage = ref<'join' | 'questions'>('questions')
const isWorkspacesEnabled = useIsWorkspacesEnabled()

// Use the composable instead of direct query
const { discoverableWorkspaces, hasDiscoverableWorkspaces } =
useDiscoverableWorkspaces()

onMounted(async () => {
// If workspaces feature is disabled, go straight to questions
if (!isWorkspacesEnabled.value) {
currentStage.value = 'questions'
isLoading.value = false
return
}

// Short delay to allow the composable to fetch data
await nextTick()

currentStage.value = hasDiscoverableWorkspaces.value ? 'join' : 'questions'
isLoading.value = false
})
</script>

0 comments on commit 4613521

Please sign in to comment.