Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata } from 'next'
import './globals.css'
import { OrganizationProvider } from '@/providers/organization-provider'

export const metadata: Metadata = {
title: 'v0 App',
Expand All @@ -14,7 +15,11 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body>{children}</body>
<body>
<OrganizationProvider>
{children}
</OrganizationProvider>
</body>
</html>
)
}
77 changes: 44 additions & 33 deletions components/folder-tree.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
"use client"

import { useState } from "react"
import { useEffect, useState } from "react"
import { ChevronRight, ChevronDown, Folder, FolderOpen, Plus, Users } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import type { FolderItem } from "@/components/password-manager"
import useOrganization from "@/hooks/use-organization"

interface FolderTreeProps {
folders: FolderItem[]
selectedFolderId: string | null
onSelectFolder: (folderId: string | null) => void
onAddFolder: (name: string, parentId: string | null) => void
className?: string
}
export function FolderTree() {
const { folders, selectedFolderId, loadings, onCreateFolder, updateSelectedFolderId } = useOrganization()

export function FolderTree({ folders, selectedFolderId, onSelectFolder, onAddFolder, className }: FolderTreeProps) {
const [expandedFolders, setExpandedFolders] = useState<Record<string, boolean>>({})
const [newFolderParentId, setNewFolderParentId] = useState<string | null>(null)
const [newFolderName, setNewFolderName] = useState("")

// Get root level folders
const rootFolders = folders.filter((folder) => folder.parentId === null)
const getRootFolders = () => {
return folders.filter((folder) => !folders.map(f => f.id).includes(folder.parentId ?? ''))
}

const [rootFolders, setRootFolders] = useState<FolderItem[]>(getRootFolders())

useEffect(() => {
setRootFolders(getRootFolders())
}, [folders])

const toggleExpand = (folderId: string) => {
setExpandedFolders((prev) => ({
Expand All @@ -32,9 +34,8 @@ export function FolderTree({ folders, selectedFolderId, onSelectFolder, onAddFol

const handleAddFolder = () => {
if (newFolderName.trim()) {
onAddFolder(newFolderName.trim(), newFolderParentId)
onCreateFolder(newFolderName.trim(), newFolderParentId)
setNewFolderName("")
setNewFolderParentId(null)
}
}

Expand All @@ -60,7 +61,7 @@ export function FolderTree({ folders, selectedFolderId, onSelectFolder, onAddFol
>
{isExpanded ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
</button>
<button type="button" className="flex items-center gap-2 flex-1" onClick={() => onSelectFolder(folder.id)}>
<button type="button" className="flex items-center gap-2 flex-1" onClick={() => updateSelectedFolderId(folder.id)}>
{folder.shared ? (
<Users className="h-4 w-4 text-blue-500" />
) : isExpanded ? (
Expand All @@ -70,24 +71,28 @@ export function FolderTree({ folders, selectedFolderId, onSelectFolder, onAddFol
)}
<span>{folder.name}</span>
</button>
<Button
type="button"
variant="ghost"
size="icon"
className="h-6 w-6 opacity-0 group-hover:opacity-100 hover:opacity-100 focus:opacity-100"
onClick={() => setNewFolderParentId(folder.id)}
>
<Plus className="h-3 w-3" />
<span className="sr-only">Ajouter un sous-dossier</span>
</Button>
{
level < 2 && (
<Button
type="button"
variant="ghost"
size="icon"
className="h-6 w-6 opacity-0 group-hover:opacity-100 hover:opacity-100 focus:opacity-100"
onClick={() => setNewFolderParentId(folder.id)}
>
<Plus className="h-3 w-3" />
<span className="sr-only">Ajouter un sous-dossier</span>
</Button>
)
}
</div>

{isExpanded && hasChildren && (
<div className="mt-1">{childFolders.map((childFolder) => renderFolder(childFolder, level + 1))}</div>
)}

{newFolderParentId === folder.id && (
<div className="flex items-center gap-2 mt-1 ml-8">
<div className="flex flex-col mt-1 ml-8">
<Input
value={newFolderName}
onChange={(e) => setNewFolderName(e.target.value)}
Expand All @@ -99,34 +104,40 @@ export function FolderTree({ folders, selectedFolderId, onSelectFolder, onAddFol
if (e.key === "Escape") setNewFolderParentId(null)
}}
/>
<Button type="button" variant="outline" size="sm" className="h-7" onClick={handleAddFolder}>
Ajouter
</Button>
<Button type="button" variant="ghost" size="sm" className="h-7" onClick={() => setNewFolderParentId(null)}>
Annuler
</Button>
<div className="flex items-center gap-2 justify-between mt-1">
<Button type="button" variant="ghost" size="sm" className="h-7" onClick={() => setNewFolderParentId(null)}>
Annuler
</Button>
<Button type="button" variant="outline" size="sm" className="h-7" onClick={handleAddFolder}>
Ajouter
</Button>
</div>
</div>
)}
</div>
)
}

return (
<div className={cn("space-y-1", className)}>
<div className={cn("space-y-1", { 'flex flex-col justify-between h-[300px]': rootFolders.length === 0 })}>
<button
type="button"
className={cn(
"flex items-center gap-2 w-full py-1 px-2 rounded-md text-sm transition-colors",
selectedFolderId === null ? "bg-primary text-primary-foreground" : "hover:bg-muted",
)}
onClick={() => onSelectFolder(null)}
onClick={() => updateSelectedFolderId(null)}
>
<Folder className="h-4 w-4 text-muted-foreground" />
<span>Tous les mots de passe</span>
</button>

{rootFolders.map((folder) => renderFolder(folder))}

{rootFolders.length === 0 && !loadings.foldersLoading && (
<p className="text-center text-muted-foreground">Aucun dossier trouvé</p>
)}

{newFolderParentId === null && (
<div className="flex items-center gap-2 mt-2">
<Input
Expand Down
182 changes: 182 additions & 0 deletions components/password-credential-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { PasswordCredential } from "@/types/credential"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card"
import { cn } from "@/lib/utils"
import { DropdownMenu, DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
import { Button } from "./ui/button"
import { Copy, FileText, MoreHorizontal } from "lucide-react"
import { DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from "./ui/dropdown-menu"
import { Badge } from "./ui/badge"
import { usePassword } from "@/lib/api/security"
import { useState } from "react"

interface PasswordCredentialProps {
credential: PasswordCredential
}

export function PasswordCredentialCard({ credential }: PasswordCredentialProps) {
const [revealedPasswords, setRevealedPasswords] = useState<Record<string, boolean>>({})

const copyToClipboard = async (text: string, idPassword: string) => {
try {
await navigator.clipboard.writeText(text)
await usePassword(idPassword)
// Mettre à jour le compteur local
// setPasswordUsages(prev => ({
// ...prev,
// [idPassword]: (prev[idPassword] || 0) + 1
// }))
} catch (error) {
console.error('Erreur lors de la copie:', error)
}
}

return (
<Card
key={credential.id}
className={cn(
"overflow-hidden",
// password.breached && "border-red-200",
// password.reused && "border-blue-200",
// password.old && "border-amber-200",
)}
>
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-lg flex items-center gap-2">
{credential.title}
{/* {password.breached && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<AlertTriangle className="h-4 w-4 text-red-500" />
</TooltipTrigger>
<TooltipContent>
<p>Identifiants compromis</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)} */}
{/* {password.reused && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<RefreshCw className="h-4 w-4 text-blue-500" />
</TooltipTrigger>
<TooltipContent>
<p>Mot de passe réutilisé</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)} */}
</CardTitle>
<CardDescription>{credential.user_identifier}</CardDescription>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">Menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem onClick={() => copyToClipboard(credential.user_identifier, credential.id)}>
Copier l'identifiant
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyToClipboard(credential.password, credential.id)}>
Copier le mot de passe
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* <DropdownMenuItem
onClick={() => {
setSelectedPassword(password)
}}
>
Voir les détails
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setSelectedPassword(password)
setShowShareDialog(true)
}}
>
Partager
</DropdownMenuItem> */}
<DropdownMenuSeparator />
<DropdownMenuItem>Modifier</DropdownMenuItem>
<DropdownMenuItem className="text-red-600">Supprimer</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* <div className="mt-1 flex flex-wrap gap-1">
<Badge variant="outline" className="bg-muted/50 text-xs" title={getFolderPath(password.folderId)}>
{getFolderName(password.folderId)}
</Badge>
{password.tags.slice(0, 2).map((tag, index) => (
<Badge key={index} variant="secondary" className="text-xs">
{tag}
</Badge>
))}
{password.tags.length > 2 && (
<Badge variant="secondary" className="text-xs">
+{password.tags.length - 2}
</Badge>
)}
</div> */}
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">Mot de passe:</span>
<span className="text-sm">
{revealedPasswords[credential.password] ? "ExamplePass123" : credential.password}
</span>
</div>
<div className="flex gap-1">
{/* <Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => togglePasswordVisibility(credential.password)}
>
{revealedPasswords[password.id] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
<span className="sr-only">
{revealedPasswords[password.id] ? "Masquer" : "Afficher"} le mot de passe
</span>
</Button> */}
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => copyToClipboard("ExamplePass123", credential.password)}
>
<Copy className="h-4 w-4" />
<span className="sr-only">Copier le mot de passe</span>
</Button>
</div>
</div>
{/* <div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Mis à jour: {password.lastUpdated}</span>
<Badge variant="outline" className={getStrengthColor(password.strength)}>
{password.strength === "strong" ? "Fort" : password.strength === "medium" ? "Moyen" : "Faible"}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Nombre de vues :</span>
<span className="text-xs font-semibold">
{!isMounted || isLoadingUsages ? '...' : passwordUsages[password.id] ?? 0}
</span>
</div> */}
{credential.note && (
<div className="pt-2 flex items-center text-xs text-muted-foreground">
<FileText className="h-3 w-3 mr-1" />
Notes
</div>
)}
</div>
</CardContent>
</Card>
)
}
Loading