-
Notifications
You must be signed in to change notification settings - Fork 0
Replace mocks with API hooks #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/stats
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
|
|
@@ -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) | ||
|
|
||
| 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: [], | ||
| })) | ||
| setCredentials(mapped) | ||
| } catch (err) { | ||
| console.error(err) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| fetchCredentials() | ||
| }, []) | ||
| }, [userId]) | ||
|
|
||
| return { credentials, isLoading } | ||
| } | ||
| } | ||
| 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, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Robustness gaps in Folder parent handling rails.The |
||
| })) | ||
| setFolders(mapped) | ||
| } catch (err) { | ||
| console.error(err) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| fetchFolders() | ||
| }, [userId]) | ||
|
|
||
| return { folders, isLoading } | ||
| } | ||
| 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 } | ||
| } |
| 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 | ||
| } |
There was a problem hiding this comment.
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
useCredentialshook hardcodesfolderIdto an empty string andtagsto an empty array when mapping API responses. This breaks folder and tag filtering, as credentials won't match expected criteria. Additionally, potentiallynullAPI fields (e.g.,title,domain_name) are directly assigned to required string types inPasswordEntry, risking runtime errors.