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
6 changes: 3 additions & 3 deletions components/folder-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { ChevronRight, ChevronDown, Folder, FolderOpen, Plus, Users } from "luci
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 type { Folder } from "@/types/folder"

interface FolderTreeProps {
folders: FolderItem[]
folders: Folder[]
selectedFolderId: string | null
onSelectFolder: (folderId: string | null) => void
onAddFolder: (name: string, parentId: string | null) => void
Expand Down Expand Up @@ -38,7 +38,7 @@ export function FolderTree({ folders, selectedFolderId, onSelectFolder, onAddFol
}
}

const renderFolder = (folder: FolderItem, level = 0) => {
const renderFolder = (folder: Folder, level = 0) => {
const isExpanded = expandedFolders[folder.id]
const isSelected = selectedFolderId === folder.id
const childFolders = folders.filter((f) => f.parentId === folder.id)
Expand Down
6 changes: 3 additions & 3 deletions components/password-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import { Slider } from "@/components/ui/slider"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Textarea } from "@/components/ui/textarea"
import type { FolderItem } from "@/components/password-manager"
import type { Folder } from "@/types/folder"
import { Switch } from "@/components/ui/switch"

interface PasswordFormProps {
onAddPassword: (password: any) => void
folders: FolderItem[]
folders: Folder[]
onAddFolder: (name: string, parentId: string | null) => void
onCancel: () => void
allTags: string[]
Expand Down Expand Up @@ -236,7 +236,7 @@ export function PasswordForm({ onAddPassword, folders, onAddFolder, onCancel, al
}

// Get folder path for display
const getFolderPath = (folder: FolderItem): string => {
const getFolderPath = (folder: Folder): string => {
if (!folder.parentId) return folder.name

const parent = folders.find((f) => f.id === folder.parentId)
Expand Down
5 changes: 3 additions & 2 deletions components/password-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
} from "@/components/ui/dropdown-menu"
import { Badge } from "@/components/ui/badge"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import type { FolderItem, PasswordEntry } from "@/components/password-manager"
import type { Folder } from "@/types/folder"
import type { PasswordEntry } from "@/types/password-entry"
import {
Dialog,
DialogContent,
Expand All @@ -30,7 +31,7 @@ import { Textarea } from "@/components/ui/textarea"
import { getPasswordUsage, usePassword } from "@/lib/api/security"
interface PasswordListProps {
passwords: PasswordEntry[]
folders: FolderItem[]
folders: Folder[]
}

export function PasswordList({ passwords, folders }: PasswordListProps) {
Expand Down
149 changes: 26 additions & 123 deletions components/password-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { useState } from "react"
import { useState, useEffect } from "react"
import { Search, Plus, Key, AlertTriangle } from "lucide-react"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
Expand All @@ -11,136 +11,39 @@ import { PasswordForm } from "@/components/password-form"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"

// Types for our data model
export interface FolderItem {
id: string
name: string
parentId: string | null
shared?: boolean
}

export interface PasswordEntry {
id: string
title: string
website: string
username: string
password: string
strength: "weak" | "medium" | "strong"
lastUpdated: string
folderId: string
tags: string[]
notes?: string
customFields?: { label: string; value: string; type: string }[]
files?: { name: string; size: number; type: string }[]
breached?: boolean
reused?: boolean
old?: boolean
}

// Sample data
const initialFolders: FolderItem[] = [
{ id: "personal", name: "Personnel", parentId: null },
{ id: "work", name: "Travail", parentId: null },
{ id: "work-dev", name: "Développement", parentId: "work" },
{ id: "entertainment", name: "Divertissement", parentId: null },
{ id: "shopping", name: "Shopping", parentId: null },
{ id: "finance", name: "Finance", parentId: null },
{ id: "finance-banking", name: "Banque", parentId: "finance" },
{ id: "shared", name: "Partagé", parentId: null, shared: true },
{ id: "shared-team", name: "Équipe", parentId: "shared", shared: true },
]

const initialPasswords: PasswordEntry[] = [
{
id: "1",
title: "Gmail",
website: "gmail.com",
username: "[email protected]",
password: "••••••••••••",
strength: "strong",
lastUpdated: "2023-12-01",
folderId: "personal",
tags: ["Important", "Email"],
notes: "Compte email principal",
customFields: [{ label: "Téléphone de récupération", value: "+33612345678", type: "text" }],
},
{
id: "2",
title: "GitHub",
website: "github.com",
username: "devuser",
password: "••••••••",
strength: "medium",
lastUpdated: "2023-11-15",
folderId: "work-dev",
tags: ["Développement"],
reused: true,
},
{
id: "3",
title: "Netflix",
website: "netflix.com",
username: "moviefan",
password: "•••••••••",
strength: "weak",
lastUpdated: "2023-10-20",
folderId: "entertainment",
tags: ["Streaming", "Partagé"],
breached: true,
},
{
id: "4",
title: "Amazon",
website: "amazon.com",
username: "shopper123",
password: "•••••••••••",
strength: "strong",
lastUpdated: "2023-09-05",
folderId: "shopping",
tags: ["Shopping"],
old: true,
},
{
id: "5",
title: "Banque Populaire",
website: "banquepopulaire.fr",
username: "client123",
password: "••••••••••••••",
strength: "strong",
lastUpdated: "2023-11-28",
folderId: "finance-banking",
tags: ["Banque", "Important"],
customFields: [
{ label: "Numéro de compte", value: "FR76 1234 5678 9012 3456 7890 123", type: "text" },
{ label: "Code secret", value: "••••", type: "password" },
],
},
{
id: "6",
title: "Projet X",
website: "projet-x.com",
username: "team-member",
password: "•••••••••••",
strength: "strong",
lastUpdated: "2023-12-10",
folderId: "shared-team",
tags: ["Projet", "Équipe"],
shared: true,
},
]

const allTags = Array.from(new Set(initialPasswords.flatMap((p) => p.tags)))
import type { Folder } from "@/types/folder"
import type { PasswordEntry } from "@/types/password-entry"
import { useCredentials } from "@/hooks/use-credentials"
import { useFolders } from "@/hooks/use-folders"
import { useTags } from "@/hooks/use-tags"

export function PasswordManager() {
const [passwords, setPasswords] = useState<PasswordEntry[]>(initialPasswords)
const [folders, setFolders] = useState<FolderItem[]>(initialFolders)
const userId = process.env.NEXT_PUBLIC_USER_ID || 'demo'
const { credentials: fetchedPasswords, isLoading: loadingCredentials } = useCredentials(userId)
const { folders: fetchedFolders, isLoading: loadingFolders } = useFolders(userId)
const { tags, isLoading: loadingTags } = useTags()
const allTags = tags.map(t => t.name)

const [passwords, setPasswords] = useState<PasswordEntry[]>([])
const [folders, setFolders] = useState<Folder[]>([])
const [searchQuery, setSearchQuery] = useState("")
const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null)
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [showAddForm, setShowAddForm] = useState(false)
const [searchFilter, setSearchFilter] = useState<"all" | "login" | "website" | "tags">("all")

useEffect(() => {
setPasswords(fetchedPasswords)
}, [fetchedPasswords])

useEffect(() => {
setFolders(fetchedFolders)
}, [fetchedFolders])

if (loadingCredentials || loadingFolders || loadingTags) {
return <div>Chargement...</div>
}

// Function to get all child folder IDs recursively
const getAllChildFolderIds = (folderId: string): string[] => {
const directChildren = folders.filter((f) => f.parentId === folderId).map((f) => f.id)
Expand Down
52 changes: 31 additions & 21 deletions hooks/use-credentials.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
import { useState, useEffect } from 'react'
import { Credential } from '@/types/credential'
import { mockCredentials } from '@/lib/mocks/credentials'
import type { PasswordEntry } from '@/types/password-entry'

export function useCredentials() {
const [credentials, setCredentials] = useState<Credential[]>([])
function computeStrength(password: string): 'weak' | 'medium' | 'strong' {
if (password.length > 12 && /[A-Z]/.test(password) && /[^A-Za-z0-9]/.test(password)) return 'strong'
if (password.length >= 8) return 'medium'
return 'weak'
}

export function useCredentials(userId: string) {
const [credentials, setCredentials] = useState<PasswordEntry[]>([])
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
const fetchCredentials = async () => {
async function fetchCredentials() {
try {
// En développement, utiliser les données mockées
if (process.env.NODE_ENV === 'development') {
setCredentials(mockCredentials)
setIsLoading(false)
return
}

// En production, faire l'appel API
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/credentials`)
if (!response.ok) {
const url = new URL(`${process.env.NEXT_PUBLIC_API_URL}/users/credentials`)
url.searchParams.append('user_id', userId)
const res = await fetch(url.toString(), { cache: 'no-store' })
if (!res.ok) {
throw new Error('Erreur lors de la récupération des credentials')
}
const data = await response.json()
setCredentials(data)
} catch (error) {
console.error('Erreur:', error)
const data = await res.json()
const mapped = data.map((c: any) => ({
id: c.id,
title: c.title,
website: c.domain_name,
username: c.user_identifier,
password: c.password,
strength: computeStrength(c.password ?? ''),
lastUpdated: c.updated_at ?? c.created_at,
folderId: '',
tags: [],
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Hardcoded defaults break filtering and types in credentials

The useCredentials hook hardcodes folderId to an empty string and tags to an empty array when mapping API responses. This breaks folder and tag filtering, as credentials won't match expected criteria. Additionally, potentially null API fields (e.g., title, domain_name) are directly assigned to required string types in PasswordEntry, risking runtime errors.

Fix in Cursor Fix in Web

setCredentials(mapped)
} catch (err) {
console.error(err)
} finally {
setIsLoading(false)
}
}

fetchCredentials()
}, [])
}, [userId])

return { credentials, isLoading }
}
}
35 changes: 35 additions & 0 deletions hooks/use-folders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState, useEffect } from 'react'
import type { Folder } from '@/types/folder'

export function useFolders(userId: string) {
const [folders, setFolders] = useState<Folder[]>([])
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
async function fetchFolders() {
try {
const url = new URL(`${process.env.NEXT_PUBLIC_API_URL}/folders`)
url.searchParams.append('user_id', userId)
const res = await fetch(url.toString(), { cache: 'no-store' })
if (!res.ok) {
throw new Error('Erreur lors de la récupération des dossiers')
}
const data = await res.json()
const mapped = data.folders.map((f: any) => ({
id: f.Id,
name: f.Name,
parentId: f.ParentID,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Robustness gaps in Folder parent handling rails.

The useFolders hook assumes the API response contains a folders property and that f.ParentID is never undefined. If the API returns a different structure or undefined for ParentID, this can lead to runtime errors or type mismatches with the Folder type's parentId: string | null expectation.

Fix in Cursor Fix in Web

}))
setFolders(mapped)
} catch (err) {
console.error(err)
} finally {
setIsLoading(false)
}
}

fetchFolders()
}, [userId])

return { folders, isLoading }
}
37 changes: 37 additions & 0 deletions hooks/use-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useState, useEffect } from 'react'
import type { Tag } from '@/types/tag'

export function useTags() {
const [tags, setTags] = useState<Tag[]>([])
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
async function fetchTags() {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/tags`, { cache: 'no-store' })
if (!res.ok) {
throw new Error('Erreur lors de la récupération des tags')
}
const data = await res.json()
const mapped = data.map((t: any) => ({
id: t.id,
name: t.name,
color: t.color,
folderId: t.folder_id,
createdBy: t.created_by,
createdAt: t.created_at,
updatedAt: t.updated_at,
}))
setTags(mapped)
} catch (err) {
console.error(err)
} finally {
setIsLoading(false)
}
}

fetchTags()
}, [])

return { tags, isLoading }
}
6 changes: 6 additions & 0 deletions types/folder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Folder {
id: string
name: string
parentId: string | null
shared?: boolean
}
Loading