DEV Community

Cover image for How to Build an Apollo Style Collaborative CRM with v0 and Velt🔥
Astrodevil
Astrodevil

Posted on

How to Build an Apollo Style Collaborative CRM with v0 and Velt🔥

Sales teams waste hours every day jumping between Slack, email, and their CRM just to figure out what happened with a deal. Who talked to the client last? What did they say? The context is scattered everywhere.

Apollo.io solved this by making its CRM truly collaborative. Sales reps can see exactly who's working on what, leave contextual comments right on deal records, and get notified when something important happens. No more hunting for information or stepping on each other's toes.

The thing is, building real-time collaboration isn't simple. You need websockets for live updates, user presence systems, comment threading, and notifications that actually work. That's where Velt comes in. It handles all the collaborative complexity so you can focus on your features.

In this tutorial, you'll learn how to build a collaborative CRM that looks and feels like Apollo's interface. You'll be using the following technologies:

  • Next.js for the React framework
  • Velt SDK for real-time comments, presence, and notifications
  • v0 for rapidly generating UI components with AI

Sales reps will see live presence when teammates are viewing deals, add comments directly on customer records, and get instant notifications when something changes.

Our final CRM will look like this:
Demo CRM

By the end of this article, you'll have a functional collaborative CRM, just like Apollo's interface, but built specifically for your needs.

But before we start building, let’s take a look at the technologies we’re going to use to make this CRM.

What is Velt?

Velt is a developer platform that enables teams to integrate real-time collaboration features into their web applications. No need to handle raw websockets, user presence, and comment systems from scratch. Instead of building websockets, user presence, and comment systems from scratch, Velt provides ready-made components that handle all the complexity. You get features like live cursors, contextual comments, @mentions, and notifications out of the box.

What is v0?

v0 is Vercel's AI-powered tool that generates React components from text descriptions. Rather than spending hours writing UI code, you describe what you want, and v0 creates working components with proper TypeScript and Tailwind CSS styling. For complex interfaces like a CRM, v0 handles the heavy lifting of component structure and responsive design.

Building the CRM Interface with v0

Now that we understand what we're building, it's time to create the actual CRM interface. We need something that looks professional and handles complex data like Apollo's interface, but we don't want to spend weeks writing UI code from scratch.

This is where v0 really shines. Instead of manually coding every component, table row, and responsive breakpoint, we'll describe what we want and let AI generate the entire interface for us.

If you want to directly jump into the codebase and skip the UI generation steps, you can grab the complete code from our GitHub repository. Otherwise, Or, you can build it from scratch to understand how everything works together.

Let’s start.

Generating the UI with v0

Head over to v0.dev and create a new project. The key to getting good results from v0 is writing detailed prompts that describe exactly what you want.

Here's a preview of our prompt (the full version is in the link below):

Design a world-class, visually sophisticated CRM Inbox UI for a sales tool.
Draw inspiration from Apollo's inbox, clean data tables and deal views,
and HubSpot's advanced interaction paradigms.

Include:
Navbar with user avatars, presence indicators, and theme switcher
Left sidebar with lead segments (Interested, Cold, Onboarding)
Central data table with columns: Company, Domains, Stage, Value, Owner, Last Activity
Deal detail pane that opens on row click
Support for both light and dark themes
Modern, clean design with subtle shadows and smooth transitions...
Enter fullscreen mode Exit fullscreen mode

Copy the full prompt from here, it includes additional styling details and component specifications.

v0 will generate a complete CRM interface with four main components:

v0

  1. The Navbar component handles user switching, presence indicators, and theme toggling.
  2. The Sidebar component contains the lead segments and navigation.
  3. The Main Table component displays all the deal data in a clean, scannable format.
  4. The Deal Detail Pane component slides out when you click a table row.

Setting Up the Project

Once v0 generates your interface, you have two options to get the code:

Option 1: Use v0's GitHub integration
Click "Push to GitHub" in v0, then clone the repository:

v0 export

Run these commands to clone and set up the project locally:

git clone your-generated-repo-url
cd your-project-name
npm install
Enter fullscreen mode Exit fullscreen mode

Option 2: Copy the CLI command
v0 provides a CLI command that sets up everything automatically. Look for the CLI command in the top right corner of the v0 interface, copy it, and run it in your terminal:

Command

Here's what the command will look like:

