diff --git a/apps/api/src/app/auth/usecases/onboard-user/onboard-user.usecase.ts b/apps/api/src/app/auth/usecases/onboard-user/onboard-user.usecase.ts index d65bb37fe..8cedefec0 100644 --- a/apps/api/src/app/auth/usecases/onboard-user/onboard-user.usecase.ts +++ b/apps/api/src/app/auth/usecases/onboard-user/onboard-user.usecase.ts @@ -3,6 +3,7 @@ import { UserRepository } from '@impler/dal'; import { LEAD_SIGNUP_USING } from '@shared/constants'; import { OnboardUserCommand } from './onboard-user.command'; import { LeadService } from '@shared/services/lead.service'; +import { captureException } from '@shared/helpers/common.helper'; import { CreateProject, CreateProjectCommand } from 'app/project/usecases'; @Injectable() @@ -33,15 +34,19 @@ export class OnboardUser { const updatedUser = await this.userRepository.findOne({ _id: command._userId }); if (updatedUser) { - await this.leadService.createLead({ - 'First Name': updatedUser.firstName, - 'Last Name': updatedUser.lastName, - 'Lead Email': updatedUser.email, - 'Lead Source': updatedUser.source, - 'Mentioned Role': updatedUser.role, - 'Signup Method': updatedUser.signupMethod as LEAD_SIGNUP_USING, - 'Company Size': updatedUser.companySize, - }); + try { + await this.leadService.createLead({ + 'First Name': updatedUser.firstName, + 'Last Name': updatedUser.lastName, + 'Lead Email': updatedUser.email, + 'Lead Source': updatedUser.source, + 'Mentioned Role': updatedUser.role, + 'Signup Method': updatedUser.signupMethod as LEAD_SIGNUP_USING, + 'Company Size': updatedUser.companySize, + }); + } catch (error) { + captureException(error); + } } return createdProject; diff --git a/apps/api/src/app/auth/usecases/register-user/register-user.usecase.ts b/apps/api/src/app/auth/usecases/register-user/register-user.usecase.ts index 9edca689c..6d617ede3 100644 --- a/apps/api/src/app/auth/usecases/register-user/register-user.usecase.ts +++ b/apps/api/src/app/auth/usecases/register-user/register-user.usecase.ts @@ -69,11 +69,11 @@ export class RegisterUser { screen: SCREENS.VERIFY, token, }; - } else if (!!this.emailService.isConnected) { - return { - screen: SCREENS.ONBOARD, - token, - }; } + + return { + screen: SCREENS.ONBOARD, + token, + }; } } diff --git a/apps/api/src/app/auth/usecases/verify/verify.usecase.ts b/apps/api/src/app/auth/usecases/verify/verify.usecase.ts index ca8eb9adb..367a359ad 100644 --- a/apps/api/src/app/auth/usecases/verify/verify.usecase.ts +++ b/apps/api/src/app/auth/usecases/verify/verify.usecase.ts @@ -4,6 +4,7 @@ import { SCREENS } from '@impler/shared'; import { VerifyCommand } from './verify.command'; import { PaymentAPIService } from '@impler/services'; import { AuthService } from 'app/auth/services/auth.service'; +import { captureException } from '@shared/helpers/common.helper'; import { UserRepository, EnvironmentRepository } from '@impler/dal'; import { InvalidVerificationCodeException } from '@shared/exceptions/otp-verification.exception'; @@ -32,7 +33,11 @@ export class Verify { externalId: user.email, }; - await this.paymentAPIService.createUser(userData); + try { + await this.paymentAPIService.createUser(userData); + } catch (error) { + captureException(error); + } await this.userRepository.findOneAndUpdate( { diff --git a/apps/api/src/app/shared/helpers/common.helper.ts b/apps/api/src/app/shared/helpers/common.helper.ts index 70f2eb845..f1769d788 100644 --- a/apps/api/src/app/shared/helpers/common.helper.ts +++ b/apps/api/src/app/shared/helpers/common.helper.ts @@ -1,3 +1,4 @@ +import * as Sentry from '@sentry/node'; import { BadRequestException } from '@nestjs/common'; import { APIMessages } from '../constants'; import { PaginationResult, Defaults, FileMimeTypesEnum } from '@impler/shared'; @@ -76,3 +77,9 @@ export function generateVerificationCode(): string { return otp; } + +export function captureException(error: any) { + if (Sentry.isInitialized()) { + Sentry.captureException(error); + } else console.error(error); +} diff --git a/apps/api/src/app/shared/services/lead.service.ts b/apps/api/src/app/shared/services/lead.service.ts index 31fde19f3..0ebbed82f 100644 --- a/apps/api/src/app/shared/services/lead.service.ts +++ b/apps/api/src/app/shared/services/lead.service.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { Injectable } from '@nestjs/common'; import { LEAD_SIGNUP_USING } from '@shared/constants'; +import { captureException } from '@shared/helpers/common.helper'; interface ILeadInformation { 'First Name': string; @@ -82,16 +83,20 @@ export class LeadService { const maUrl = `https://marketingautomation.zoho.com/api/v1/json/listsubscribe?listkey=${process.env.LEAD_LIST_KEY}&leadinfo=${leadData}&topic_id=${process.env.LEAD_TOPIC_ID}`; if (this.log) console.log(maUrl); - const maResponse = await axios.post( - maUrl, - {}, - { - headers: { - Authorization: `Zoho-oauthtoken ${maAccessToken}`, - }, - } - ); - if (this.log) console.log('Lead created', maResponse.data); + try { + const maResponse = await axios.post( + maUrl, + {}, + { + headers: { + Authorization: `Zoho-oauthtoken ${maAccessToken}`, + }, + } + ); + if (this.log) console.log('Lead created', maResponse.data); + } catch (error) { + captureException(error); + } } const crmAccessToken = await this.getCRMAccessToken(); if (crmAccessToken) { @@ -99,28 +104,32 @@ export class LeadService { const crmUrl = `https://www.zohoapis.com/crm/v6/Leads`; if (this.log) console.log(crmUrl); - const crmResponse = await axios.post( - crmUrl, - { - data: [ - { - Last_Name: data['Last Name'], - First_Name: data['First Name'], - Email: data['Lead Email'], - Lead_Source: data['Lead Source'], - Signup_Method: data['Signup Method'], - Mentioned_Role: data['Mentioned Role'], - Company_Size: data['Company Size'], - }, - ], - }, - { - headers: { - Authorization: `Zoho-oauthtoken ${crmAccessToken}`, + try { + const crmResponse = await axios.post( + crmUrl, + { + data: [ + { + Last_Name: data['Last Name'], + First_Name: data['First Name'], + Email: data['Lead Email'], + Lead_Source: data['Lead Source'], + Signup_Method: data['Signup Method'], + Mentioned_Role: data['Mentioned Role'], + Company_Size: [data['Company Size']], + }, + ], }, - } - ); - if (this.log) console.log('CRM LEad created', crmResponse.data); + { + headers: { + Authorization: `Zoho-oauthtoken ${crmAccessToken}`, + }, + } + ); + if (this.log) console.log('CRM LEad created', crmResponse.data); + } catch (error) { + captureException(error); + } } } } diff --git a/apps/widget/src/components/Common/Container/Container.tsx b/apps/widget/src/components/Common/Container/Container.tsx index 7608abbd4..7ac7cf8c4 100644 --- a/apps/widget/src/components/Common/Container/Container.tsx +++ b/apps/widget/src/components/Common/Container/Container.tsx @@ -9,7 +9,7 @@ import { ApiService } from '@impler/client'; import { MessageHandlerDataType } from '@types'; import { generateShades, ParentWindow, deepMerge } from '@util'; import { API_URL, colors, mantineConfig, variables } from '@config'; -import { IWidgetShowPayload, WidgetEventTypesEnum, WIDGET_TEXTS } from '@impler/shared'; +import { IWidgetShowPayload, WidgetEventTypesEnum, WIDGET_TEXTS, isObject } from '@impler/shared'; let api: ApiService; @@ -48,9 +48,35 @@ export function Container({ children }: PropsWithChildren<{}>) { } setShowWidget(true); setSecondaryPayload({ - ...data.value, + accessToken: data.value.accessToken, + host: data.value.host, + projectId: data.value.projectId, + uuid: data.value.uuid, + extra: isObject(data.value.extra) ? JSON.stringify(data.value.extra) : data.value.extra, + templateId: data.value.templateId, + authHeaderValue: data.value.authHeaderValue, primaryColor: data.value.primaryColor || colors.primary, + colorScheme: data.value.colorScheme, + title: data.value.title, texts: deepMerge(WIDGET_TEXTS, data.value.texts), + schema: + typeof data.value.schema === 'string' + ? data.value.schema + : Array.isArray(data.value.schema) + ? JSON.stringify(data.value.schema) + : undefined, + data: + typeof data.value.data === 'string' + ? data.value.data + : Array.isArray(data.value.data) + ? JSON.stringify(data.value.data) + : undefined, + output: + typeof data.value.output === 'string' + ? data.value.output + : isObject(data.value.output) + ? JSON.stringify(data.value.output) + : undefined, }); } else if (data && data.type === WidgetEventTypesEnum.CLOSE_WIDGET) { setShowWidget(false); diff --git a/apps/widget/src/components/Common/Provider/Provider.tsx b/apps/widget/src/components/Common/Provider/Provider.tsx index 4fb010f48..8e3be2cc0 100644 --- a/apps/widget/src/components/Common/Provider/Provider.tsx +++ b/apps/widget/src/components/Common/Provider/Provider.tsx @@ -13,7 +13,7 @@ interface IProviderProps { primaryColor: string; output?: string; schema?: string; - data?: Record[]; + data?: string; host: string; showWidget: boolean; setShowWidget: (status: boolean) => void; diff --git a/apps/widget/src/hooks/useSample.ts b/apps/widget/src/hooks/useSample.ts index 0f440f57e..aeb70d7b7 100644 --- a/apps/widget/src/hooks/useSample.ts +++ b/apps/widget/src/hooks/useSample.ts @@ -67,7 +67,7 @@ export function useSample({ onDownloadComplete }: UseSampleProps) { ); sampleData.append('schema', JSON.stringify(parsedSchema)); } - if (Array.isArray(data)) sampleData.append('data', JSON.stringify(data)); + if (data) sampleData.append('data', data); if (images && importId && imageSchema) { const imagesBlob = await images.generateAsync({ type: 'blob', compression: 'DEFLATE' }); sampleData.append('file', imagesBlob); diff --git a/apps/widget/src/types/component.types.ts b/apps/widget/src/types/component.types.ts index e5c9f6d2b..95b428f0a 100644 --- a/apps/widget/src/types/component.types.ts +++ b/apps/widget/src/types/component.types.ts @@ -1,4 +1,4 @@ -import { IWidgetShowPayload } from '@impler/shared'; +import { IUserShowPayload } from '@impler/shared'; import { EventTypesEnum, WidgetEventTypesEnum } from '@impler/shared'; export type MessageHandlerDataType = @@ -7,7 +7,7 @@ export type MessageHandlerDataType = } | { type: WidgetEventTypesEnum.SHOW_WIDGET; - value: IWidgetShowPayload; + value: IUserShowPayload; } | { type: WidgetEventTypesEnum.CLOSE_WIDGET; diff --git a/apps/widget/src/types/store.types.ts b/apps/widget/src/types/store.types.ts index 5eb43ca84..32689ad9d 100644 --- a/apps/widget/src/types/store.types.ts +++ b/apps/widget/src/types/store.types.ts @@ -22,7 +22,7 @@ export interface IAppStore { texts: typeof WIDGET_TEXTS; importId?: string; imageSchema?: string; - data?: Record[]; + data?: string; templateInfo: ITemplate; uploadInfo: IUpload; reset: () => void; diff --git a/apps/widget/src/util/helpers/common.helpers.ts b/apps/widget/src/util/helpers/common.helpers.ts index bd8e73105..ec912fc8a 100644 --- a/apps/widget/src/util/helpers/common.helpers.ts +++ b/apps/widget/src/util/helpers/common.helpers.ts @@ -4,7 +4,7 @@ import tippy from 'tippy.js'; import 'tippy.js/dist/tippy.css'; import 'tippy.js/animations/shift-away.css'; import { variables } from '@config'; -import { downloadFile, WIDGET_TEXTS } from '@impler/shared'; +import { convertStringToJson, downloadFile, isObject, WIDGET_TEXTS } from '@impler/shared'; // eslint-disable-next-line no-magic-numbers export function formatBytes(bytes, decimals = 2) { @@ -97,20 +97,23 @@ export const addTippyToElement = (element: SVGSVGElement | HTMLElement, content: }); }; -function isObject(value: any): boolean { - return value instanceof Object && value.constructor === Object; -} - // Utility function to deeply merge defaultTexts with user provided texts -export function deepMerge(defaultTexts: typeof WIDGET_TEXTS, texts?: typeof WIDGET_TEXTS): typeof WIDGET_TEXTS { - if (!texts || !isObject(texts)) return defaultTexts; +export function deepMerge( + defaultTexts: typeof WIDGET_TEXTS, + texts?: string | typeof WIDGET_TEXTS +): typeof WIDGET_TEXTS { + if (!texts) return defaultTexts; + let newTexts: typeof WIDGET_TEXTS | undefined; + if (typeof texts === 'string') newTexts = convertStringToJson(texts); + else newTexts = texts; + if (newTexts && !isObject(newTexts)) return defaultTexts; else { const mergedResult = { ...defaultTexts }; - for (const sectionKey in texts) { - if (isObject(texts[sectionKey])) { - for (const textKey in texts[sectionKey]) { - if (mergedResult[sectionKey][textKey] && typeof texts[sectionKey][textKey] === 'string') { - mergedResult[sectionKey][textKey] = texts[sectionKey][textKey]; + for (const sectionKey in newTexts) { + if (isObject(newTexts[sectionKey])) { + for (const textKey in newTexts[sectionKey]) { + if (mergedResult[sectionKey][textKey] && typeof newTexts[sectionKey][textKey] === 'string') { + mergedResult[sectionKey][textKey] = newTexts[sectionKey][textKey]; } } } diff --git a/libs/shared/src/config/texts.config.ts b/libs/shared/src/config/texts.config.ts index 178d8e8de..cba0d5c4f 100644 --- a/libs/shared/src/config/texts.config.ts +++ b/libs/shared/src/config/texts.config.ts @@ -15,7 +15,7 @@ export const WIDGET_TEXTS = { CONFIRM_JOB: 'Confirm', }, FILE_DROP_AREA: { - DROP_FILE: 'Drop and drop file here or ', + DROP_FILE: 'Drop and drop a file here or ', BROWSE_FILE: 'Browse from computer', IMAGE_FILE_SIZE: 'Image size should be less than 5 MB. Supported formats are PNG, JPG and JPEG.', BRING_FILE: 'Bring any .csv or .xlsx file here to start Import', @@ -31,7 +31,7 @@ export const WIDGET_TEXTS = { PHASE1: { SELECT_TEMPLATE_NAME: 'Template', SELECT_TEMPLATE_NAME_PLACEHOLDER: 'Select Template', - SELECT_TEMPLATE_REQUIRED_MSG: 'Please select template from the list', + SELECT_TEMPLATE_REQUIRED_MSG: 'Please select a template from the list', SELECT_SHEET_NAME: 'Select sheet to Import', SELECT_SHEET_NAME_PLACEHOLDER: 'Select Excel sheet', @@ -47,7 +47,7 @@ export const WIDGET_TEXTS = { SELECT_FILE_FORMAT_MSG: 'File type not supported! Please select a .csv or .xlsx file.', TEMPLATE_NOT_FOUND_MSG: "We couldn't find the template you're importing! Please check the passed parameters.", - INCOMPLETE_TEMPLATE_MSG: 'This import do not have any columns. Please try again after some time!', + INCOMPLETE_TEMPLATE_MSG: 'This import does not have any columns. Please try again after some time!', }, PHASE2: { REVIEW_DATA: 'Review Data', @@ -63,8 +63,8 @@ export const WIDGET_TEXTS = { }, PHASE4: { TITLE: 'Bravo! {count} rows have been uploaded', - SUB_TITLE: '{count} rows have been uploaded successfully, and currently is in process, it will be ready shortly.', - UPLOAD_AGAIN: 'Upload new File', + SUB_TITLE: '{count} rows have been uploaded successfully and currently is in process, it will be ready shortly.', + UPLOAD_AGAIN: 'Upload New File', }, AUTOIMPORT_PHASE1: { MAPCOLUMN: 'Map Column', diff --git a/libs/shared/src/types/widget/widget.types.ts b/libs/shared/src/types/widget/widget.types.ts index 37fa2e7d0..4d38fe6c1 100644 --- a/libs/shared/src/types/widget/widget.types.ts +++ b/libs/shared/src/types/widget/widget.types.ts @@ -1,22 +1,30 @@ import { WIDGET_TEXTS } from '../../config/texts.config'; +import { ISchemaItem } from '../column'; export interface ICommonShowPayload { host: string; - extra?: string; + extra?: string | any; templateId?: string; authHeaderValue?: string; primaryColor?: string; colorScheme?: string; title?: string; - schema?: string; - data?: Record[]; - output?: string; projectId: string; accessToken: string; uuid: string; } export interface IWidgetShowPayload extends ICommonShowPayload { texts?: typeof WIDGET_TEXTS; + data?: string; + schema?: string; + output?: string; +} + +export interface IUserShowPayload extends ICommonShowPayload { + texts?: string | typeof WIDGET_TEXTS; + data?: string | Record[]; + schema?: string | ISchemaItem[]; + output?: string | Record; } export interface IOption { diff --git a/libs/shared/src/utils/helpers.ts b/libs/shared/src/utils/helpers.ts index e6e51f953..fc6082b82 100644 --- a/libs/shared/src/utils/helpers.ts +++ b/libs/shared/src/utils/helpers.ts @@ -73,3 +73,15 @@ export function constructQueryString(obj: Record): stri return query ? `?${query}` : ''; } + +export const isObject = (value: any) => + typeof value === 'object' && !Array.isArray(value) && value !== null && Object.keys(value).length > 0; + +export const convertStringToJson = (value: any) => { + if (isObject(value)) return value; + try { + return JSON.parse(value); + } catch (e) { + return undefined; + } +}; diff --git a/packages/react/src/hooks/useImpler.ts b/packages/react/src/hooks/useImpler.ts index d81378db6..cc99ac779 100644 --- a/packages/react/src/hooks/useImpler.ts +++ b/packages/react/src/hooks/useImpler.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; -import { EventTypesEnum, IShowPayload } from '@impler/shared'; +import { EventTypesEnum, IUserShowPayload, isObject } from '@impler/shared'; import { logError } from '../utils/logger'; import { EventCalls, ShowWidgetProps, UseImplerProps } from '../types'; @@ -77,35 +77,25 @@ export function useImpler({ const showWidget = async ({ colorScheme, data, schema, output }: ShowWidgetProps = {}) => { if (window.impler && isImplerInitiated) { - const payload: IShowPayload = { + const payload: IUserShowPayload = { uuid, templateId, - data, host: '', projectId, accessToken, - texts, + schema, + data, + output, + title, + extra, + colorScheme, + primaryColor, }; - if (Array.isArray(schema) && schema.length > 0) { - payload.schema = JSON.stringify(schema); - } - if (typeof output === 'object' && !Array.isArray(output) && output !== null) { - payload.output = JSON.stringify(output); - } - if (title) payload.title = title; - if (colorScheme) payload.colorScheme = colorScheme; - else { + if (!colorScheme) { const preferColorScheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; payload.colorScheme = preferColorScheme; } - - if (texts) payload.texts = texts; - - if (primaryColor) payload.primaryColor = primaryColor; - if (extra) { - if (typeof extra === 'object') payload.extra = JSON.stringify(extra); - else payload.extra = extra; - } + if (isObject(texts)) payload.texts = JSON.stringify(texts); if (authHeaderValue) { if (typeof authHeaderValue === 'function' && authHeaderValue.constructor.name === 'AsyncFunction') { payload.authHeaderValue = await authHeaderValue(); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index fb889249b..ff73828ea 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,2 +1,3 @@ export * from './hooks'; +export type { ColumnTypesEnum } from '@impler/shared'; export type { ISchemaItem, CustomTexts } from './types'; diff --git a/packages/react/src/types/index.ts b/packages/react/src/types/index.ts index 39b95aeed..420c44650 100644 --- a/packages/react/src/types/index.ts +++ b/packages/react/src/types/index.ts @@ -1,4 +1,4 @@ -import { IUpload, EventTypesEnum, WIDGET_TEXTS } from '@impler/shared'; +import { IUpload, EventTypesEnum, WIDGET_TEXTS, ColumnTypesEnum } from '@impler/shared'; export interface ISchemaItem { key: string; @@ -11,7 +11,7 @@ export interface ISchemaItem { defaultValue?: string | '<>' | '<>' | '<<>>' | '<<[]>>' | '<>' | '<>'; selectValues?: string[]; dateFormats?: string[]; - type?: 'String' | 'Number' | 'Double' | 'Date' | 'Email' | 'Regex' | 'Select' | 'Any' | 'Image'; + type?: ColumnTypesEnum; regex?: string; allowMultiSelect?: boolean; }