Feature added: Mark to read (#252)
* Rendered a static reading list button to each bookcard component * Attached click listener to bookmark button to log book data * Clicking bookmark button saves book information to a localStorage array * Updated card height to account for button * Updated saveBookToLocalStorage function to use objects instead of arrays * Can now toggle books in and out of localStorage * Reverted previous 4 commits so that data isn't directly written to localStorage * Created a sidebar link to reading list * Added placeholder bookmarks page * Bookmark button now updates state in index.js * Initialized context API * Wrote a reducer function to handle bookmark state changes * Configured reducer to add books in and out of state * Reading list is now preserved between state AND localStorage when changing categories * Fixed some code format issues * Rendered saved books in reading list component * Toggle apperance of bookmark button * Hacky fix for positioning of reading list sidebar link * Adjusted style and alignment of bookmark button * Added check to determine if window is defined in useEffect * Exported the gatsby-ssr API
This commit is contained in:
@@ -5,4 +5,10 @@
|
||||
*/
|
||||
|
||||
// You can delete this file if you're not using it
|
||||
import "./src/styles/global.css"
|
||||
import React from "react"
|
||||
|
||||
import GlobalState from "./src/context/globalState"
|
||||
|
||||
export const wrapRootElement = ({ element }) => (
|
||||
<GlobalState>{element}</GlobalState>
|
||||
)
|
||||
|
||||
@@ -5,3 +5,10 @@
|
||||
*/
|
||||
|
||||
// You can delete this file if you're not using it
|
||||
import React from "react"
|
||||
|
||||
import GlobalState from "./src/context/globalState"
|
||||
|
||||
export const wrapRootElement = ({ element }) => (
|
||||
<GlobalState>{element}</GlobalState>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import StarRatings from "react-star-ratings"
|
||||
import { Card, Row, Col } from "react-bootstrap"
|
||||
|
||||
import AmazonURL from "../components/amazonurl"
|
||||
import Bookmark from "../components/bookmark"
|
||||
import GoodReadsImage from "../components/goodreadsimage"
|
||||
|
||||
const truncateContent = (content) => {
|
||||
@@ -14,7 +15,7 @@ const truncateContent = (content) => {
|
||||
};
|
||||
|
||||
const BookCard = ({ book }) => (
|
||||
<Card style={{ width: "44rem", height: "18rem", marginBottom: "15px" }}>
|
||||
<Card style={{ width: "44rem", height: "23rem", marginBottom: "15px" }}>
|
||||
<Row aria-label={book.title}>
|
||||
<Col xs={3}>
|
||||
<Card.Img
|
||||
@@ -44,6 +45,7 @@ const BookCard = ({ book }) => (
|
||||
<div style= {{ width: "30px", height: "30px" }}>
|
||||
<a href={book.url} ><GoodReadsImage /></a>
|
||||
</div>
|
||||
<Bookmark book={book} />
|
||||
</div>
|
||||
</Card.Subtitle>
|
||||
<p style={{ color: "gray", fontSize: "0.8rem", paddingTop: "1rem" }}>
|
||||
|
||||
26
app/src/components/bookmark.js
Normal file
26
app/src/components/bookmark.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Button } from "react-bootstrap"
|
||||
import { BookmarkContext } from '../context/globalState'
|
||||
|
||||
export default ({ book }) => {
|
||||
const { updateReadingList, readingList } = useContext(BookmarkContext)
|
||||
const readingListIds = readingList.bookIds
|
||||
|
||||
return (
|
||||
<div onClick={() => updateReadingList({ type: 'bookmark', retrievedBook: book })}>
|
||||
<Button style={{
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
marginLeft: "0.25rem",
|
||||
display: "grid",
|
||||
justifyContent: "center",
|
||||
alignContent: "center" }}
|
||||
variant={ readingListIds.includes(book.id) ? "success" : "light"
|
||||
}>
|
||||
<span>
|
||||
🔖
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import React from "react"
|
||||
import React, { useContext } from "react"
|
||||
import { Nav } from "react-bootstrap"
|
||||
import { StaticQuery, graphql } from "gatsby"
|
||||
import { StaticQuery, graphql, Link } from "gatsby"
|
||||
import "../styles/sidebar.css"
|
||||
import { BookmarkContext } from '../context/globalState'
|
||||
var slugify = require('slugify')
|
||||
|
||||
export default () => {
|
||||
const { readingList } = useContext(BookmarkContext)
|
||||
|
||||
return (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
@@ -26,6 +29,9 @@ export default () => {
|
||||
activeKey="/home"
|
||||
>
|
||||
<div className="sidebar-sticky" role="navigation" aria-label="Sidebar">
|
||||
<div style={{position: "relative", left: "0.9rem", paddingBottom: "0.2rem"}}>
|
||||
<Link to="/readingList">🔖 Reading List ({readingList.bookIds.length})</Link>
|
||||
</div>
|
||||
{data.allCategoriesJson.edges.map(function(x, index) {
|
||||
return (
|
||||
<Nav.Item key={x.node.name}>
|
||||
|
||||
33
app/src/context/bookReducer.js
Normal file
33
app/src/context/bookReducer.js
Normal file
@@ -0,0 +1,33 @@
|
||||
export default function bookReducer(state, action) {
|
||||
let readingListCopy = {...state}
|
||||
|
||||
switch (action.type) {
|
||||
case 'init': {
|
||||
if (action.content) {
|
||||
return action.content
|
||||
}
|
||||
return readingListCopy
|
||||
}
|
||||
case 'bookmark': {
|
||||
let { bookIds, books } = readingListCopy
|
||||
const { retrievedBook } = action
|
||||
const retrievedBookId = retrievedBook.id
|
||||
// Delete existing bookmark
|
||||
if (bookIds.includes(retrievedBookId)) {
|
||||
readingListCopy.bookIds = bookIds.filter(id => id !== retrievedBookId)
|
||||
delete books[retrievedBookId]
|
||||
if (typeof window !== undefined) {
|
||||
localStorage.setItem('Bookmarks', JSON.stringify(readingListCopy))
|
||||
}
|
||||
// Add new bookmark
|
||||
} else {
|
||||
books[retrievedBookId] = retrievedBook
|
||||
bookIds.push(retrievedBookId)
|
||||
if (typeof window !== undefined) {
|
||||
localStorage.setItem('Bookmarks', JSON.stringify(readingListCopy))
|
||||
}
|
||||
}
|
||||
return readingListCopy
|
||||
}
|
||||
}
|
||||
}
|
||||
25
app/src/context/globalState.js
Normal file
25
app/src/context/globalState.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { useReducer, useEffect } from 'react'
|
||||
import bookReducer from './bookReducer'
|
||||
|
||||
export const BookmarkContext = React.createContext()
|
||||
|
||||
export default function GlobalState({children}) {
|
||||
let [readingList, updateReadingList] = useReducer(bookReducer, {
|
||||
books: {},
|
||||
bookIds: []
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== undefined) {
|
||||
const retrievedBooks = JSON.parse(localStorage.getItem('Bookmarks'))
|
||||
console.log(retrievedBooks)
|
||||
updateReadingList({type: 'init', content: retrievedBooks})
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<BookmarkContext.Provider value={{readingList, updateReadingList}}>
|
||||
{children}
|
||||
</BookmarkContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -18,11 +18,13 @@ function myFunction(setMaximumBooksToShow, maximumBooksToShow) {
|
||||
}
|
||||
|
||||
export default ({ data }) => {
|
||||
let [maximumBooksToShow, setMaximumBooksToShow] = useState(12)
|
||||
let [maximumBooksToShow, setMaximumBooksToShow] = useState(12)
|
||||
|
||||
useEffect(() => {
|
||||
window.document.onscroll = () =>
|
||||
myFunction(setMaximumBooksToShow, maximumBooksToShow)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<SEO title="Home" />
|
||||
|
||||
36
app/src/pages/readingList.js
Normal file
36
app/src/pages/readingList.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { useContext } from "react"
|
||||
import { Link } from "gatsby"
|
||||
import { Container, Row, Col } from "react-bootstrap"
|
||||
import SideBar from "../components/sidebar"
|
||||
import Layout from "../components/layout"
|
||||
import SEO from "../components/seo"
|
||||
import Bookcard from "../components/bookcard"
|
||||
import { BookmarkContext } from "../context/globalState"
|
||||
|
||||
const ReadingList = () => {
|
||||
const { readingList } = useContext(BookmarkContext)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<SEO title="Reading list" />
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col xs={2}>
|
||||
<SideBar />
|
||||
</Col>
|
||||
<Col>
|
||||
<h2>Your reading list</h2>
|
||||
<Link to="/">Go back to the homepage</Link>
|
||||
{
|
||||
readingList.bookIds.map(bookId => {
|
||||
return <Bookcard book={readingList.books[bookId]} key={bookId} />
|
||||
})
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<p>Reading List</p>
|
||||
</Layout>
|
||||
)}
|
||||
|
||||
export default ReadingList
|
||||
Reference in New Issue
Block a user