Skip to content

Commit 4169ede

Browse files
committed
fix: populate user data before HubSpot tracking on login
1 parent fb23715 commit 4169ede

12 files changed

Lines changed: 1287 additions & 15 deletions

File tree

src/plugins/analytics/trackers/SignInTracker.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
import { hubspotFormSubmitService } from '@/services/hubspot-services'
2+
3+
/**
4+
* Maps signin type flags to the appropriate form_action value for HubSpot.
5+
* @param {Object} signupTypeFlags - The signin type flags
6+
* @returns {string} The form_action value
7+
*/
8+
const FORM_ACTION_PRIORITY = [
9+
'login_sso_google',
10+
'login_sso_github',
11+
'login_email',
12+
'signup_sso_google',
13+
'signup_sso_github',
14+
'signup_email'
15+
]
16+
17+
const DEFAULT_FORM_ACTION = 'login_email'
18+
19+
const getFormAction = (signupTypeFlags) =>
20+
FORM_ACTION_PRIORITY.find((action) => signupTypeFlags?.[action]) ?? DEFAULT_FORM_ACTION
21+
122
export class SignInTracker {
223
/**
324
* Interface for TrackerAdapter.
@@ -16,11 +37,21 @@ export class SignInTracker {
1637
/**
1738
* @param {Object} payload
1839
* @param {'google'|'azure'|'github'|'email'} payload.method
19-
* @param {Object} [payload.signupTypeFlags] - Flags for signup type tracking
40+
* @param {Object} [payload.signupTypeFlags] - Flags for signin type tracking
41+
* @param {string} [payload.email] - User email for HubSpot
42+
* @param {string} [payload.userId] - Console user ID for HubSpot
43+
* @param {string} [payload.firstname] - User first name for HubSpot
44+
* @param {string} [payload.lastname] - User last name for HubSpot
45+
* @param {string} [payload.company] - Company name for HubSpot
46+
* @param {string} [payload.githubHandle] - GitHub handle for HubSpot
47+
* @param {string} [payload.phone] - User phone for HubSpot
2048
*
2149
* @returns {AnalyticsTrackerAdapter}
2250
*/
2351
userSignedIn(payload) {
52+
// Get form_action from flags for both Segment and HubSpot
53+
const formAction = getFormAction(payload.signupTypeFlags)
54+
2455
this.#trackerAdapter.addEvent({
2556
eventName: 'User Signed In',
2657
props: {
@@ -33,6 +64,22 @@ export class SignInTracker {
3364
signup_email: payload.signupTypeFlags?.signup_email ?? false
3465
}
3566
})
67+
68+
// Submit to HubSpot if email and userId are provided
69+
if (payload.email && payload.userId) {
70+
hubspotFormSubmitService({
71+
email: payload.email,
72+
form_action: formAction,
73+
user_id__rtm_: payload.userId,
74+
segment_userid: payload.userId,
75+
firstname: payload.firstname,
76+
lastname: payload.lastname,
77+
mobilephone: payload.phone,
78+
company: payload.company,
79+
github_handle: payload.githubHandle
80+
})
81+
}
82+
3683
return this.#trackerAdapter
3784
}
3885

