mirror of
https://github.com/reconurge/flowsint.git
synced 2026-03-12 01:44:42 -05:00
feat: rls
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -13,15 +14,21 @@ import {
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
const DashboardLayout = ({
|
||||
const DashboardLayout = async ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const supabase = await createClient()
|
||||
const { data, error: userError } = await supabase.auth.getUser()
|
||||
if (userError || !data?.user) {
|
||||
redirect('/login')
|
||||
}
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<AppSidebar user={data?.user} />
|
||||
<SidebarInset>
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
|
||||
@@ -9,7 +9,7 @@ const DashboardPage = async () => {
|
||||
const { investigations, error } = await getInvestigations()
|
||||
if (error) return <div>An error occured.</div>
|
||||
return (
|
||||
<div className='space-y-6 max-w-6xl mx-auto p-6'>
|
||||
<div className='space-y-6 w-full max-w-5xl mx-auto p-6'>
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold mb-2">Vos investigations</h1>
|
||||
<p className="mb-6 opacity-70">
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion"
|
||||
import { useFlowStore } from '@/components/contexts/use-flow-store';
|
||||
import { usePlatformIcons } from '@/lib/hooks/use-platform-icons';
|
||||
|
||||
|
||||
const Left = ({ investigation_id }: { investigation_id: string }) => {
|
||||
@@ -21,6 +22,7 @@ const Left = ({ investigation_id }: { investigation_id: string }) => {
|
||||
const { emails, isLoading: isLoadingEmails, refetch: refetchEmails } = useEmails(investigation_id)
|
||||
const { phones, isLoading: isLoadingPhones, refetch: refetchPhones } = usePhones(investigation_id)
|
||||
const { socials, isLoading: isLoadingSocials, refetch: refetchSocials } = useSocials(investigation_id)
|
||||
const platformsIcons = usePlatformIcons()
|
||||
|
||||
const { currentNode, setCurrentNode } = useFlowStore()
|
||||
|
||||
@@ -116,7 +118,10 @@ const Left = ({ investigation_id }: { investigation_id: string }) => {
|
||||
{socials?.map((social: any) => (
|
||||
<li className={cn('hover:bg-sidebar-accent text-sidebar-accent-foreground/90 hover:text-sidebar-accent-foreground text-sm', currentNode === social.id && "bg-sidebar-accent text-sidebar-accent-foreground")} key={social.id}>
|
||||
<button onClick={() => setCurrentNode(social.id)} className='flex items-center p-1 px-4 w-full gap-2'>
|
||||
<PhoneIcon className='h-3 w-3 opacity-60' />
|
||||
<span className='opacity-60'>
|
||||
{/* @ts-ignore */}
|
||||
{platformsIcons?.[social?.platform]?.icon}
|
||||
</span>
|
||||
{social.username || social.profile_url}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -9,7 +9,6 @@ const DashboardPage = async ({
|
||||
}) => {
|
||||
const { investigation_id } = await (params)
|
||||
const { nodes, edges } = await getInvestigationData(investigation_id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InvestigationGraph initialNodes={nodes} initialEdges={edges} />
|
||||
|
||||
@@ -156,7 +156,11 @@ const data = {
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
||||
user: any;
|
||||
}
|
||||
|
||||
export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<SidebarHeader>
|
||||
@@ -167,7 +171,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
<NavProjects projects={data.projects} />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
<NavUser user={user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { supabase } from "@/lib/supabase/client"
|
||||
import { createNewCase } from "@/lib/actions/investigations"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -15,25 +18,30 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { useFormStatus } from "react-dom"
|
||||
|
||||
function SubmitButton() {
|
||||
const { pending } = useFormStatus()
|
||||
|
||||
return (
|
||||
<Button type="submit" disabled={pending}>
|
||||
{pending ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function NewCase({ children }: { children: React.ReactNode }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
async function handleNewCase(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
const data = Object.fromEntries(formData)
|
||||
|
||||
try {
|
||||
const { data: investigation, error } = await supabase.from("investigations").insert(data).select("id").single()
|
||||
|
||||
if (error) throw error
|
||||
if (investigation) router.push(`/investigations/${investigation.id}`)
|
||||
async function handleNewCase(formData: FormData) {
|
||||
const result = await createNewCase(formData)
|
||||
if (result.success) {
|
||||
router.push(`/investigations/${result.id}`)
|
||||
setOpen(false)
|
||||
} catch (error) {
|
||||
console.error("Error creating new case:", error)
|
||||
// Handle error (e.g., show error message to user)
|
||||
} else {
|
||||
console.error(result.error)
|
||||
// You might want to show an error message to the user here
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +69,7 @@ export default function NewCase({ children }: { children: React.ReactNode }) {
|
||||
<DialogTitle>New case</DialogTitle>
|
||||
<DialogDescription>Create a new blank case.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleNewCase}>
|
||||
<form action={handleNewCase}>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="title">Investigation name</Label>
|
||||
@@ -80,7 +88,7 @@ export default function NewCase({ children }: { children: React.ReactNode }) {
|
||||
<Button type="button" variant="outline" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Save</Button>
|
||||
<SubmitButton />
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
|
||||
@@ -54,7 +54,7 @@ const SearchModal = ({ investigation_id }: { investigation_id: string }) => {
|
||||
<CardContent className='p-3'>
|
||||
<div className='flex flex-col gap-1 text-left'>
|
||||
<span className='text-xs text-muted-foreground'>Individual</span>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='flex items-center flex-col items-start gap-1'>
|
||||
<span className='truncate'>
|
||||
<Highlighter
|
||||
searchWords={search.split(" ")}
|
||||
@@ -62,7 +62,7 @@ const SearchModal = ({ investigation_id }: { investigation_id: string }) => {
|
||||
textToHighlight={item?.full_name}
|
||||
/>
|
||||
</span>
|
||||
<span className='truncate text-sm text-muted-foreground'>
|
||||
<span className='truncate text-ellipsis text-sm text-muted-foreground'>
|
||||
<Highlighter
|
||||
searchWords={search.split(" ")}
|
||||
autoEscape={true}
|
||||
@@ -82,7 +82,7 @@ const SearchModal = ({ investigation_id }: { investigation_id: string }) => {
|
||||
<Button variant="outline" size="icon" onClick={() => setOpen(true)}>
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
<DialogContent className="sm:max-w-[425px] min-h-none">
|
||||
<DialogContent className="min-h-none">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Search</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client"
|
||||
import Dagre from '@dagrejs/dagre';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
@@ -26,11 +25,10 @@ import { useParams } from 'next/navigation';
|
||||
import { Tooltip, TooltipContent } from '@/components/ui/tooltip';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { TooltipTrigger } from '@radix-ui/react-tooltip';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useInvestigationContext } from '@/components/contexts/investigation-provider';
|
||||
import { useFlowStore } from '../contexts/use-flow-store';
|
||||
import CurrentNode from './current-node-card';
|
||||
// import CurrentNode from './current-node-card';
|
||||
|
||||
const edgeTypes = {
|
||||
"custom": FloatingEdge
|
||||
@@ -140,7 +138,7 @@ const LayoutFlow = ({ theme }: { theme: ColorMode }) => {
|
||||
</Button>
|
||||
<NewActions addNodes={addNodes} />
|
||||
</div>
|
||||
{currentNode && getNode(currentNode) && (
|
||||
{/* {currentNode && getNode(currentNode) && (
|
||||
getNode(currentNode)?.type === "individual" ?
|
||||
// @ts-ignore
|
||||
<CurrentNode individual={getNode(currentNode).data} /> :
|
||||
@@ -149,7 +147,7 @@ const LayoutFlow = ({ theme }: { theme: ColorMode }) => {
|
||||
{getNode(currentNode)?.data.label}
|
||||
</>
|
||||
</Card>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</Panel>
|
||||
<Panel position="bottom-left" className='flex flex-col items-center gap-1'>
|
||||
|
||||
@@ -30,7 +30,7 @@ const InvestigationLayout = ({
|
||||
const { panelOpen, setPanelOpen } = useInvestigationContext()
|
||||
return (
|
||||
<SidebarProvider defaultOpen={false}>
|
||||
<AppSidebar defaultChecked={false} />
|
||||
<AppSidebar user={user} defaultChecked={false} />
|
||||
<PanelGroup autoSaveId="conditional" className='h-screen w-screen flex' direction="horizontal">
|
||||
{panelOpen && <Panel id="left" order={1} className='h-screen' defaultSize={20} minSize={15}>
|
||||
<div className='flex flex-col w-full h-full rounded-none shadow-none border-r'>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Input } from "./ui/input"
|
||||
export function LoginForm() {
|
||||
return (
|
||||
<Card className="mx-auto w-full max-w-md !p-6">
|
||||
<div className="flex flex-cl gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
Login
|
||||
</div>
|
||||
@@ -15,9 +15,9 @@ export function LoginForm() {
|
||||
Login into your account or create a new one.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-cl gap-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<form>
|
||||
<div className="flex flex-cl gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="email">
|
||||
<div>
|
||||
Email
|
||||
@@ -43,7 +43,7 @@ export function LoginForm() {
|
||||
</div>
|
||||
</form>
|
||||
<form>
|
||||
<div className="flex flex-cl gap-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Button formAction={signInWithGithub} variant="outline" className="w-full">
|
||||
Login with Github
|
||||
</Button>
|
||||
|
||||
@@ -29,15 +29,12 @@ import {
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { logout } from "@/lib/actions/auth"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
user: any
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
@@ -52,7 +49,7 @@ export function NavUser({
|
||||
>
|
||||
<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 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>
|
||||
@@ -102,7 +99,7 @@ export function NavUser({
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={logout}>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -2,11 +2,36 @@
|
||||
import { createClient } from "../supabase/server"
|
||||
import { Investigation } from "@/types/investigation"
|
||||
import { NodeData, EdgeData } from "@/types"
|
||||
import { notFound } from "next/navigation"
|
||||
import { notFound, redirect } from "next/navigation"
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
interface ReturnTypeGetInvestigations {
|
||||
investigations: Investigation[],
|
||||
error?: any
|
||||
}
|
||||
|
||||
export async function createNewCase(formData: FormData) {
|
||||
const supabase = await createClient()
|
||||
const { data: session, error: userError } = await supabase.auth.getUser()
|
||||
if (userError || !session?.user) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
const data = {
|
||||
title: formData.get("title"),
|
||||
description: formData.get("description"),
|
||||
}
|
||||
try {
|
||||
const { data: investigation, error } = await supabase.from("investigations").insert({ ...data, owner_id: session?.user?.id }).select("id").single()
|
||||
if (error) throw error
|
||||
revalidatePath("/")
|
||||
return { success: true, id: investigation.id }
|
||||
} catch (error) {
|
||||
console.error("Error creating new case:", error)
|
||||
return { success: false, error: "Failed to create new case" }
|
||||
}
|
||||
}
|
||||
|
||||
export async function getInvestigations(): Promise<ReturnTypeGetInvestigations> {
|
||||
const supabase = await createClient()
|
||||
let { data: investigations, error } = await supabase
|
||||
|
||||
Reference in New Issue
Block a user