npx shadcn@latest add "https://v0.app/chat/b/b_9HW7o6W2IMR?token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0....."
cd crm-tool-velt
Enter fullscreen mode Exit fullscreen mode

Either way, you'll end up with a complete Next.js project that includes:

  • All the CRM components generated by v0
  • Tailwind CSS configured and ready
  • Shadcn/UI components already installed
  • TypeScript setup complete

Setting up the Velt

In this section, you’re going to install Velt and get an API Key.

Now that you have built the CRM interface, we need to add collaboration features with Velt. In this section, we will install Velt and get our API Key to use it

Installing Velt SDK

Now that you have the CRM interface, add the collaboration features. Install the Velt SDK packages:

Install the following Velt packages with this command

npm install @veltdev/react @veltdev/types
Enter fullscreen mode Exit fullscreen mode

These packages handle all the real-time collaboration features you'll need, like comments, presence, notifications, and user management.

Getting Your Velt API Key

Head over to velt.dev and create a free account.

Velt

Once you're logged in, go to Velt’s console and get your free API key.

Velt dashboard

Copy the key, and create a .env.local file in your project root and add your Velt API Key:

NEXT_PUBLIC_VELT_API_KEY=your_api_key_here
Enter fullscreen mode Exit fullscreen mode

Project Structure

After installing Velt, you need to organize your project files correctly. v0 generates the core UI components (crm-navbar.tsx, main-table.tsx, etc.), but you may need to extract specific parts into separate files for better organization. For example, if v0 puts everything in one file, create individual component files and import them into crm-inbox.tsx as the main orchestrator.

Your final project structure should look like this:

crm-tool-velt/
├── app/
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ui/ # Shadcn components (from v0)
│ ├── crm-inbox.tsx # Main container component
│ ├── navbar.tsx # Top navigation with user presence
│ ├── crm-sidebar.tsx # Left navigation and filters
│ ├── main-table.tsx # Deal data table
│ ├── deal-detail-pane.tsx # Deal details slider
│ ├── theme-provider.tsx # Theme switching functionality
│ ├── velt-auth.tsx # Velt authentication setup
│ ├── velt-comments-dynamic.tsx # Dynamic comment components
│ ├── velt-presence-dynamic.tsx # Dynamic presence components
│ └── velt-wrapper.tsx # Velt provider wrapper
├── hooks/
├── lib/
│ ├── user-manager.ts # User switching logic
│ └── utils.ts # Utility functions
└── package.json
Enter fullscreen mode Exit fullscreen mode

The crm-inbox.tsx component acts as the main container, bringing together all the CRM pieces. It manages the overall layout and handles state like which deal is currently selected and whether the sidebar is collapsed.

The velt-* files are the collaboration components we'll create in the upcoming sections. These handle user authentication, dynamic component loading, and presence indicators.

Adding Collaboration with Velt

Now that the project setup is ready, we can start adding Velt and its components to our application.

Setting Up Velt Provider

To make Velt work, we first need to wrap our entire app with the VeltProvider. We'll create a new file called veltWrapper.tsx, which will handle the provider setup and theme synchronization.

"use client"

import { VeltProvider } from '@veltdev/react'
import { useTheme } from 'next-themes'

