supermemory/apps/web/components/new/header.tsx
MaheshtheDev 59c294b299 feat: deep-research on user profile and tiptap integration (#672)
deep-research on user profile

add novel integration

tiptap 3.x integration
2026-01-15 21:53:53 +00:00

277 lines
9.4 KiB
TypeScript

"use client"
import { Logo } from "@ui/assets/Logo"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useAuth } from "@lib/auth-context"
import { useEffect, useState } from "react"
import {
ChevronsLeftRight,
LayoutGridIcon,
Plus,
SearchIcon,
FolderIcon,
LogOut,
Settings,
Home,
Code2,
ExternalLink,
} from "lucide-react"
import { Button } from "@ui/components/button"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { Tabs, TabsList, TabsTrigger } from "@ui/components/tabs"
import { useProjectName } from "@/hooks/use-project-name"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@ui/components/dropdown-menu"
import { useQuery } from "@tanstack/react-query"
import { $fetch } from "@repo/lib/api"
import { authClient } from "@lib/auth"
import { DEFAULT_PROJECT_ID } from "@repo/lib/constants"
import { useProjectMutations } from "@/hooks/use-project-mutations"
import { useProject } from "@/stores"
import { useRouter } from "next/navigation"
import Link from "next/link"
import type { Project } from "@repo/lib/types"
interface HeaderProps {
onAddMemory?: () => void
onOpenMCP?: () => void
}
export function Header({ onAddMemory, onOpenMCP }: HeaderProps) {
const { user } = useAuth()
const [name, setName] = useState<string>("")
const projectName = useProjectName()
const { selectedProject } = useProject()
const { switchProject } = useProjectMutations()
const router = useRouter()
const { data: projects = [] } = useQuery({
queryKey: ["projects"],
queryFn: async () => {
const response = await $fetch("@get/projects")
if (response.error) {
throw new Error(response.error?.message || "Failed to load projects")
}
return response.data?.projects || []
},
staleTime: 30 * 1000,
})
useEffect(() => {
const storedName =
localStorage.getItem("username") || localStorage.getItem("userName") || ""
setName(storedName)
}, [])
const userName = name ? `${name.split(" ")[0]}'s` : "My"
return (
<div className="flex p-4 justify-between items-center">
<div className="flex items-center justify-center gap-4 z-10!">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className="flex items-center rounded-lg px-2 py-1.5 -ml-2 cursor-pointer hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 transition-colors"
>
<Logo className="h-7" />
{name && (
<div className="flex flex-col items-start justify-center ml-2">
<p className="text-[#8B8B8B] text-[11px] leading-tight">
{userName}
</p>
<p className="text-white font-bold text-xl leading-none -mt-1">
supermemory
</p>
</div>
)}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className="w-56 bg-[#0D121A] rounded-xl border-none p-1.5 ml-4 shadow-[0_0_20px_rgba(255,255,255,0.15)]"
>
<DropdownMenuItem
asChild
className="px-3 py-2 rounded-md hover:bg-[#293952]/40 cursor-pointer"
>
<Link href="/new">
<Home className="h-4 w-4" />
Home
</Link>
</DropdownMenuItem>
<DropdownMenuItem
asChild
className="px-3 py-2 rounded-md hover:bg-[#293952]/40 cursor-pointer"
>
<a
href="https://console.supermemory.ai"
target="_blank"
rel="noreferrer"
>
<Code2 className="h-4 w-4" />
Developer console
</a>
</DropdownMenuItem>
<DropdownMenuItem
asChild
className="px-3 py-2 rounded-md hover:bg-[#293952]/40 cursor-pointer"
>
<a href="https://supermemory.ai" target="_blank" rel="noreferrer">
<ExternalLink className="h-4 w-4" />
supermemory.ai
</a>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="self-stretch w-px bg-[#FFFFFF33]" />
<div className="flex items-center gap-2">
<p>📁 {projectName}</p>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className="cursor-pointer hover:opacity-70 transition-opacity"
aria-label="Change project"
>
<ChevronsLeftRight className="size-4 rotate-90" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuItem
onClick={() => switchProject(DEFAULT_PROJECT_ID)}
className={cn(
"cursor-pointer",
selectedProject === DEFAULT_PROJECT_ID && "bg-accent",
)}
>
<FolderIcon className="h-3.5 w-3.5 mr-2" />
<span className="text-sm">Default Project</span>
</DropdownMenuItem>
{projects
.filter((p: Project) => p.containerTag !== DEFAULT_PROJECT_ID)
.map((project: Project) => (
<DropdownMenuItem
key={project.id}
onClick={() => switchProject(project.containerTag)}
className={cn(
"cursor-pointer",
selectedProject === project.containerTag && "bg-accent",
)}
>
<FolderIcon className="h-3.5 w-3.5 mr-2 opacity-70" />
<span className="text-sm truncate">{project.name}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<Tabs defaultValue="grid">
<TabsList className="rounded-full border border-[#161F2C] h-11! z-10!">
<TabsTrigger
value="grid"
className={cn(
"rounded-full data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
dmSansClassName(),
)}
>
<LayoutGridIcon className="size-4" />
Grid
</TabsTrigger>
<TabsTrigger
value="graph"
className={cn(
"rounded-full dark:data-[state=active]:bg-[#00173C]! dark:data-[state=active]:border-[#2261CA33]! px-4 py-4",
dmSansClassName(),
)}
>
<LayoutGridIcon className="size-4" />
Graph
</TabsTrigger>
</TabsList>
</Tabs>
<div className="flex items-center gap-2 z-10!">
<Button
variant="headers"
className="rounded-full text-base gap-2 h-10!"
onClick={onAddMemory}
>
<div className="flex items-center gap-2">
<Plus className="size-4" />
Add memory
</div>
<span
className={cn(
"bg-[#21212180] border border-[#73737333] text-[#737373] rounded-sm size-4 text-[10px] flex items-center justify-center",
dmSansClassName(),
)}
>
C
</span>
</Button>
<Button
variant="headers"
className="rounded-full text-base gap-2 h-10!"
onClick={onOpenMCP}
>
<div className="flex items-center gap-2">MCP</div>
</Button>
<Button
variant="headers"
className="rounded-full text-base gap-2 h-10!"
>
<SearchIcon className="size-4" />
<span className="bg-[#21212180] border border-[#73737333] text-[#737373] rounded-sm text-[10px] flex items-center justify-center gap-0.5 px-1">
<svg
className="size-[7.5px]"
viewBox="0 0 9 9"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<title>Search Icon</title>
<path
d="M6.66663 0.416626C6.33511 0.416626 6.01716 0.548322 5.78274 0.782743C5.54832 1.01716 5.41663 1.33511 5.41663 1.66663V6.66663C5.41663 6.99815 5.54832 7.31609 5.78274 7.55051C6.01716 7.78493 6.33511 7.91663 6.66663 7.91663C6.99815 7.91663 7.31609 7.78493 7.55051 7.55051C7.78493 7.31609 7.91663 6.99815 7.91663 6.66663C7.91663 6.33511 7.78493 6.01716 7.55051 5.78274C7.31609 5.54832 6.99815 5.41663 6.66663 5.41663H1.66663C1.33511 5.41663 1.01716 5.54832 0.782743 5.78274C0.548322 6.01716 0.416626 6.33511 0.416626 6.66663C0.416626 6.99815 0.548322 7.31609 0.782743 7.55051C1.01716 7.78493 1.33511 7.91663 1.66663 7.91663C1.99815 7.91663 2.31609 7.78493 2.55051 7.55051C2.78493 7.31609 2.91663 6.99815 2.91663 6.66663V1.66663C2.91663 1.33511 2.78493 1.01716 2.55051 0.782743C2.31609 0.548322 1.99815 0.416626 1.66663 0.416626C1.33511 0.416626 1.01716 0.548322 0.782743 0.782743C0.548322 1.01716 0.416626 1.33511 0.416626 1.66663C0.416626 1.99815 0.548322 2.31609 0.782743 2.55051C1.01716 2.78493 1.33511 2.91663 1.66663 2.91663H6.66663C6.99815 2.91663 7.31609 2.78493 7.55051 2.55051C7.78493 2.31609 7.91663 1.99815 7.91663 1.66663C7.91663 1.33511 7.78493 1.01716 7.55051 0.782743C7.31609 0.548322 6.99815 0.416626 6.66663 0.416626Z"
stroke="#737373"
strokeWidth="0.833333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className={cn(dmSansClassName())}>K</span>
</span>
</Button>
{user && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="border border-border h-8 w-8 md:h-10 md:w-10 cursor-pointer">
<AvatarImage src={user?.image ?? ""} />
<AvatarFallback>{user?.name?.charAt(0)}</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => router.push("/new/settings")}>
<Settings className="h-4 w-4" />
Settings
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
authClient.signOut()
router.push("/login/new")
}}
>
<LogOut className="h-4 w-4" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
)
}