diff --git a/components/folder-tree.tsx b/components/folder-tree.tsx index 3d87e19..616db34 100644 --- a/components/folder-tree.tsx +++ b/components/folder-tree.tsx @@ -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 @@ -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) diff --git a/components/password-form.tsx b/components/password-form.tsx index aef7f7b..371797f 100644 --- a/components/password-form.tsx +++ b/components/password-form.tsx @@ -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[] @@ -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) diff --git a/components/password-list.tsx b/components/password-list.tsx index b2875fd..9311fc1 100644 --- a/components/password-list.tsx +++ b/components/password-list.tsx @@ -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, @@ -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) { diff --git a/components/password-manager.tsx b/components/password-manager.tsx index c0809da..65090d9 100644 --- a/components/password-manager.tsx +++ b/components/password-manager.tsx @@ -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" @@ -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: "user@example.com", - 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(initialPasswords) - const [folders, setFolders] = useState(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([]) + const [folders, setFolders] = useState([]) const [searchQuery, setSearchQuery] = useState("") const [selectedFolderId, setSelectedFolderId] = useState(null) const [selectedTags, setSelectedTags] = useState([]) 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
Chargement...
+ } + // 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) diff --git a/hooks/use-credentials.ts b/hooks/use-credentials.ts index e0b1378..5fed4ab 100644 --- a/hooks/use-credentials.ts +++ b/hooks/use-credentials.ts @@ -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([]) +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([]) 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: [], + })) + setCredentials(mapped) + } catch (err) { + console.error(err) } finally { setIsLoading(false) } } fetchCredentials() - }, []) + }, [userId]) return { credentials, isLoading } -} \ No newline at end of file +} diff --git a/hooks/use-folders.ts b/hooks/use-folders.ts new file mode 100644 index 0000000..3108e64 --- /dev/null +++ b/hooks/use-folders.ts @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react' +import type { Folder } from '@/types/folder' + +export function useFolders(userId: string) { + const [folders, setFolders] = useState([]) + 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, + })) + setFolders(mapped) + } catch (err) { + console.error(err) + } finally { + setIsLoading(false) + } + } + + fetchFolders() + }, [userId]) + + return { folders, isLoading } +} diff --git a/hooks/use-tags.ts b/hooks/use-tags.ts new file mode 100644 index 0000000..caf9a19 --- /dev/null +++ b/hooks/use-tags.ts @@ -0,0 +1,37 @@ +import { useState, useEffect } from 'react' +import type { Tag } from '@/types/tag' + +export function useTags() { + const [tags, setTags] = useState([]) + 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 } +} diff --git a/types/folder.ts b/types/folder.ts new file mode 100644 index 0000000..28b5222 --- /dev/null +++ b/types/folder.ts @@ -0,0 +1,6 @@ +export interface Folder { + id: string + name: string + parentId: string | null + shared?: boolean +} diff --git a/types/password-entry.ts b/types/password-entry.ts new file mode 100644 index 0000000..7625fe8 --- /dev/null +++ b/types/password-entry.ts @@ -0,0 +1,17 @@ +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 +} diff --git a/types/tag.ts b/types/tag.ts new file mode 100644 index 0000000..1fead4d --- /dev/null +++ b/types/tag.ts @@ -0,0 +1,9 @@ +export interface Tag { + id: string + name: string + color: string + folderId: string + createdBy: string + createdAt: string + updatedAt: string +}