Merge branch 'main' into iain/ratelimiter-should-respect-configuration

This commit is contained in:
Iain Sproat
2025-02-27 11:34:04 +00:00
19 changed files with 226 additions and 68 deletions
@@ -38,4 +38,7 @@ const dialogButtons = computed((): LayoutDialogButton[] => [
}
}
])
// testing deployments in prod, ignore this
markUsed('a')
</script>
@@ -26,11 +26,7 @@
<script setup lang="ts">
import { useForm } from 'vee-validate'
import type {
OnboardingRole,
OnboardingPlan,
OnboardingSource
} from '~/lib/auth/helpers/onboarding'
import type { OnboardingRole, OnboardingPlan, OnboardingSource } from '@speckle/shared'
import { useProcessOnboarding } from '~~/lib/auth/composables/onboarding'
import { homeRoute } from '~/lib/common/helpers/route'
@@ -50,7 +46,11 @@ const onSubmit = handleSubmit(async () => {
if (values.role) {
setMixpanelSegments({ role: values.role })
}
await setUserOnboardingComplete()
await setUserOnboardingComplete({
role: values.role,
plans: values.plan,
source: values.source
})
navigateTo(homeRoute)
})
</script>
@@ -30,7 +30,8 @@
<script setup lang="ts">
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { OnboardingPlan, PlanTitleMap } from '~/lib/auth/helpers/onboarding'
import { PlanTitleMap } from '~/lib/auth/helpers/onboarding'
import { OnboardingPlan } from '@speckle/shared'
import { isRequired } from '~~/lib/common/helpers/validation'
const props = defineProps<{
@@ -26,7 +26,8 @@
<script setup lang="ts">
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { OnboardingRole, RoleTitleMap } from '~/lib/auth/helpers/onboarding'
import { RoleTitleMap } from '~/lib/auth/helpers/onboarding'
import { OnboardingRole } from '@speckle/shared'
import { isRequired } from '~~/lib/common/helpers/validation'
const props = defineProps<{
@@ -26,7 +26,8 @@
<script setup lang="ts">
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { OnboardingSource, SourceTitleMap } from '~/lib/auth/helpers/onboarding'
import { SourceTitleMap } from '~/lib/auth/helpers/onboarding'
import { OnboardingSource } from '@speckle/shared'
import { isRequired } from '~~/lib/common/helpers/validation'
const props = defineProps<{
@@ -1,7 +1,12 @@
import { useApolloClient } from '@vue/apollo-composable'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { UnsupportedEnvironmentError } from '~~/lib/core/errors/base'
import type { OnboardingState } from '~~/lib/auth/helpers/onboarding'
import type {
OnboardingPlan,
OnboardingRole,
OnboardingSource,
OnboardingState
} from '@speckle/shared'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import { OnboardingError } from '~~/lib/auth/errors/errors'
import { finishOnboardingMutation } from '~~/lib/auth/graphql/mutations'
@@ -86,14 +91,22 @@ export const useProcessOnboarding = () => {
/**
* Marks the current user as having completed the onboarding - we're using this as a flag to
* know that we've set up the sample project once.
* know that we've set up the sample project once. Also updates their Mailchimp tags.
*/
const setUserOnboardingComplete = async () => {
const setUserOnboardingComplete = async (onboardingData?: {
role?: OnboardingRole
plans?: OnboardingPlan[]
source?: OnboardingSource
}) => {
const user = activeUser.value
if (!user) throw new OnboardingError('Attempting to onboard unidentified user')
await apollo
.mutate({
mutation: finishOnboardingMutation,
variables: {
input: onboardingData
},
update: (cache, { data }) => {
if (!data?.activeUserMutations.finishOnboarding) return
@@ -1,9 +1,9 @@
import { graphql } from '~~/lib/common/generated/gql'
export const finishOnboardingMutation = graphql(`
mutation FinishOnboarding {
mutation FinishOnboarding($input: OnboardingCompletionInput) {
activeUserMutations {
finishOnboarding
finishOnboarding(input: $input)
}
}
`)
@@ -1,32 +1,4 @@
export enum OnboardingRole {
ComputationalDesign = 'computational-design',
BIM = 'bim',
ArchitecturePlanning = 'architecture-planning',
EngineeringAEC = 'engineering-aec',
EngineeringSoftware = 'engineering-software',
Education = 'education',
Management = 'management',
Other = 'other'
}
export enum OnboardingPlan {
Exploring = 'exploring',
DataExchange = 'data-exchange',
Analytics = 'analytics',
Collaboration = 'collaboration',
DataWarehouse = 'data-warehouse',
Development = 'development',
Other = 'other'
}
export enum OnboardingSource {
SocialMedia = 'social-media',
Search = 'internet-search',
Referral = 'friend-or-colleague',
Event = 'event-conference',
Education = 'university-course',
Other = 'other'
}
import { OnboardingRole, OnboardingPlan, OnboardingSource } from '@speckle/shared'
export const RoleTitleMap: Record<OnboardingRole, string> = {
[OnboardingRole.ComputationalDesign]: 'Computational Design',
@@ -58,9 +30,3 @@ export const SourceTitleMap: Record<OnboardingSource, string> = {
[OnboardingSource.Education]: 'University or course',
[OnboardingSource.Other]: 'Other'
}
export type OnboardingState = {
role?: OnboardingRole
plans?: OnboardingPlan[]
source?: OnboardingSource
}
@@ -122,10 +122,10 @@ type Documents = {
"\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n inviteId\n role\n title\n updatedAt\n user {\n id\n ...LimitedUserAvatar\n }\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n": typeof types.SettingsWorkspacesMembersMembersTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersMembersTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": typeof types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesRegionsSelect_ServerRegionItem on ServerRegionItem {\n id\n key\n name\n description\n }\n": typeof types.SettingsWorkspacesRegionsSelect_ServerRegionItemFragmentDoc,
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain on WorkspaceDomain {\n id\n domain\n }\n": typeof types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragmentDoc,
@@ -151,7 +151,7 @@ type Documents = {
"\n fragment WorkspaceWizardStepRegion_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": typeof types.WorkspaceWizardStepRegion_ServerInfoFragmentDoc,
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": typeof types.ActiveUserMainMetadataDocument,
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": typeof types.CreateOnboardingProjectDocument,
"\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n": typeof types.FinishOnboardingDocument,
"\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n": typeof types.FinishOnboardingDocument,
"\n mutation RequestVerificationByEmail($email: String!) {\n requestVerificationByEmail(email: $email)\n }\n": typeof types.RequestVerificationByEmailDocument,
"\n query AuthLoginPanel {\n serverInfo {\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n }\n }\n": typeof types.AuthLoginPanelDocument,
"\n query AuthRegisterPanel($token: String) {\n serverInfo {\n inviteOnly\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n ...ServerTermsOfServicePrivacyPolicyFragment\n }\n serverInviteByToken(token: $token) {\n id\n email\n }\n }\n": typeof types.AuthRegisterPanelDocument,
@@ -307,9 +307,12 @@ type Documents = {
"\n query SettingsWorkspaceBilling($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesBilling_Workspace\n }\n }\n": typeof types.SettingsWorkspaceBillingDocument,
"\n query SettingsWorkspaceBillingCustomerPortal($workspaceId: String!) {\n workspace(id: $workspaceId) {\n customerPortalUrl\n }\n }\n": typeof types.SettingsWorkspaceBillingCustomerPortalDocument,
"\n query SettingsWorkspaceRegions($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesRegions_Workspace\n }\n serverInfo {\n ...SettingsWorkspacesRegions_ServerInfo\n }\n }\n": typeof types.SettingsWorkspaceRegionsDocument,
"\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersDocument,
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersSearchDocument,
"\n query SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesJoinRequestsSearchDocument,
"\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersDocument,
"\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersTableDocument,
"\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsDocument,
"\n query SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesDocument,
"\n query SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsDocument,
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersSearchDocument,
"\n query SettingsWorkspacesInvitesSearch(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesInvitesSearchDocument,
"\n query SettingsWorkspacesProjects(\n $slug: String!\n $limit: Int!\n $cursor: String\n $filter: WorkspaceProjectsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n slug\n readOnly\n projects(limit: $limit, cursor: $cursor, filter: $filter) {\n cursor\n ...SettingsWorkspacesProjects_ProjectCollection\n }\n }\n }\n": typeof types.SettingsWorkspacesProjectsDocument,
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n": typeof types.SettingsWorkspaceSecurityDocument,
@@ -391,7 +394,7 @@ type Documents = {
"\n query SettingsServerRegions {\n serverInfo {\n multiRegion {\n regions {\n id\n ...SettingsServerRegionsTable_ServerRegionItem\n }\n availableKeys\n }\n }\n }\n": typeof types.SettingsServerRegionsDocument,
"\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n role\n plan {\n name\n status\n createdAt\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n items {\n id\n role\n }\n }\n }\n": typeof types.SettingsWorkspacesBilling_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneral_Workspace on Workspace {\n ...SettingsWorkspacesGeneralEditAvatar_Workspace\n ...SettingsWorkspaceGeneralDeleteDialog_Workspace\n ...SettingsWorkspacesGeneralEditSlugDialog_Workspace\n id\n name\n slug\n description\n logo\n role\n defaultProjectRole\n plan {\n status\n name\n }\n }\n": typeof types.SettingsWorkspacesGeneral_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n": typeof types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests {\n items {\n id\n status\n }\n totalCount\n }\n }\n": typeof types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": typeof types.SettingsWorkspacesProjects_ProjectCollectionFragmentDoc,
"\n fragment SettingsWorkspacesRegions_Workspace on Workspace {\n id\n role\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n hasAccessToMultiRegion: hasAccessToFeature(\n featureName: workspaceDataRegionSpecificity\n )\n hasProjects: projects(limit: 0) {\n totalCount\n }\n }\n": typeof types.SettingsWorkspacesRegions_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesRegions_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": typeof types.SettingsWorkspacesRegions_ServerInfoFragmentDoc,
@@ -535,7 +538,7 @@ const documents: Documents = {
"\n fragment WorkspaceWizardStepRegion_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": types.WorkspaceWizardStepRegion_ServerInfoFragmentDoc,
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": types.ActiveUserMainMetadataDocument,
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": types.CreateOnboardingProjectDocument,
"\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n": types.FinishOnboardingDocument,
"\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n": types.FinishOnboardingDocument,
"\n mutation RequestVerificationByEmail($email: String!) {\n requestVerificationByEmail(email: $email)\n }\n": types.RequestVerificationByEmailDocument,
"\n query AuthLoginPanel {\n serverInfo {\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n }\n }\n": types.AuthLoginPanelDocument,
"\n query AuthRegisterPanel($token: String) {\n serverInfo {\n inviteOnly\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n ...ServerTermsOfServicePrivacyPolicyFragment\n }\n serverInviteByToken(token: $token) {\n id\n email\n }\n }\n": types.AuthRegisterPanelDocument,
@@ -1350,7 +1353,7 @@ export function graphql(source: "\n mutation CreateOnboardingProject {\n
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n"): (typeof documents)["\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n"];
export function graphql(source: "\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n"): (typeof documents)["\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
File diff suppressed because one or more lines are too long
@@ -173,11 +173,17 @@ type UserSearchResultCollection {
items: [LimitedUser!]!
}
input OnboardingCompletionInput {
role: String
plans: [String!]
source: String
}
type ActiveUserMutations {
"""
Mark onboarding as complete
"""
finishOnboarding: Boolean!
finishOnboarding(input: OnboardingCompletionInput): Boolean!
"""
Edit a user's profile
@@ -4,6 +4,7 @@ import { md5 } from '@/modules/shared/helpers/cryptoHelper'
import { getMailchimpConfig } from '@/modules/shared/helpers/envHelper'
import { UserRecord } from '@/modules/core/helpers/types'
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
import { OnboardingCompletionInput } from '@/modules/core/graph/generated/graphql'
let mailchimpInitialized = false
@@ -63,4 +64,55 @@ async function triggerMailchimpCustomerJourney(
})
}
export { addToMailchimpAudience, triggerMailchimpCustomerJourney }
async function updateMailchimpMemberTags(
user: UserRecord,
listId: string,
onboardingData: OnboardingCompletionInput
) {
initializeMailchimp()
const subscriberHash = md5(user.email.toLowerCase())
// Check if user is already in audience (meaning they consented to marketing emails)
try {
await mailchimp.lists.getListMember(listId, subscriberHash)
} catch {
throw new Error(
`User ${user.email} not found in Mailchimp audience. They should have been added during registration.`
)
}
const tags: { name: string; status: 'active' | 'inactive' }[] = []
if (onboardingData.role) {
tags.push({
name: `Role: ${onboardingData.role}`,
status: 'active'
})
}
if (onboardingData.plans?.length) {
onboardingData.plans.forEach((plan) => {
tags.push({
name: `Use case: ${plan}`,
status: 'active'
})
})
}
if (onboardingData.source) {
tags.push({
name: `Source: ${onboardingData.source}`,
status: 'active'
})
}
await mailchimp.lists.updateListMemberTags(listId, subscriberHash, {
tags
})
}
export {
addToMailchimpAudience,
triggerMailchimpCustomerJourney,
updateMailchimpMemberTags
}
@@ -49,6 +49,11 @@ export type ActiveUserMutations = {
};
export type ActiveUserMutationsFinishOnboardingArgs = {
input?: InputMaybe<OnboardingCompletionInput>;
};
export type ActiveUserMutationsUpdateArgs = {
user: UserUpdateInput;
};
@@ -1875,6 +1880,12 @@ export type ObjectCreateInput = {
streamId: Scalars['String']['input'];
};
export type OnboardingCompletionInput = {
plans?: InputMaybe<Array<Scalars['String']['input']>>;
role?: InputMaybe<Scalars['String']['input']>;
source?: InputMaybe<Scalars['String']['input']>;
};
export const PaidWorkspacePlans = {
Business: 'business',
Plus: 'plus',
@@ -5031,6 +5042,7 @@ export type ResolversTypes = {
Object: ResolverTypeWrapper<ObjectGraphQLReturn>;
ObjectCollection: ResolverTypeWrapper<Omit<ObjectCollection, 'objects'> & { objects: Array<ResolversTypes['Object']> }>;
ObjectCreateInput: ObjectCreateInput;
OnboardingCompletionInput: OnboardingCompletionInput;
PaidWorkspacePlans: PaidWorkspacePlans;
PasswordStrengthCheckFeedback: ResolverTypeWrapper<PasswordStrengthCheckFeedback>;
PasswordStrengthCheckResults: ResolverTypeWrapper<PasswordStrengthCheckResults>;
@@ -5338,6 +5350,7 @@ export type ResolversParentTypes = {
Object: ObjectGraphQLReturn;
ObjectCollection: Omit<ObjectCollection, 'objects'> & { objects: Array<ResolversParentTypes['Object']> };
ObjectCreateInput: ObjectCreateInput;
OnboardingCompletionInput: OnboardingCompletionInput;
PasswordStrengthCheckFeedback: PasswordStrengthCheckFeedback;
PasswordStrengthCheckResults: PasswordStrengthCheckResults;
PendingStreamCollaborator: PendingStreamCollaboratorGraphQLReturn;
@@ -5531,7 +5544,7 @@ export type IsOwnerDirectiveResolver<Result, Parent, ContextType = GraphQLContex
export type ActiveUserMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ActiveUserMutations'] = ResolversParentTypes['ActiveUserMutations']> = {
emailMutations?: Resolver<ResolversTypes['UserEmailMutations'], ParentType, ContextType>;
finishOnboarding?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
finishOnboarding?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, Partial<ActiveUserMutationsFinishOnboardingArgs>>;
update?: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<ActiveUserMutationsUpdateArgs, 'user'>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -40,6 +40,11 @@ import { getAdminUsersListCollectionFactory } from '@/modules/core/services/user
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { getEventBus } from '@/modules/shared/services/eventBus'
import {
getMailchimpStatus,
getMailchimpOnboardingIds
} from '@/modules/shared/helpers/envHelper'
import { updateMailchimpMemberTags } from '@/modules/auth/services/mailchimp'
const getUser = legacyGetUserFactory({ db })
const getUserByEmail = legacyGetUserByEmailFactory({ db })
@@ -248,8 +253,30 @@ export = {
activeUserMutations: () => ({})
},
ActiveUserMutations: {
async finishOnboarding(_parent, _args, ctx) {
return await markOnboardingComplete(ctx.userId || '')
async finishOnboarding(_parent, args, ctx) {
const userId = ctx.userId
if (!userId) return false
const success = await markOnboardingComplete(userId)
// If onboarding was marked complete successfully and we have onboarding data
if (success && args.input && getMailchimpStatus()) {
try {
const user = await getUser(userId)
const { listId } = getMailchimpOnboardingIds()
await updateMailchimpMemberTags(user, listId, {
role: args.input?.role || undefined,
plans: args.input?.plans || undefined,
source: args.input?.source || undefined
})
} catch (error) {
// Log but don't fail the request
ctx.log.warn({ err: error }, 'Failed to update Mailchimp tags')
}
}
return success
},
async update(_parent, args, context) {
const newUser = await updateUserAndNotify(context.userId!, args.user)
@@ -30,6 +30,11 @@ export type ActiveUserMutations = {
};
export type ActiveUserMutationsFinishOnboardingArgs = {
input?: InputMaybe<OnboardingCompletionInput>;
};
export type ActiveUserMutationsUpdateArgs = {
user: UserUpdateInput;
};
@@ -1856,6 +1861,12 @@ export type ObjectCreateInput = {
streamId: Scalars['String']['input'];
};
export type OnboardingCompletionInput = {
plans?: InputMaybe<Array<Scalars['String']['input']>>;
role?: InputMaybe<Scalars['String']['input']>;
source?: InputMaybe<Scalars['String']['input']>;
};
export const PaidWorkspacePlans = {
Business: 'business',
Plus: 'plus',
@@ -31,6 +31,11 @@ export type ActiveUserMutations = {
};
export type ActiveUserMutationsFinishOnboardingArgs = {
input?: InputMaybe<OnboardingCompletionInput>;
};
export type ActiveUserMutationsUpdateArgs = {
user: UserUpdateInput;
};
@@ -1857,6 +1862,12 @@ export type ObjectCreateInput = {
streamId: Scalars['String']['input'];
};
export type OnboardingCompletionInput = {
plans?: InputMaybe<Array<Scalars['String']['input']>>;
role?: InputMaybe<Scalars['String']['input']>;
source?: InputMaybe<Scalars['String']['input']>;
};
export const PaidWorkspacePlans = {
Business: 'business',
Plus: 'plus',
+1
View File
@@ -5,3 +5,4 @@ export * as SpeckleViewer from './viewer/index.js'
export * as Automate from './automate/index.js'
export * from './core/index.js'
export * from './workspaces/index.js'
export * from './onboarding/index.js'
@@ -0,0 +1,35 @@
export enum OnboardingRole {
ComputationalDesign = 'computational-design',
BIM = 'bim',
ArchitecturePlanning = 'architecture-planning',
EngineeringAEC = 'engineering-aec',
EngineeringSoftware = 'engineering-software',
Education = 'education',
Management = 'management',
Other = 'other'
}
export enum OnboardingPlan {
Exploring = 'exploring',
DataExchange = 'data-exchange',
Analytics = 'analytics',
Collaboration = 'collaboration',
DataWarehouse = 'data-warehouse',
Development = 'development',
Other = 'other'
}
export enum OnboardingSource {
SocialMedia = 'social-media',
Search = 'internet-search',
Referral = 'friend-or-colleague',
Event = 'event-conference',
Education = 'university-course',
Other = 'other'
}
export type OnboardingState = {
role?: OnboardingRole
plans?: OnboardingPlan[]
source?: OnboardingSource
}
+1
View File
@@ -0,0 +1 @@
export * from './helpers/index.js'