2026-03-22 18:45:37 +01:00
|
|
|
|
import axios from 'axios'
|
|
|
|
|
|
import { useState } from 'react'
|
|
|
|
|
|
import { useBooks } from '../context/BooksContext.jsx'
|
|
|
|
|
|
|
|
|
|
|
|
const OPEN_LIBRARY_SEARCH = 'https://openlibrary.org/search.json'
|
|
|
|
|
|
|
|
|
|
|
|
function docToBookPayload(doc) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
title: doc.title || 'Sans titre',
|
|
|
|
|
|
author: Array.isArray(doc.author_name)
|
|
|
|
|
|
? doc.author_name[0]
|
|
|
|
|
|
: 'Auteur inconnu',
|
|
|
|
|
|
year:
|
|
|
|
|
|
typeof doc.first_publish_year === 'number'
|
|
|
|
|
|
? doc.first_publish_year
|
|
|
|
|
|
: new Date().getFullYear(),
|
|
|
|
|
|
genre: Array.isArray(doc.subject) ? doc.subject[0] : '',
|
2026-04-25 16:04:54 +02:00
|
|
|
|
price: 10,
|
2026-03-22 18:45:37 +01:00
|
|
|
|
read: false,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resultKey(doc, index) {
|
|
|
|
|
|
return doc.key || doc.cover_edition_key || `${doc.title || 'work'}-${index}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function SearchPage() {
|
|
|
|
|
|
const { postBook } = useBooks()
|
|
|
|
|
|
const [q, setQ] = useState('')
|
|
|
|
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
|
|
const [error, setError] = useState(null)
|
|
|
|
|
|
const [docs, setDocs] = useState([])
|
|
|
|
|
|
const [addedKeys, setAddedKeys] = useState(() => new Set())
|
|
|
|
|
|
const [lastQuery, setLastQuery] = useState('')
|
|
|
|
|
|
|
|
|
|
|
|
async function handleSearch(e) {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
const term = q.trim()
|
|
|
|
|
|
if (!term) return
|
|
|
|
|
|
setLoading(true)
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
setDocs([])
|
|
|
|
|
|
setAddedKeys(() => new Set())
|
|
|
|
|
|
setLastQuery(term)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await axios.get(OPEN_LIBRARY_SEARCH, {
|
|
|
|
|
|
params: { q: term, limit: 15 },
|
|
|
|
|
|
timeout: 15000,
|
|
|
|
|
|
})
|
|
|
|
|
|
setDocs(Array.isArray(data.docs) ? data.docs : [])
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
setError(
|
|
|
|
|
|
err.response?.data?.error ||
|
|
|
|
|
|
err.message ||
|
|
|
|
|
|
'Impossible de contacter Open Library.',
|
|
|
|
|
|
)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleAdd(doc, index) {
|
|
|
|
|
|
postBook(docToBookPayload(doc))
|
|
|
|
|
|
const k = resultKey(doc, index)
|
|
|
|
|
|
setAddedKeys((prev) => new Set(prev).add(k))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="search-page">
|
|
|
|
|
|
<h2 className="search-page-title">Recherche Open Library</h2>
|
|
|
|
|
|
<p className="search-page-lead">
|
|
|
|
|
|
Interroge l’API publique Open Library avec Axios, puis ajoute un
|
|
|
|
|
|
ouvrage à ton catalogue local.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<form className="search-form" onSubmit={handleSearch}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="search"
|
|
|
|
|
|
className="search search-wide"
|
|
|
|
|
|
placeholder="Titre, auteur, sujet…"
|
|
|
|
|
|
value={q}
|
|
|
|
|
|
onChange={(e) => setQ(e.target.value)}
|
|
|
|
|
|
aria-label="Requête Open Library"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button type="submit" className="btn primary" disabled={loading}>
|
|
|
|
|
|
{loading ? 'Recherche…' : 'Rechercher'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
|
|
{error ? (
|
|
|
|
|
|
<p className="search-error" role="alert">
|
|
|
|
|
|
{error}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
{!loading && docs.length === 0 && !error && !lastQuery ? (
|
|
|
|
|
|
<p className="search-hint">Saisis un mot-clé et lance la recherche.</p>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
{!loading && docs.length === 0 && !error && lastQuery ? (
|
|
|
|
|
|
<p className="search-hint">Aucun résultat pour « {lastQuery} ».</p>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
{docs.length > 0 ? (
|
|
|
|
|
|
<ul className="ol-results">
|
|
|
|
|
|
{docs.map((doc, i) => {
|
|
|
|
|
|
const key = resultKey(doc, i)
|
|
|
|
|
|
const year =
|
|
|
|
|
|
typeof doc.first_publish_year === 'number'
|
|
|
|
|
|
? doc.first_publish_year
|
|
|
|
|
|
: '—'
|
|
|
|
|
|
const authors = Array.isArray(doc.author_name)
|
|
|
|
|
|
? doc.author_name.slice(0, 2).join(', ')
|
|
|
|
|
|
: '—'
|
|
|
|
|
|
const justAdded = addedKeys.has(key)
|
|
|
|
|
|
return (
|
|
|
|
|
|
<li key={key} className="ol-result-card">
|
|
|
|
|
|
<h3 className="ol-result-title">
|
|
|
|
|
|
{doc.title || 'Sans titre'}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="ol-result-meta">
|
|
|
|
|
|
{authors} · {year}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="btn small primary"
|
|
|
|
|
|
onClick={() => handleAdd(doc, i)}
|
|
|
|
|
|
disabled={justAdded}
|
|
|
|
|
|
>
|
|
|
|
|
|
{justAdded ? 'Enregistré' : 'Enregistrer (POST /api/books)'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|