Skip to content

Commit

Permalink
Merge pull request #5286 from novuhq/feature/sign-up-question-form-hu…
Browse files Browse the repository at this point in the history
…bspot

feat: add hubspot component in shared web
  • Loading branch information
jainpawan21 committed Mar 13, 2024
2 parents 906a41c + 8d5bcf8 commit d21f6e4
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 12 deletions.
4 changes: 4 additions & 0 deletions .cspell.json
Expand Up @@ -605,6 +605,10 @@
"Pyroscope",
"PYROSCOPE",
"usecases",
"hbspt",
"prepopulating",
"Vonage",
"fieldtype",
"usecase",
"zulip",
"uuidv",
Expand Down
8 changes: 1 addition & 7 deletions apps/api/src/app/auth/dtos/user-registration.dto.ts
@@ -1,11 +1,5 @@
import { IsDefined, IsEmail, IsOptional, MinLength, Matches, MaxLength, IsString, IsEnum } from 'class-validator';
import {
JobTitleEnum,
passwordConstraints,
ProductUseCases,
ProductUseCasesEnum,
SignUpOriginEnum,
} from '@novu/shared';
import { JobTitleEnum, passwordConstraints, ProductUseCases, SignUpOriginEnum } from '@novu/shared';
export class UserRegistrationBodyDto {
@IsDefined()
@IsEmail()
Expand Down
@@ -1,5 +1,5 @@
import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator';
import { ICreateOrganizationDto, JobTitleEnum, ProductUseCases, ProductUseCasesEnum } from '@novu/shared';
import { ICreateOrganizationDto, JobTitleEnum, ProductUseCases } from '@novu/shared';

export class CreateOrganizationDto implements ICreateOrganizationDto {
@IsString()
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/constants/hubspotForms.ts
@@ -0,0 +1,3 @@
export const HUBSPOT_FORM_IDS = {
SIGN_UP: 'c551a45f-a9fe-47eb-a2e5-4e8540d27695',
};
7 changes: 5 additions & 2 deletions apps/web/src/pages/auth/QuestionnairePage.tsx
Expand Up @@ -3,9 +3,12 @@ import AuthContainer from '../../components/layout/components/AuthContainer';
import { QuestionnaireForm } from './components/QuestionnaireForm';
import { useVercelIntegration } from '../../hooks';
import SetupLoader from './components/SetupLoader';
import { ENV, IS_DOCKER_HOSTED } from '@novu/shared-web';
import { HubspotSignupForm } from './components/HubspotSignupForm';

export default function QuestionnairePage() {
const { isLoading } = useVercelIntegration();
const isNovuProd = !IS_DOCKER_HOSTED && ENV === 'production';

return (
<AuthLayout>
Expand All @@ -14,9 +17,9 @@ export default function QuestionnairePage() {
) : (
<AuthContainer
title="Customize your experience"
description="Your answers can decrease the time to get started"
description={!isNovuProd ? 'Your answers can decrease the time to get started' : ''}
>
<QuestionnaireForm />
{!isNovuProd ? <QuestionnaireForm /> : <HubspotSignupForm />}
</AuthContainer>
)}
</AuthLayout>
Expand Down
140 changes: 140 additions & 0 deletions apps/web/src/pages/auth/components/HubspotSignupForm.tsx
@@ -0,0 +1,140 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
import decode from 'jwt-decode';
import { useMantineColorScheme } from '@mantine/core';

import { JobTitleEnum } from '@novu/shared';
import type { ProductUseCases, IResponseError, ICreateOrganizationDto, IJwtPayload } from '@novu/shared';
import { HubspotForm, useSegment } from '@novu/shared-web';

import { api } from '../../../api/api.client';
import { useAuthContext } from '../../../components/providers/AuthProvider';
import { useVercelIntegration, useVercelParams } from '../../../hooks';
import { ROUTES } from '../../../constants/routes.enum';
import { HUBSPOT_FORM_IDS } from '../../../constants/hubspotForms';
import SetupLoader from './SetupLoader';

export function HubspotSignupForm() {
const [loading, setLoading] = useState<boolean>();
const navigate = useNavigate();
const { setToken, token, currentUser } = useAuthContext();
const { startVercelSetup } = useVercelIntegration();
const { isFromVercel } = useVercelParams();
const { colorScheme } = useMantineColorScheme();

const segment = useSegment();

const { mutateAsync: createOrganizationMutation } = useMutation<
{ _id: string },
IResponseError,
ICreateOrganizationDto
>((data: ICreateOrganizationDto) => api.post(`/v1/organizations`, data));

useEffect(() => {
if (token) {
const userData = decode<IJwtPayload>(token);

if (userData.environmentId) {
if (isFromVercel) {
startVercelSetup();

return;
}

navigate(ROUTES.HOME);
}
}
}, [token, navigate, isFromVercel, startVercelSetup]);

async function createOrganization(data: IOrganizationCreateForm) {
const { organizationName, jobTitle, ...rest } = data;
const createDto: ICreateOrganizationDto = { ...rest, name: organizationName, jobTitle };
const organization = await createOrganizationMutation(createDto);
const organizationResponseToken = await api.post(`/v1/auth/organizations/${organization._id}/switch`, {});

setToken(organizationResponseToken);
}

function jwtHasKey(key: string) {
if (!token) return false;
const jwt = decode<IJwtPayload>(token);

return jwt && jwt[key];
}

const handleCreateOrganization = async (data: IOrganizationCreateForm) => {
if (!data?.organizationName) return;

segment.track('Button Clicked - [Signup]', { action: 'hubspot questionnaire form submit' });

setLoading(true);

if (!jwtHasKey('organizationId')) {
await createOrganization({ ...data });
}

setLoading(false);
if (isFromVercel) {
startVercelSetup();

return;
}

navigate(ROUTES.GET_STARTED);
};

if (!currentUser || loading) {
return <SetupLoader title="Loading..." />;
} else {
return (
<HubspotForm
formId={HUBSPOT_FORM_IDS.SIGN_UP}
properties={{
firstname: currentUser?.firstName as string,
lastname: currentUser?.lastName as string,
email: currentUser?.email as string,

company: '',
role___onboarding: '',
heard_about_novu: '',
use_case___onboarding: '',
role___onboarding__other_: '',
heard_about_novu__other_: '',
}}
readonlyProperties={['email', 'firstname', 'lastname']}
focussedProperty="company"
onFormSubmitted={($form, values) => {
const submissionValues = values?.submissionValues as unknown as {
company: string;
role___onboarding: string;
};

handleCreateOrganization({
organizationName: submissionValues?.company,
jobTitle: hubspotRoleToJobTitleMapping[submissionValues?.role___onboarding],
});
}}
colorScheme={colorScheme}
/>
);
}
}

interface IOrganizationCreateForm {
organizationName: string;
jobTitle: JobTitleEnum;
domain?: string;
productUseCases?: ProductUseCases;
}

const hubspotRoleToJobTitleMapping: Record<string, JobTitleEnum> = {
'Engineer/developer': JobTitleEnum.ENGINEER,
Product: JobTitleEnum.PRODUCT_MANAGER,
Architect: JobTitleEnum.ARCHITECT,
'Engineering Manager': JobTitleEnum.ENGINEERING_MANAGER,
Designer: JobTitleEnum.DESIGNER,
'CxO/Founder': JobTitleEnum.FOUNDER,
Marketing: JobTitleEnum.MARKETING_MANAGER,
'Other (specify)': JobTitleEnum.OTHER,
};
1 change: 1 addition & 0 deletions libs/shared-web/package.json
Expand Up @@ -31,6 +31,7 @@
"@mantine/hooks": "^5.7.1",
"@novu/shared": "^0.24.0",
"@segment/analytics-next": "1.59.0",
"@emotion/styled": "^11.6.0",
"@sentry/react": "^7.40.0",
"@tanstack/react-query": "^4.20.4",
"axios": "^1.6.0",
Expand Down

0 comments on commit d21f6e4

Please sign in to comment.