Skip to content

JoshuaRamc/web-dev-final-frontend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 

Repository files navigation

web-dev-final-frontend

Repository for frontend final project web dev.

EXTRA CREDIT:

  • Extra Credit of completing a project that is not the employee task is fulfilled.
  • Extra Credit having at least one child component, within a parent component, within a parent component that you create.
  • Within the Search component, we have a BookCard component that is shown as search results, and within the BookCard component, there is the BookModel component that is displayed by clicking on a search result and a pop-up showing up.

REQUIREMENTS FOR FRONTEND CHECKLIST:

  • UI (React)
    • Navbar/sidebar component:
      import { useRef } from "react";
      import LibraryBooksIcon from "@mui/icons-material/LibraryBooks";
      import MenuBookIcon from "@mui/icons-material/MenuBook";
      import ManageSearchIcon from "@mui/icons-material/ManageSearch";
      import Button from "@mui/material/Button";
      import { Link } from "react-router-dom";
      import { FaBars, FaTimes } from "react-icons/fa";
      import "./Navbar.css";
      import GitHubIcon from "@mui/icons-material/GitHub";
      function Navbar() {
      const navRef = useRef();
      const showNavbar = () => {
      navRef.current.classList.toggle("responsive_nav");
      };
      return (
      <header>
      <div>
      <Link to="/">
      <MenuBookIcon fontSize="large" sx={{ color: "black" }} />
      </Link>
      </div>
      <div>
      <nav ref={navRef}>
      <Link to="/forums">
      <Button
      variant="text"
      sx={{ color: "black" }}
      startIcon={<LibraryBooksIcon />}
      >
      Forums
      </Button>
      </Link>
      <Link to="/search">
      <Button
      variant="text"
      sx={{ color: "black" }}
      startIcon={<ManageSearchIcon />}
      >
      Search
      </Button>
      </Link>
      <button className="nav-btn nav-close-btn" onClick={showNavbar}>
      <FaTimes />
      </button>
      </nav>
      <button className="nav-btn" onClick={showNavbar}>
      <FaBars />
      </button>
      </div>
      <div>
      <Link
      to="https://github.com/JoshuaRamc/web-dev-final-frontend"
      target="_blank"
      >
      <GitHubIcon
      fontSize="large"
      style={{ color: "black", marginRight: "25px" }}
      ></GitHubIcon>
      </Link>
      </div>
      </header>
      );
      }
      export default Navbar;
    • 3 or more additional components (only listing three random because we have over 8 components):
      • import React, { useState } from "react";
        import BookModel from "../Search/BookModel";
        const BookCard = ({ book }) => {
        const [show, setShow] = useState(false);
        const [bookItem, setItem] = useState();
        console.log(book);
        return (
        <>
        {book.map((item) => {
        let thumbnail =
        item.volumeInfo.imageLinks &&
        item.volumeInfo.imageLinks.smallThumbnail;
        let amount =
        item.saleInfo.listPrice && item.saleInfo.listPrice.amount;
        if (thumbnail !== undefined && amount !== undefined) {
        return (
        <>
        <div
        className='card'
        onClick={() => {
        setShow(true);
        setItem(item);
        }}
        >
        <img src={thumbnail} alt='' />
        <div className='bottom'>
        <h3 className='title'>
        {item.volumeInfo.title}
        </h3>
        <p className='amount'>&#8377;{amount}</p>
        </div>
        </div>
        <BookModel
        show={show}
        item={bookItem}
        onClose={() => setShow(false)}
        />
        </>
        );
        }
        })}
        </>
        );
        };
        export default BookCard;
      • const BookList = (props) => {
        const { books, setBooks } = useContext(BooksContext);
        const navigate = useNavigate();
        const fetchData = async () => {
        try {
        const res = await BookLookup.get("/findall");
        setBooks(res.data);
        } catch (e) {
        console.log(e);
        }
        };
        const handleBookSelect = (isbn) => {
        navigate(`/book/${isbn}`);
        };
        useEffect(() => {
        fetchData();
        // eslint-disable-next-line
        }, []);
        const handleDelete = async (isbn) => {
        try {
        const res = await BookLookup.delete(`/${isbn}`);
        console.log(res);
        window.location.reload(false);
        } catch (e) {
        console.log(e);
        }
        };
        return (
        <>
        <div className="list-group">
        <table className="table table-hover table-light">
        <thead>
        <tr style={{ backgroundColor: "pink" }}>
        <th scope="col">Title</th>
        <th scope="col">Author(s)</th>
        <th scope="col">Publication Date</th>
        <th scope="col">Page Count</th>
        <th scope="col">ISBN</th>
        <th scope="col">Conversation</th>
        <th scope="col">Remove Forum</th>
        </tr>
        </thead>
        <tbody>
        {books.map((book) => {
        return (
        <tr key={book.isbn}>
        <td>{book.title}</td>
        <td>{book.authors}</td>
        <td>{book.publication_date}</td>
        <td>{book.page_count}</td>
        <td>{book.isbn}</td>
        <td>
        <button
        className="btn btn-warning"
        style={{ backgroundColor: "PaleGreen" }}
        onClick={() => handleBookSelect(book.isbn)}
        >
        Conversation Page
        </button>
        </td>
        <td>
        {" "}
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<RemoveCircleOutlineIcon />}
        onClick={() => handleDelete(book.isbn)}
        ></Button>
        </td>
        </tr>
        );
        })}
        </tbody>
        </table>
        </div>
        </>
        );
        };
        export default BookList;
      • const AddThread = () => {
        const { isbn } = useParams();
        const [name, setName] = useState("");
        const [comment, setComment] = useState();
        const [rating, setRating] = useState("1");
        const handleThreadAdd = async (e) => {
        e.preventDefault();
        try {
        const newThread = await BookLookup.post(`/thread/create`, {
        isbn_num: isbn,
        name,
        review: comment,
        rating,
        });
        console.log(newThread);
        window.location.reload(false);
        } catch (err) {
        console.log(err);
        }
        };
        return (
        <div className="mb-2" style={{ marginTop: "1rem" }}>
        <form action="">
        <div className="form-row">
        <div className="form-group col-8">
        <label htmlFor="name">Name</label>
        <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        id="name"
        placeholder="name"
        type="text"
        className="form-control"
        />
        </div>
        <div className="form-group col-4">
        <label htmlFor="rating">Rating</label>
        <select
        value={rating}
        onChange={(e) => setRating(e.target.value)}
        id="rating"
        className="custom-select"
        >
        <option disabled>Rating</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
        <option value="4">4</option>
        <option value="5">5</option>
        </select>
        </div>
        </div>
        <div className="form-group">
        <label htmlFor="Comment">Comment</label>
        <div style={{ display: "flex" }}>
        <textarea
        id="Comment"
        className="form-control"
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        style={{ maxWidth: "75%" }}
        ></textarea>
        <Button
        type="submit"
        onClick={handleThreadAdd}
        variant="contained"
        style={{
        backgroundColor: "#7B68EE",
        color: "white",
        marginLeft: "10px",
        }}
        >
        Add Thread
        </Button>
        </div>
        </div>
        </form>
        </div>
        );
        };
        export default AddThread;
    • 1 or more components components should display data representing a single instance from a model and Clicking on one of these components should show additional information related to that instance
      • const BookList = (props) => {
        const { books, setBooks } = useContext(BooksContext);
        const navigate = useNavigate();
        const fetchData = async () => {
        try {
        const res = await BookLookup.get("/findall");
        setBooks(res.data);
        } catch (e) {
        console.log(e);
        }
        };
        const handleBookSelect = (isbn) => {
        navigate(`/book/${isbn}`);
        };
        useEffect(() => {
        fetchData();
        // eslint-disable-next-line
        }, []);
        const handleDelete = async (isbn) => {
        try {
        const res = await BookLookup.delete(`/${isbn}`);
        console.log(res);
        window.location.reload(false);
        } catch (e) {
        console.log(e);
        }
        };
        return (
        <>
        <div className="list-group">
        <table className="table table-hover table-light">
        <thead>
        <tr style={{ backgroundColor: "pink" }}>
        <th scope="col">Title</th>
        <th scope="col">Author(s)</th>
        <th scope="col">Publication Date</th>
        <th scope="col">Page Count</th>
        <th scope="col">ISBN</th>
        <th scope="col">Conversation</th>
        <th scope="col">Remove Forum</th>
        </tr>
        </thead>
        <tbody>
        {books.map((book) => {
        return (
        <tr key={book.isbn}>
        <td>{book.title}</td>
        <td>{book.authors}</td>
        <td>{book.publication_date}</td>
        <td>{book.page_count}</td>
        <td>{book.isbn}</td>
        <td>
        <button
        className="btn btn-warning"
        style={{ backgroundColor: "PaleGreen" }}
        onClick={() => handleBookSelect(book.isbn)}
        >
        Conversation Page
        </button>
        </td>
        <td>
        {" "}
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<RemoveCircleOutlineIcon />}
        onClick={() => handleDelete(book.isbn)}
        ></Button>
        </td>
        </tr>
        );
        })}
        </tbody>
        </table>
        </div>
        </>
        );
        };
        export default BookList;
    • 1 or more components should display data based on store state
      • const isShown = useSelector((state) => state.index);
        const handleClose = async () => {
        setShow(false);
        dispatch(toggleShown(false));
        };
        return (
        <>
        {book.map((item) => {
        let thumbnail =
        item.volumeInfo.imageLinks &&
        item.volumeInfo.imageLinks.smallThumbnail;
        if (thumbnail !== undefined) {
        return (
        <>
        <div
        className="BookCard"
        onClick={() => {
        dispatch(toggleShown(true));
        setShow(true);
        setItem(item);
        }}
        >
        <img src={thumbnail} alt="" />
        <div className="bottom">
        <h3 className="title">{item.volumeInfo.title}</h3>
        </div>
        </div>
        <BookModel
        show={show}
        item={bookItem}
        onClose={() => handleClose()}
        />
        </>
        );
        } else {
        return null;
        }
        })}
        </>
        );
        };
        export default BookCard;
    • 1 or more components should take text-based user input
      • Text based user input component:
        const AddThread = () => {
        const { isbn } = useParams();
        const [name, setName] = useState("");
        const [comment, setComment] = useState();
        const [rating, setRating] = useState("1");
        const handleThreadAdd = async (e) => {
        e.preventDefault();
        try {
        const newThread = await BookLookup.post(`/thread/create`, {
        isbn_num: isbn,
        name,
        review: comment,
        rating,
        });
        console.log(newThread);
        window.location.reload(false);
        } catch (err) {
        console.log(err);
        }
        };
        return (
        <div className="mb-2" style={{ marginTop: "1rem" }}>
        <form action="">
        <div className="form-row">
        <div className="form-group col-8">
        <label htmlFor="name">Name</label>
        <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        id="name"
        placeholder="name"
        type="text"
        className="form-control"
        />
        </div>
        <div className="form-group col-4">
        <label htmlFor="rating">Rating</label>
        <select
        value={rating}
        onChange={(e) => setRating(e.target.value)}
        id="rating"
        className="custom-select"
        >
        <option disabled>Rating</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
        <option value="4">4</option>
        <option value="5">5</option>
        </select>
        </div>
        </div>
        <div className="form-group">
        <label htmlFor="Comment">Comment</label>
        <div style={{ display: "flex" }}>
        <textarea
        id="Comment"
        className="form-control"
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        style={{ maxWidth: "75%" }}
        ></textarea>
        <Button
        type="submit"
        onClick={handleThreadAdd}
        variant="contained"
        style={{
        backgroundColor: "#7B68EE",
        color: "white",
        marginLeft: "10px",
        }}
        >
        Add Thread
        </Button>
        </div>
        </div>
        </form>
        </div>
        );
        };
        export default AddThread;
    • Components should enable to user to perform CRUD operations on the backend models (listed snippets below are just a few examples and are not all instances of CRUD OPS)
      • For CREATE:
        const BookModel = ({ show, item, onClose }) => {
        const navigate = useNavigate();
        if (!show) {
        return null;
        }
        let thumbnail =
        item.volumeInfo.imageLinks && item.volumeInfo.imageLinks.smallThumbnail;
        const handleBookAdd = async (
        isbnNum,
        bookTitle,
        bookAuthors,
        bookPublication,
        bookDescription,
        bookPages,
        bookGenre
        ) => {
        try {
        const newBook = await BookLookup.post(`/createbook`, {
        isbn: isbnNum,
        title: bookTitle,
        authors: bookAuthors,
        publication_date: bookPublication,
        description: bookDescription,
        page_count: bookPages,
        genre: bookGenre,
        });
        console.log(newBook);
        navigate(`/forums`);
        } catch (err) {
        console.log(err);
        }
        };
        return (
        <>
        <div className="overlay">
        <div className="overlay-inner">
        <button className="close" onClick={onClose}>
        X
        </button>
        <div className="inner-box">
        <img src={thumbnail} alt="" />
        <div className="info">
        <h1>{item.volumeInfo.title}</h1>
        <h3>{item.volumeInfo.authors}</h3>
        <h4>
        <div style={{ display: "flex", flexDirection: "column" }}>
        {item.volumeInfo.categories[0]}
        <span>{item.volumeInfo.publishedDate}</span>
        <span>
        {item.volumeInfo.industryIdentifiers[0].identifier}
        </span>
        </div>
        </h4>
        <br />
        <div style={{ display: "flex" }}>
        <a href={item.volumeInfo.previewLink}>
        <button>More</button>
        </a>
        <Button
        style={{ marginLeft: "5px", backgroundColor: "OliveDrab" }}
        variant="contained"
        onClick={() =>
        handleBookAdd(
        item.volumeInfo.industryIdentifiers[0].identifier,
        item.volumeInfo.title,
        item.volumeInfo.authors[0],
        item.volumeInfo.publishedDate,
        item.volumeInfo.description,
        item.volumeInfo.pageCount,
        item.volumeInfo.categories[0]
        )
        }
        >
        Thread!
        </Button>
        </div>
        </div>
        </div>
        <h4 className="description">{item.volumeInfo.description}</h4>
        </div>
        </div>
        </>
        );
        };
        export default BookModel;
      • For READ:
        const BookList = (props) => {
        const { books, setBooks } = useContext(BooksContext);
        const navigate = useNavigate();
        const fetchData = async () => {
        try {
        const res = await BookLookup.get("/findall");
        setBooks(res.data);
        } catch (e) {
        console.log(e);
        }
        };
        const handleBookSelect = (isbn) => {
        navigate(`/book/${isbn}`);
        };
        useEffect(() => {
        fetchData();
        // eslint-disable-next-line
        }, []);
        const handleDelete = async (isbn) => {
        try {
        const res = await BookLookup.delete(`/${isbn}`);
        console.log(res);
        window.location.reload(false);
        } catch (e) {
        console.log(e);
        }
        };
        return (
        <>
        <div className="list-group">
        <table className="table table-hover table-light">
        <thead>
        <tr style={{ backgroundColor: "pink" }}>
        <th scope="col">Title</th>
        <th scope="col">Author(s)</th>
        <th scope="col">Publication Date</th>
        <th scope="col">Page Count</th>
        <th scope="col">ISBN</th>
        <th scope="col">Conversation</th>
        <th scope="col">Remove Forum</th>
        </tr>
        </thead>
        <tbody>
        {books.map((book) => {
        return (
        <tr key={book.isbn}>
        <td>{book.title}</td>
        <td>{book.authors}</td>
        <td>{book.publication_date}</td>
        <td>{book.page_count}</td>
        <td>{book.isbn}</td>
        <td>
        <button
        className="btn btn-warning"
        style={{ backgroundColor: "PaleGreen" }}
        onClick={() => handleBookSelect(book.isbn)}
        >
        Conversation Page
        </button>
        </td>
        <td>
        {" "}
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<RemoveCircleOutlineIcon />}
        onClick={() => handleDelete(book.isbn)}
        ></Button>
        </td>
        </tr>
        );
        })}
        </tbody>
        </table>
        </div>
        </>
        );
        };
        export default BookList;
      • For UPDATE:
        function Edit() {
        const { id } = useParams();
        const handleEdit = async (e) => {
        try {
        const res = await BookLookup.put(`/thread/${id}`, {
        review: comment,
        rating: rating,
        });
        console.log(res);
        } catch (e) {
        console.log(e);
        }
        };
        const [rating, setRating] = useState("1");
        const [comment, setComment] = useState("");
        return (
        <div className="mb-2" style={{ marginTop: "1rem" }}>
        <form action="">
        <div className="form-row">
        <div className="form-group col-4">
        <label htmlFor="rating">Rating</label>
        <select
        value={rating}
        onChange={(e) => setRating(e.target.value)}
        id="rating"
        className="custom-select"
        >
        <option disabled>Rating</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
        <option value="4">4</option>
        <option value="5">5</option>
        </select>
        </div>
        </div>
        <div className="form-group">
        <label htmlFor="Comment">Comment</label>
        <div style={{ display: "flex" }}>
        <textarea
        id="Comment"
        className="form-control"
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        style={{ maxWidth: "75%" }}
        ></textarea>
        <Button
        type="submit"
        // onClick={handleThreadAdd}
        variant="contained"
        style={{
        backgroundColor: "#7B68EE",
        color: "white",
        marginLeft: "10px",
        }}
        onClick={() => handleEdit()}
        >
        Confirm Edit
        </Button>
        </div>
        </div>
        </form>
        </div>
        );
        }
        export default Edit;
      • For DELETE:
        const handleDelete = async (id) => {
        try {
        const res = await BookLookup.delete(`/thread/${id}`);
        setThreads(
        threads.filter((thread) => {
        return thread.id !== id;
        })
        );
        console.log(res);
        } catch (e) {
        console.log(e);
        }
        };
        const handleEdit = async (id) => {
        try {
        navigate(`/edit/${id}`);
        } catch (e) {
        console.log(e);
        }
        };
        useEffect(() => {
        fetchData();
        console.log(selectedBook);
        console.log(threads);
        // eslint-disable-next-line
        }, []);
        return (
        <div>
        <div
        style={{ display: "flex", justifyContent: "center", marginTop: "10px" }}
        >
        <h1>{selectedBook.title}</h1>
        </div>
        <div
        style={{
        display: "flex",
        justifyContent: "center",
        marginTop: "10px",
        fontSize: 14,
        textAlign: "center",
        }}
        >
        "{selectedBook.description}"
        </div>
        <AddThread />
        {selectedBook && (
        <>
        <div className="mt-3">
        <div
        className="row row-cols-3 mb-2"
        style={{ display: "flex", justifyContent: "space-evenly" }}
        >
        {threads.map((thread) => {
        return (
        <div
        className="card text-white mb-3 mr-4"
        style={{
        maxWidth: "30%",
        minWidth: "30%",
        backgroundColor: "MediumPurple",
        }}
        >
        <div className="card-header d-flex justify-content-between">
        <span>{thread.name}</span>
        <span>{thread.rating}/5</span>
        </div>
        <div className="card-body">
        <p className="card-text">{thread.review}</p>
        </div>
        <div
        style={{
        display: "flex",
        justifyContent: "space-between",
        }}
        >
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<EditIcon />}
        onClick={() => handleEdit(thread.id)}
        ></Button>
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<HighlightOffIcon />}
        onClick={() => handleDelete(thread.id)}
  • Client-Side Routing (React Router)
    • Create 2 or more routes that display different componetns based on the URL that are accessible from the navbar/topbar
      • Route Creation:
        import { Route, Routes } from "react-router-dom";
        import SearchLibrary from "./components/Search/SearchLibrary";
        import Navbar from "./components/Navbar/Navbar";
        import Forum from "./pages/Forum/Forum";
        import Conversation from "./pages/Conversation/Conversation";
        import { BooksContextProvider } from "./apis/BooksContext";
        import Home from "./pages/Home/Home";
        import Edit from "./pages/Edit/Edit";
        function App() {
        return (
        <BooksContextProvider>
        <Navbar />
        <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/forums" element={<Forum />} />
        <Route path="/search" element={<SearchLibrary />} />
        <Route path="/book/:isbn" element={<Conversation />} />
        <Route path="/edit/:id" element={<Edit />} />
        </Routes>
        </BooksContextProvider>
        );
        }
        export default App;
      • Navbar Accessiblity:
        return (
        <header>
        <div>
        <Link to="/">
        <MenuBookIcon fontSize="large" sx={{ color: "black" }} />
        </Link>
        </div>
        <div>
        <nav ref={navRef}>
        <Link to="/forums">
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<LibraryBooksIcon />}
        >
        Forums
        </Button>
        </Link>
        <Link to="/search">
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<ManageSearchIcon />}
        >
        Search
        </Button>
        </Link>
        <button className="nav-btn nav-close-btn" onClick={showNavbar}>
        <FaTimes />
        </button>
        </nav>
        <button className="nav-btn" onClick={showNavbar}>
        <FaBars />
        </button>
        </div>
        <div>
        <Link
        to="https://github.com/JoshuaRamc/web-dev-final-frontend"
        target="_blank"
        >
        <GitHubIcon
        fontSize="large"
        style={{ color: "black", marginRight: "25px" }}
        ></GitHubIcon>
        </Link>
        </div>
        </header>
        );
        }
        export default Navbar;
    • Use dynamic segments to display appropriate info based on the segment info (Examples contain additional embedded dynamic segments)
      • Navigate to dynamic segment:
        const handleBookSelect = (isbn) => {
        navigate(`/book/${isbn}`);
        };
        useEffect(() => {
        fetchData();
        // eslint-disable-next-line
        }, []);
        const handleDelete = async (isbn) => {
        try {
        const res = await BookLookup.delete(`/${isbn}`);
        console.log(res);
        window.location.reload(false);
        } catch (e) {
        console.log(e);
        }
        };
        return (
        <>
        <div className="list-group">
        <table className="table table-hover table-light">
        <thead>
        <tr style={{ backgroundColor: "pink" }}>
        <th scope="col">Title</th>
        <th scope="col">Author(s)</th>
        <th scope="col">Publication Date</th>
        <th scope="col">Page Count</th>
        <th scope="col">ISBN</th>
        <th scope="col">Conversation</th>
        <th scope="col">Remove Forum</th>
        </tr>
        </thead>
        <tbody>
        {books.map((book) => {
        return (
        <tr key={book.isbn}>
        <td>{book.title}</td>
        <td>{book.authors}</td>
        <td>{book.publication_date}</td>
        <td>{book.page_count}</td>
        <td>{book.isbn}</td>
        <td>
        <button
        className="btn btn-warning"
        style={{ backgroundColor: "PaleGreen" }}
        onClick={() => handleBookSelect(book.isbn)}
        >
        Conversation Page
        </button>
      • Rendered component when navigating to dynamic segment:
        function Conversation() {
        const { isbn } = useParams();
        const { selectedBook, setSelectedBook } = useContext(BooksContext);
        const { threads, setThreads } = useContext(BooksContext);
        const navigate = useNavigate();
        const fetchData = async () => {
        try {
        const res = await BookLookup.get(`/${isbn}`, {
        params: {
        limit: 1,
        },
        });
        const threadRes = await BookLookup.get(`/thread/isbn/${isbn}`);
        setSelectedBook(res.data);
        setThreads(threadRes.data);
        } catch (e) {
        console.log(e);
        }
        };
        const handleDelete = async (id) => {
        try {
        const res = await BookLookup.delete(`/thread/${id}`);
        setThreads(
        threads.filter((thread) => {
        return thread.id !== id;
        })
        );
        console.log(res);
        } catch (e) {
        console.log(e);
        }
        };
        const handleEdit = async (id) => {
        try {
        navigate(`/edit/${id}`);
        } catch (e) {
        console.log(e);
        }
        };
        useEffect(() => {
        fetchData();
        console.log(selectedBook);
        console.log(threads);
        // eslint-disable-next-line
        }, []);
        return (
        <div>
        <div
        style={{ display: "flex", justifyContent: "center", marginTop: "10px" }}
        >
        <h1>{selectedBook.title}</h1>
        </div>
        <div
        style={{
        display: "flex",
        justifyContent: "center",
        marginTop: "10px",
        fontSize: 14,
        textAlign: "center",
        }}
        >
        "{selectedBook.description}"
        </div>
        <AddThread />
        {selectedBook && (
        <>
        <div className="mt-3">
        <div
        className="row row-cols-3 mb-2"
        style={{ display: "flex", justifyContent: "space-evenly" }}
        >
        {threads.map((thread) => {
        return (
        <div
        className="card text-white mb-3 mr-4"
        style={{
        maxWidth: "30%",
        minWidth: "30%",
        backgroundColor: "MediumPurple",
        }}
        >
        <div className="card-header d-flex justify-content-between">
        <span>{thread.name}</span>
        <span>{thread.rating}/5</span>
        </div>
        <div className="card-body">
        <p className="card-text">{thread.review}</p>
        </div>
        <div
        style={{
        display: "flex",
        justifyContent: "space-between",
        }}
        >
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<EditIcon />}
        onClick={() => handleEdit(thread.id)}
        ></Button>
        <Button
        variant="text"
        sx={{ color: "black" }}
        startIcon={<HighlightOffIcon />}
        onClick={() => handleDelete(thread.id)}
        ></Button>
        </div>
        </div>
        );
        })}
        </div>
        </div>
        </>
        )}
        </div>
        );
        }
        export default Conversation;
  • Stae Management (Redux)
    • Create a store and a reducer to handle incoming actions:
    • Create 1 or more action creators to create actions based on input:
    • Update store using dispatch and actions
      • Update store:
        const isShown = useSelector((state) => state.index);
        const handleClose = async () => {
        setShow(false);
        dispatch(toggleShown(false));
        };
        return (
        <>
        {book.map((item) => {
        let thumbnail =
        item.volumeInfo.imageLinks &&
        item.volumeInfo.imageLinks.smallThumbnail;
        if (thumbnail !== undefined) {
        return (
        <>
        <div
        className="BookCard"
        onClick={() => {
        dispatch(toggleShown(true));
        setShow(true);
        setItem(item);
        }}
        >
        <img src={thumbnail} alt="" />
        <div className="bottom">
        <h3 className="title">{item.volumeInfo.title}</h3>
        </div>
        </div>
        <BookModel
        show={show}
        item={bookItem}
        onClose={() => handleClose()}
    • Reflect updates to the state in the frontend UI:
      • Reflected updates show the card:
        const BookModel = ({ show, item, onClose }) => {
        const navigate = useNavigate();
        if (!show) {
        return null;
        }
        let thumbnail =
        item.volumeInfo.imageLinks && item.volumeInfo.imageLinks.smallThumbnail;
        const handleBookAdd = async (
        isbnNum,
        bookTitle,
        bookAuthors,
        bookPublication,
        bookDescription,
        bookPages,
        bookGenre
        ) => {
        try {
        const newBook = await BookLookup.post(`/createbook`, {
        isbn: isbnNum,
        title: bookTitle,
        authors: bookAuthors,
        publication_date: bookPublication,
        description: bookDescription,
        page_count: bookPages,
        genre: bookGenre,
        });
        console.log(newBook);
        navigate(`/forums`);
        } catch (err) {
        console.log(err);
        }
        };
  • API CALLS:
    • Backend: Using backend routes, should be able to perform CRUD on database models ( The following examples are not the only examples in code.)
      • CREATE:
        const navigate = useNavigate();
        if (!show) {
        return null;
        }
        let thumbnail =
        item.volumeInfo.imageLinks && item.volumeInfo.imageLinks.smallThumbnail;
        const handleBookAdd = async (
        isbnNum,
        bookTitle,
        bookAuthors,
        bookPublication,
        bookDescription,
        bookPages,
        bookGenre
        ) => {
        try {
        const newBook = await BookLookup.post(`/createbook`, {
        isbn: isbnNum,
        title: bookTitle,
        authors: bookAuthors,
        publication_date: bookPublication,
        description: bookDescription,
        page_count: bookPages,
        genre: bookGenre,
        });
        console.log(newBook);
        navigate(`/forums`);
        } catch (err) {
        console.log(err);
        }
        };
      • READ:
        const BookList = (props) => {
        const { books, setBooks } = useContext(BooksContext);
        const navigate = useNavigate();
        const fetchData = async () => {
        try {
        const res = await BookLookup.get("/findall");
        setBooks(res.data);
        } catch (e) {
        console.log(e);
        }
        };
        const handleBookSelect = (isbn) => {
        navigate(`/book/${isbn}`);
        };
        useEffect(() => {
        fetchData();
        // eslint-disable-next-line
        }, []);
      • UPDATE:
        function Edit() {
        const { id } = useParams();
        const handleEdit = async (e) => {
        try {
        const res = await BookLookup.put(`/thread/${id}`, {
        review: comment,
        rating: rating,
        });
        console.log(res);
        } catch (e) {
        console.log(e);
        }
        };
        const [rating, setRating] = useState("1");
        const [comment, setComment] = useState("");
        return (
        <div className="mb-2" style={{ marginTop: "1rem" }}>
        <form action="">
        <div className="form-row">
        <div className="form-group col-4">
        <label htmlFor="rating">Rating</label>
        <select
        value={rating}
        onChange={(e) => setRating(e.target.value)}
        id="rating"
        className="custom-select"
        >
        <option disabled>Rating</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
        <option value="4">4</option>
        <option value="5">5</option>
        </select>
        </div>
        </div>
        <div className="form-group">
        <label htmlFor="Comment">Comment</label>
        <div style={{ display: "flex" }}>
        <textarea
        id="Comment"
        className="form-control"
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        style={{ maxWidth: "75%" }}
        ></textarea>
        <Button
        type="submit"
        // onClick={handleThreadAdd}
        variant="contained"
        style={{
        backgroundColor: "#7B68EE",
        color: "white",
        marginLeft: "10px",
        }}
        onClick={() => handleEdit()}
        >
        Confirm Edit
        </Button>
        </div>
        </div>
        </form>
        </div>
        );
        }
        export default Edit;
      • DELETE:
        const handleDelete = async (id) => {
        try {
        const res = await BookLookup.delete(`/thread/${id}`);
        setThreads(
        threads.filter((thread) => {
        return thread.id !== id;
        })
        );
        console.log(res);
        } catch (e) {
        console.log(e);
        }
        };
        const handleEdit = async (id) => {
        try {
        navigate(`/edit/${id}`);
        } catch (e) {
        console.log(e);
        }
        };
        useEffect(() => {
        fetchData();
        console.log(selectedBook);
        console.log(threads);
        // eslint-disable-next-line
        }, []);
    • External: Should make 2 or more External API Calls
      • External API Calls:
        const SearchLibrary = () => {
        const [search, setSearch] = useState("");
        const [bookData, setData] = useState([]);
        const searchBook = (evt) => {
        axios
        .get(
        "https://www.googleapis.com/books/v1/volumes?q=" +
        search +
        "&key=AIzaSyAGX87cdFiNL82Y2_Ez1zuDXnrm-y53Cto" +
        "&maxResults=40"
        )
        .then((res) => setData(res.data.items))
        .catch((err) => console.log(err));
        };
        return (
        <>
        <div className="header-search">
        <div className="row1">
        <h1>Book Finder</h1>
        </div>
        <div className="row2">
        <div className="search">
        <input
        type="text"
        placeholder="Enter Your Book Name"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        />
        <Button
        style={{ background: "#7B68EE" }}
        onClick={searchBook}
        variant="contained"
        >
        Search Google Books
        </Button>
        </div>
        <img src="./images/bg2.png" alt="" />
        </div>
        </div>
        <div className="container">{<Card book={bookData} />}</div>
        </>
        );
        };
        export default SearchLibrary;
      • External API Calls:
        const BookModel = ({ show, item, onClose }) => {
        const navigate = useNavigate();
        if (!show) {
        return null;
        }
        let thumbnail =
        item.volumeInfo.imageLinks && item.volumeInfo.imageLinks.smallThumbnail;
        const handleBookAdd = async (
        isbnNum,
        bookTitle,
        bookAuthors,
        bookPublication,
        bookDescription,
        bookPages,
        bookGenre
        ) => {
        try {
        const newBook = await BookLookup.post(`/createbook`, {
        isbn: isbnNum,
        title: bookTitle,
        authors: bookAuthors,
        publication_date: bookPublication,
        description: bookDescription,
        page_count: bookPages,
        genre: bookGenre,
        });
        console.log(newBook);
        navigate(`/forums`);
        } catch (err) {
        console.log(err);
        }
        };
        return (
        <>
        <div className="overlay">
        <div className="overlay-inner">
        <button className="close" onClick={onClose}>
        X
        </button>
        <div className="inner-box">
        <img src={thumbnail} alt="" />
        <div className="info">
        <h1>{item.volumeInfo.title}</h1>
        <h3>{item.volumeInfo.authors}</h3>
        <h4>
        <div style={{ display: "flex", flexDirection: "column" }}>
        {item.volumeInfo.categories[0]}
        <span>{item.volumeInfo.publishedDate}</span>
        <span>
        {item.volumeInfo.industryIdentifiers[0].identifier}
        </span>
        </div>
        </h4>
        <br />
        <div style={{ display: "flex" }}>
        <a href={item.volumeInfo.previewLink}>
        <button>More</button>
        </a>
        <Button
        style={{ marginLeft: "5px", backgroundColor: "OliveDrab" }}
        variant="contained"
        onClick={() =>
        handleBookAdd(
        item.volumeInfo.industryIdentifiers[0].identifier,
        item.volumeInfo.title,
        item.volumeInfo.authors[0],
        item.volumeInfo.publishedDate,
        item.volumeInfo.description,
        item.volumeInfo.pageCount,
        item.volumeInfo.categories[0]
        )
        }

About

Repository for frontend final project web dev.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors