From 57b1f4fa91a3888bc3b94553fa8893e8deef198f Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 2 Jun 2025 19:24:53 -0700 Subject: [PATCH] Add Intercom support and enhance phishing detection in AI search (#1184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Add Intercom integration and enhance phishing detection in mail display ## Description This PR adds Intercom integration for customer support and enhances the phishing detection capabilities in the mail display component. The changes include: 1. Integrating Intercom messenger for user support with secure JWT authentication 2. Enhancing the AI web search query to better identify suspicious domains and potential phishing attempts 3. Reorganizing the help and feedback UI elements in the navigation sidebar ## Type of Change - ✨ New feature (non-breaking change which adds functionality) - 🔒 Security enhancement - 🎨 UI/UX improvement ## Areas Affected - [x] User Interface/Experience - [x] Authentication/Authorization - [x] Security ## Testing Done - [x] Manual testing performed ## Security Considerations - [x] No sensitive data is exposed - [x] Authentication checks are in place - [x] JWT implementation for secure Intercom authentication ## Checklist - [x] I have performed a self-review of my code - [x] My changes generate no new warnings ## Additional Notes The phishing detection enhancement improves security by adding domain validation to the AI search query, helping users identify suspicious email domains more effectively. The Intercom integration provides a direct support channel for users within the application. _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ ## Summary by CodeRabbit - **New Features** - Added Intercom messenger integration, allowing users to access help via a new "Help" button in the sidebar. - Introduced a "Feedback" navigation item in the sidebar for quick access to external feedback submission. - **Enhancements** - Improved person background search to include phishing detection and domain validity context. - **Chores** - Updated dependencies to support new features. - Adjusted navigation configuration for sidebar items. --- apps/mail/components/mail/mail-display.tsx | 3 +- apps/mail/components/ui/nav-main.tsx | 43 ++++++++++++++++++---- apps/mail/config/navigation.ts | 7 ---- apps/mail/package.json | 1 + apps/server/package.json | 1 + apps/server/src/trpc/routes/user.ts | 11 ++++++ apps/server/wrangler.jsonc | 1 + pnpm-lock.yaml | 16 ++++++++ 8 files changed, 68 insertions(+), 15 deletions(-) diff --git a/apps/mail/components/mail/mail-display.tsx b/apps/mail/components/mail/mail-display.tsx index fdd71583e..3aea5a543 100644 --- a/apps/mail/components/mail/mail-display.tsx +++ b/apps/mail/components/mail/mail-display.tsx @@ -643,7 +643,8 @@ const MoreAboutPerson = ({ } = useMutation(trpc.ai.webSearch.mutationOptions()); const handleSearch = useCallback(() => { doSearch({ - query: `In 100 words or less: What is the background of ${person.name} & ${person.email}, of ${person.email.split('@')[1]}. `, + query: `In 100 words or less: What is the background of ${person.name} & ${person.email}, of ${person.email.split('@')[1]}. + This could be a phishing email address, indicate if the domain is suspicious, example: x.io is not a valid domain for x.com | example: x.com is a valid domain for x.com | example: paypalcom.com is not a valid domain for paypal.com`, }); }, [person.name]); diff --git a/apps/mail/components/ui/nav-main.tsx b/apps/mail/components/ui/nav-main.tsx index 2598d2cd3..444c19b41 100644 --- a/apps/mail/components/ui/nav-main.tsx +++ b/apps/mail/components/ui/nav-main.tsx @@ -18,11 +18,13 @@ import { Collapsible, CollapsibleTrigger } from '@/components/ui/collapsible'; import { useActiveConnection, useConnections } from '@/hooks/use-connections'; import { type MessageKey, type NavItem } from '@/config/navigation'; import { LabelDialog } from '@/components/labels/label-dialog'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import Intercom, { show } from '@intercom/messenger-js-sdk'; +import { CurvedArrow, MessageSquare } from '../icons/icons'; import { useSearchValue } from '@/hooks/use-search-value'; import { useSidebar } from '../context/sidebar-context'; import { useTRPC } from '@/providers/query-provider'; import { RecursiveFolder } from './recursive-folder'; -import { useMutation } from '@tanstack/react-query'; import type { Label as LabelType } from '@/types'; import { Link, useLocation } from 'react-router'; import { Button } from '@/components/ui/button'; @@ -32,7 +34,6 @@ import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { useStats } from '@/hooks/use-stats'; import SidebarLabels from './sidebar-labels'; -import { CurvedArrow } from '../icons/icons'; import { Command, Plus } from 'lucide-react'; import { Tree } from '../magicui/file-tree'; import { useCallback, useRef } from 'react'; @@ -76,14 +77,20 @@ export function NavMain({ items }: NavMainProps) { const pathname = location.pathname; const searchParams = new URLSearchParams(); const [category] = useQueryState('category'); - - const [isDialogOpen, setIsDialogOpen] = React.useState(false); - const { data: session } = useSession(); const { data: connections } = useConnections(); const { data: stats } = useStats(); const { data: activeConnection } = useActiveConnection(); - const trpc = useTRPC(); + const { data: intercomToken } = useQuery(trpc.user.getIntercomToken.queryOptions()); + + React.useEffect(() => { + if (intercomToken) { + Intercom({ + app_id: 'aavenrba', + intercom_user_jwt: intercomToken, + }); + } + }, [intercomToken]); const { mutateAsync: createLabel } = useMutation(trpc.labels.create.mutationOptions()); @@ -189,9 +196,10 @@ export function NavMain({ items }: NavMainProps) { }, [pathname, searchParams], ); + const t = useTranslations(); const onSubmit = async (data: LabelType) => { - await toast.promise(createLabel(data), { + toast.promise(createLabel(data), { loading: 'Creating label...', success: 'Label created successfully', error: 'Failed to create label', @@ -201,6 +209,27 @@ export function NavMain({ items }: NavMainProps) { return ( + {isBottomNav ? ( + <> + show()} + tooltip={state === 'collapsed' ? t('help' as MessageKey) : undefined} + className="flex cursor-pointer items-center" + > + +

Help

+
+ + + ) : null} {items.map((section) => ( { @@ -11,4 +12,14 @@ export const userRouter = router({ }); return { success, message }; }), + getIntercomToken: privateProcedure.query(async ({ ctx }) => { + const token = await jwt.sign( + { + user_id: ctx.session.user.id, + email: ctx.session.user.email, + }, + ctx.c.env.JWT_SECRET, + ); + return token; + }), }); diff --git a/apps/server/wrangler.jsonc b/apps/server/wrangler.jsonc index 4a07bf064..a48f88d01 100644 --- a/apps/server/wrangler.jsonc +++ b/apps/server/wrangler.jsonc @@ -66,6 +66,7 @@ "COOKIE_DOMAIN": "localhost", "VITE_PUBLIC_BACKEND_URL": "http://localhost:8787", "VITE_PUBLIC_APP_URL": "http://localhost:3000", + "JWT_SECRET": "secret", }, "kv_namespaces": [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92ac4b1cd..ac49f63ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,6 +109,9 @@ importers: '@hookform/resolvers': specifier: 4.1.2 version: 4.1.2(react-hook-form@7.54.2(react@19.1.0)) + '@intercom/messenger-js-sdk': + specifier: 0.0.14 + version: 0.0.14 '@react-email/components': specifier: ^0.0.36 version: 0.0.36(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -482,6 +485,9 @@ importers: '@trpc/server': specifier: 'catalog:' version: 11.1.4(typescript@5.8.3) + '@tsndr/cloudflare-worker-jwt': + specifier: 3.2.0 + version: 3.2.0 '@upstash/ratelimit': specifier: ^2.0.5 version: 2.0.5(@upstash/redis@1.34.9) @@ -1599,6 +1605,9 @@ packages: cpu: [x64] os: [win32] + '@intercom/messenger-js-sdk@0.0.14': + resolution: {integrity: sha512-2dH4BDAh9EI90K7hUkAdZ76W79LM45Sd1OBX7t6Vzy8twpNiQ5X+7sH9G5hlJlkSGnf+vFWlFcy9TOYAyEs1hA==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3413,6 +3422,9 @@ packages: react-dom: '>=18.2.0' typescript: '>=5.7.2' + '@tsndr/cloudflare-worker-jwt@3.2.0': + resolution: {integrity: sha512-y45452JzKxFDfCUHNGrdgIcrJTkYa6xtrKtCQTjKj+hjzw8dOHF9R0uGuU8WxvCCMi4sMzZ1KREnZTsTy7DsiQ==} + '@types/accept-language-parser@1.5.8': resolution: {integrity: sha512-6+dKdh9q/I8xDBnKQKddCBKaWBWLmJ97HTiSbAXVpL7LEgDfOkKF98UVCaZ5KJrtdN5Wa5ndXUiqD3XR9XGqWQ==} @@ -8117,6 +8129,8 @@ snapshots: '@img/sharp-win32-x64@0.34.2': optional: true + '@intercom/messenger-js-sdk@0.0.14': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -10041,6 +10055,8 @@ snapshots: react-dom: 19.1.0(react@19.1.0) typescript: 5.8.3 + '@tsndr/cloudflare-worker-jwt@3.2.0': {} + '@types/accept-language-parser@1.5.8': {} '@types/canvas-confetti@1.9.0': {}