src/plugins/analytics/trackers/SignUpTracker.js

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1+
import { hubspotFormSubmitService } from '@/services/hubspot-services'
2+
3+
const FORM_ACTION_PRIORITY = [
4+
'signup_sso_google',
5+
'signup_sso_github',
6+
'signup_email',
7+
'login_sso_google',
8+
'login_sso_github',
9+
'login_email'
10+
]
11+
12+
const DEFAULT_FORM_ACTION = 'signup_email'
13+
14+
const getFormAction = (signupTypeFlags) =>
15+
FORM_ACTION_PRIORITY.find((action) => signupTypeFlags?.[action]) ?? DEFAULT_FORM_ACTION
16+
117
export class SignUpTracker {
218
/**
319
* Interface for TrackerAdapter.
420
* @typedef {Object} trackerAdapter
521
* @property {function({eventName: string, props: Object}): void} addEvent - Method to add an event.
22+
* @property {function(): Object} getUserContext - Method to get user context for HubSpot.
623
*/
724
#trackerAdapter
825

@@ -17,18 +34,28 @@ export class SignUpTracker {
1734
* @param {Object} payload
1835
* @param {'google'|'azure'|'github'|'email'} payload.method
1936
* @param {string} [payload.firstSessionUrl] - The first session URL
20-
* @param {string} [payload.signupType] - 'sso' or 'email'
2137
* @param {Object} [payload.signupTypeFlags] - Flags for signup type tracking
38+
* @param {string} [payload.email] - User email for HubSpot
39+
* @param {string} [payload.userId] - Console user ID for HubSpot
40+
* @param {string} [payload.firstname] - User first name for HubSpot
41+
* @param {string} [payload.lastname] - User last name for HubSpot
42+
* @param {string} [payload.company] - Company name for HubSpot
43+
* @param {string} [payload.githubHandle] - GitHub handle for HubSpot
44+
* @param {string} [payload.phone] - User phone for HubSpot
2245
*
2346
* @returns {AnalyticsTrackerAdapter}
2447
*/
2548
userSignedUp(payload) {
49+
// Get signup_type from flags for both Segment and HubSpot
50+
const signupType = getFormAction(payload.signupTypeFlags)
51+
52+
// Add Segment event
2653
this.#trackerAdapter.addEvent({
2754
eventName: 'User Signed Up',
2855
props: {
2956
method: payload.method,
3057
first_session_url: payload.firstSessionUrl,
31-
signup_type: payload.signupType,
58+
signup_type: signupType,
3259
login_sso_google: payload.signupTypeFlags?.login_sso_google ?? false,
3360
login_sso_github: payload.signupTypeFlags?.login_sso_github ?? false,
3461
login_email: payload.signupTypeFlags?.login_email ?? false,
@@ -37,6 +64,22 @@ export class SignUpTracker {
3764
signup_email: payload.signupTypeFlags?.signup_email ?? false
3865
}
3966
})
67+
68+
// Submit to HubSpot if email and userId are provided
69+
if (payload.email && payload.userId) {
70+
hubspotFormSubmitService({
71+
email: payload.email,
72+
form_action: signupType,
73+
user_id__rtm_: payload.userId,
74+
segment_userid: payload.userId,
75+
firstname: payload.firstname,
76+
lastname: payload.lastname,
77+
mobilephone: payload.phone,
78+
company: payload.company,
79+
github_handle: payload.githubHandle
80+
})
81+
}
82+
4083
return this.#trackerAdapter
4184
}
4285

@@ -60,18 +103,20 @@ export class SignUpTracker {
60103
* @param {Object} payload
61104
* @param {'google'|'azure'|'github'} payload.method
62105
* @param {string} [payload.firstSessionUrl] - The first session URL
63-
* @param {string} [payload.signupType] - 'sso' or 'email'
64106
* @param {Object} [payload.signupTypeFlags] - Flags for signup type tracking
65107
*
66108
* @returns {AnalyticsTrackerAdapter}
67109
*/
68110
userAuthorizedSso(payload) {
111+
// Get signup_type from flags for consistency
112+
const signupType = getFormAction(payload.signupTypeFlags)
113+
69114
this.#trackerAdapter.addEvent({
70115
eventName: 'User Authorized SSO',
71116
props: {
72117
method: payload.method,
73118
first_session_url: payload.firstSessionUrl,
74-
signup_type: payload.signupType,
119+
signup_type: signupType,
75120
login_sso_google: payload.signupTypeFlags?.login_sso_google ?? false,
76121
login_sso_github: payload.signupTypeFlags?.login_sso_github ?? false,
77122
login_email: payload.signupTypeFlags?.login_email ?? false,

src/router/routes/signup-routes/index.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,50 @@ export const signupRoutes = {
3939
beforeEnter: (__, ___, next) => {
4040
const accountStore = useAccountStore()
4141
const isFirstLogin = accountStore.isFirstLogin
42+
const signupTypeFlags = accountStore.getSignupTypeFlags()
4243

43-
if (isFirstLogin && accountStore.ssoSignUpMethod) {
44+
// Check if this is a first login with signup tracking needed
45+
const isEmailSignup = signupTypeFlags.signup_email
46+
const isSsoSignup = accountStore.ssoSignUpMethod
47+
48+
if (isFirstLogin && (isSsoSignup || isEmailSignup)) {
4449
/** @type {import('@/plugins/adapters/AnalyticsTrackerAdapter').AnalyticsTrackerAdapter} */
4550
const tracker = inject('tracker')
46-
const method = accountStore.ssoSignUpMethod
4751

48-
// Set the appropriate signup flag
49-
const signupFlag = `signup_sso_${method}`
50-
accountStore.setSignupTypeFlag(signupFlag)
52+
// Determine the method for tracking
53+
const method = isSsoSignup || 'email'
54+
55+
// Get user data for HubSpot tracking
56+
const { userId: consoleUserId, accountData } = accountStore
57+
const userEmail = accountData?.email
58+
const userName = accountData?.name || ''
59+
const companyName = accountData?.company_name || ''
60+
61+
// Parse first and last name from full name
62+
const nameParts = userName.split(' ')
63+
const firstname = nameParts[0] || ''
64+
const lastname = nameParts.slice(1).join(' ') || ''
5165

5266
const signUpPayload = {
5367
method,
5468
firstSessionUrl: getFirstSessionUrl(),
55-
signupType: 'sso',
56-
signupTypeFlags: accountStore.getSignupTypeFlags()
69+
signupTypeFlags,
70+
// HubSpot required fields
71+
email: userEmail,
72+
userId: consoleUserId,
73+
firstname,
74+
lastname,
75+
company: companyName,
76+
githubHandle: method === 'github' ? accountData?.github_handle : undefined
5777
}
5878

59-
tracker.signUp.userSignedUp(signUpPayload).signUp.userAuthorizedSso(signUpPayload)
79+
// Track user signup with Segment and HubSpot
80+
tracker.signUp.userSignedUp(signUpPayload)
81+
82+
// For SSO, also track the SSO authorization event
83+
if (isSsoSignup) {
84+
tracker.signUp.userAuthorizedSso(signUpPayload)
85+
}
6086
}
6187

6288
if (isFirstLogin) {

src/router/routes/switch-account-routes/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { AccountHandler } from '@/helpers/account-handler'
22
import * as AuthServices from '@/services/auth-services'
33
import { listTypeAccountService } from '@/services/switch-account-services/list-type-account-service'
4+
import { useAccountStore } from '@/stores/account'
5+
import { loadUserAndAccountInfo } from '@/helpers/account-data'
6+
import { inject } from 'vue'
47

58
/** @type {import('vue-router').RouteRecordRaw} */
69
export const switchAccountRoutes = {
@@ -16,6 +19,9 @@ export const switchAccountRoutes = {
1619
)
1720
const verify = AuthServices.verifyAuthenticationService
1821
const refresh = AuthServices.refreshAuthenticationService
22+
/** @type {import('@/plugins/analytics/AnalyticsTrackerAdapter').AnalyticsTrackerAdapter} */
23+
const tracker = inject('tracker')
24+
const accountStore = useAccountStore()
1925

2026
try {
2127
const EnableSocialLogin = true
@@ -24,6 +30,35 @@ export const switchAccountRoutes = {
2430
refresh,
2531
EnableSocialLogin
2632
)
33+
34+
// Track SSO sign-in for returning users (not first login)
35+
const signupTypeFlags = accountStore.getSignupTypeFlags()
36+
const isSsoLogin = signupTypeFlags.login_sso_google || signupTypeFlags.login_sso_github
37+
38+
if (isSsoLogin) {
39+
// Load user data to check firstLogin status and for HubSpot tracking
40+
await loadUserAndAccountInfo()
41+
const { userId: consoleUserId, accountData, isFirstLogin } = accountStore
42+
43+
// Only track sign-in for returning users (first login is tracked in signup-routes)
44+
if (!isFirstLogin) {
45+
const ssoMethod = signupTypeFlags.login_sso_google ? 'google' : 'github'
46+
47+
tracker.signIn
48+
.userSignedIn({
49+
method: ssoMethod,
50+
signupTypeFlags,
51+
email: accountData?.email,
52+
userId: consoleUserId,
53+
firstname: accountData?.first_name,
54+
lastname: accountData?.last_name,
55+
company: accountData?.company_name,
56+
githubHandle: ssoMethod === 'github' ? accountData?.github_handle : undefined
57+
})
58+
.track()
59+
}
60+
}
61+
2762
next(redirect)
2863
} catch {
2964
next({ name: 'login' })
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { AxiosHttpClientAdapter } from '../axios/AxiosHttpClientAdapter'
2+
3+
const HUBSPOT_FORM_ID = 'b37e4a6a-8808-4e53-862a-7f82728138b9'
4+
const HUBSPOT_PORTAL_ID = '145499104'
5+
6+
/**
7+
* Submits a form to HubSpot Forms API.
8+
* @param {Object} payload - The form payload
9+
* @param {string} payload.email - User email (mandatory)
10+
* @param {string} payload.form_action - Form action type (mandatory): signup_email | signup_sso_google | signup_sso_github | login_email | login_sso_google | login_sso_github
11+
* @param {string} payload.user_id__rtm_ - Console user ID (mandatory, must equal segment_userid)
12+
* @param {string} payload.segment_userid - Console user ID (mandatory)
13+
* @param {string} [payload.firstname] - User first name
14+
* @param {string} [payload.lastname] - User last name
15+
* @param {string} [payload.mobilephone] - User phone
16+
* @param {string} [payload.company] - Company name
17+
* @param {string} [payload.github_handle] - GitHub handle
18+
* @returns {Promise<void>}
19+
*/
20+
export const hubspotFormSubmitService = async (payload) => {
21+
const fields = [
22+
{ name: 'email', value: payload.email },
23+
{ name: 'form_action', value: payload.form_action },
24+
{ name: 'user_id__rtm_', value: payload.user_id__rtm_ },
25+
{ name: 'segment_userid', value: payload.segment_userid }
26+
]
27+
28+
// Add optional fields if provided
29+
if (payload.firstname) {
30+
fields.push({ name: 'firstname', value: payload.firstname })
31+
}
32+
if (payload.lastname) {
33+
fields.push({ name: 'lastname', value: payload.lastname })
34+
}
35+
if (payload.mobilephone) {
36+
fields.push({ name: 'mobilephone', value: payload.mobilephone })
37+
}
38+
if (payload.company) {
39+
fields.push({ name: 'company', value: payload.company })
40+
}
41+
if (payload.github_handle) {
42+
fields.push({ name: 'github_handle', value: payload.github_handle })
43+
}
44+
45+
const url = `https://api.hsforms.com/submissions/v3/integration/submit/${HUBSPOT_PORTAL_ID}/${HUBSPOT_FORM_ID}`
46+
47+
try {
48+
await AxiosHttpClientAdapter.request({
49+
url,
50+
method: 'POST',
51+
body: { fields },
52+
headers: {
53+
'Content-Type': 'application/json'
54+
}
55+
})
56+
} catch (error) {
57+
// Log error but don't block the signup flow
58+
// eslint-disable-next-line no-console
59+
console.warn('HubSpot form submission failed:', error)
60+
}
61+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { hubspotFormSubmitService } from './hubspot-form-submit-service'
2+
3+
export { hubspotFormSubmitService }

src/templates/sign-in-block/index.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@
167167
import * as yup from 'yup'
168168
import { useToast } from '@aziontech/webkit/use-toast'
169169
import { useAccountStore } from '@/stores/account'
170+
import { loadUserAndAccountInfo } from '@/helpers/account-data'
170171
/**@type {import('@/plugins/analytics/AnalyticsTrackerAdapter').AnalyticsTrackerAdapter} */
171172
const tracker = inject('tracker')
172173
const accountStore = useAccountStore()
@@ -271,7 +272,21 @@
271272
await props.authenticationLoginService(loginData)
272273
const { twoFactor, trustedDevice, user_tracking_info: userInfo } = await verify()
273274
const signupTypeFlags = accountStore.getSignupTypeFlags()
274-
tracker.signIn.userSignedIn({ method: 'email', signupTypeFlags }).track()
275+
276+
// Load user and account info to populate accountStore for HubSpot tracking
277+
await loadUserAndAccountInfo()
278+
const { userId: consoleUserId, accountData } = accountStore
279+
tracker.signIn
280+
.userSignedIn({
281+
method: 'email',
282+
signupTypeFlags,
283+
email: accountData?.email || values.email,
284+
userId: consoleUserId,
285+
firstname: accountData?.first_name || accountData?.name?.split(' ')[0],
286+
lastname: accountData?.last_name || accountData?.name?.split(' ').slice(1).join(' '),
287+
company: accountData?.company_name
288+
})
289+
.track()
275290
if (twoFactor) {
276291
const mfaRoute = trustedDevice ? 'authentication' : 'setup'
277292
router.push(`/mfa/${mfaRoute}`)

0 commit comments

Comments
 (0)