export function VeltWrapper({ children }: { children: React.ReactNode }) {
  const apiKey = process.env.NEXT_PUBLIC_VELT_API_KEY
  const { theme } = useTheme()

  if (!apiKey) {
    console.error('NEXT_PUBLIC_VELT_API_KEY is not set')
    return <>{children}</>
  }

  return (
    <VeltProvider 
      apiKey={apiKey}
      config={{
        darkMode: theme === 'dark'
      }}
    >
      {children}
    </VeltProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

Then add it to your root layout to make Velt available throughout your app:

import { VeltWrapper } from "@/components/velt-wrapper"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ThemeProvider>
          <VeltWrapper>   // <---------- Here
            {children}
          </VeltWrapper>
        </ThemeProvider>
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

Creating the Auth Component

Now that you have the VeltProvider wrapper set up, you need to create an authentication component. Velt requires user identification to track who's commenting, viewing deals, and collaborating in your tool. Without proper user setup, the collaboration features won't know which team member is performing which actions.

For this CRM, we'll create a system that lets you switch between different users. This simulates multiple sales reps using the CRM simultaneously.

First, create a user management file at lib/user-manager.ts, and add the logic for managing users and switching between them.

const HARDCODED_USERS = [
  {
    userId: 'user_alex_chen',
    name: 'Alex Chen',
    email: 'alex.chen@techcorp.com',
    organizationId: 'crm-tool-basepoint-org',
    photoUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user_alex_chen'
  },
  {
    userId: 'user_sarah_johnson',
    name: 'Sarah Johnson', 
    email: 'sarah.johnson@innovatelab.com',
    organizationId: 'crm-tool-basepoint-org',
    photoUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user_sarah_johnson'
  }
]

const getCurrentUserIndex = (): number => {
  if (typeof window === 'undefined') return 0
  const storedIndex = localStorage.getItem('velt_current_user_index')
  return storedIndex ? parseInt(storedIndex, 10) : 0
}

const setCurrentUserIndex = (index: number): void => {
  if (typeof window === 'undefined') return
  localStorage.setItem('velt_current_user_index', index.toString())
}

export const getCurrentUser = () => {
  if (typeof window === 'undefined') return null

  const currentIndex = getCurrentUserIndex()
  return HARDCODED_USERS[currentIndex]
}

export const getOrCreateUser = () => {
  return getCurrentUser()
}

export const switchUser = () => {
  if (typeof window === 'undefined') return null

  const currentIndex = getCurrentUserIndex()
  const newIndex = currentIndex === 0 ? 1 : 0
  setCurrentUserIndex(newIndex)

  return HARDCODED_USERS[newIndex]
}

export const getAvailableUsers = () => {
  return HARDCODED_USERS
}
Enter fullscreen mode Exit fullscreen mode

This file manages two test users that represent different sales reps. The switchUser function lets you toggle between them to see how collaboration works from different perspectives.

Next, create the Velt authentication component at components/velt-auth.tsx:

export function VeltAuth() {
  const { client } = useVeltClient()

  useEffect(() => {
    if (!client) return

    const initializeUser = async () => {
      const user = getOrCreateUser()

      *// Identify the user with Velt*
      await client.identify(user, { forceReset: userSwitchTrigger > 0 })

      *// Set document for this CRM session*
      await client.setDocument('crm-dashboard-basepoint', {
        documentName: 'CRM Dashboard Basepoint'
      })
    }

    initializeUser()
  }, [client, userSwitchTrigger])

  return (
    <>
      {/* Here, we'll add the comment component */}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

In the above code, we used client.identify() which helps Velt understand who's currently using the CRM. This is essential because when someone adds a comment to a deal, Velt needs to know which sales rep wrote it.

We also set up a document context using client.setDocument(). This is a necessary step that tells Velt all users are working within the same collaborative space, in this case, our CRM dashboard. Think of it like opening the same Google Doc where everyone can see each other's edits and comments.

The userSwitchTrigger state handles switching between different test users.

Finally, the component only renders the comment and sidebar components which we’ll create in next sections.

Import and use the VeltAuth component in your app/page.tsx:

 "use client"

import { CRMInbox } from "@/components/crm-inbox"
import { VeltAuth } from "@/components/velt-auth"

export default function Home() {
  return (
    <>
      <VeltAuth />  {/* <--------------- Here */}
      <CRMInbox />
    </>
  )
}

Enter fullscreen mode Exit fullscreen mode

Setting Up Comment Component

Now that authentication is set up, we can use all of Velt’s components. We'll start with the Comment feature.

To use Velt's collaborative comment feature, we need to use the <VeltComments /> component to enable it across the entire application. This component will be used in our veltAuth.tsx file. We'll create dynamic component wrappers to manage all Velt components in one place, ensuring they load only on the client side.

Create velt-comments-dynamic.tsx:

"use client"

import dynamic from 'next/dynamic'

const VeltComments = dynamic(
  () => import('@veltdev/react').then(mod => ({ default: mod.VeltComments })),
  {
    ssr: false,
    loading: () => null
  }
)

const VeltCommentTool = dynamic(
  () => import('@veltdev/react').then(mod => ({ default: mod.VeltCommentTool })),
  {
    ssr: false,
    loading: () => null
  }
);

export {
  VeltComments as DynamicVeltComments,
  VeltCommentTool as DynamicVeltCommentTool,
};

Enter fullscreen mode Exit fullscreen mode

In the above code, we imported 2 components from Velt:

  1. VeltComments enables the commenting feature across your entire site.

    1. VeltCommentTool is a button that activates the commenting functionality for users.

We export them as <DynamicVeltComments /> and <DynamicVeltCommentTool />.

Now, import the <DynamicVeltComments /> component into the VeltAuth file.

"use client"

// same imports
import { DynamicVeltComments } from './velt-comments-dynamic'

export function VeltAuth() {
  // same code no changes
  return (
    <>
      <DynamicVeltComments popoverMode={true} textMode={false} />
    </>
  )
}

Enter fullscreen mode Exit fullscreen mode

The popoverMode={true} enables comments to be attached to specific HTML elements, like deal rows, buttons, cards, images, or any designated part of your interface. This is perfect for our CRM, where we want comments on specific deal rows.

The textMode={false} disables text selection commenting. By default, users can select any text to add comments (like Google Docs), but we want comments only on designated deal records, not random text throughout the interface.

Now that you have the comment system configured, you need to prepare your data table to work with these comments. Each deal row needs specific attributes so Velt knows where to attach comments and show presence indicators.

Table Structure with Comment Integration

Here, the data table displays all deal information. Each row represents a customer company with key sales data.

const companies = [
  {
    id: 1,
    name: "Vercel",
    logo: "/vercel.svg",
    domain: "vercel.com",
    deals: [
      { name: "Vercel", avatar: "/vercel.svg" },
      { name: "Vercel - Expansion", avatar: "/vercel.svg" },
    ],
    icpFit: "Excellent",
    estimatedArr: "$100M-$250M",
    connectionStrength: "Very strong",
  },
  // More companies...
]

Enter fullscreen mode Exit fullscreen mode

Each table row gets specific attributes that enable Velt's collaboration features:

<TableRow
  key={company.id}
  className="hover:bg-muted/50 cursor-pointer group"
  onClick={() => onSelectDeal(company)}
  data-velt-target-comment-element-id={`company-${company.id}`}
  id={`company-${company.id}`}
>

Enter fullscreen mode Exit fullscreen mode

The data-velt-target-comment-element-id attribute tells Velt that this specific row can receive comments.

Adding Interactive Comment Tools

Now that our table rows are properly configured for comments, we can add the <DynamicVeltCommentTool /> component that appears when users hover over deal records:

<TableCell>
  <div className="flex items-center justify-between w-full">
    <div className="flex items-center space-x-3">
      <Avatar className="h-6 w-6">
        <AvatarImage src={company.logo} alt={company.name} />
      </Avatar>
      <span className="font-medium">{company.name}</span>
    </div>
    <div className="flex items-center space-x-1 opacity-0 group-hover:opacity-100">
      <DynamicVeltCommentTool targetElementId={`company-${company.id}`} />
    </div>
  </div>
</TableCell>

Enter fullscreen mode Exit fullscreen mode

The comment tool appears when users hover over table rows. Clicking it opens a comment thread specific to that deal record. The targetElementId prop connects the tool to the same element ID we defined in the table row attributes, ensuring comments are properly attached to specific deals.

Demo gif

Building Real-time Collaboration Features

Now that we've successfully attached the comment feature, let's add all the real-time collaboration features like presence, notifications, and more.

Let’s start with the comment sidebar.

Comment Sidebar

The comment sidebar provides a centralized view of all discussions occurring across various deals.

First, update your dynamic components file components/velt-comments-dynamic.tsx to include the sidebar:

// ... previous code 

const VeltCommentsSidebar = dynamic(
  () => import('@veltdev/react').then(mod => ({ default: mod.VeltCommentsSidebar })),
  {
    ssr: false,
    loading: () => null
  }
)

const VeltSidebarButton = dynamic(
  () => import('@veltdev/react').then(mod => ({ default: mod.VeltSidebarButton })),
  {
    ssr: false,
    loading: () => null
  }
)

export {
  VeltComments as DynamicVeltComments,
  VeltCommentsSidebar as DynamicVeltCommentsSidebar,
  VeltSidebarButton as DynamicVeltSidebarButton,
  VeltCommentTool as DynamicVeltCommentTool,
};

Enter fullscreen mode Exit fullscreen mode

Then update your components/velt-auth.tsx to include the sidebar:

"use client"

import { DynamicVeltComments, DynamicVeltCommentsSidebar } from './velt-comments-dynamic'

export function VeltAuth() {
 // ...previous code

  return (
    <>
      <DynamicVeltComments popoverMode={true} textMode={false} />
      <DynamicVeltCommentsSidebar />  {/* <-------- Here*/}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

demo2

The sidebar automatically groups comments by sales rep and updates in real-time when new discussions are added.

Deal-Specific Comment Section

Beyond the global sidebar, each deal can have its own comment section in the detail pane. This provides a dedicated space for detailed discussions about a specific topic.

Create velt-comments-section.tsx:

"use client"

import { DynamicVeltInlineCommentsSection } from './velt-comments-dynamic'

interface VeltCommentsSectionProps {
  companyId: number
}

export function VeltCommentsSection({ companyId }: VeltCommentsSectionProps) {
  const targetElementId = `company-${companyId}`

  return (
    <div className="min-h-full overflow-scroll">
      <DynamicVeltInlineCommentsSection
        targetElementId={targetElementId}
        multiThread={true}
      />
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

Then, integrate it into your deal detail pane deal-detail-pane.tsx:

import { VeltCommentsSection } from "./velt-comments-section";

export function DealDetailPane({ deal, onClose }: DealDetailPaneProps) {
  const [activeTab, setActiveTab] = useState("details");

  return (
    <div className="w-96 border-l bg-background">
      {/* Header with tab buttons */}
      <div className="flex flex-wrap gap-2">
        <Button onClick={() => setActiveTab("details")}>
          <Mail className="h-4 w-4 mr-2" />
          Email
        </Button>
        <Button onClick={() => setActiveTab("comments")}>
          <MessageSquare className="h-4 w-4 mr-2" />
          Comments
        </Button>
      </div>

      {/* Tabbed content */}
      <Tabs value={activeTab} onValueChange={setActiveTab}>
        <TabsContent value="details">
          {/* Deal details content */}
        </TabsContent>

        <TabsContent value="comments" className="flex-1 mt-0 overflow-y-scroll px-4 py-2">
          <VeltCommentsSection companyId={deal.id} />
        </TabsContent>
      </Tabs>
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode
  • The VeltInlineCommentsSection component creates an embedded comment interface that's scoped to the selected deal. The targetElementId={company-${companyId}} prop ensures that only comments attached to that specific table row appear in this section.
  • The multiThread={true} prop allows multiple conversation threads within the same deal.

Now, import this <DealDetailPane /> component into crm-inbox.tsx file.

"use client"

import { useState } from "react"
import { Navbar } from "./navbar"
import { Sidebar } from "./crm-sidebar"
import { MainTable } from "./main-table"
import { DealDetailPane } from "./deal-detail-pane"

export function CRMInbox() {
  const [selectedDeal, setSelectedDeal] = useState<any>(null)
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false)

  return (
    <div className="h-screen flex flex-col bg-background">
      <Navbar />
      <div className="flex flex-1 overflow-hidden">
        <Sidebar collapsed={sidebarCollapsed} onToggle={() => setSidebarCollapsed(!sidebarCollapsed)} />
        <div className="flex-1 flex overflow-hidden">
          <MainTable onSelectDeal={setSelectedDeal} />
          {selectedDeal && <DealDetailPane deal={selectedDeal} onClose={() => setSelectedDeal(null)} />}   {/* <-------------Here*/}
        </div>
      </div>
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

Unlike the global sidebar that shows all comments, this section will have deal-specific discussions.

demo3

Now, let's add the multi-user feature.

User Switching

For testing with multiple users, implement a switching logic in your navigation bar. Update your components/navbar.tsx:

import { DynamicVeltSidebarButton } from './velt-comments-dynamic'
import { getOrCreateUser, switchUser, getAvailableUsers } from '@/lib/user-manager'

export function Navbar() {
  const [currentUser, setCurrentUser] = useState(null)
  const [availableUsers, setAvailableUsers] = useState([])

  const handleUserSwitch = async () => {
    const newUser = switchUser()
    if (newUser) {
      setCurrentUser(newUser)
      // Update available users list
      const users = getAvailableUsers()
      setAvailableUsers(users)

      // Small delay to ensure state is updated
      await new Promise(resolve => setTimeout(resolve, 50))

      // Trigger Velt user switch event
      window.dispatchEvent(new CustomEvent('velt-user-switch'))
    }
  }

  return (
    <nav className="h-14 border-b bg-background/95">
      {/* ... other navbar elements ... */}

      {/* Comments Sidebar Button */}
      <div className="hidden sm:flex">
        <DynamicVeltSidebarButton />
      </div>

      {/* User Dropdown with switch functionality */}
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          {/* User image */}
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end" className="w-64">
          {/* Current User Display */}
        </DropdownMenuItem>

          {/* Switch User Option */}
          <DropdownMenuItem onClick={handleUserSwitch}>
            Switch to {availableUsers.find(u => u.userId !== currentUser?.userId)?.name}
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </nav>
  )
}
Enter fullscreen mode Exit fullscreen mode

The switching logic works on multiple levels to create a seamless collaborative experience:

  • When handleUserSwitch is called, it updates the React state to display the new user's avatar and information in the navbar. The dropdown shows both the current active user and provides an option to switch to the other team member.
  • The custom event 'velt-user-switch' triggers the VeltAuth component to re-initialize with the new user credentials. This ensures all collaborative features (comments, presence, notifications) are properly associated with the switched user identity.
  • After switching, all existing comments, mentions, and presence indicators immediately reflect the new user's perspective. Comments you previously made as "Alex Chen" will now show as external comments when viewing as "Sarah Johnson", and vice versa.

Velt demo4

User Presence

Show who's currently viewing deals by adding presence indicators. Create components/velt-presence-dynamic.tsx:

"use client"

import dynamic from 'next/dynamic'

const VeltPresence = dynamic(
  () => import('@veltdev/react').then(mod => ({ default: mod.VeltPresence })),
  {
    ssr: false,
    loading: () => <div className="h-8 w-20 bg-muted animate-pulse rounded-full" />
  }
)

function VeltPresenceWrapper() {
  return (
    <div className="flex items-center">
      <VeltPresence />
      {/* Presence indicator */}
      <div className="text-xs text-muted-foreground ml-2 border border-dashed border-gray-300 px-2 py-1 rounded">
        Presence
      </div>
    </div>
  )
}

export { VeltPresenceWrapper as DynamicVeltPresence }
Enter fullscreen mode Exit fullscreen mode

This file creates a dynamic wrapper for Velt's presence indicators to show which users are currently online in real-time. We simply imported the component from Velt, so there's no need to deal with complexity.

Add presence to your components/navbar.tsx:

import { DynamicVeltPresence } from './velt-presence-dynamic'

export function Navbar() {
return (
<nav className="h-14 border-b bg-background/95">
{/* ... other elements ... */}

  {/* Show online users */}
  <div className="flex items-center mr-2 min-w-[80px]">
    <DynamicVeltPresence />
  </div>

  {/* ... rest of navbar ... */}
</nav>
)
}
Enter fullscreen mode Exit fullscreen mode

Velt Notification

Notifications

Now let’s add the notification system to show alerts for mentions and new comments. Update your components/navbar.tsx to include notifications:

import { VeltNotificationsTool } from "@veltdev/react"

export function Navbar() {
return (
<nav className="h-14 border-b bg-background/95">
{/* ... other elements ... */}

  {/* Notifications Bell */}
  <Button variant="ghost" size="icon" className="h-9 w-9 relative hidden sm:flex">
    <VeltNotificationsTool />
  </Button>

  {/* ... rest of navbar ... */}
</nav>
)
}
Enter fullscreen mode Exit fullscreen mode

The notification bell automatically shows a badge count for unread mentions and comment activity. Users get notified when:

  • Someone mentions them with @username
  • New comments are added to deals they're following
  • Activity happens on deals they've previously commented on

All notifications updates in real-time without page refreshes, keeping your sales team synchronized on deal activities.

Apollo crm demo

Project Demo

That's it! You now have a fully collaborative CRM where sales teams can comment on deals, see who's online, and get notified about important updates, just like Apollo's interface.

Start your development server to test it out:

npm run dev

// or

pnpm run dev
Enter fullscreen mode Exit fullscreen mode

Try the live demo: https://crm-tools-velt.vercel.app/

Conclusion

And that's it for this blog. We created a collaborative CRM without dealing with websockets, building comment systems from scratch, or managing real-time syncing. Velt handled the complex parts of collaboration, so we could focus on adding CRM features.

But this is just the start.

This CRM now has features like contextual commenting, user presence, notifications, and real-time updates. Velt also offers more advanced features like live cursors, reactions, and video huddles for meetings. These can turn your CRM from a basic data table into a fully collaborative sales workspace.

If you're creating tools for sales teams to work on deals, review customer data, or manage pipelines together, adding more of Velt's features can elevate your CRM.

Try expanding the current setup with more features, or bring that Google Docs-like collaboration directly into your sales process. The foundation is already there; you just need to build on it.

Resources

Top comments (0)