1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-08 23:15:24 +02:00

Merge pull request #27 from MichaelDeBoey/remove-intercom-usage

feat: remove Intercom usage
This commit is contained in:
Josh Pigford 2024-01-11 08:51:50 -06:00 committed by GitHub
commit 80df3ea261
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 18 additions and 255 deletions

View file

@ -1,28 +1,9 @@
import { useAuth0 } from '@auth0/auth0-react' import { useAuth0 } from '@auth0/auth0-react'
import { useIntercom } from '@maybe-finance/client/shared'
import { useRouter } from 'next/router'
import { useEffect } from 'react' import { useEffect } from 'react'
import * as Sentry from '@sentry/react' import * as Sentry from '@sentry/react'
export default function APM() { export default function APM() {
const { user } = useAuth0() const { user } = useAuth0()
const router = useRouter()
const intercom = useIntercom()
// Boot intercom
useEffect(() => {
const isBooted = intercom.boot()
const handleRouteChange = () => {
if (isBooted) {
intercom.update()
}
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => router.events.off('routeChangeComplete', handleRouteChange)
}, [intercom, router.events])
// Identify Sentry user // Identify Sentry user
useEffect(() => { useEffect(() => {

View file

@ -48,15 +48,6 @@ export default function Meta() {
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css"
rel="stylesheet" rel="stylesheet"
/> />
{/* Intercom */}
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html: `window.INTERCOM_APP_ID='${env.NEXT_PUBLIC_INTERCOM_APP_ID}';(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/' + window.INTERCOM_APP_ID;var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s, x);};if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();`,
}}
/>
{/* End Intercom */}
</Head> </Head>
) )
} }

View file

@ -10,7 +10,6 @@ const env = {
process.env.NEXT_PUBLIC_LD_CLIENT_SIDE_ID || 'REPLACE_THIS', process.env.NEXT_PUBLIC_LD_CLIENT_SIDE_ID || 'REPLACE_THIS',
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
NEXT_PUBLIC_SENTRY_ENV: process.env.NEXT_PUBLIC_SENTRY_ENV, NEXT_PUBLIC_SENTRY_ENV: process.env.NEXT_PUBLIC_SENTRY_ENV,
NEXT_PUBLIC_INTERCOM_APP_ID: process.env.NEXT_PUBLIC_INTERCOM_APP_ID || 'REPLACE_THIS',
} }
export default env export default env

View file

@ -1,4 +1,4 @@
import { BrowserUtil, usePlaid } from '@maybe-finance/client/shared' import { usePlaid } from '@maybe-finance/client/shared'
import { Disclosure, Transition } from '@headlessui/react' import { Disclosure, Transition } from '@headlessui/react'
import { RiArrowUpSFill } from 'react-icons/ri' import { RiArrowUpSFill } from 'react-icons/ri'
import { Button, LoadingSpinner } from '@maybe-finance/design-system' import { Button, LoadingSpinner } from '@maybe-finance/design-system'
@ -56,14 +56,9 @@ export default function OAuth() {
</li> </li>
<li> <li>
Still not working?{' '} Still not working?{' '}
<button <a className="underline text-cyan" href="mailto:hello@maybe.co">
onClick={() =>
BrowserUtil.showIntercom()
}
className="underline text-cyan"
>
Let us know! Let us know!
</button> </a>
</li> </li>
</ul> </ul>
</Disclosure.Panel> </Disclosure.Panel>

View file

@ -145,19 +145,6 @@ router.get(
}) })
) )
router.get(
'/intercom',
endpoint.create({
resolve: async ({ ctx }) => {
if (!ctx.user || !ctx.user.id) {
throw new Error('User not found')
}
return ctx.userService.getIntercomMetadata(ctx.user.id, env.NX_INTERCOM_SECRET)
},
})
)
router.put( router.put(
'/', '/',
endpoint.create({ endpoint.create({

View file

@ -65,8 +65,6 @@ const envSchema = z.object({
.string() .string()
.default(process.env.NODE_ENV === 'development' ? 'dev' : 'combined'), .default(process.env.NODE_ENV === 'development' ? 'dev' : 'combined'),
NX_INTERCOM_SECRET: z.string().optional(),
NX_STRIPE_SECRET_KEY: z.string().default('REPLACE_THIS'), NX_STRIPE_SECRET_KEY: z.string().default('REPLACE_THIS'),
NX_STRIPE_WEBHOOK_SECRET: z.string().default('whsec_REPLACE_THIS'), NX_STRIPE_WEBHOOK_SECRET: z.string().default('whsec_REPLACE_THIS'),
NX_STRIPE_PREMIUM_MONTHLY_PRICE_ID: z.string().default('price_REPLACE_THIS'), NX_STRIPE_PREMIUM_MONTHLY_PRICE_ID: z.string().default('price_REPLACE_THIS'),

View file

@ -179,15 +179,6 @@ export class ServerStack extends Stack {
} }
) )
), ),
NX_INTERCOM_SECRET: ECSSecret.fromSsmParameter(
StringParameter.fromSecureStringParameterAttributes(
this,
'IntercomSecretParam',
{
parameterName: '/providers/NX_INTERCOM_SECRET',
}
)
),
NX_STRIPE_SECRET_KEY: ECSSecret.fromSsmParameter( NX_STRIPE_SECRET_KEY: ECSSecret.fromSsmParameter(
StringParameter.fromSecureStringParameterAttributes( StringParameter.fromSecureStringParameterAttributes(
this, this,

View file

@ -13,7 +13,6 @@ import {
RiMore2Fill, RiMore2Fill,
} from 'react-icons/ri' } from 'react-icons/ri'
import { HiOutlineSparkles } from 'react-icons/hi' import { HiOutlineSparkles } from 'react-icons/hi'
import { BrowserUtil } from '@maybe-finance/client/shared'
import Link from 'next/link' import Link from 'next/link'
import { AnimatePresence, motion } from 'framer-motion' import { AnimatePresence, motion } from 'framer-motion'
@ -399,13 +398,11 @@ export function SidebarOnboarding({ onClose, onHide }: Props) {
</Disclosure> </Disclosure>
<p className="text-sm text-gray-100"> <p className="text-sm text-gray-100">
If you have any issues with connecting accounts, please let us know{' '} If you have any issues with connecting accounts,{' '}
<button <a className="text-cyan underline" href="mailto:hello@maybe.co">
onClick={() => BrowserUtil.showIntercom()} please let us know
className="text-cyan cursor-pointer hover:underline" </a>
> .
via live chat
</button>
</p> </p>
</> </>
) )

View file

@ -1,5 +1,5 @@
import { useAuth0 } from '@auth0/auth0-react' import { useAuth0 } from '@auth0/auth0-react'
import { BoxIcon, BrowserUtil, linkAuth0AccountCtx, useUserApi } from '@maybe-finance/client/shared' import { BoxIcon, linkAuth0AccountCtx, useUserApi } from '@maybe-finance/client/shared'
import { Button, DialogV2 } from '@maybe-finance/design-system' import { Button, DialogV2 } from '@maybe-finance/design-system'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useState } from 'react' import { useState } from 'react'
@ -253,12 +253,9 @@ function LinkError({ onClose, error }: { onClose(): void; error: string }) {
<p className="mb-2 text-gray-50 text-base">{error}</p> <p className="mb-2 text-gray-50 text-base">{error}</p>
<button <a className="underline text-cyan text-base mb-6" href="mailto:hello@maybe.co">
className="underline text-cyan text-base mb-6"
onClick={BrowserUtil.showIntercom}
>
Please contact us. Please contact us.
</button> </a>
<div className="flex w-full gap-4"> <div className="flex w-full gap-4">
<Button fullWidth onClick={onClose}> <Button fullWidth onClick={onClose}>

View file

@ -8,10 +8,9 @@ import type {
} from '@tanstack/react-query' } from '@tanstack/react-query'
import { useMemo } from 'react' import { useMemo } from 'react'
import sumBy from 'lodash/sumBy'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useAxiosWithAuth, useIntercom } from '..' import { useAxiosWithAuth } from '..'
import { invalidateAccountQueries } from '../utils' import { invalidateAccountQueries } from '../utils'
const AccountApi = (axios: AxiosInstance) => ({ const AccountApi = (axios: AxiosInstance) => ({
@ -135,7 +134,6 @@ export function useAccountApi() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const { axios } = useAxiosWithAuth() const { axios } = useAxiosWithAuth()
const api = useMemo(() => AccountApi(axios), [axios]) const api = useMemo(() => AccountApi(axios), [axios])
const { update: updateIntercom } = useIntercom()
const useAccounts = ( const useAccounts = (
options?: Omit< options?: Omit<
@ -152,13 +150,6 @@ export function useAccountApi() {
staleTime: staleTimes.accounts, staleTime: staleTimes.accounts,
onSuccess: (...args) => { onSuccess: (...args) => {
if (options?.onSuccess) options.onSuccess(...args) if (options?.onSuccess) options.onSuccess(...args)
const [{ accounts, connections }] = args
updateIntercom({
'Manual Accounts': accounts.length,
'Connected Accounts': sumBy(connections, (c) => c.accounts.length),
Connections: connections.length,
})
}, },
...options, ...options,
}) })

View file

@ -38,11 +38,6 @@ const UserApi = (
return data return data
}, },
async getIntercomMetadata() {
const { data } = await axios.get<SharedType.UserIntercomMetadata>('/users/intercom')
return data
},
async update(userData: SharedType.UpdateUser) { async update(userData: SharedType.UpdateUser) {
const { data } = await axios.put<SharedType.User>('/users', userData) const { data } = await axios.put<SharedType.User>('/users', userData)
return data return data
@ -254,22 +249,6 @@ export function useUserApi() {
staleTime: staleTimes.insights, staleTime: staleTimes.insights,
}) })
const useIntercomMetadata = (
options?: Omit<
UseQueryOptions<
SharedType.UserIntercomMetadata,
unknown,
SharedType.UserIntercomMetadata,
any[]
>,
'queryKey' | 'queryFn'
>
) =>
useQuery(['users', 'intercom-metadata'], api.getIntercomMetadata, {
refetchOnWindowFocus: false,
...options,
})
const useProfile = ( const useProfile = (
options?: Omit< options?: Omit<
UseQueryOptions<SharedType.User, unknown, SharedType.User, any[]>, UseQueryOptions<SharedType.User, unknown, SharedType.User, any[]>,
@ -471,7 +450,6 @@ export function useUserApi() {
useNetWorthSeries, useNetWorthSeries,
useInsights, useInsights,
useCurrentNetWorth, useCurrentNetWorth,
useIntercomMetadata,
useProfile, useProfile,
useUpdateProfile, useUpdateProfile,
useAuth0Profile, useAuth0Profile,

View file

@ -1,7 +1,6 @@
export * from './useAxiosWithAuth' export * from './useAxiosWithAuth'
export * from './useDebounce' export * from './useDebounce'
export * from './useFinicity' export * from './useFinicity'
export * from './useIntercom'
export * from './useInterval' export * from './useInterval'
export * from './useLastUpdated' export * from './useLastUpdated'
export * from './useLocalStorage' export * from './useLocalStorage'

View file

@ -5,12 +5,10 @@ import type {
ConnectCancelEvent, ConnectCancelEvent,
ConnectDoneEvent, ConnectDoneEvent,
ConnectErrorEvent, ConnectErrorEvent,
ConnectRouteEvent,
} from '@finicity/connect-web-sdk' } from '@finicity/connect-web-sdk'
import { useFinicityApi } from '../api' import { useFinicityApi } from '../api'
import { useAccountContext, useUserAccountContext } from '../providers' import { useAccountContext, useUserAccountContext } from '../providers'
import { useLogger } from './useLogger' import { useLogger } from './useLogger'
import { BrowserUtil } from '..'
export function useFinicity() { export function useFinicity() {
const logger = useLogger() const logger = useLogger()
@ -35,9 +33,6 @@ export function useFinicity() {
FinicityConnect.launch(link, { FinicityConnect.launch(link, {
onDone(evt: ConnectDoneEvent) { onDone(evt: ConnectDoneEvent) {
logger.debug(`Finicity Connect onDone event`, evt) logger.debug(`Finicity Connect onDone event`, evt)
BrowserUtil.trackIntercomEvent('FINICITY_CONNECT_DONE', {
...evt,
})
setExpectingAccounts(true) setExpectingAccounts(true)
}, },
onError(evt: ConnectErrorEvent) { onError(evt: ConnectErrorEvent) {
@ -50,25 +45,15 @@ export function useFinicity() {
'finicity.error.reason': evt.reason, 'finicity.error.reason': evt.reason,
}, },
}) })
BrowserUtil.trackIntercomEvent('FINICITY_CONNECT_ERROR', {
...evt,
})
}, },
onCancel(evt: ConnectCancelEvent) { onCancel(evt: ConnectCancelEvent) {
logger.debug(`Finicity Connect onCancel event`, evt) logger.debug(`Finicity Connect onCancel event`, evt)
BrowserUtil.trackIntercomEvent('FINICITY_CONNECT_CANCEL', {
...evt,
})
}, },
onUser(evt: any) { onUser() {
BrowserUtil.trackIntercomEvent('FINICITY_CONNECT_USER_ACTION', { // ...
...evt,
})
}, },
onRoute(evt: ConnectRouteEvent) { onRoute() {
BrowserUtil.trackIntercomEvent('FINICITY_CONNECT_ROUTE_EVENT', { // ...
...evt,
})
}, },
}) })
}, },

View file

@ -1,50 +0,0 @@
import { useCallback } from 'react'
import { useAuth0 } from '@auth0/auth0-react'
import type { SharedType } from '@maybe-finance/shared'
import { useUserApi } from '../api'
import { BrowserUtil } from '..'
export function useIntercom() {
const { user, isAuthenticated } = useAuth0<SharedType.Auth0ReactUser>()
const { useIntercomMetadata } = useUserApi()
const { data: intercomMetadata } = useIntercomMetadata({ enabled: isAuthenticated })
const boot = useCallback(
(data?: BrowserUtil.IntercomData) => {
if (!user?.sub || !intercomMetadata?.hash) return false
BrowserUtil.bootIntercom({
user_id: user.sub,
user_hash: intercomMetadata.hash,
email: user.email,
name: user.name,
last_request_at: Math.floor(new Date().getTime() / 1000),
...data,
})
return true
},
[user, intercomMetadata]
)
const update = useCallback(
(data?: BrowserUtil.IntercomData, updateLastRequestAt = true) => {
if (!user?.sub || !intercomMetadata?.hash) return
BrowserUtil.updateIntercom({
user_id: user.sub,
user_hash: intercomMetadata.hash,
email: user.email,
name: user.name,
last_request_at: updateLastRequestAt
? Math.floor(new Date().getTime() / 1000)
: undefined,
...data,
})
},
[user, intercomMetadata]
)
return { boot, update }
}

View file

@ -2,7 +2,6 @@ import * as Sentry from '@sentry/react'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { usePlaidLink } from 'react-plaid-link' import { usePlaidLink } from 'react-plaid-link'
import { BrowserUtil } from '..'
import { usePlaidApi } from '../api' import { usePlaidApi } from '../api'
import { useAccountContext } from '../providers' import { useAccountContext } from '../providers'
import { useLogger } from './useLogger' import { useLogger } from './useLogger'
@ -68,22 +67,8 @@ export function usePlaid(mode: 'default' | 'oauth' = 'default') {
} }
}, },
// https://plaid.com/docs/link/web/#onexit // https://plaid.com/docs/link/web/#onexit
onExit: (error, metadata) => { onExit: () => {
if (error) { // ...
const { error_code, error_type, error_message, display_message } = error
BrowserUtil.trackIntercomEvent(`PLAID_LINK_EXIT_ERROR`, {
error_type,
error_code,
error_message,
display_message,
reference: 'https://plaid.com/docs/errors/',
})
}
BrowserUtil.trackIntercomEvent('PLAID_EXIT_EVENT', {
...error,
...metadata,
})
}, },
// https://plaid.com/docs/link/web/#onevent // https://plaid.com/docs/link/web/#onevent
onEvent: (event, metadata) => { onEvent: (event, metadata) => {
@ -97,9 +82,6 @@ export function usePlaid(mode: 'default' | 'oauth' = 'default') {
}, },
}) })
// Capture all events to Intercom
BrowserUtil.trackIntercomEvent(event, metadata)
logger.debug( logger.debug(
`Plaid link event: ${event} for session ID ${metadata.link_session_id}`, `Plaid link event: ${event} for session ID ${metadata.link_session_id}`,
metadata metadata

View file

@ -1,5 +1,4 @@
export * from './image-loaders' export * from './image-loaders'
export * from './intercom'
export * from './browser-utils' export * from './browser-utils'
export * from './account-utils' export * from './account-utils'
export * from './agreement-utils' export * from './agreement-utils'

View file

@ -1,36 +0,0 @@
export type IntercomData = {
user_id?: string
user_hash?: string
email?: string
name?: string
last_request_at?: number
'Manual Accounts'?: number
'Connected Accounts'?: number
Connections?: number
}
export function bootIntercom(data?: IntercomData) {
const w = window as any
w.Intercom('boot', {
app_id: w.INTERCOM_APP_ID,
...data,
})
}
export function updateIntercom(data?: IntercomData) {
;(window as any).Intercom('update', {
...data,
})
}
export function trackIntercomEvent(name: string, data: Record<string, any>) {
;(window as any).Intercom('trackEvent', name, {
...data,
})
}
export function showIntercom() {
;(window as any).Intercom('show')
}

View file

@ -20,7 +20,6 @@ import type { IAccountQueryService } from '../account'
import type { SharedType } from '@maybe-finance/shared' import type { SharedType } from '@maybe-finance/shared'
import { CopyObjectCommand, MetadataDirective } from '@aws-sdk/client-s3' import { CopyObjectCommand, MetadataDirective } from '@aws-sdk/client-s3'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import crypto from 'crypto'
import { DbUtil } from '@maybe-finance/server/shared' import { DbUtil } from '@maybe-finance/server/shared'
import { DateUtil } from '@maybe-finance/shared' import { DateUtil } from '@maybe-finance/shared'
import { flatten } from 'lodash' import { flatten } from 'lodash'
@ -279,22 +278,6 @@ export class UserService implements IUserService {
} }
} }
async getIntercomMetadata(
userId: User['id'],
secret?: string
): Promise<SharedType.UserIntercomMetadata> {
const { auth0Id } = await this.prisma.user.findUniqueOrThrow({
select: { auth0Id: true },
where: { id: userId },
})
return {
hash: secret
? crypto.createHmac('sha256', secret).update(auth0Id).digest('hex')
: undefined,
}
}
async getSignedAgreements(userId: User['id']) { async getSignedAgreements(userId: User['id']) {
return this.prisma.agreement.findMany({ return this.prisma.agreement.findMany({
distinct: 'type', distinct: 'type',

View file

@ -227,10 +227,6 @@ export type UserSubscription = {
currentPeriodEnd: DateTime | null currentPeriodEnd: DateTime | null
} }
export type UserIntercomMetadata = {
hash?: string
}
export type UserMemberCardDetails = { export type UserMemberCardDetails = {
memberNumber: number memberNumber: number
name: string name: string