Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fe2): Create/Join Workspace as part of signup flow #3997

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b2305e3
New middleware. New page structure
andrewwallacespeckle Feb 14, 2025
b6a3409
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 17, 2025
c83be11
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 17, 2025
39ab666
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 18, 2025
c5246ad
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 18, 2025
67c0bb7
Changes from designs
andrewwallacespeckle Feb 18, 2025
122799f
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 19, 2025
35af53c
New workspace creation flow
andrewwallacespeckle Feb 19, 2025
58ddf4f
FF Hide SSO
andrewwallacespeckle Feb 19, 2025
6267d28
No middleware with no FF
andrewwallacespeckle Feb 19, 2025
ffbd862
When to show join
andrewwallacespeckle Feb 19, 2025
48f203a
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 19, 2025
f4d9265
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 21, 2025
baab45a
Merge branch 'main' into andrew/everything-is-a-workspace
Mikehrn Feb 21, 2025
322a4e5
Update Join description text based on count
andrewwallacespeckle Feb 21, 2025
d5712f4
Merge branch 'main' into andrew/everything-is-a-workspace
Mikehrn Feb 21, 2025
c2f00b9
Merge branch 'andrew/everything-is-a-workspace' of https://github.com…
andrewwallacespeckle Feb 24, 2025
b4dafc1
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 24, 2025
955587e
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 24, 2025
d39e8a6
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 25, 2025
1f615ef
Use new FF
andrewwallacespeckle Feb 25, 2025
bc41840
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 25, 2025
239064f
Major changes
andrewwallacespeckle Feb 25, 2025
8cf18ae
Update join text
andrewwallacespeckle Feb 25, 2025
6191c37
New FF in middleware
andrewwallacespeckle Feb 25, 2025
be41269
Discoverable Banners
andrewwallacespeckle Feb 25, 2025
58974bc
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 25, 2025
dbc3a6d
Fix cache warning
andrewwallacespeckle Feb 25, 2025
de2a38d
Undo merge conflict
andrewwallacespeckle Feb 25, 2025
21e4319
Revert merge conflicts
andrewwallacespeckle Feb 25, 2025
cb35b38
Remove unneeded change
andrewwallacespeckle Feb 25, 2025
a0e1b12
Rename
andrewwallacespeckle Feb 25, 2025
313f2ed
Revert merge issues
andrewwallacespeckle Feb 25, 2025
c9dae87
Fix error
andrewwallacespeckle Feb 25, 2025
5645178
Remove FF
andrewwallacespeckle Feb 25, 2025
1287a92
Check workspaces is enabled
andrewwallacespeckle Feb 25, 2025
4613521
Use FF to show old onboarding flow
andrewwallacespeckle Feb 26, 2025
71d82ba
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 26, 2025
96f2e0c
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 27, 2025
34acb42
Remove unused FF
andrewwallacespeckle Feb 27, 2025
3cc5bc0
Fixes from PR
andrewwallacespeckle Feb 27, 2025
1935225
Merge branch 'main' into andrew/everything-is-a-workspace
andrewwallacespeckle Feb 27, 2025
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- FF-CLEANUP: Remove when workspaces plans released -->
<template>
<div class="flex flex-col items-center gap-2 w-full">
<CommonCard
Expand Down
24 changes: 16 additions & 8 deletions packages/frontend-2/components/projects/DashboardHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,24 @@
:invite="invite"
/>
<WorkspaceInviteDiscoverableWorkspaceBanner
v-for="workspace in discoverableWorkspaces"
v-for="workspace in filteredDiscoverableWorkspaces"
:key="workspace.id"
:workspace="workspace"
@dismiss="handleDismiss"
/>
</div>
</div>
</template>
<script setup lang="ts">
import type { MaybeNullOrUndefined } from '@speckle/shared'
import { useSynchronizedCookie } from '~/lib/common/composables/reactiveCookie'
import { graphql } from '~/lib/common/generated/gql'
import type {
ProjectsDashboardHeaderProjects_UserFragment,
ProjectsDashboardHeaderWorkspaces_UserFragment
} from '~/lib/common/generated/gql/graphql'
import { CookieKeys } from '~/lib/common/helpers/constants'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import { useDiscoverableWorkspaces } from '~/lib/workspaces/composables/discoverableWorkspaces'

