feat: rls

This commit is contained in:
dextmorgn
2025-02-19 15:49:21 +01:00
parent c855c124e1
commit cfea8988eb
12 changed files with 88 additions and 45 deletions

View File

@@ -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">

View File

@@ -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">

View File

@@ -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>

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'>

View File

@@ -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'>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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