Add Intercom support and enhance phishing detection in AI search (#1184)

# 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._

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Adam
2025-06-02 19:24:53 -07:00
committed by GitHub
parent 9a66a0ff6a
commit 57b1f4fa91
8 changed files with 68 additions and 15 deletions

View File

@@ -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]);

View File

@@ -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 (
<SidebarGroup className={`${state !== 'collapsed' ? '' : 'mt-1'} space-y-2.5 py-0 md:px-0`}>
<SidebarMenu>
{isBottomNav ? (
<>
<SidebarMenuButton
onClick={() => show()}
tooltip={state === 'collapsed' ? t('help' as MessageKey) : undefined}
className="flex cursor-pointer items-center"
>
<MessageSquare className="relative mr-2.5 h-3 w-3.5" />
<p className="mt-0.5 truncate text-[13px]">Help</p>
</SidebarMenuButton>
<NavItem
key={'feedback'}
isActive={isUrlActive('https://feedback.0.email')}
href={'https://feedback.0.email'}
url={'https://feedback.0.email'}
icon={MessageSquare}
target={'_blank'}
title={'navigation.sidebar.feedback'}
/>
</>
) : null}
{items.map((section) => (
<Collapsible
key={section.title}

View File

@@ -219,13 +219,6 @@ export const bottomNavItems = [
{
title: '',
items: [
{
id: 'feedback',
title: 'navigation.sidebar.feedback',
url: 'https://feedback.0.email',
icon: MessageSquare,
target: '_blank',
},
{
id: 'settings',
title: 'navigation.sidebar.settings',

View File

@@ -21,6 +21,7 @@
"@fontsource-variable/geist": "^5.2.6",
"@fontsource-variable/geist-mono": "^5.2.6",
"@hookform/resolvers": "4.1.2",
"@intercom/messenger-js-sdk": "0.0.14",
"@react-email/components": "^0.0.36",
"@react-email/html": "^0.0.11",
"@react-email/render": "^1.1.2",

View File

@@ -34,6 +34,7 @@
"@react-email/render": "^1.1.0",
"@trpc/client": "catalog:",
"@trpc/server": "catalog:",
"@tsndr/cloudflare-worker-jwt": "3.2.0",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.34.9",
"agents": "0.0.93",

View File

@@ -1,4 +1,5 @@
import { privateProcedure, router } from '../trpc';
import jwt from '@tsndr/cloudflare-worker-jwt';
export const userRouter = router({
delete: privateProcedure.mutation(async ({ ctx }) => {
@@ -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;
}),
});

View File

@@ -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": [
{

16
pnpm-lock.yaml generated
View File

@@ -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': {}