Skip to content

Commit f502ad4

Browse files
committed
feat/add-structured-data-to-improve-SEO
1 parent 2844c5a commit f502ad4

File tree

6 files changed

+242
-149
lines changed

6 files changed

+242
-149
lines changed

app/src/components/bookcard.js

Lines changed: 0 additions & 95 deletions
This file was deleted.

app/src/components/bookcard.jsx

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import PropTypes from "prop-types"
2+
import React, { useState } from "react"
3+
import StarRatings from "react-star-ratings"
4+
import { Card, Row, Col } from "react-bootstrap"
5+
6+
import AmazonURL from "../components/amazonurl"
7+
import Bookmark from "../components/bookmark"
8+
import GoodReadsImage from "../components/goodreadsimage"
9+
10+
const truncateContent = content => {
11+
if (!content) {
12+
return ""
13+
}
14+
return content.length > 350 ? content.substring(0, 350) + "..." : content
15+
}
16+
17+
const showFullText = content => {
18+
if (!content) {
19+
return ""
20+
}
21+
return content
22+
}
23+
24+
const BookCard = ({ book }) => {
25+
const [show, toggleShow] = useState(false)
26+
27+
const bookJsonLd = {
28+
"@context": "https://schema.org",
29+
"@type": "Book",
30+
name: book.title,
31+
author: {
32+
"@type": "Person",
33+
name: book.author,
34+
},
35+
datePublished: book.year ? String(book.year) : undefined,
36+
image: book.image_url,
37+
description: book.description,
38+
aggregateRating: book.rating
39+
? {
40+
"@type": "AggregateRating",
41+
ratingValue: String(book.rating),
42+
bestRating: "5",
43+
worstRating: "1",
44+
}
45+
: undefined,
46+
sameAs: book.url,
47+
offers: book.amazon_url
48+
? {
49+
"@type": "Offer",
50+
url: book.amazon_url,
51+
availability: "https://schema.org/InStock",
52+
}
53+
: undefined,
54+
}
55+
56+
return (
57+
<>
58+
<Card style={{ marginBottom: "15px" }}>
59+
<Row>
60+
<Col xs={6} sm={6} md={4} xl={2}>
61+
<Card.Img
62+
style={{
63+
paddingLeft: "15px",
64+
paddingRight: "15px",
65+
paddingTop: "30px",
66+
}}
67+
src={book.image_url}
68+
alt={book.title}
69+
/>
70+
</Col>
71+
<Col xs={12} sm={6} md={8} xl={10}>
72+
<Card.Body>
73+
<Card.Title>{book.title}</Card.Title>
74+
<Card.Subtitle className="text-muted">
75+
<Card.Text style={{ paddingTop: "2px" }}>
76+
{book.author} <b>{book.year ? book.year : null}</b>
77+
</Card.Text>
78+
<StarRatings
79+
rating={parseFloat(book.rating)}
80+
numberOfStars={5}
81+
starDimension="18px"
82+
starSpacing="1px"
83+
starRatedColor="#fa604a"
84+
/>
85+
<div
86+
style={{
87+
display: "flex",
88+
alignItems: "center",
89+
paddingTop: ".75rem",
90+
}}
91+
>
92+
<div
93+
style={{
94+
width: "30px",
95+
height: "30px",
96+
marginRight: "5px",
97+
}}
98+
>
99+
{book.amazon_url ? <AmazonURL book={book} /> : null}
100+
</div>
101+
<div style={{ width: "30px", height: "30px" }}>
102+
<a href={book.url} target="_blank" rel="noreferrer">
103+
<GoodReadsImage />
104+
</a>
105+
</div>
106+
<Bookmark book={book} />
107+
</div>
108+
</Card.Subtitle>
109+
<p
110+
style={{
111+
color: "gray",
112+
fontSize: "0.8rem",
113+
paddingTop: "1rem",
114+
}}
115+
>
116+
{!show && truncateContent(book.description)}
117+
{show && showFullText(book.description)}
118+
</p>
119+
{!show && book.description.length > 350 && (
120+
<button
121+
type="button"
122+
className="btn btn-sm btn-primary"
123+
onClick={() => toggleShow(true)}
124+
>
125+
Show More
126+
</button>
127+
)}
128+
{show && (
129+
<button
130+
type="button"
131+
className="btn btn-sm btn-primary"
132+
onClick={() => toggleShow(false)}
133+
>
134+
Show Less
135+
</button>
136+
)}
137+
</Card.Body>
138+
</Col>
139+
</Row>
140+
</Card>
141+
{/* eslint-disable-next-line react/no-danger */}
142+
<script
143+
type="application/ld+json"
144+
dangerouslySetInnerHTML={{ __html: JSON.stringify(bookJsonLd) }}
145+
/>
146+
</>
147+
)
148+
}
149+
150+
BookCard.propTypes = {
151+
book: PropTypes.shape({
152+
title: PropTypes.string.isRequired,
153+
author: PropTypes.string,
154+
year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
155+
image_url: PropTypes.string,
156+
description: PropTypes.string,
157+
rating: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
158+
url: PropTypes.string,
159+
amazon_url: PropTypes.string,
160+
}),
161+
}
162+
163+
BookCard.defaultProps = {
164+
book: {
165+
title: "",
166+
author: "",
167+
year: "",
168+
image_url: "",
169+
description: "",
170+
rating: 0,
171+
url: "",
172+
amazon_url: "",
173+
},
174+
}
175+
176+
export default BookCard

app/src/components/feed.js

Lines changed: 0 additions & 28 deletions
This file was deleted.

app/src/components/feed.jsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from "react"
2+
import PropTypes from "prop-types"
3+
import "../styles/sidebar.css"
4+
import BookCard from "./bookcard"
5+
import SortByDropdown, {
6+
FIELDS_TO_SORT_BY,
7+
compareFunctions,
8+
} from "./sortByDropdown"
9+
10+
const Feed = ({ data, limit }) => {
11+
const [sortBy, setSortBy] = React.useState(FIELDS_TO_SORT_BY[0])
12+
13+
const sortedBooks = React.useMemo(
14+
() => [...data.allBooksJson.edges].sort(compareFunctions[sortBy.value]),
15+
[data, sortBy]
16+
)
17+
18+
return (
19+
<>
20+
<SortByDropdown sortBy={sortBy.label} onSortByItemClick={setSortBy} />
21+
{sortedBooks.map((x, index) => {
22+
const book = x.node
23+
if (!limit || index < limit) {
24+
if (!book.description || book.description.length < 10) {
25+
return null
26+
}
27+
return <BookCard book={book} key={book.id} />
28+
}
29+
return null
30+
})}
31+
</>
32+
)
33+
}
34+
35+
Feed.propTypes = {
36+
data: PropTypes.shape({
37+
allBooksJson: PropTypes.shape({
38+
edges: PropTypes.arrayOf(
39+
PropTypes.shape({
40+
node: PropTypes.shape({
41+
id: PropTypes.string.isRequired,
42+
title: PropTypes.string,
43+
author: PropTypes.string,
44+
year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
45+
image_url: PropTypes.string,
46+
description: PropTypes.string,
47+
rating: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
48+
url: PropTypes.string,
49+
amazon_url: PropTypes.string,
50+
}),
51+
})
52+
),
53+
}),
54+
}).isRequired,
55+
limit: PropTypes.number,
56+
}
57+
58+
Feed.defaultProps = {
59+
limit: null,
60+
}
61+
62+
export default Feed

0 commit comments

Comments
 (0)