From 8f8c2da727ace650d49c494d10918a4ccd16ca58 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Sun, 10 Mar 2024 12:29:33 +0530 Subject: [PATCH 1/9] feat: add hubspot component in shared web --- apps/web/src/config/index.ts | 2 + apps/web/src/constants/hupspotForms.ts | 3 + apps/web/src/pages/auth/QuestionnairePage.tsx | 6 +- .../auth/components/HubspotSignupForm.tsx | 119 +++++++++ libs/shared-web/package.json | 1 + .../shared-web/src/components/HubspotForm.tsx | 240 ++++++++++++++++++ libs/shared-web/src/components/index.ts | 1 + libs/shared-web/src/config.ts | 2 + libs/shared-web/src/index.ts | 1 + .../organization/create-organization.dto.ts | 2 +- pnpm-lock.yaml | 26 +- 11 files changed, 389 insertions(+), 14 deletions(-) create mode 100644 apps/web/src/constants/hupspotForms.ts create mode 100644 apps/web/src/pages/auth/components/HubspotSignupForm.tsx create mode 100644 libs/shared-web/src/components/HubspotForm.tsx create mode 100644 libs/shared-web/src/components/index.ts diff --git a/apps/web/src/config/index.ts b/apps/web/src/config/index.ts index 2be75692c1b..c596dd50cf6 100644 --- a/apps/web/src/config/index.ts +++ b/apps/web/src/config/index.ts @@ -14,6 +14,7 @@ import { MAIL_SERVER_DOMAIN, LAUNCH_DARKLY_CLIENT_SIDE_ID, FEATURE_FLAGS, + HUBSPOT_PORTAL_ID, } from '@novu/shared-web'; export { @@ -32,6 +33,7 @@ export { MAIL_SERVER_DOMAIN, LAUNCH_DARKLY_CLIENT_SIDE_ID, FEATURE_FLAGS, + HUBSPOT_PORTAL_ID, }; export const IS_EU_ENV = (ENV === 'production' || ENV === 'prod') && API_ROOT.includes('eu.api.novu.co'); diff --git a/apps/web/src/constants/hupspotForms.ts b/apps/web/src/constants/hupspotForms.ts new file mode 100644 index 00000000000..40dcc6dd8a1 --- /dev/null +++ b/apps/web/src/constants/hupspotForms.ts @@ -0,0 +1,3 @@ +export const HUBSPOT_FORM_IDS = { + SIGN_UP: 'c03c3a65-9b90-432f-a1f2-e394525e49f8', +}; diff --git a/apps/web/src/pages/auth/QuestionnairePage.tsx b/apps/web/src/pages/auth/QuestionnairePage.tsx index acbbd4c242f..fcb72045e3a 100644 --- a/apps/web/src/pages/auth/QuestionnairePage.tsx +++ b/apps/web/src/pages/auth/QuestionnairePage.tsx @@ -3,6 +3,8 @@ import AuthContainer from '../../components/layout/components/AuthContainer'; import { QuestionnaireForm } from './components/QuestionnaireForm'; import { useVercelIntegration } from '../../hooks'; import SetupLoader from './components/SetupLoader'; +import { IS_DOCKER_HOSTED } from '@novu/shared-web'; +import { HubspotSignupForm } from './components/HubspotSignupForm'; export default function QuestionnairePage() { const { isLoading } = useVercelIntegration(); @@ -14,9 +16,9 @@ export default function QuestionnairePage() { ) : ( - + {IS_DOCKER_HOSTED ? : } )} diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx new file mode 100644 index 00000000000..2a484c3f5c3 --- /dev/null +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -0,0 +1,119 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useMutation } from '@tanstack/react-query'; +import decode from 'jwt-decode'; + +import { JobTitleEnum } from '@novu/shared'; +import type { ProductUseCases, IResponseError, ICreateOrganizationDto, IJwtPayload } from '@novu/shared'; + +import { api } from '../../../api/api.client'; +import { useAuthContext } from '../../../components/providers/AuthProvider'; +import { useVercelIntegration, useVercelParams } from '../../../hooks'; +import { ROUTES } from '../../../constants/routes.enum'; +import { HubspotForm } from '@novu/shared-web'; +import { HUBSPOT_FORM_IDS } from '../../../constants/hupspotForms'; +import SetupLoader from './SetupLoader'; + +export function HubspotSignupForm() { + const [loading, setLoading] = useState(); + const navigate = useNavigate(); + const { setToken, token } = useAuthContext(); + const { startVercelSetup } = useVercelIntegration(); + const { isFromVercel } = useVercelParams(); + + const { currentUser } = useAuthContext(); + + const { mutateAsync: createOrganizationMutation } = useMutation< + { _id: string }, + IResponseError, + ICreateOrganizationDto + >((data: ICreateOrganizationDto) => api.post(`/v1/organizations`, data)); + + useEffect(() => { + if (token) { + const userData = decode(token); + + if (userData.environmentId) { + if (isFromVercel) { + startVercelSetup(); + + return; + } + + navigate(ROUTES.HOME); + } + } + }, [token, navigate, isFromVercel, startVercelSetup]); + + async function createOrganization(data: IOrganizationCreateForm) { + const { organizationName, ...rest } = data; + const createDto: ICreateOrganizationDto = { ...rest, name: organizationName }; + 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(token); + + return jwt && jwt[key]; + } + + const onCreateOrganization = async (data: IOrganizationCreateForm) => { + if (!data?.organizationName) return; + + setLoading(true); + + if (!jwtHasKey('organizationId')) { + await createOrganization({ ...data }); + } + + setLoading(false); + if (isFromVercel) { + startVercelSetup(); + + return; + } + + navigate(ROUTES.GET_STARTED); + }; + + if (!currentUser || loading) { + return ; + } else { + return ( + { + const submissionValues = values?.submissionValues as unknown as { company: string; role__onboarding: string }; + onCreateOrganization({ + organizationName: submissionValues?.company, + jobTitle: submissionValues?.role__onboarding, + }); + }} + colorScheme="dark" + /> + ); + } +} + +interface IOrganizationCreateForm { + organizationName: string; + jobTitle: string | JobTitleEnum; + domain?: string; + productUseCases?: ProductUseCases; +} diff --git a/libs/shared-web/package.json b/libs/shared-web/package.json index f373d2dbc7d..96e63f89c38 100644 --- a/libs/shared-web/package.json +++ b/libs/shared-web/package.json @@ -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", diff --git a/libs/shared-web/src/components/HubspotForm.tsx b/libs/shared-web/src/components/HubspotForm.tsx new file mode 100644 index 00000000000..07fbda0a65a --- /dev/null +++ b/libs/shared-web/src/components/HubspotForm.tsx @@ -0,0 +1,240 @@ +import { useEffect } from 'react'; +import styled from '@emotion/styled'; +import { HUBSPOT_PORTAL_ID } from '../config'; + +// TODO: remove design system colors after fixing circular dependency from ee +const colors = { + white: '#FFFFFF', + black: '#000000', + B80: '#BEBECC', + B40: '#525266', + B20: '#292933', + vertical: `linear-gradient(0deg, #FF512F 0%, #DD2476 100%)`, + horizontal: `linear-gradient(99deg, #DD2476 0% 0%, #FF512F 100% 100%)`, +}; + +declare global { + interface Window { + hbspt: any; + } +} + +/** + * For the full list of available Hubspot Form options: + * + * @see https://developers.hubspot.com/docs/methods/forms/advanced_form_options + */ +export type HubspotFormProps< + TProperties extends Record, + TKey extends keyof TProperties & string = keyof TProperties & string +> = { + /** + * The Hubspot form ID. This can be found in the Hubspot form embed snippet. + */ + formId: string; + /** + * Properties for prepopulating fields. Keys must match the field names created in the Hubspot form. + */ + properties?: TProperties; + /** + * Read-only properties for prepopulating fields. Keys must match the field names created in the Hubspot form. + */ + readonlyProperties?: Array; + /** + * The name of the property to focus when the form is ready. + */ + focussedProperty?: TKey; + /** + * Callback function to be called when the form is submitted. + */ + onFormSubmitted?: ($form?: any, values?: Record) => void; + /** + * colorScheme + * + */ + colorScheme: 'dark' | 'light'; +}; + +const HUBSPOT_FORMS_URL = 'https://js.hsforms.net/forms/v2.js'; +const HUBSPOT_REGION = 'na1'; + +const cssClass = 'hubspot-form-wrapper'; +const StyledHubspotForm = styled.div<{ isDark: boolean }>` + .${cssClass} { + color: ${({ isDark }) => (isDark ? colors.B80 : colors.B40)}; + display: flex; + flex-direction: column; + gap: 16px; + + /** Column layout */ + .form-columns-1, + .form-columns-2 { + min-width: 100%; + display: flex; + gap: 20px; + + > * { + width: 100%; + } + + .hs-input { + width: 100%; + } + } + + /** Hyperlinks */ + a { + background-image: ${colors.horizontal}; + background-clip: text; + -webkit-text-fill-color: transparent; + text-decoration: none; + } + + .input { + input, + textarea, + select { + &:focus-visible { + outline: none; + border-color: ${({ isDark }) => (isDark ? colors.white : colors.black)}; + } + } + } + + /** Form fields */ + .hs-input { + appearance: none; + background-color: transparent; + border-radius: 7px; + border: 1px solid ${({ isDark }) => (isDark ? colors.B20 : colors.B80)}; + box-sizing: border-box; + display: block; + font-family: Lato, sans serif; + font-size: 14px; + height: 42px; + line-height: 40px; + margin: 5px 0px; /* Adjusted margin */ + min-height: 50px; + padding-left: 14px; + padding-right: 14px; + resize: none; + text-align: left; + transition: border-color 100ms ease; + width: 100%; + } + + /** Form text area */ + .hs-fieldtype-textarea { + resize: vertical; + min-height: 100px; + } + + /** Form button */ + .hs-button { + appearance: none; + background-color: transparent; + background-image: ${colors.horizontal}; + border-radius: 7px; + border: 0; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + font-family: Lato, sans serif; + font-size: 14px; + font-weight: 600; + height: 42px; + line-height: 1; + padding-left: 22px; + padding-right: 22px; + position: relative; + color: #fff; + text-align: right; + text-decoration: none; + user-select: none; + width: auto; + } + + /** Form field label */ + .hs-form-field label { + cursor: default; + display: inline-block; + font-size: 14px; + font-weight: 700; + line-height: 17px; + margin: 5px 0px; + word-break: break-word; + } + + /** Form Submit action alignment */ + .hs-submit .actions { + display: flex; + justify-content: flex-end; + } + + /** Legal consent container */ + .legal-consent-container { + font-size: 12px; + color: ${colors.B40}; + line-height: 16px; + + .p { + margin-top: 0; + margin-bottom: 0; + } + } + } +`; + +export const HubspotForm = >(props: HubspotFormProps) => { + const elementId = `hubspotForm-${props.formId}`; + + const createForm = () => { + if (window.hbspt) { + window.hbspt.forms.create({ + target: `#${elementId}`, + portalId: HUBSPOT_PORTAL_ID, + region: HUBSPOT_REGION, + cssClass, + onFormReady: (form) => { + if (props.focussedProperty) { + const selector = CSS.escape(`${props.focussedProperty}-${props.formId}`); + const input = form.querySelector(`#${selector}`) as HTMLInputElement; + if (input) { + input.focus(); + } + } + if (props.readonlyProperties) { + props.readonlyProperties.forEach((property) => { + const selector = CSS.escape(`${property}-${props.formId}`); + const input = form.querySelector(`#${selector}`) as HTMLInputElement; + if (input) { + input.setAttribute('readonly', 'true'); + } + }); + } + }, + ...props, + }); + } + }; + + useEffect(() => { + if (!window.hbspt) { + const script = document.createElement('script'); + script.src = HUBSPOT_FORMS_URL; + document.body.appendChild(script); + + script.addEventListener('load', () => { + createForm(); + }); + } else { + createForm(); + } + }, [props]); + + return ( + +
+
+ ); +}; diff --git a/libs/shared-web/src/components/index.ts b/libs/shared-web/src/components/index.ts new file mode 100644 index 00000000000..b88b589cb4d --- /dev/null +++ b/libs/shared-web/src/components/index.ts @@ -0,0 +1 @@ +export * from './HubspotForm'; diff --git a/libs/shared-web/src/config.ts b/libs/shared-web/src/config.ts index f7a96914637..7c10b628fe3 100644 --- a/libs/shared-web/src/config.ts +++ b/libs/shared-web/src/config.ts @@ -65,3 +65,5 @@ export const FEATURE_FLAGS = Object.values(FeatureFlagsKeysEnum).reduce((acc, ke return acc; }, {} as Record); + +export const HUBSPOT_PORTAL_ID = window._env_.REACT_APP_HUBSPOT_PORTAL_ID || process.env.REACT_APP_HUBSPOT_PORTAL_ID; diff --git a/libs/shared-web/src/index.ts b/libs/shared-web/src/index.ts index d1df7e9f466..626bbdcc373 100644 --- a/libs/shared-web/src/index.ts +++ b/libs/shared-web/src/index.ts @@ -3,3 +3,4 @@ export * from './api'; export * from './hooks'; export * from './providers'; export * from './constants'; +export * from './components'; diff --git a/libs/shared/src/dto/organization/create-organization.dto.ts b/libs/shared/src/dto/organization/create-organization.dto.ts index 36e6b0b5d14..252b931e2dc 100644 --- a/libs/shared/src/dto/organization/create-organization.dto.ts +++ b/libs/shared/src/dto/organization/create-organization.dto.ts @@ -6,7 +6,7 @@ export interface ICreateOrganizationDto { name: string; logo?: string; taxIdentifier?: string; - jobTitle?: JobTitleEnum; + jobTitle?: string | JobTitleEnum; domain?: string; productUseCases?: ProductUseCases; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc47e18ac14..42440544e67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2737,6 +2737,9 @@ importers: libs/shared-web: dependencies: + '@emotion/styled': + specifier: ^11.6.0 + version: 11.10.6(@emotion/react@11.10.6)(@types/react@17.0.62)(react@17.0.2) '@mantine/hooks': specifier: ^5.7.1 version: 5.10.5(react@17.0.2) @@ -16222,7 +16225,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.23.2 '@emotion/babel-plugin': 11.10.6 '@emotion/is-prop-valid': 1.2.0 '@emotion/react': 11.10.6(@types/react@17.0.62)(react@17.0.2) @@ -37264,6 +37267,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 5.5.0 + dev: true /debug@3.2.7(supports-color@8.1.1): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -38550,7 +38554,7 @@ packages: /eslint-import-resolver-node@0.3.7: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.13.0 resolve: 1.22.2 transitivePeerDependencies: @@ -38565,7 +38569,7 @@ packages: webpack: '>=1.11.0' dependencies: array.prototype.find: 2.2.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) enhanced-resolve: 0.9.1 eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.58.0)(eslint-import-resolver-webpack@0.13.7)(eslint@8.38.0) find-root: 1.1.0 @@ -38603,7 +38607,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.58.0(eslint@8.38.0)(typescript@4.9.5) - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-webpack: 0.13.7(eslint-plugin-import@2.28.1)(webpack@5.78.0) @@ -38633,7 +38637,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.58.0(eslint@8.51.0)(typescript@4.9.5) - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) eslint: 8.51.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-webpack: 0.13.7(eslint-plugin-import@2.28.1)(webpack@5.78.0) @@ -38714,7 +38718,7 @@ packages: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 @@ -38749,7 +38753,7 @@ packages: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.51.0 eslint-import-resolver-node: 0.3.7 @@ -43459,7 +43463,7 @@ packages: pretty-format: 27.5.1 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1(@types/node@16.11.7)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) transitivePeerDependencies: - bufferutil - canvas @@ -47648,7 +47652,7 @@ packages: hasBin: true requiresBuild: true dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) iconv-lite: 0.6.3 sax: 1.2.4 transitivePeerDependencies: @@ -50023,7 +50027,7 @@ packages: engines: {node: '>= 0.12.0'} dependencies: async: 2.6.4 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) mkdirp: 0.5.6 transitivePeerDependencies: - supports-color @@ -53882,7 +53886,7 @@ packages: jest-worker: 26.6.2 rollup: 2.79.1 serialize-javascript: 4.0.0 - terser: 5.22.0 + terser: 5.16.9 dev: true /rollup-plugin-terser@7.0.2(rollup@3.20.2): From fe0ce39e342eeec458bf4a027b0b7e996c69ab4b Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Sun, 10 Mar 2024 12:37:33 +0530 Subject: [PATCH 2/9] fix: cspell issue --- .cspell.json | 5 ++++- apps/web/src/constants/{hupspotForms.ts => hubspotForms.ts} | 0 apps/web/src/pages/auth/components/HubspotSignupForm.tsx | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) rename apps/web/src/constants/{hupspotForms.ts => hubspotForms.ts} (100%) diff --git a/.cspell.json b/.cspell.json index d40cee8342e..6ccf96d6c8d 100644 --- a/.cspell.json +++ b/.cspell.json @@ -603,7 +603,10 @@ "HEAY", "Pyroscope", "PYROSCOPE", - "usecases" + "usecases", + "hbspt", + "prepopulating", + "fieldtype", ], "flagWords": [], "patterns": [ diff --git a/apps/web/src/constants/hupspotForms.ts b/apps/web/src/constants/hubspotForms.ts similarity index 100% rename from apps/web/src/constants/hupspotForms.ts rename to apps/web/src/constants/hubspotForms.ts diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx index 2a484c3f5c3..54a81ad3b81 100644 --- a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -11,7 +11,7 @@ import { useAuthContext } from '../../../components/providers/AuthProvider'; import { useVercelIntegration, useVercelParams } from '../../../hooks'; import { ROUTES } from '../../../constants/routes.enum'; import { HubspotForm } from '@novu/shared-web'; -import { HUBSPOT_FORM_IDS } from '../../../constants/hupspotForms'; +import { HUBSPOT_FORM_IDS } from '../../../constants/hubspotForms'; import SetupLoader from './SetupLoader'; export function HubspotSignupForm() { From 91cad96883c6304a61cbd60ab4df0190f1637a5b Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Sun, 10 Mar 2024 21:03:18 +0530 Subject: [PATCH 3/9] fix: user jobtitle update --- .../app/organization/dtos/create-organization.dto.ts | 8 ++++---- .../create-organization.command.ts | 6 +++--- .../create-organization.usecase.ts | 2 +- .../src/pages/auth/components/HubspotSignupForm.tsx | 11 +++++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/api/src/app/organization/dtos/create-organization.dto.ts b/apps/api/src/app/organization/dtos/create-organization.dto.ts index 8a11687f979..8327f32bed3 100644 --- a/apps/api/src/app/organization/dtos/create-organization.dto.ts +++ b/apps/api/src/app/organization/dtos/create-organization.dto.ts @@ -1,5 +1,5 @@ -import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; -import { ICreateOrganizationDto, JobTitleEnum, ProductUseCases, ProductUseCasesEnum } from '@novu/shared'; +import { IsDefined, IsOptional, IsString } from 'class-validator'; +import { ICreateOrganizationDto, JobTitleEnum, ProductUseCases } from '@novu/shared'; export class CreateOrganizationDto implements ICreateOrganizationDto { @IsString() @@ -11,8 +11,8 @@ export class CreateOrganizationDto implements ICreateOrganizationDto { logo?: string; @IsOptional() - @IsEnum(JobTitleEnum) - jobTitle?: JobTitleEnum; + @IsString() + jobTitle?: string | JobTitleEnum; @IsString() @IsOptional() diff --git a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts index ca6e9bc08b4..18536dc9215 100644 --- a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts +++ b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts @@ -1,4 +1,4 @@ -import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; +import { IsDefined, IsOptional, IsString } from 'class-validator'; import { JobTitleEnum, ProductUseCases } from '@novu/shared'; @@ -14,8 +14,8 @@ export class CreateOrganizationCommand extends AuthenticatedCommand { public readonly logo?: string; @IsOptional() - @IsEnum(JobTitleEnum) - jobTitle?: JobTitleEnum; + @IsString() + jobTitle?: string | JobTitleEnum; @IsString() @IsOptional() diff --git a/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts b/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts index f78b81d41d4..33ad14686b8 100644 --- a/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts +++ b/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts @@ -102,7 +102,7 @@ export class CreateOrganization { return organizationAfterChanges as OrganizationEntity; } - private async updateJobTitle(user, jobTitle: JobTitleEnum) { + private async updateJobTitle(user, jobTitle: string | JobTitleEnum) { await this.userRepository.update( { _id: user._id, diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx index 54a81ad3b81..dabf2cd2273 100644 --- a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -46,8 +46,8 @@ export function HubspotSignupForm() { }, [token, navigate, isFromVercel, startVercelSetup]); async function createOrganization(data: IOrganizationCreateForm) { - const { organizationName, ...rest } = data; - const createDto: ICreateOrganizationDto = { ...rest, name: organizationName }; + 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`, {}); @@ -99,10 +99,13 @@ export function HubspotSignupForm() { readonlyProperties={['email', 'firstname', 'lastname']} focussedProperty="company" onFormSubmitted={($form, values) => { - const submissionValues = values?.submissionValues as unknown as { company: string; role__onboarding: string }; + const submissionValues = values?.submissionValues as unknown as { + company: string; + role___onboarding: string; + }; onCreateOrganization({ organizationName: submissionValues?.company, - jobTitle: submissionValues?.role__onboarding, + jobTitle: submissionValues?.role___onboarding, }); }} colorScheme="dark" From c24be3fbea71d1e8f9699c4375cf594b246aae91 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Mon, 11 Mar 2024 22:55:23 +0530 Subject: [PATCH 4/9] feat: role to job title mapping --- .../src/app/auth/dtos/user-registration.dto.ts | 8 +------- .../dtos/create-organization.dto.ts | 2 +- .../create-organization.command.ts | 2 +- .../create-organization.usecase.ts | 2 +- apps/web/src/constants/hubspotForms.ts | 2 +- .../auth/components/HubspotSignupForm.tsx | 18 ++++++++++++++++-- .../organization/create-organization.dto.ts | 2 +- libs/shared/src/types/organization/index.ts | 4 ++++ 8 files changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/api/src/app/auth/dtos/user-registration.dto.ts b/apps/api/src/app/auth/dtos/user-registration.dto.ts index 01fdbe93f1a..03e71995418 100644 --- a/apps/api/src/app/auth/dtos/user-registration.dto.ts +++ b/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() diff --git a/apps/api/src/app/organization/dtos/create-organization.dto.ts b/apps/api/src/app/organization/dtos/create-organization.dto.ts index 8327f32bed3..eea13acbc7b 100644 --- a/apps/api/src/app/organization/dtos/create-organization.dto.ts +++ b/apps/api/src/app/organization/dtos/create-organization.dto.ts @@ -12,7 +12,7 @@ export class CreateOrganizationDto implements ICreateOrganizationDto { @IsOptional() @IsString() - jobTitle?: string | JobTitleEnum; + jobTitle?: JobTitleEnum; @IsString() @IsOptional() diff --git a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts index 18536dc9215..cde23e74263 100644 --- a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts +++ b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts @@ -15,7 +15,7 @@ export class CreateOrganizationCommand extends AuthenticatedCommand { @IsOptional() @IsString() - jobTitle?: string | JobTitleEnum; + jobTitle?: JobTitleEnum; @IsString() @IsOptional() diff --git a/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts b/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts index 33ad14686b8..f78b81d41d4 100644 --- a/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts +++ b/apps/api/src/app/organization/usecases/create-organization/create-organization.usecase.ts @@ -102,7 +102,7 @@ export class CreateOrganization { return organizationAfterChanges as OrganizationEntity; } - private async updateJobTitle(user, jobTitle: string | JobTitleEnum) { + private async updateJobTitle(user, jobTitle: JobTitleEnum) { await this.userRepository.update( { _id: user._id, diff --git a/apps/web/src/constants/hubspotForms.ts b/apps/web/src/constants/hubspotForms.ts index 40dcc6dd8a1..4bc2edbd2c1 100644 --- a/apps/web/src/constants/hubspotForms.ts +++ b/apps/web/src/constants/hubspotForms.ts @@ -1,3 +1,3 @@ export const HUBSPOT_FORM_IDS = { - SIGN_UP: 'c03c3a65-9b90-432f-a1f2-e394525e49f8', + SIGN_UP: 'ae2194a3-2b9d-4625-9c64-454187c42e8b', }; diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx index dabf2cd2273..0e45a1fc7a3 100644 --- a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -95,6 +95,8 @@ export function HubspotSignupForm() { role___onboarding: '', heard_about_novu: '', use_case___onboarding: '', + role___onboarding__other_: '', + heard_about_novu__other_: '', }} readonlyProperties={['email', 'firstname', 'lastname']} focussedProperty="company" @@ -103,9 +105,10 @@ export function HubspotSignupForm() { company: string; role___onboarding: string; }; + onCreateOrganization({ organizationName: submissionValues?.company, - jobTitle: submissionValues?.role___onboarding, + jobTitle: hubspotRoleToJobTitleMapping[submissionValues?.role___onboarding], }); }} colorScheme="dark" @@ -116,7 +119,18 @@ export function HubspotSignupForm() { interface IOrganizationCreateForm { organizationName: string; - jobTitle: string | JobTitleEnum; + jobTitle: JobTitleEnum; domain?: string; productUseCases?: ProductUseCases; } + +const hubspotRoleToJobTitleMapping: Record = { + 'Engineer/developer': JobTitleEnum.ENGINEER, + Product: JobTitleEnum.PRODUCT_MANAGER, + Architect: JobTitleEnum.ARCHITECT, + 'Engineering Manager': JobTitleEnum.ENGINEERING_MANAGER, + Designer: JobTitleEnum.DESIGNER, + 'CxO/Founder': JobTitleEnum.CXO_FOUNDER, + Marketing: JobTitleEnum.MARKETING_MANAGER, + 'Other (specify)': JobTitleEnum.OTHER, +}; diff --git a/libs/shared/src/dto/organization/create-organization.dto.ts b/libs/shared/src/dto/organization/create-organization.dto.ts index 252b931e2dc..36e6b0b5d14 100644 --- a/libs/shared/src/dto/organization/create-organization.dto.ts +++ b/libs/shared/src/dto/organization/create-organization.dto.ts @@ -6,7 +6,7 @@ export interface ICreateOrganizationDto { name: string; logo?: string; taxIdentifier?: string; - jobTitle?: string | JobTitleEnum; + jobTitle?: JobTitleEnum; domain?: string; productUseCases?: ProductUseCases; } diff --git a/libs/shared/src/types/organization/index.ts b/libs/shared/src/types/organization/index.ts index 7c49ccf5c82..6d4cbe3bb83 100644 --- a/libs/shared/src/types/organization/index.ts +++ b/libs/shared/src/types/organization/index.ts @@ -22,6 +22,8 @@ export enum JobTitleEnum { ARCHITECT = 'architect', PRODUCT_MANAGER = 'product_manager', DESIGNER = 'designer', + CXO_FOUNDER = 'cxo_founder', + MARKETING_MANAGER = 'marketing_manager', OTHER = 'other', } @@ -31,5 +33,7 @@ export const jobTitleToLabelMapper = { [JobTitleEnum.PRODUCT_MANAGER]: 'Product Manager', [JobTitleEnum.DESIGNER]: 'Designer', [JobTitleEnum.ENGINEERING_MANAGER]: 'Engineering Manager', + [JobTitleEnum.CXO_FOUNDER]: 'CXO Founder', + [JobTitleEnum.MARKETING_MANAGER]: 'Marketing Manager', [JobTitleEnum.OTHER]: 'Other', }; From 01814bd64de8d8f933981ecbc721a5b0d721e908 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Tue, 12 Mar 2024 17:17:13 +0530 Subject: [PATCH 5/9] fix: founder enum value --- .../src/app/organization/dtos/create-organization.dto.ts | 4 ++-- apps/web/src/pages/auth/components/HubspotSignupForm.tsx | 6 ++---- libs/shared/src/types/organization/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/api/src/app/organization/dtos/create-organization.dto.ts b/apps/api/src/app/organization/dtos/create-organization.dto.ts index eea13acbc7b..ef7a6ab559e 100644 --- a/apps/api/src/app/organization/dtos/create-organization.dto.ts +++ b/apps/api/src/app/organization/dtos/create-organization.dto.ts @@ -1,4 +1,4 @@ -import { IsDefined, IsOptional, IsString } from 'class-validator'; +import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; import { ICreateOrganizationDto, JobTitleEnum, ProductUseCases } from '@novu/shared'; export class CreateOrganizationDto implements ICreateOrganizationDto { @@ -11,7 +11,7 @@ export class CreateOrganizationDto implements ICreateOrganizationDto { logo?: string; @IsOptional() - @IsString() + @IsEnum(JobTitleEnum) jobTitle?: JobTitleEnum; @IsString() diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx index 0e45a1fc7a3..28b2fc9a39c 100644 --- a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -17,12 +17,10 @@ import SetupLoader from './SetupLoader'; export function HubspotSignupForm() { const [loading, setLoading] = useState(); const navigate = useNavigate(); - const { setToken, token } = useAuthContext(); + const { setToken, token, currentUser } = useAuthContext(); const { startVercelSetup } = useVercelIntegration(); const { isFromVercel } = useVercelParams(); - const { currentUser } = useAuthContext(); - const { mutateAsync: createOrganizationMutation } = useMutation< { _id: string }, IResponseError, @@ -130,7 +128,7 @@ const hubspotRoleToJobTitleMapping: Record = { Architect: JobTitleEnum.ARCHITECT, 'Engineering Manager': JobTitleEnum.ENGINEERING_MANAGER, Designer: JobTitleEnum.DESIGNER, - 'CxO/Founder': JobTitleEnum.CXO_FOUNDER, + 'CxO/Founder': JobTitleEnum.FOUNDER, Marketing: JobTitleEnum.MARKETING_MANAGER, 'Other (specify)': JobTitleEnum.OTHER, }; diff --git a/libs/shared/src/types/organization/index.ts b/libs/shared/src/types/organization/index.ts index 6d4cbe3bb83..7ee58494dc9 100644 --- a/libs/shared/src/types/organization/index.ts +++ b/libs/shared/src/types/organization/index.ts @@ -22,7 +22,7 @@ export enum JobTitleEnum { ARCHITECT = 'architect', PRODUCT_MANAGER = 'product_manager', DESIGNER = 'designer', - CXO_FOUNDER = 'cxo_founder', + FOUNDER = 'cxo_founder', MARKETING_MANAGER = 'marketing_manager', OTHER = 'other', } @@ -33,7 +33,7 @@ export const jobTitleToLabelMapper = { [JobTitleEnum.PRODUCT_MANAGER]: 'Product Manager', [JobTitleEnum.DESIGNER]: 'Designer', [JobTitleEnum.ENGINEERING_MANAGER]: 'Engineering Manager', - [JobTitleEnum.CXO_FOUNDER]: 'CXO Founder', + [JobTitleEnum.FOUNDER]: 'CXO Founder', [JobTitleEnum.MARKETING_MANAGER]: 'Marketing Manager', [JobTitleEnum.OTHER]: 'Other', }; From 47342481144e94f40fae842c9df16731ca1a90c0 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Tue, 12 Mar 2024 22:56:04 +0530 Subject: [PATCH 6/9] fix: add color scheme --- .../create-organization/create-organization.command.ts | 4 ++-- apps/web/src/config/index.ts | 2 -- apps/web/src/pages/auth/QuestionnairePage.tsx | 7 ++++--- .../src/pages/auth/components/HubspotSignupForm.tsx | 10 ++++++---- libs/shared-web/src/components/HubspotForm.tsx | 1 - 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts index cde23e74263..ca6e9bc08b4 100644 --- a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts +++ b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts @@ -1,4 +1,4 @@ -import { IsDefined, IsOptional, IsString } from 'class-validator'; +import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; import { JobTitleEnum, ProductUseCases } from '@novu/shared'; @@ -14,7 +14,7 @@ export class CreateOrganizationCommand extends AuthenticatedCommand { public readonly logo?: string; @IsOptional() - @IsString() + @IsEnum(JobTitleEnum) jobTitle?: JobTitleEnum; @IsString() diff --git a/apps/web/src/config/index.ts b/apps/web/src/config/index.ts index c596dd50cf6..2be75692c1b 100644 --- a/apps/web/src/config/index.ts +++ b/apps/web/src/config/index.ts @@ -14,7 +14,6 @@ import { MAIL_SERVER_DOMAIN, LAUNCH_DARKLY_CLIENT_SIDE_ID, FEATURE_FLAGS, - HUBSPOT_PORTAL_ID, } from '@novu/shared-web'; export { @@ -33,7 +32,6 @@ export { MAIL_SERVER_DOMAIN, LAUNCH_DARKLY_CLIENT_SIDE_ID, FEATURE_FLAGS, - HUBSPOT_PORTAL_ID, }; export const IS_EU_ENV = (ENV === 'production' || ENV === 'prod') && API_ROOT.includes('eu.api.novu.co'); diff --git a/apps/web/src/pages/auth/QuestionnairePage.tsx b/apps/web/src/pages/auth/QuestionnairePage.tsx index fcb72045e3a..2afcf1ab85e 100644 --- a/apps/web/src/pages/auth/QuestionnairePage.tsx +++ b/apps/web/src/pages/auth/QuestionnairePage.tsx @@ -3,11 +3,12 @@ import AuthContainer from '../../components/layout/components/AuthContainer'; import { QuestionnaireForm } from './components/QuestionnaireForm'; import { useVercelIntegration } from '../../hooks'; import SetupLoader from './components/SetupLoader'; -import { IS_DOCKER_HOSTED } from '@novu/shared-web'; +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 ( @@ -16,9 +17,9 @@ export default function QuestionnairePage() { ) : ( - {IS_DOCKER_HOSTED ? : } + {!isNovuProd ? : } )} diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx index 28b2fc9a39c..3b71da8653b 100644 --- a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -2,15 +2,16 @@ 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 } 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 { HubspotForm } from '@novu/shared-web'; import { HUBSPOT_FORM_IDS } from '../../../constants/hubspotForms'; import SetupLoader from './SetupLoader'; @@ -20,6 +21,7 @@ export function HubspotSignupForm() { const { setToken, token, currentUser } = useAuthContext(); const { startVercelSetup } = useVercelIntegration(); const { isFromVercel } = useVercelParams(); + const { colorScheme } = useMantineColorScheme(); const { mutateAsync: createOrganizationMutation } = useMutation< { _id: string }, @@ -59,7 +61,7 @@ export function HubspotSignupForm() { return jwt && jwt[key]; } - const onCreateOrganization = async (data: IOrganizationCreateForm) => { + const handleCreateOrganization = async (data: IOrganizationCreateForm) => { if (!data?.organizationName) return; setLoading(true); @@ -104,12 +106,12 @@ export function HubspotSignupForm() { role___onboarding: string; }; - onCreateOrganization({ + handleCreateOrganization({ organizationName: submissionValues?.company, jobTitle: hubspotRoleToJobTitleMapping[submissionValues?.role___onboarding], }); }} - colorScheme="dark" + colorScheme={colorScheme} /> ); } diff --git a/libs/shared-web/src/components/HubspotForm.tsx b/libs/shared-web/src/components/HubspotForm.tsx index 07fbda0a65a..0707ec97edf 100644 --- a/libs/shared-web/src/components/HubspotForm.tsx +++ b/libs/shared-web/src/components/HubspotForm.tsx @@ -50,7 +50,6 @@ export type HubspotFormProps< onFormSubmitted?: ($form?: any, values?: Record) => void; /** * colorScheme - * */ colorScheme: 'dark' | 'light'; }; From 4ae7867d02c4ba0360dee75fa0cf0ec82bd39850 Mon Sep 17 00:00:00 2001 From: Richard Fontein <32132657+rifont@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:18:26 +0000 Subject: [PATCH 7/9] fix: Add Vonage to cspell --- .cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.json b/.cspell.json index 75e71a36671..b054ccb441a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -606,6 +606,7 @@ "usecases", "hbspt", "prepopulating", + "Vonage", "fieldtype", "usecase", "zulip" From 565872f228fa59647966a4da6d18a2a07572fc50 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Wed, 13 Mar 2024 16:04:29 +0530 Subject: [PATCH 8/9] feat: update prod form value --- apps/web/src/constants/hubspotForms.ts | 2 +- apps/web/src/pages/auth/components/HubspotSignupForm.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/web/src/constants/hubspotForms.ts b/apps/web/src/constants/hubspotForms.ts index 4bc2edbd2c1..e1cfcecc845 100644 --- a/apps/web/src/constants/hubspotForms.ts +++ b/apps/web/src/constants/hubspotForms.ts @@ -1,3 +1,3 @@ export const HUBSPOT_FORM_IDS = { - SIGN_UP: 'ae2194a3-2b9d-4625-9c64-454187c42e8b', + SIGN_UP: 'c551a45f-a9fe-47eb-a2e5-4e8540d27695', }; diff --git a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx index 3b71da8653b..fb9278f4b67 100644 --- a/apps/web/src/pages/auth/components/HubspotSignupForm.tsx +++ b/apps/web/src/pages/auth/components/HubspotSignupForm.tsx @@ -6,7 +6,7 @@ import { useMantineColorScheme } from '@mantine/core'; import { JobTitleEnum } from '@novu/shared'; import type { ProductUseCases, IResponseError, ICreateOrganizationDto, IJwtPayload } from '@novu/shared'; -import { HubspotForm } from '@novu/shared-web'; +import { HubspotForm, useSegment } from '@novu/shared-web'; import { api } from '../../../api/api.client'; import { useAuthContext } from '../../../components/providers/AuthProvider'; @@ -23,6 +23,8 @@ export function HubspotSignupForm() { const { isFromVercel } = useVercelParams(); const { colorScheme } = useMantineColorScheme(); + const segment = useSegment(); + const { mutateAsync: createOrganizationMutation } = useMutation< { _id: string }, IResponseError, @@ -64,6 +66,8 @@ export function HubspotSignupForm() { const handleCreateOrganization = async (data: IOrganizationCreateForm) => { if (!data?.organizationName) return; + segment.track('Button Clicked - [Signup]', { action: 'hubspot questionnaire form submit' }); + setLoading(true); if (!jwtHasKey('organizationId')) { From 8d5bcf805583c34afcd89c4752814a07f3477547 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Wed, 13 Mar 2024 16:57:04 +0530 Subject: [PATCH 9/9] fix: change hubspot portal env --- libs/shared-web/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/shared-web/src/config.ts b/libs/shared-web/src/config.ts index 7c10b628fe3..a8b77f3875d 100644 --- a/libs/shared-web/src/config.ts +++ b/libs/shared-web/src/config.ts @@ -66,4 +66,4 @@ export const FEATURE_FLAGS = Object.values(FeatureFlagsKeysEnum).reduce((acc, ke return acc; }, {} as Record); -export const HUBSPOT_PORTAL_ID = window._env_.REACT_APP_HUBSPOT_PORTAL_ID || process.env.REACT_APP_HUBSPOT_PORTAL_ID; +export const HUBSPOT_PORTAL_ID = window._env_.REACT_APP_HUBSPOT_EMBED || process.env.REACT_APP_HUBSPOT_EMBED;