React Seiten mit React Bootstrap erstellen
In diesem Abschnitt erstellen wir verschiedene Seiten für unsere Anwendung mit React Bootstrap, damit wir ein konsistentes Design haben. Diese Seiten werden später durch das Routing verknüpft.
1. Ordnerstruktur für Seiten anlegen
Abschnitt betitelt „1. Ordnerstruktur für Seiten anlegen“Zunächst erstellen wir die notwendigen Ordner für unsere Seiten:
mkdir -p src/pages/HomePagemkdir -p src/pages/SearchPagemkdir -p src/pages/BookDetailsPagemkdir -p src/pages/FavoritesPagemkdir -p src/pages/NotFoundPage2. HomePage erstellen
Abschnitt betitelt „2. HomePage erstellen“Die Startseite zeigt eine Willkommensnachricht und empfohlene Bücher an:
import { useState, useEffect } from "react";import { Container, Row, Col, Spinner, Alert } from "react-bootstrap";import BookList from "../../components/BookList/BookList";import { searchGoogleBooks } from "../../services/googleBooksService";
function HomePage() { const [featuredBooks, setFeaturedBooks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { async function loadFeaturedBooks() { try { setLoading(true); const data = await searchGoogleBooks("bestseller fiction", 8); setFeaturedBooks(data); setError(null); } catch (err) { setError("Fehler beim Laden der Bücher."); console.error("Error loading featured books:", err); } finally { setLoading(false); } }
loadFeaturedBooks(); }, []);
return ( <Container> <Row className="my-4"> <Col> <h1>Willkommen im Bücher-Projekt</h1> <p className="lead"> Entdecke neue Bücher und verwalte deine Favoriten. </p> </Col> </Row>
<h2>Empfohlene Bücher</h2> {error && <Alert variant="danger">{error}</Alert>} {loading ? ( <div className="text-center my-5"> <Spinner animation="border" /> <p>Empfohlene Bücher werden geladen...</p> </div> ) : ( <BookList books={featuredBooks} /> )} </Container> );}
export default HomePage;3. SearchPage erstellen
Abschnitt betitelt „3. SearchPage erstellen“Die Suchseite ermöglicht es Benutzern, nach Büchern zu suchen:
import { useState } from "react";import { Container, Alert, Spinner } from "react-bootstrap";import SearchBar from "../../components/SearchBar/SearchBar";import BookList from "../../components/BookList/BookList";import { searchGoogleBooks } from "../../services/googleBooksService";
function SearchPage() { const [books, setBooks] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [searchPerformed, setSearchPerformed] = useState(false); const [searchTerm, setSearchTerm] = useState("");
const handleSearch = async (query) => { setSearchTerm(query);
if (!query.trim()) { return; }
try { setLoading(true); setError(null); const results = await searchGoogleBooks(query); setBooks(results); setSearchPerformed(true); } catch (err) { setError("Fehler bei der Suche."); console.error("Error searching books:", err); } finally { setLoading(false); } };
return ( <Container> <h1>Bücher suchen</h1> <p>Gib einen Suchbegriff ein, um nach Büchern zu suchen.</p>
<SearchBar onSearch={handleSearch} />
{error && <Alert variant="danger">{error}</Alert>}
{searchPerformed && !loading && ( <Alert variant="info"> <h5>Suchergebnisse für "{searchTerm}"</h5> <p className="mb-0"> {books.length} {books.length === 1 ? "Buch" : "Bücher"} gefunden </p> </Alert> )}
{loading ? ( <div className="text-center my-5"> <Spinner animation="border" /> <p>Bücher werden gesucht...</p> </div> ) : ( searchPerformed && <BookList books={books} /> )} </Container> );}
export default SearchPage;4. BookDetailsPage erstellen
Abschnitt betitelt „4. BookDetailsPage erstellen“Diese Seite zeigt detaillierte Informationen zu einem ausgewählten Buch:
import { useState, useEffect } from "react";import { useParams, Link } from "react-router-dom";import { Container, Row, Col, Button, Card, Spinner, Alert, Badge,} from "react-bootstrap";import { getGoogleBookById } from "../../services/googleBooksService";
function BookDetailsPage() { const { id } = useParams(); const [book, setBook] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { async function fetchBookDetails() { try { setLoading(true); const bookData = await getGoogleBookById(id); setBook(bookData); setError(null); } catch (err) { setError("Fehler beim Laden der Buchdetails."); console.error("Error fetching book details:", err); } finally { setLoading(false); } }
fetchBookDetails(); }, [id]);
if (loading) { return ( <Container className="text-center my-5"> <Spinner animation="border" /> <p>Buchdetails werden geladen...</p> </Container> ); }
if (error) { return <Alert variant="danger">{error}</Alert>; }
if (!book) { return <Alert variant="warning">Buch nicht gefunden.</Alert>; }
return ( <Container> <Link to="/search" className="btn btn-outline-primary mb-4"> ← Zurück zur Suche </Link>
<Card> <Card.Body> <Row> <Col md={4} className="text-center mb-4 mb-md-0"> <img src={ book.imageUrl || "https://via.placeholder.com/200x300?text=Kein+Bild" } alt={`Cover von ${book.title}`} className="img-fluid shadow-sm" style={{ maxHeight: "300px" }} /> <div className="mt-3"> <Button variant="outline-danger" className="w-100 mb-2"> Zu Favoriten hinzufügen 🤍 </Button>
{book.previewLink && ( <Button href={book.previewLink} target="_blank" variant="primary" className="w-100" > Vorschau bei Google Books </Button> )} </div> </Col>
<Col md={8}> <Card.Title as="h1">{book.title}</Card.Title> <Card.Subtitle className="mb-3 text-muted"> von {book.author} </Card.Subtitle>
{book.publishedDate && ( <p className="text-muted mb-2"> Veröffentlicht: {book.publishedDate} </p> )}
{book.pageCount && book.pageCount !== "Unbekannt" && ( <p className="text-muted mb-2">{book.pageCount} Seiten</p> )}
{book.categories && book.categories.length > 0 && ( <div className="mb-3"> <strong>Kategorien:</strong> <br /> {book.categories.map((category, index) => ( <Badge bg="secondary" className="me-1 mb-1" key={index}> {category} </Badge> ))} </div> )}
<Card.Text className="mt-4"> <h5>Beschreibung</h5> <div dangerouslySetInnerHTML={{ __html: book.description }} /> </Card.Text> </Col> </Row> </Card.Body> </Card> </Container> );}
export default BookDetailsPage;5. FavoritesPage erstellen
Abschnitt betitelt „5. FavoritesPage erstellen“Diese Seite zeigt die vom Benutzer favorisierten Bücher an:
import { Container, Alert, Button } from "react-bootstrap";import { Link } from "react-router-dom";import BookList from "../../components/BookList/BookList";
function FavoritesPage() { // Hier würden wir später echte Favoriten-Daten verwenden const favorites = [];
return ( <Container> <h1>Meine Favoriten</h1>
{favorites.length > 0 ? ( <> <Alert variant="success"> Sie haben {favorites.length}{" "} {favorites.length === 1 ? "Buch" : "Bücher"} in ihren Favoriten </Alert> <BookList books={favorites} /> </> ) : ( <Alert variant="info"> <Alert.Heading>Keine Favoriten</Alert.Heading> <p> Sie haben noch keine Bücher zu ihren Favoriten hinzugefügt. Füge Bücher hinzu, indem Sie auf das Herz-Symbol auf der Buchkarte klicken. </p> <hr /> <div className="d-flex justify-content-center"> <Button as={Link} to="/search" variant="primary"> Bücher suchen </Button> </div> </Alert> )} </Container> );}
export default FavoritesPage;6. NotFoundPage erstellen
Abschnitt betitelt „6. NotFoundPage erstellen“Diese Seite wird angezeigt, wenn ein Benutzer eine nicht existierende URL aufruft:
import { Container, Alert, Button } from "react-bootstrap";import { Link } from "react-router-dom";
function NotFoundPage() { return ( <Container className="text-center py-5"> <h1 className="display-1">404</h1> <Alert variant="danger"> <h2>Seite nicht gefunden</h2> <p>Die angeforderte Seite existiert leider nicht.</p> </Alert> <Button as={Link} to="/" variant="primary"> Zurück zur Startseite </Button> </Container> );}
export default NotFoundPage;Zusammenfassung
Abschnitt betitelt „Zusammenfassung“In diesem Abschnitt haben wir:
Fünf Seiten erstellt für unsere Anwendung:
- HomePage: Zeigt Willkommensnachricht und empfohlene Bücher
- SearchPage: Ermöglicht die Suche nach Büchern
- BookDetailsPage: Zeigt detaillierte Informationen zu einem Buch
- FavoritesPage: Zeigt Lieblingsbücher des Benutzers
- NotFoundPage: 404-Fehlerseite für nicht existierende URLs
React Bootstrap für ein konsistentes Design verwendet:
- Container für das Layout
- Cards für strukturierte Informationen
- Alerts für Meldungen und Fehler
- Spinners für Ladezustände
- Buttons für Aktionen
Alle Seiten sind mit React Bootstrap erstellt, ohne dass eigenes CSS erforderlich ist. Dies macht unsere Anwendung sofort responsive und optisch ansprechend.