mirror of
https://github.com/reconurge/flowsint.git
synced 2026-03-12 01:44:42 -05:00
feat: new layout
This commit is contained in:
@@ -18,7 +18,6 @@ export default function TokensPage() {
|
||||
])
|
||||
|
||||
return (
|
||||
<DashboardLayout items={[{ name: "Dashboard", href: "/dashboard" }, { name: "Keys and tokens" }]}>
|
||||
<div className="space-y-12 container mx-auto py-12 px-8">
|
||||
<div>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
@@ -146,7 +145,6 @@ export default function TokensPage() {
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { redirect } from "next/navigation";
|
||||
import { Radar, Settings } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MainNav } from "@/components/dashboard/main-nav";
|
||||
import { NavUser } from "@/components/nav-user";
|
||||
import { SubNav } from "@/components/dashboard/sub-nav";
|
||||
import Feedback from "@/components/dashboard/feedback";
|
||||
import Link from "next/link";
|
||||
|
||||
const DashboardLayout = async ({
|
||||
children,
|
||||
@@ -18,11 +24,28 @@ const DashboardLayout = async ({
|
||||
if (userError || !data?.user) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar user={data?.user} />
|
||||
<SidebarInset>
|
||||
<div className="flex flex-1 flex-col gap-4">
|
||||
<div className="flex flex-col flex-1 min-h-screen flex-col bg-background">
|
||||
<header className="sticky top-0 z-50 bg-background border-b">
|
||||
<div className="flex h-14 items-center px-4">
|
||||
<Link href="/dashboard" className="flex items-center gap-1">
|
||||
<Radar className="mr-2 h-6 w-6" />
|
||||
<h2 className="text-lg font-semibold mr-6">flowsint</h2>
|
||||
</Link>
|
||||
<MainNav className="mx-6" />
|
||||
<div className="ml-auto flex items-center space-x-2">
|
||||
<div className="lg:flex hidden items-center space-x-2">
|
||||
<Feedback />
|
||||
<Button size={"sm"} variant={"ghost"}>Changelog</Button>
|
||||
<Button size={"sm"} variant={"ghost"}>Docs</Button>
|
||||
</div>
|
||||
<NavUser user={data?.user} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
</div>
|
||||
</SidebarInset>
|
||||
|
||||
@@ -17,6 +17,7 @@ import NewProject from "@/components/dashboard/new-project"
|
||||
import { AvatarList } from "@/components/avatar-list"
|
||||
import { cn } from "@/lib/utils"
|
||||
import DashboardLayout from "@/components/dashboard/layout"
|
||||
import { SubNav } from "@/components/dashboard/sub-nav"
|
||||
|
||||
const DashboardPage = () => {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
@@ -38,7 +39,10 @@ const DashboardPage = () => {
|
||||
)
|
||||
|
||||
return (
|
||||
<DashboardLayout items={[{ name: "Dashboard" }, { name: "Projects" }]}>
|
||||
<>
|
||||
<div className="sticky z-40 bg-background w-full hidden md:flex top-[56px] border-b">
|
||||
<SubNav />
|
||||
</div>
|
||||
<div className="w-full space-y-8 container mx-auto py-12 px-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Welcome back, Eliott</h1>
|
||||
@@ -160,7 +164,7 @@ const DashboardPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import InvestigationGraph from "@/components/investigations/sketch/graph"
|
||||
import LargeInvestigationGraph from '@/components/investigations/sketch/large-data-graph'
|
||||
import IndividualModal from "@/components/investigations/individual-modal"
|
||||
import { notFound } from "next/navigation"
|
||||
import { useQueryState } from "nuqs"
|
||||
interface ProfileProps {
|
||||
projectId: string
|
||||
individualId: string
|
||||
}
|
||||
export default function DashboardClient({ projectId, individualId }: ProfileProps) {
|
||||
// Use the initial data from the server, but enable background updates
|
||||
const [view, _] = useQueryState("view", { defaultValue: "flow-graph" })
|
||||
const graphQuery = useQuery({
|
||||
queryKey: ["project", individualId, "individuals", individualId],
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/projects/${projectId}/individuals/${individualId}`)
|
||||
if (!res.ok) {
|
||||
notFound()
|
||||
}
|
||||
return res.json()
|
||||
},
|
||||
refetchOnWindowFocus: true,
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
{view === "flow-graph" ?
|
||||
<InvestigationGraph graphQuery={graphQuery} />
|
||||
:
|
||||
<LargeInvestigationGraph graphQuery={graphQuery} />
|
||||
}
|
||||
<IndividualModal />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import LargeInvestigationGraph from '@/components/investigations/sketch/large-da
|
||||
import IndividualModal from "@/components/investigations/individual-modal"
|
||||
import { notFound } from "next/navigation"
|
||||
import { useQueryState } from "nuqs"
|
||||
import { InvestigationtNavigation } from "@/components/investigations/investigation-navigation"
|
||||
interface DashboardClientProps {
|
||||
projectId: string
|
||||
investigationId: string
|
||||
@@ -24,14 +25,17 @@ export default function DashboardClient({ projectId, investigationId }: Dashboar
|
||||
refetchOnWindowFocus: true,
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<div className="sticky z-40 bg-background top-[56px] border-b">
|
||||
<InvestigationtNavigation project_id={projectId} investigation_id={investigationId} />
|
||||
</div>
|
||||
{view === "flow-graph" ?
|
||||
<InvestigationGraph graphQuery={graphQuery} />
|
||||
:
|
||||
<LargeInvestigationGraph graphQuery={graphQuery} />
|
||||
}
|
||||
<IndividualModal />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,19 +11,11 @@ import Loader from "@/components/loader"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { format, formatDistanceToNow } from "date-fns"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb"
|
||||
import { Investigation } from "@/types/investigation"
|
||||
import NewCase from "@/components/dashboard/new-sketch"
|
||||
import { DocumentList } from "@/components/projects/documents-list"
|
||||
import { cn } from "@/lib/utils"
|
||||
import DashboardLayout from "@/components/dashboard/layout"
|
||||
import { ProjectNavigation } from "@/components/investigations/project-navigation"
|
||||
|
||||
const DashboardPage = () => {
|
||||
const { project_id } = useParams()
|
||||
@@ -64,7 +56,10 @@ const DashboardPage = () => {
|
||||
const isRefetching = isRefetchingDocs || isRefetchingSketches
|
||||
|
||||
return (
|
||||
<DashboardLayout items={[{ name: "Projects", href: "/dashboard" }, { name: project?.name || "..." }]}>
|
||||
<>
|
||||
<div className="sticky z-40 bg-background top-[56px] border-b">
|
||||
<ProjectNavigation project_id={project_id as string} />
|
||||
</div>
|
||||
<div className="w-full space-y-8 container mx-auto py-12 px-8">
|
||||
<div className="flex items-center gap-2 justify-between mb-6">
|
||||
<div className="relative w-full max-w-md">
|
||||
@@ -180,7 +175,7 @@ const DashboardPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,22 +39,20 @@ interface SettingsLayoutProps {
|
||||
|
||||
export default function SettingsLayout({ children }: SettingsLayoutProps) {
|
||||
return (
|
||||
<DashboardLayout items={[{ name: "Dashboard", href: "/dashboard" }, { name: "Settings" }]}>
|
||||
<div className="space-y-12 container mx-auto py-12 px-8">
|
||||
<div className="space-y-0.5">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your account settings and set e-mail preferences.
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="my-6" />
|
||||
<div className="flex flex-col space-y-8 md:flex-row space-x-12 gap-6">
|
||||
<aside className="lg:w-1/5">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div className="flex-1 md:max-w-2xl">{children}</div>
|
||||
</div>
|
||||
<div className="space-y-12 container mx-auto py-12 px-8">
|
||||
<div className="space-y-0.5">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your account settings and set e-mail preferences.
|
||||
</p>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
<Separator className="my-6" />
|
||||
<div className="flex flex-col space-y-8 md:flex-row space-x-12 gap-6">
|
||||
<aside className="lg:w-1/5">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div className="flex-1 md:max-w-2xl">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
47
flowsint-web/src/components/dashboard/main-nav.tsx
Normal file
47
flowsint-web/src/components/dashboard/main-nav.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client"
|
||||
import type React from "react"
|
||||
import Link from "next/link"
|
||||
import { cn } from "@/lib/utils"
|
||||
import ProjectSelector from "../investigations/project-selector"
|
||||
import CaseSelector from "../investigations/case-selector"
|
||||
import { useParams } from "next/navigation"
|
||||
|
||||
export function MainNav({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||
const { investigation_id, project_id } = useParams()
|
||||
|
||||
if (project_id) return (
|
||||
<div className='flex gap-1 items-center p-2'>
|
||||
<ProjectSelector />{investigation_id && <><span className='opacity-60'>/</span><CaseSelector /></>}
|
||||
</div >
|
||||
)
|
||||
|
||||
return (
|
||||
<nav className={cn("md:flex hidden w-full items-center space-x-4 lg:space-x-6", className)} {...props}>
|
||||
<Link
|
||||
href="/"
|
||||
className="text-sm font-medium transition-colors hover:text-primary bg-muted/50 px-3 py-1.5 rounded-md"
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="/sources"
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary px-3 py-1.5 rounded-md"
|
||||
>
|
||||
Sources
|
||||
</Link>
|
||||
<Link
|
||||
href="/analyses"
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary px-3 py-1.5 rounded-md"
|
||||
>
|
||||
Analyses
|
||||
</Link>
|
||||
<Link
|
||||
href="/templates"
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary px-3 py-1.5 rounded-md"
|
||||
>
|
||||
Templates
|
||||
</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
73
flowsint-web/src/components/dashboard/sub-nav.tsx
Normal file
73
flowsint-web/src/components/dashboard/sub-nav.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Eye, Users, Camera, Database, Settings } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
|
||||
|
||||
export function SubNav() {
|
||||
const pathname = usePathname()
|
||||
const sections = [
|
||||
{
|
||||
id: "overview",
|
||||
name: "Overview",
|
||||
href: "/dashboard",
|
||||
icon: Eye,
|
||||
count: 18,
|
||||
},
|
||||
{
|
||||
id: "investigations",
|
||||
name: "Investigations",
|
||||
href: "/dashboard/investigations",
|
||||
icon: Users,
|
||||
count: 24,
|
||||
},
|
||||
{
|
||||
id: "surveillance",
|
||||
name: "Surveillance",
|
||||
href: "/dashboard/surveillance",
|
||||
icon: Camera,
|
||||
count: 6,
|
||||
},
|
||||
{
|
||||
id: "intelligence",
|
||||
name: "Intelligence",
|
||||
href: "/dashboard/intelligence",
|
||||
icon: Database,
|
||||
count: 16,
|
||||
},
|
||||
{
|
||||
id: "configurations",
|
||||
name: "Configurations",
|
||||
href: "/dashboard/settings",
|
||||
icon: Settings,
|
||||
count: null,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex overflow-auto">
|
||||
{sections.map((section) => (
|
||||
<Link
|
||||
key={section.id}
|
||||
href={section?.href || ""}
|
||||
className={cn(
|
||||
"flex items-center text-xs gap-2 px-4 py-2 transition-colors",
|
||||
section?.href == pathname
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-accent/50",
|
||||
)}
|
||||
>
|
||||
<section.icon className="h-4 w-4" />
|
||||
<span>{section.name}</span>
|
||||
{section.count !== null && (
|
||||
<span className="ml-1 rounded-full bg-primary/10 px-2 py-0.5 text-xs">{section.count}</span>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use client"
|
||||
import { useInvestigations } from "@/lib/hooks/investigation/investigation";
|
||||
import { useInvestigationStore } from '@/store/investigation-store';
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
|
||||
@@ -89,9 +89,9 @@ const IndividualModal = () => {
|
||||
|
||||
return (
|
||||
<Dialog open={Boolean(individualId)} onOpenChange={handleCloseModal}>
|
||||
<DialogContent className="sm:max-w-[90vw] max-h-[90vh] p-0 bg-black text-white border-zinc-800 overflow-hidden">
|
||||
<DialogContent className="sm:max-w-[90vw] max-h-[90vh] p-0 bg-background overflow-y-auto">
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex justify-between items-center p-4 border-b border-zinc-800">
|
||||
<div className="flex justify-between sticky top-0 items-center p-4 border-b">
|
||||
<DialogTitle className="text-xl font-bold">{individual?.full_name || "User Profile"}</DialogTitle>
|
||||
<div className="flex items-center gap-2 mr-12">
|
||||
<Button
|
||||
@@ -119,10 +119,10 @@ const IndividualModal = () => {
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
{/* Left column - Profile image and basic info */}
|
||||
<div className="flex flex-col gap-4 md:w-1/3">
|
||||
<div className="flex flex-col items-center gap-4 bg-zinc-900 rounded-xl p-6">
|
||||
<Avatar className="h-32 w-32 border-2 border-zinc-700">
|
||||
<div className="flex flex-col items-center gap-4 rounded-xl p-6">
|
||||
<Avatar className="h-32 w-32 border-2">
|
||||
<AvatarImage src={image || undefined} alt={individual?.full_name} />
|
||||
<AvatarFallback className="bg-zinc-800 text-zinc-300 text-2xl">
|
||||
<AvatarFallback className="bg-zinc-800 text-2xl">
|
||||
{individual?.full_name?.[0] || "?"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -134,35 +134,35 @@ const IndividualModal = () => {
|
||||
<div className="w-full space-y-4 mt-2">
|
||||
{individual?.email && (
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Mail className="h-4 w-4 text-zinc-500" />
|
||||
<span className="text-zinc-300">{individual.email}</span>
|
||||
<Mail className="h-4 w-4" />
|
||||
<span className="">{individual.email}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{phones[0] && (
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Phone className="h-4 w-4 text-zinc-500" />
|
||||
<span className="text-zinc-300">{phones[0]}</span>
|
||||
<Phone className="h-4 w-4" />
|
||||
<span className="">{phones[0]}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{individual?.location && (
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<MapPin className="h-4 w-4 text-zinc-500" />
|
||||
<span className="text-zinc-300">{individual.location}</span>
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span className="">{individual.location}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{individual?.company && (
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Building2 className="h-4 w-4 text-zinc-500" />
|
||||
<span className="text-zinc-300">{individual.company}</span>
|
||||
<Building2 className="h-4 w-4" />
|
||||
<span className="">{individual.company}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900 rounded-xl p-6">
|
||||
<div className="bg-background rounded-xl p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Profile</h3>
|
||||
<div className="space-y-3">
|
||||
{individual?.birth_date && (
|
||||
@@ -203,7 +203,7 @@ const IndividualModal = () => {
|
||||
</div>
|
||||
|
||||
{/* Score card */}
|
||||
<div className="bg-zinc-900 rounded-xl p-6">
|
||||
<div className="bg-background rounded-xl p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold">Overall score</h3>
|
||||
<div className="relative h-16 w-16">
|
||||
@@ -265,7 +265,7 @@ const IndividualModal = () => {
|
||||
{/* Right column - Tabs content */}
|
||||
<div className="flex-1">
|
||||
<Tabs defaultValue="overview" className="w-full">
|
||||
<TabsList className="w-full justify-start bg-zinc-900 p-1 rounded-lg mb-4">
|
||||
<TabsList className="w-full justify-start p-1 rounded-lg mb-4">
|
||||
<TabsTrigger
|
||||
value="overview"
|
||||
className="data-[state=active]:bg-zinc-800 data-[state=active]:text-white"
|
||||
@@ -319,7 +319,7 @@ const IndividualModal = () => {
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="bg-zinc-900 rounded-xl p-6">
|
||||
<div className="bg-background rounded-xl p-6">
|
||||
<TabsContent value="overview">
|
||||
{editMode ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@@ -335,7 +335,7 @@ const IndividualModal = () => {
|
||||
placeholder={key}
|
||||
name={key}
|
||||
disabled={key === "id" || key === "investigation_id" || !editMode}
|
||||
className="bg-zinc-800 border-zinc-700 text-white"
|
||||
className="bg-zinc-800 text-white"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@@ -405,12 +405,12 @@ const IndividualModal = () => {
|
||||
)}
|
||||
|
||||
{accounts.map((account: any, index) => (
|
||||
<Card key={index} className="bg-zinc-800 border-zinc-700 p-4">
|
||||
<Card key={index} className="bg-zinc-800 p-4">
|
||||
<CardContent className="flex items-center gap-3 p-0">
|
||||
<Avatar className="h-10 w-10 bg-zinc-700">
|
||||
{/* @ts-ignore */}
|
||||
<AvatarImage src={platformsIcons[account?.platform]?.icon} />
|
||||
<AvatarFallback className="bg-zinc-700 text-zinc-300">
|
||||
<AvatarFallback className="bg-zinc-700 ">
|
||||
{account?.platform?.[0] || "?"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -446,7 +446,7 @@ const IndividualModal = () => {
|
||||
<Button
|
||||
onClick={() => handleAddField(setAccounts)}
|
||||
variant="outline"
|
||||
className="border-dashed border-zinc-700 hover:border-zinc-500 bg-transparent text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||
className="border-dashed hover:border-zinc-500 bg-transparent text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" /> Add account
|
||||
</Button>
|
||||
@@ -469,7 +469,7 @@ const IndividualModal = () => {
|
||||
className={
|
||||
email.breaches.length > 0
|
||||
? "bg-red-950/50 border-red-900 text-red-200"
|
||||
: "bg-zinc-800 border-zinc-700"
|
||||
: "bg-zinc-800"
|
||||
}
|
||||
>
|
||||
<AlertDescription className="flex items-center gap-2">
|
||||
@@ -496,7 +496,7 @@ const IndividualModal = () => {
|
||||
|
||||
{phones.map((phone, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div className="bg-zinc-800 border border-zinc-700 rounded-md p-3 flex items-center gap-3 flex-1">
|
||||
<div className="bg-zinc-800 border rounded-md p-3 flex items-center gap-3 flex-1">
|
||||
<Phone className="h-4 w-4 text-zinc-400" />
|
||||
{editMode ? (
|
||||
<Input
|
||||
@@ -528,7 +528,7 @@ const IndividualModal = () => {
|
||||
<Button
|
||||
onClick={() => handleAddField(setPhones)}
|
||||
variant="outline"
|
||||
className="border-dashed border-zinc-700 hover:border-zinc-500 bg-transparent text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||
className="border-dashed hover:border-zinc-500 bg-transparent text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" /> Add Phone
|
||||
</Button>
|
||||
@@ -546,7 +546,7 @@ const IndividualModal = () => {
|
||||
|
||||
{ips.map((ip, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div className="bg-zinc-800 border border-zinc-700 rounded-md p-3 flex items-center gap-3 flex-1">
|
||||
<div className="bg-zinc-800 border rounded-md p-3 flex items-center gap-3 flex-1">
|
||||
<svg
|
||||
className="h-4 w-4 text-zinc-400"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -605,7 +605,7 @@ const IndividualModal = () => {
|
||||
<Button
|
||||
onClick={() => handleAddField(setIps)}
|
||||
variant="outline"
|
||||
className="border-dashed border-zinc-700 hover:border-zinc-500 bg-transparent text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||
className="border-dashed hover:border-zinc-500 bg-transparent text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" /> Add IP address
|
||||
</Button>
|
||||
@@ -624,13 +624,13 @@ const IndividualModal = () => {
|
||||
{relations.map((relation) => (
|
||||
<Card
|
||||
key={relation.id}
|
||||
className="bg-zinc-800 border-zinc-700 p-4 hover:bg-zinc-750 transition-colors cursor-pointer"
|
||||
className="bg-zinc-800 p-4 hover:bg-zinc-750 transition-colors cursor-pointer"
|
||||
onClick={() => setIndividualId(relation.id)}
|
||||
>
|
||||
<CardContent className="flex items-center gap-3 p-0">
|
||||
<Avatar className="h-10 w-10 border border-zinc-700">
|
||||
<Avatar className="h-10 w-10 border">
|
||||
<AvatarImage src={relation?.image_url} alt={relation.full_name} />
|
||||
<AvatarFallback className="bg-zinc-700 text-zinc-300">
|
||||
<AvatarFallback className="bg-zinc-700 ">
|
||||
{relation.full_name?.[0] || "?"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -652,7 +652,7 @@ const IndividualModal = () => {
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setEditMode(false)}
|
||||
className="bg-transparent border-zinc-700 text-zinc-300 hover:bg-zinc-800 hover:text-white"
|
||||
className="bg-transparent hover:bg-zinc-800 hover:text-white"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Users, TimerIcon, MapIcon, WaypointsIcon } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
|
||||
export function InvestigationtNavigation({ project_id, investigation_id }: { project_id: string, investigation_id: string }) {
|
||||
const pathname = usePathname()
|
||||
const sections = [
|
||||
{
|
||||
id: "sketch",
|
||||
name: "Sketch",
|
||||
href: `/dashboard/projects/${project_id}/investigations/${investigation_id}`,
|
||||
icon: WaypointsIcon
|
||||
},
|
||||
{
|
||||
id: "individuals",
|
||||
name: "Individuals",
|
||||
href: `/dashboard/projects/${project_id}/investigations/${investigation_id}/individuals`,
|
||||
icon: Users,
|
||||
count: 24,
|
||||
},
|
||||
{
|
||||
id: "timeline",
|
||||
name: "Timeline",
|
||||
href: `/dashboard/projects/${project_id}/investigations/${investigation_id}/timeline`,
|
||||
icon: TimerIcon,
|
||||
},
|
||||
{
|
||||
id: "map",
|
||||
name: "Map",
|
||||
href: `/dashboard/projects/${project_id}/investigations/${investigation_id}/map`,
|
||||
icon: MapIcon,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex overflow-auto">
|
||||
{sections.map((section) => (
|
||||
<Link
|
||||
key={section.id}
|
||||
href={section?.href || ""}
|
||||
className={cn(
|
||||
"flex items-center text-xs gap-2 px-4 py-2 transition-colors",
|
||||
section?.href == pathname
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-accent/50",
|
||||
)}
|
||||
>
|
||||
<section.icon className="h-4 w-4" />
|
||||
<span>{section.name}</span>
|
||||
{section.count && (
|
||||
<span className="ml-1 rounded-full bg-primary/10 px-2 py-0.5 text-xs">{section.count}</span>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,75 +1,16 @@
|
||||
"use client"
|
||||
import React from 'react'
|
||||
import MoreMenu from './more-menu';
|
||||
import CaseSelector from './case-selector';
|
||||
import NewCase from '@/components/dashboard/new-sketch';
|
||||
import SearchModal from '@/components/dashboard/search-palette';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import { PanelRightIcon, PlusIcon, RotateCwIcon } from 'lucide-react';
|
||||
import Logo from '@/components/logo';
|
||||
import { useInvestigationStore } from '@/store/investigation-store';
|
||||
import { Button } from '../ui/button';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
import Assistant from '../assistant';
|
||||
import { AppSidebar } from '../app-sidebar';
|
||||
import { SidebarProvider } from '../ui/sidebar';
|
||||
import { ScanDrawer } from './scan-drawer';
|
||||
import { ScanButton } from './scans-drawer/scan-button';
|
||||
import ProjectSelector from './project-selector';
|
||||
|
||||
const InvestigationLayout = ({
|
||||
children,
|
||||
left,
|
||||
investigation_id,
|
||||
user
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
left: React.ReactNode;
|
||||
investigation_id: string
|
||||
user: any
|
||||
}) => {
|
||||
const { panelOpen, setPanelOpen } = useInvestigationStore()
|
||||
return (
|
||||
<>
|
||||
{/* <PanelGroup autoSaveId="conditional" className='h-screen w-screen flex' direction="horizontal"> */}
|
||||
{/* {panelOpen && <Panel id="left" order={1} className='h-screen' defaultSize={20} minSize={10}>
|
||||
<div className='flex flex-col w-full h-full rounded-none shadow-none border-r'>
|
||||
<div className='w-full rounded-none shadow-none h-12 border-b flex items-center gap-1 flex-row justify-end p-2'>
|
||||
<div className='flex gap-1'>
|
||||
<SearchModal investigation_id={investigation_id} />
|
||||
<NewCase>
|
||||
<Button variant="outline" size="icon">
|
||||
<PlusIcon className="h-5" />
|
||||
</Button>
|
||||
</NewCase>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea type="auto" className='h-full grow overflow-y-auto'>
|
||||
<div className="flex flex-col">
|
||||
{left}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</Panel>}
|
||||
<PanelResizeHandle /> */}
|
||||
{/* <Panel id="right" order={2} defaultSize={90} minSize={50} className='grow flex flex-col'> */}
|
||||
<div>
|
||||
<div className='w-full bg-sidebar rounded-none shadow-none h-12 justify-between border-b flex flex-row items-center'>
|
||||
<div className='grow flex items-center justify-between p-2'>
|
||||
<div className='flex gap-1 items-center p-2'>
|
||||
<ProjectSelector /><span className='opacity-60'>/</span><CaseSelector />
|
||||
</div>
|
||||
<MoreMenu />
|
||||
</div>
|
||||
<div className='border-l h-full flex items-center'>
|
||||
<ScanButton />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
{/* </Panel>
|
||||
</PanelGroup >
|
||||
*/}
|
||||
{children}
|
||||
<ScanDrawer />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Eye, Users, Camera, Settings } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
|
||||
export function ProjectNavigation({ project_id }: { project_id: string }) {
|
||||
const pathname = usePathname()
|
||||
const sections = [
|
||||
{
|
||||
id: "overview",
|
||||
name: "Overview",
|
||||
href: `/dashboard/projects/${project_id}`,
|
||||
icon: Eye,
|
||||
count: 18,
|
||||
},
|
||||
{
|
||||
id: "sketches",
|
||||
name: "Sketches",
|
||||
href: `/dashboard/projects/${project_id}?filter=sketch`,
|
||||
icon: Users,
|
||||
count: 24,
|
||||
},
|
||||
{
|
||||
id: "documents",
|
||||
name: "Documents",
|
||||
href: `/dashboard/projects/${project_id}?filter=document`,
|
||||
icon: Camera,
|
||||
count: 6,
|
||||
},
|
||||
{
|
||||
id: "configurations",
|
||||
name: "Configurations",
|
||||
href: `/dashboard/projects/${project_id}/settings`,
|
||||
icon: Settings,
|
||||
count: null,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex overflow-auto">
|
||||
{sections.map((section) => (
|
||||
<Link
|
||||
key={section.id}
|
||||
href={section?.href || ""}
|
||||
className={cn(
|
||||
"flex items-center text-xs gap-2 px-4 py-2 transition-colors",
|
||||
section?.href == pathname
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-accent/50",
|
||||
)}
|
||||
>
|
||||
<section.icon className="h-4 w-4" />
|
||||
<span>{section.name}</span>
|
||||
{section.count !== null && (
|
||||
<span className="ml-1 rounded-full bg-primary/10 px-2 py-0.5 text-xs">{section.count}</span>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -170,19 +170,6 @@ const FlowControls = memo(
|
||||
<TooltipContent>Zoom out</TooltipContent>
|
||||
</Tooltip>
|
||||
</Panel>
|
||||
<Panel position="top-center" className="text-center">
|
||||
<Tabs defaultValue="sketch">
|
||||
<TabsList>
|
||||
<TabsTrigger value="sketch">Sketch</TabsTrigger>
|
||||
<TabsTrigger value="map" disabled>Map</TabsTrigger>
|
||||
<TabsTrigger value="timeline" disabled>Timeline</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
{/* {currentNode && (
|
||||
<Panel position="top-right">
|
||||
<ProfilePanel type={currentNode.type} data={currentNode.data} />
|
||||
</Panel>)} */}
|
||||
</>
|
||||
)
|
||||
},
|
||||
@@ -292,7 +279,7 @@ const LayoutFlow = ({ refetch, theme }: LayoutFlowProps) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="h-[calc(100vh_-_48px)] relative">
|
||||
<div className="w-full grow relative">
|
||||
<TooltipProvider>
|
||||
<Dialog>
|
||||
<ContextMenu>
|
||||
@@ -379,7 +366,7 @@ function Graph({ graphQuery }: { graphQuery: any }) {
|
||||
|
||||
if (!mounted || isLoading) {
|
||||
return (
|
||||
<div className="h-[calc(100vh_-_48px)] w-full flex items-center justify-center">
|
||||
<div className="grow w-full flex items-center justify-center">
|
||||
<Loader /> Loading...
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -47,17 +47,11 @@ export function NavUser({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg uppercase">{user.email[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
@@ -70,7 +64,7 @@ export function NavUser({
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
<AvatarFallback className="rounded-lg">{user.email[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
|
||||
Reference in New Issue
Block a user