graphql(`
fragment ProjectsDashboardHeaderProjects_User on User {
Expand All @@ -40,9 +42,6 @@ graphql(`

graphql(`
fragment ProjectsDashboardHeaderWorkspaces_User on User {
discoverableWorkspaces {
...WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace
}
workspaceInvites {
...WorkspaceInviteBanner_PendingWorkspaceCollaborator
}
Expand All @@ -61,10 +60,12 @@ const dismissedDiscoverableWorkspaces = useSynchronizedCookie<string[]>(
}
)

const { discoverableWorkspaces } = useDiscoverableWorkspaces()

const workspaceInvites = computed(() => props.workspacesInvites?.workspaceInvites || [])
const discoverableWorkspaces = computed(
const filteredDiscoverableWorkspaces = computed(
() =>
props.workspacesInvites?.discoverableWorkspaces?.filter(
discoverableWorkspaces.value?.filter(
(workspace) => !dismissedDiscoverableWorkspaces.value.includes(workspace.id)
) || []
)
Expand All @@ -73,7 +74,14 @@ const hasBanners = computed(() => {
return (
props.projectsInvites?.projectInvites?.length ||
workspaceInvites.value.length ||
discoverableWorkspaces.value.length
filteredDiscoverableWorkspaces.value.length
)
})

const handleDismiss = (workspaceId: string) => {
dismissedDiscoverableWorkspaces.value = [
...dismissedDiscoverableWorkspaces.value,
workspaceId
]
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</template>
<template #nothing-selected>
<span class="min-w-64 block">
{{ multiple ? 'Select regions' : 'Select a region' }}
{{ multiple ? 'Select default data regions' : 'Select default data region' }}
</span>
</template>
<template #something-selected="{ value }">
Expand Down
29 changes: 28 additions & 1 deletion packages/frontend-2/components/workspace/CreatePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@
/>
</template>
<template #header-right>
<FormButton size="sm" color="outline" @click="onCancelClick">Cancel</FormButton>
<FormButton v-if="isForcedCreation" size="sm" color="outline" @click="logout()">
Sign out
</FormButton>
<FormButton v-else size="sm" color="outline" @click="onCancelClick">
Cancel
</FormButton>
</template>

<WorkspaceWizard :workspace-id="workspaceId" />

<div v-if="isForcedCreation && isFirstStep" class="w-full max-w-sm mx-auto mt-4">
<CommonAlert color="neutral" size="xs" hide-icon>
<template #title>Why am I seeing this?</template>
<template #description>
This server now requires you to be a member of a workspace. Please create a
new workspace to continue.
</template>
</CommonAlert>
</div>

<WorkspaceWizardCancelDialog
v-model:open="isCancelDialogOpen"
:workspace-id="workspaceId"
Expand All @@ -25,18 +41,29 @@ import { workspacesRoute } from '~~/lib/common/helpers/route'
import { WizardSteps } from '~/lib/workspaces/helpers/types'
import { useWorkspacesWizard } from '~/lib/workspaces/composables/wizard'
import { useMixpanel } from '~/lib/core/composables/mp'
import { useAuthManager } from '~/lib/auth/composables/auth'
import { useDiscoverableWorkspaces } from '~/lib/workspaces/composables/discoverableWorkspaces'

defineProps<{
workspaceId?: string
}>()

const { currentStep, resetWizardState } = useWorkspacesWizard()
const mixpanel = useMixpanel()
const { logout } = useAuthManager()
const { hasDiscoverableWorkspacesOrJoinRequests } = useDiscoverableWorkspaces()
const { requiresWorkspaceCreation } = useActiveUser()

const isCancelDialogOpen = ref(false)

const isFirstStep = computed(() => currentStep.value === WizardSteps.Details)

const isForcedCreation = computed(() => {
return (
requiresWorkspaceCreation.value && !hasDiscoverableWorkspacesOrJoinRequests.value
)
})

const onCancelClick = () => {
if (isFirstStep.value) {
navigateTo(workspacesRoute)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>
<div class="flex flex-col items-center gap-2 w-full max-w-lg mx-auto">
<h1 class="text-heading-xl text-forefround mb-2 font-normal mt-4">
Join teammates
</h1>
<p class="text-center text-body-sm text-foreground-2 mb-8">
{{ description }}
</p>
<CommonCard
v-for="discoverableWorkspace in discoverableWorkspacesAndJoinRequests"
:key="`discoverable-${discoverableWorkspace.id}`"
class="w-full bg-foundation"
>
<div class="flex gap-4">
<div>
<WorkspaceAvatar
:name="discoverableWorkspace.name"
:logo="discoverableWorkspace.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">{{ discoverableWorkspace.name }}</h6>
<p class="text-body-2xs text-foreground-2">
{{ discoverableWorkspace.team?.totalCount }}
{{ discoverableWorkspace.team?.totalCount === 1 ? 'member' : 'members' }}
</p>
</div>
<FormButton
v-if="discoverableWorkspace.requestStatus"
color="outline"
size="sm"
disabled
class="capitalize"
>
{{ discoverableWorkspace.requestStatus }}
</FormButton>
<FormButton
v-else
color="outline"
size="sm"
@click="() => processRequest(true, discoverableWorkspace.id)"
>
Request to join
</FormButton>
</div>
</div>
</CommonCard>
<div class="mt-2 w-full">
<FormButton
size="lg"
full-width
color="outline"
@click="navigateTo(workspaceCreateRoute())"
>
Create a new workspace
</FormButton>
</div>
</div>
</template>

<script setup lang="ts">
import { workspaceCreateRoute } from '~~/lib/common/helpers/route'
import { useDiscoverableWorkspaces } from '~/lib/workspaces/composables/discoverableWorkspaces'

const {
processRequest,
discoverableWorkspacesAndJoinRequestsCount,
discoverableWorkspacesAndJoinRequests
} = useDiscoverableWorkspaces()

const description = computed(() => {
if (discoverableWorkspacesAndJoinRequestsCount.value === 1) {
return 'We found a workspace that matches your email domain'
}
return 'We found workspaces that match your email domain'
})
</script>
24 changes: 24 additions & 0 deletions packages/frontend-2/components/workspace/JoinPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<HeaderWithEmptyPage empty-header>
<template #header-left>
<HeaderLogoBlock :active="false" class="min-w-40 cursor-pointer" no-link />
</template>
<template #header-right>
<FormButton
size="sm"
color="outline"
@click="() => logout({ skipRedirect: false })"
>
Sign out
</FormButton>
</template>

<WorkspaceDiscoverableWorkspaces />
</HeaderWithEmptyPage>
</template>

<script setup lang="ts">
import { useAuthManager } from '~/lib/auth/composables/auth'

const { logout } = useAuthManager()
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<InviteBanner :invite="invite" @processed="processRequest">
<InviteBanner :invite="invite" @processed="handleRequest">
<template #message>
Your team is already using Workspaces, request to join the
<span class="font-medium">{{ workspace.name }}</span>
Expand All @@ -9,41 +9,17 @@
</template>

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

graphql(`
fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {
id
name
slug
description
logo
}
`)
import type { LimitedWorkspace } from '~/lib/common/generated/gql/graphql'

const props = defineProps<{
workspace: WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragment
workspace: LimitedWorkspace
}>()

const { mutate: requestToJoin } = useMutation(dashboardRequestToJoinWorkspaceMutation)
const { processRequest } = useDiscoverableWorkspaces()
const mixpanel = useMixpanel()
const { triggerNotification } = useGlobalToast()
const dismissedDiscoverableWorkspaces = useSynchronizedCookie<string[]>(
CookieKeys.DismissedDiscoverableWorkspaces,
{
default: () => []
}
)

const invite = computed(() => ({
workspace: {
Expand All @@ -53,52 +29,22 @@ const invite = computed(() => ({
}
}))

const processRequest = async (accept: boolean) => {
if (accept) {
const result = await requestToJoin({
input: { workspaceId: props.workspace.id }
}).catch(convertThrowIntoFetchResult)

if (result?.data) {
// Dismiss it show it doesnt show again
dismissedDiscoverableWorkspaces.value = [
...dismissedDiscoverableWorkspaces.value,
props.workspace.id
]

mixpanel.track('Workspace Join Request Sent', {
workspaceId: props.workspace.id,
location: 'discovery banner',
// eslint-disable-next-line camelcase
workspace_id: props.workspace.id
})
const emit = defineEmits<{
(e: 'dismiss', workspaceId: string): void
}>()

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
})
}
const handleRequest = async (accept: boolean) => {
if (accept) {
await processRequest(true, props.workspace.id)
emit('dismiss', props.workspace.id)
} else {
dismissedDiscoverableWorkspaces.value = [
...dismissedDiscoverableWorkspaces.value,
props.workspace.id
]

emit('dismiss', props.workspace.id)
mixpanel.track('Workspace Discovery Banner Dismissed', {
workspaceId: props.workspace.id,
location: 'discovery banner',
// eslint-disable-next-line camelcase
workspace_id: props.workspace.id
})

triggerNotification({
title: 'Discoverable workspace dismissed',
type: ToastNotificationType.Info
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend-2/components/workspace/wizard/Wizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
/>
<WorkspaceWizardStepInvites v-else-if="currentStep === WizardSteps.Invites" />
<WorkspaceWizardStepPricing v-else-if="currentStep === WizardSteps.Pricing" />
<WorkspaceWizardStepRegion v-else-if="currentStep === WizardSteps.Region" />
<!-- FF-CLEANUP -->
<WorkspaceWizardStepRegion
v-else-if="currentStep === WizardSteps.Region && !isWorkspaceNewPlansEnabled"
/>
<WorkspaceWizardStepRegionNew
v-else-if="currentStep === WizardSteps.Region && isWorkspaceNewPlansEnabled"
/>
<WorkspaceWizardStepSso v-else-if="currentStep === WizardSteps.Sso" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also show in the existing create a workspace wizard?

Copy link
Contributor Author

@andrewwallacespeckle andrewwallacespeckle Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image image

I updated the composable so it will check for the FF and remove SSO if its false.

</template>
</div>
</template>
Expand Down Expand Up @@ -51,6 +58,7 @@ const { cancelCheckoutSession } = useBillingActions()
const route = useRoute()
const mixpanel = useMixpanel()
const { goToStep, currentStep, isLoading, state } = useWorkspacesWizard()
const isWorkspaceNewPlansEnabled = useWorkspaceNewPlansEnabled()

const { loading: queryLoading, onResult } = useQuery(
workspaceWizardQuery,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<WorkspaceWizardStep title="Create a workspace" description="Start with a good name">
<WorkspaceWizardStep
title="Create a workspace"
description="Workspaces are environments where you can safely collaborate with your team and manage guests."
>
<form class="flex flex-col gap-4 w-full md:w-96" @submit="onSubmit">
<FormTextInput
id="workspace-name"
Expand Down
Loading