├── .gitignore
├── .static
├── README.md
├── app.json
├── package-lock.json
├── package.json
├── public
├── index.html
└── manifest.json
└── src
├── App.js
├── App.test.js
├── back.jpg
├── back1.svg
├── bookdiary
├── 1080p Card.png
├── Landing Page.png
├── iPhone 6-7-8 – 1.png
└── iPhone 6-7-8 – 2.png
├── components
├── AuthorCard.js
├── Card.js
├── Cards.js
├── CategoryCard.js
├── Icon.js
├── Input.js
├── LandingPage.js
├── LandingPageContent.js
├── Menu.js
├── MenuLikes.js
├── SearchList.js
└── SinglePageCard.js
├── config.js
├── css
└── index.css
├── index.js
├── serviceWorker.js
├── store
├── actions
│ └── BooksAction.js
└── reducers
│ └── rootReducer.js
└── strip.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.static:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/.static
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Book App
2 | 
3 | # [Demo](https://cosmicjs.com/apps/react-book-app)
4 | Uses:
5 | - React for UI
6 | - Redux for state mangaement
7 | - [Cosmic JS](https://cosmicjs.com) for content management
8 | - HTML
9 | - CSS
10 | - AdobeXD
11 | - (RWD - mobile, tablet, desktop) / css-grid, media
12 |
13 |
14 | ## Screenshots
15 | 
16 | 
17 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dokku": {
4 | "predeploy": "npm run build"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-book-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.6.0",
7 | "react-dom": "^16.6.0",
8 | "react-redux": "^5.1.0",
9 | "react-router-dom": "^4.3.1",
10 | "react-scripts": "2.1.1",
11 | "redux": "^4.0.1",
12 | "redux-thunk": "^2.3.0",
13 | "sal.js": "^0.5.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "browserslist": [
25 | ">0.2%",
26 | "not dead",
27 | "not ie <= 11",
28 | "not op_mini all"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
26 | React App
27 |
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
33 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {BrowserRouter, Route, Switch} from 'react-router-dom'
3 | import { connect } from 'react-redux';
4 | import sal from 'sal.js';
5 |
6 | import LandingPage from './components/LandingPage';
7 | import { FetchApiBooks } from './store/actions/BooksAction';
8 | import Icon from './components/Icon';
9 | import SinglePageCard from './components/SinglePageCard';
10 | import CategoryCard from './components/CategoryCard';
11 | import AuthorCard from './components/AuthorCard';
12 |
13 | class App extends Component {
14 |
15 | componentDidMount () {
16 |
17 | this.props.fetchBooks();
18 | sal({
19 | threshold: 1,
20 | once: false,
21 | });
22 |
23 | }
24 | render() {
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | const mapDispatchToProps = (dispatch) => {
43 | return{
44 | fetchBooks: () => {
45 | dispatch(FetchApiBooks());
46 | }
47 | }
48 | }
49 | export default connect(null,mapDispatchToProps)(App);
50 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/back.jpg
--------------------------------------------------------------------------------
/src/back1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/bookdiary/1080p Card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/bookdiary/1080p Card.png
--------------------------------------------------------------------------------
/src/bookdiary/Landing Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/bookdiary/Landing Page.png
--------------------------------------------------------------------------------
/src/bookdiary/iPhone 6-7-8 – 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/bookdiary/iPhone 6-7-8 – 1.png
--------------------------------------------------------------------------------
/src/bookdiary/iPhone 6-7-8 – 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/bookdiary/iPhone 6-7-8 – 2.png
--------------------------------------------------------------------------------
/src/components/AuthorCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux';
3 | import Cards from './Cards';
4 | import LandingPageContent from './LandingPageContent';
5 | function AuthorCard(props) {
6 |
7 | return (
8 |
9 | {props.author && }
10 | {props.booksList ? : }
11 |
12 | )
13 | }
14 | const mapStateToProps = (state, ownProps) => {
15 | const author = ownProps.match.params.author_slug;
16 | console.log('author', state);
17 | return{
18 | booksList:state.books.filter(cat => {
19 | return cat.metadata.author.slug === author
20 | }),
21 | author: state.author.find(item => {return(item.slug === author)})
22 | }
23 | }
24 |
25 | export default connect(mapStateToProps)(AuthorCard);
26 |
--------------------------------------------------------------------------------
/src/components/Card.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/components/Card.js
--------------------------------------------------------------------------------
/src/components/Cards.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import { Link } from 'react-router-dom'
4 |
5 | const Cards = (props) => {
6 |
7 | const Save = (item) => {
8 | props.hitLike(item);
9 | }
10 |
11 | return (
12 |
13 |
14 |
15 | {
16 | props.booksList.map((item, index) => {
17 | return(
18 |
22 |
23 |
24 |
25 |
26 |
27 | Save(index, item.slug)} >
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
{item.title}
38 |
39 |
40 |
Author: {item.metadata.author.title}
41 |
Genres: {item.metadata.category.title}
42 |
43 | )
44 | })
45 | }
46 |
47 | )
48 | }
49 | const mapStateToProps= (state) => {
50 | return{
51 | like: state.likes
52 |
53 | }
54 | }
55 | const mapDispatchToProps = (dispatch) => {
56 | return{
57 | hitLike: (item) => {
58 | dispatch({type:'HANDLE_LIKE', like: item })
59 | },
60 | hitAdd: (item) => {
61 | dispatch({type:'HANDLE_ADD', add: item })
62 | }
63 | }
64 | }
65 | export default connect(mapStateToProps, mapDispatchToProps)(Cards);
66 |
--------------------------------------------------------------------------------
/src/components/CategoryCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import Cards from './Cards';
4 | import LandingPageContent from './LandingPageContent';
5 |
6 | function CategoryCard(props) {
7 |
8 | return (
9 |
10 | {props.category && }
11 | {props.booksList ? : }
12 |
13 | )
14 | }
15 | const mapStateToProps = (state, ownProps) => {
16 | const category = ownProps.match.params.category_slug;
17 | return{
18 | booksList:state.books.filter(cat => {
19 | // console.log("from map", cat.metadata.category.slug);
20 | return cat.metadata.category.slug === category
21 | }),
22 | category: state.menu.find(item => (item.slug === category))
23 | }
24 | }
25 | export default connect(mapStateToProps)(CategoryCard);
26 |
--------------------------------------------------------------------------------
/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Menu from './Menu';
3 | import MenuLikes from './MenuLikes';
4 | import Input from './Input';
5 | import { Link } from 'react-router-dom';
6 | import SearchList from './SearchList';
7 |
8 | export default function Icon() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Input.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 | class Input extends Component {
4 | render() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 | }
12 | const mapStateToProps = (state) => {
13 | return{
14 | input: state.input
15 | }
16 | }
17 | const mapDispatchToProps = (dispatch) => {
18 | return{
19 | handleInput: (e) => {
20 | dispatch({type:'INPUT_CHANGE', input:e.target.value})
21 | }
22 | }
23 | }
24 | export default connect(mapStateToProps, mapDispatchToProps)(Input);
25 |
--------------------------------------------------------------------------------
/src/components/LandingPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Cards from './Cards';
3 | import { Link } from 'react-router-dom';
4 | import { connect } from 'react-redux';
5 |
6 |
7 | const LandingPage = (props)=>{
8 | return (
9 |
10 |
11 |
12 |
16 |
Books Diary
17 |
" Gabriel.s small cottage was lit by a single candle that cast light on a collection of weapons along one wall and a few books on a bookshelf on another "
18 |
19 |
20 |
21 |
Favourite
22 |
23 |
24 | )
25 | }
26 | const mapStateToProps = (state) => {
27 | return {
28 | booksList: state.books
29 | }
30 | }
31 | export default connect(mapStateToProps)(LandingPage)
32 |
--------------------------------------------------------------------------------
/src/components/LandingPageContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom';
3 |
4 | export default function LandingPageContent(props) {
5 | return (
6 |
7 |
8 |
9 |
10 |
{props.page}
11 | {props.body ?
:
Loading...
}
12 |
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Menu.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux';
3 | import { NavLink} from 'react-router-dom';
4 | class Menu extends Component {
5 |
6 | componentDidMount () {
7 |
8 | }
9 | render() {
10 | const { menuCategory } = this.props
11 | console.log("From component", this.props.menuCategory);
12 | return (
13 |
14 |
15 | {
16 | menuCategory && menuCategory.map((item, index) => {
17 | return (
18 | {item.title}
19 | )
20 | })
21 | }
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | const mapStateToProps = (state) => {
29 |
30 | return {
31 | menuCategory: state.menu
32 | }
33 | }
34 | export default connect(mapStateToProps)(Menu);
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/MenuLikes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function MenuLikes() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | // import React, { Component } from 'react'
21 |
22 | // export default class MenuLikes extends Component {
23 |
24 | // render() {
25 | // console.log(this.props);
26 | // const Save = () => {
27 | // localStorage.setItem("Like", "hey" );
28 | // }
29 | // return (
30 | //
31 | //
32 | //
33 | //
34 | //
35 | //
36 | //
37 | // )
38 | // }
39 | // }
40 |
--------------------------------------------------------------------------------
/src/components/SearchList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import { Link } from 'react-router-dom';
4 | function SearchList(props) {
5 | return (
6 |
7 | {
8 | props.value && props.lists.map((item, index) => {
9 | return(
10 |
11 |
{item.title}
12 |
{item.metadata.author.title}
13 |
14 | )
15 | })
16 | }
17 |
18 | )
19 | }
20 | const mapStateToProps = (state) => {
21 | return{
22 | lists: state.search,
23 | value: state.value
24 | }
25 | }
26 |
27 | export default connect(mapStateToProps)(SearchList);
28 |
--------------------------------------------------------------------------------
/src/components/SinglePageCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux';
3 |
4 | import { Link } from 'react-router-dom';
5 |
6 | function SinglePageCard(props) {
7 |
8 | return (
9 | props.book ?
10 |
14 |
15 |
16 |
{
}
17 |
18 |
19 |
{props.book.title}
20 |
21 |
22 |
Go back
23 |
24 |
25 |
26 |
27 | { props.book.metadata.category.title}
28 |
29 |
30 |
31 |
:
32 | )
33 | }
34 | const mapStateToProps = (state, ownProps) => {
35 | let slug = ownProps.match.params.item_slug;
36 | return{
37 | book: state.books.find(item => item.slug === slug)
38 | }
39 | }
40 | export default connect(mapStateToProps)(SinglePageCard);
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | COSMIC_BUCKET: process.env.COSMIC_BUCKET || 'react-book-app',
3 | COSMIC_READ_KEY: process.env.COSMIC_READ_KEY || ''
4 | }
--------------------------------------------------------------------------------
/src/css/index.css:
--------------------------------------------------------------------------------
1 | @import '/../node_modules/sal.js/dist/sal.css';
2 | :root{
3 | --clr1: #333533;
4 | --clr2: #242423;
5 | --clr3: #F5CB5C;
6 | --clr4: #E8EDDF;
7 | --clr5: #CFDBD5;
8 | }
9 |
10 | html{
11 | margin: 0;
12 | padding: 0;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | padding: 0;
18 | font-family: 'SourceSansPro', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
19 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
20 | sans-serif;
21 | -webkit-font-smoothing: antialiased;
22 | -moz-osx-font-smoothing: grayscale;
23 | background:var(--clr5);
24 |
25 | }
26 | .main-wrap h1 {
27 | color: rgb(48, 48, 48);
28 | font-family:'Playfair Display';
29 | font-size: 5.5rem;
30 | font-weight: 900;
31 | }
32 | .main-wrap p {
33 | color: var(--clr1);
34 | font-weight: 400;
35 | font-size: 1.3rem;
36 | max-width: 80%;
37 | font-family: 'SourceSansPro', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
38 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue";
39 | opacity:0.9;
40 | }
41 |
42 |
43 | a{
44 | color:var(--clr2);
45 | text-decoration: none;
46 | }
47 |
48 |
49 | /* landing page */
50 | h1,h2{
51 | margin:0;
52 | padding:0;
53 | }
54 |
55 |
56 | .container {
57 | min-height: 100vh;
58 | width:100%;
59 | /* background:#F5CB5C; */
60 | background: url('../back1.svg');
61 | background-repeat: no-repeat;
62 | background-size: cover;
63 | display:flex;
64 | flex-direction: column;
65 | justify-content: center;
66 | align-items: center;
67 |
68 |
69 | }
70 |
71 | .main-slide{
72 |
73 | text-align: center;
74 | padding: 20% 30px;
75 | width:80%;
76 | flex:4;
77 | display:flex;
78 | flex-direction: column;
79 | align-items: center;
80 | }
81 | .main-slide p{
82 | text-align: center;
83 | }
84 |
85 | /* menu likes */
86 |
87 | .like-menu{
88 |
89 | background: var(--clr3);
90 | width:300px;
91 | margin-bottom: 5%;
92 | border-bottom-left-radius: 5px;
93 | border-bottom-right-radius: 5px;
94 | flex:1;
95 | }
96 | .like-menu ul{
97 | list-style-type: none;
98 | display: flex;
99 | justify-content: center;
100 | align-items: center;
101 |
102 | }
103 | .like-menu li{
104 | flex: 1;
105 | padding:0 20px;
106 |
107 | }
108 | .category-menu{
109 | width:100%;
110 | flex:1;
111 | padding:5%;
112 | box-sizing: border-box;
113 | }
114 | .category-menu ul{
115 | list-style-type: none;
116 | display: flex;
117 | justify-content: space-between;
118 | align-items: center;
119 | flex-wrap: wrap;
120 | text-align: center;
121 | padding: 0;
122 | }
123 | .category-menu li{
124 | background:var(--clr3);
125 | padding:5px 10px;
126 | border-radius:10px;
127 | }
128 |
129 | /* cards */
130 |
131 | .cards{
132 | max-width:360px;
133 | height: 100%;
134 | display:grid;
135 | grid-template-columns: 320px;
136 | grid-template-rows: auto;
137 | margin: 20px auto;
138 | padding-bottom: 3%;
139 | align-items: baseline;
140 | justify-content: center;
141 | background:var(--clr5);
142 | grid-row-gap: 50px;
143 |
144 |
145 | }
146 | .card{
147 | width:320px;
148 | height: auto;
149 | color:var(--clr2);
150 | position: relative;
151 | }
152 | .card img{
153 | width:100%;
154 | height: 500px;
155 | }
156 | .overlay{
157 | width: 100%;
158 | height: 500px;
159 | box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
160 | transition: all 0.3s cubic-bezier(.25,.8,.25,1);
161 | position: relative;
162 | margin-bottom: 20px;
163 | }
164 | .overlay:hover {
165 | box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
166 | }
167 |
168 | .strip{
169 | height: 80px;
170 | width:100%;
171 | /* background: var(--clr4); */
172 | background:url('../strip.png');
173 | position: absolute;
174 | background-position: center;
175 |
176 | left:0;
177 | bottom:0;
178 | }
179 | .strip .like-menu {
180 | background: transparent;
181 | display:flex;
182 | justify-content: center;
183 | height: 100px;
184 | }
185 | .cards-title{
186 | max-width:320px;
187 | margin:40px auto;
188 | font-size: 3rem;
189 | color:var(--clr2);
190 |
191 | }
192 | .cards-title::before{
193 | content: '\f005 ';
194 | font-family: "FontAwesome";
195 |
196 |
197 | }
198 | .fa-2x{
199 | color:var(--clr2);
200 | }
201 | .fa-spinner{
202 | position: fixed;
203 | top:45vh;
204 | left:0;
205 | transform: translateX(49%);
206 | }
207 | .top-bar{
208 | position: absolute;
209 | top:20px;
210 | right:30px;
211 | color:var(--clr1);
212 | z-index:9999;
213 | cursor: pointer;
214 |
215 | }
216 | .search{
217 | height: 100%;
218 | padding-top:20%;
219 | display:flex;
220 | margin:0 auto;
221 | flex-direction: column;
222 | align-items: center;
223 | width:320px;
224 |
225 | }
226 | .search .like-menu{
227 | background:var(--clr5);
228 | }
229 | .search-list{
230 | display:flex;
231 | flex-direction: column;
232 | margin:20px auto;
233 | width:100%;
234 | align-items: center;
235 | }
236 | .input{
237 | width:320px;
238 | height: 50px;
239 | border:0;
240 | background:var(--clr4);
241 | font-size: 1.2em;
242 | color:var(--clr2);
243 | padding-left: 3%;
244 | box-sizing: border-box;
245 | font-weight: 300;
246 |
247 | }
248 | .item {
249 | display:grid;
250 | grid-template-columns: 3fr 2fr;
251 | grid-template-rows: auto;
252 | padding:20px 4%;
253 | width:100%;
254 | margin:5px 0;
255 | grid-column-gap: 5%;
256 | background: var(--clr4);
257 | color:var(--clr2);
258 | box-sizing: border-box;
259 |
260 | }
261 | .item-author{
262 | font-size:0.9rem;
263 | }
264 | .item-title{
265 | font-size:1.05rem;
266 | }
267 | .item:hover{
268 | background: var(--clr3);
269 | }
270 | /* menu bar */
271 | .div{
272 | height: 100%;
273 | }
274 | .single-card{
275 | background:var(--clr4);
276 | display:grid;
277 | align-content: center;
278 | grid-template-columns:1fr;
279 | grid-template-rows: 600px 1fr 80px;
280 | grid-row-gap: 40px;
281 |
282 |
283 | }
284 | .single-card h1{
285 | color: var(--clr2);
286 | font-family:'Playfair Display';
287 | font-size: 3.5rem;
288 | font-weight: 900;
289 | }
290 | .single-card p{
291 | font-family: 'Segoe UI', 'Source Sans Pro', Tahoma, Geneva, Verdana, sans-serif;
292 | color: var(--clr1);
293 | font-weight: 300;
294 | font-size: 1.3rem;
295 | opacity:0.9;
296 | line-height: 36px;
297 | }
298 | .book{
299 | background:var(--clr3);
300 | display:flex;
301 | justify-content: center;
302 | height: 600px;
303 | }
304 | .book img{
305 | width:320px;
306 | height: 480px;
307 | padding:70px 0 0;
308 |
309 |
310 | }
311 | .book-text{
312 | background:var(--clr4);
313 | width:320px;
314 | padding:20px 0;
315 | box-sizing: border-box;
316 | margin:0px auto;
317 | }
318 | .btn-back{
319 | background:var(--clr5);
320 | padding:10px 20px;
321 | text-align: center;
322 | width: 120px;
323 | margin:0 auto;
324 | font-size:1.2rem;
325 | font-weight: 400;
326 | cursor: pointer;
327 | }
328 | .single-card-category{
329 | background:var(--clr1);
330 | color:var(--clr4);
331 | text-align: center;
332 | display: flex;
333 | justify-content: center;
334 | align-items: center;
335 | font-size: 2rem;
336 | font-weight: 700;
337 | cursor: pointer;
338 | }
339 | .content{
340 | height: 100%;
341 | max-width:1600px;
342 | background: var(--clr3);
343 | padding:10%;
344 | box-sizing: border-box;
345 | display:flex;
346 | justify-content: center;
347 | align-items: center;
348 | flex-direction: column;
349 | margin:0 auto;
350 | margin-bottom: 5%;
351 | }
352 | .content h1{
353 | font-size:4.5rem;
354 | font-family:'Playfair Display';
355 | }
356 | .content-body p{
357 | font-family: 'Segoe UI', 'Source Sans Pro', Tahoma, Geneva, Verdana, sans-serif;
358 | line-height: 30px;
359 | font-size: 1.3rem;
360 | font-weight: 300;
361 | }
362 | .position{
363 | position: relative;
364 | }
365 | .vote{
366 | position: absolute;
367 | top:0;
368 | left:90px;
369 | }
370 | .reset .strip{
371 | display:none
372 | }
373 | @media(min-width:660px){
374 |
375 | .container {
376 | min-height:100%;
377 |
378 | width:100%;
379 | }
380 | .cards{
381 | max-width:660px;
382 | display:grid;
383 | height: auto;
384 | grid-template-columns: 320px 320px;
385 | grid-column-gap: 10px;
386 | grid-template-rows: auto;
387 | margin: 20px auto;
388 |
389 | justify-content: center;
390 | background:var(--clr5);
391 | }
392 | .cards-title{
393 | max-width:680px;
394 | padding-left:20px;
395 | }
396 | .main-slide p{
397 | font-size:2.2rem;
398 | max-width: 60%;
399 |
400 | }
401 | .search{
402 | height: 100%;
403 | padding-top:10%;
404 | display:flex;
405 | margin:0 auto;
406 | flex-direction: column;
407 | align-items: center;
408 | width:640px;
409 | }
410 | .input{
411 | width:640px;
412 | height: 70px;
413 | font-size: 1.3em;
414 | }
415 |
416 | .single-card{
417 | grid-template-rows: 800px 1fr 80px;
418 |
419 | }
420 | .single-card h1{
421 | color: var(--clr2);
422 | font-family:'Playfair Display';
423 | font-size: 4.5rem;
424 | font-weight: 900;
425 | }
426 | .single-card p{
427 | font-size: 1.5rem;
428 | }
429 | .book{
430 | height: 800px;
431 | }
432 | .book img{
433 | width:448px;
434 | height: 700px;
435 |
436 | }
437 | .book-text{
438 | width:550px;
439 | }
440 |
441 |
442 |
443 |
444 | }
445 |
446 | @media(min-width:980px){
447 |
448 | .cards{
449 | max-width:960px;
450 | display:grid;
451 | height: auto;
452 | grid-template-columns: 320px 320px 320px;
453 | grid-template-rows: auto;
454 | margin: 20px auto;
455 | justify-content: center;
456 | background:var(--clr5);
457 | }
458 | .cards-title{
459 | max-width:1000px;
460 | }
461 | .main-slide h1{
462 | font-size:8rem;
463 | }
464 | .main-slide p{
465 | font-size:1.8rem;
466 | max-width: 60%;
467 |
468 | }
469 | .single-card h1{
470 | color: var(--clr2);
471 | font-family:'Playfair Display';
472 | font-size: 2.7rem;
473 | font-weight: 900;
474 | margin-bottom: 10px;
475 | max-height: 170px;
476 | overflow: hidden;
477 | }
478 | .single-card .text{
479 | overflow-y: scroll;
480 | margin-bottom: 30px;
481 | height: 280px;
482 | }
483 | .text p{
484 | padding:10px 20px 0 0;
485 | font-size: 1.1rem;
486 | font-family: 'Segoe UI', 'Source Sans Pro', Tahoma, Geneva, Verdana, sans-serif;
487 | color: var(--clr1);
488 | opacity:0.9;
489 | line-height: 36px;
490 | }
491 |
492 | .div{
493 | display:flex;
494 | justify-content: center;
495 | align-items: flex-start;
496 | padding-top: 80px;
497 | margin: 0 auto;
498 | width:100%;
499 | min-height:100%;
500 | padding-bottom: 80px;
501 |
502 | background-repeat: no-repeat;
503 | background-position: top center;
504 | }
505 | .single-card{
506 | display:grid;
507 | grid-template-columns: 70px 450px 450px;
508 | grid-template-rows: 600px;
509 | align-content: center;
510 | grid-gap:10px;
511 | background: transparent;
512 | background:var(--clr4);
513 | position: relative;
514 | }
515 | .book{
516 | background:transparent;
517 | display:flex;
518 | justify-content: center;
519 | height: 550px;
520 | width:100%;
521 | position: relative;
522 | }
523 | .book img{
524 | width:440px;
525 | height: 680px;
526 | padding:0px 0 0;
527 | position: absolute;
528 | top:-40px;
529 |
530 | }
531 | .book-text{
532 | background:transparent;
533 | height: 600px;
534 | padding: 40px 20px;
535 | width:450px;
536 | box-sizing: border-box;
537 | margin:0px auto;
538 |
539 |
540 | }
541 | .single-card-category{
542 | grid-column-start: 1;
543 | grid-column-end: 2;
544 | grid-row-start: 1;
545 | grid-row-end: 2;
546 | height: 580px;
547 | margin-top: 10px;
548 | width:65px;
549 | margin-left:10px;
550 | background:var(--clr3);
551 | color:var(--clr1);
552 | word-break: break-all;
553 | font-size:2.3rem;
554 | text-transform: uppercase;
555 |
556 | }
557 | .btn-back{
558 | position: absolute;
559 | bottom:30px;
560 | right: 30px;
561 | }
562 | .ctg{
563 | writing-mode: vertical-rl;
564 | text-orientation: mixed;
565 | }
566 |
567 |
568 | }
569 |
570 | @media(min-width:1340px){
571 |
572 | .cards{
573 | max-width:1290px;
574 | display:grid;
575 | height: auto;
576 | grid-template-columns: 320px 320px 320px 320px;
577 | grid-template-rows: auto;
578 | margin: 20px auto;
579 | justify-content: center;
580 | background:var(--clr5);
581 | }
582 | .cards-title{
583 | max-width:1340px;
584 |
585 | }
586 | .main-slide h1{
587 | font-size:10rem;
588 | }
589 | .main-slide p{
590 | font-size:2.2rem;
591 | max-width: 40%;
592 |
593 |
594 | }
595 |
596 | .single-card h1{
597 | color: var(--clr2);
598 | font-family:'Playfair Display';
599 | font-size: 3.5rem;
600 | font-weight: 900;
601 | margin-bottom: 40px;
602 | }
603 | .single-card .text{
604 | overflow-y: scroll;
605 | margin-bottom: 40px;
606 | max-height: 400px;
607 | }
608 | .text p{
609 | padding:10px 30px 10px 10px;
610 | font-size: 1.3rem;
611 | font-family: 'Segoe UI', 'Source Sans Pro', Tahoma, Geneva, Verdana, sans-serif;
612 | color: var(--clr1);
613 | opacity:0.9;
614 | line-height: 36px;
615 |
616 | }
617 | .div{
618 | display:flex;
619 | justify-content: center;
620 | align-items: flex-start;
621 | padding-top: 150px;
622 | margin: 0 auto;
623 | width:100%;
624 | min-height:100%;
625 | padding-bottom: 150px;
626 | }
627 | .single-card{
628 | display:grid;
629 | grid-template-columns: 80px 600px 650px;
630 | grid-template-rows: 700px;
631 | align-content: center;
632 | grid-gap:10px;
633 | background: transparent;
634 | background:var(--clr4);
635 |
636 | }
637 | .book{
638 | background:transparent;
639 | display:flex;
640 | justify-content: center;
641 | height: 600px;
642 | width:100%;
643 | position: relative;
644 | }
645 | .book img{
646 | width:600px;
647 | height: 900px;
648 | padding:0px 0 0;
649 | position: absolute;
650 | top:-100px;
651 | }
652 | .book-text{
653 | background:transparent;
654 | height: 700px;
655 | padding: 40px;
656 | width:650px;
657 | box-sizing: border-box;
658 | margin:0px auto;
659 | position: relative;
660 | }
661 | .single-card-category{
662 | grid-column-start: 1;
663 | grid-column-end: 2;
664 | grid-row-start: 1;
665 | grid-row-end: 2;
666 | height: 680px;
667 | margin-top: 10px;
668 | width:70px;
669 | margin-left:10px;
670 | word-break: break-all;
671 | font-size:3rem;
672 |
673 | }
674 | .btn-back{
675 | position: absolute;
676 | bottom:50px;
677 | right: 50px;
678 | }
679 | .ctg{
680 | writing-mode: vertical-rl;
681 | text-orientation: mixed;
682 | }
683 |
684 |
685 |
686 | }
687 |
688 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import * as serviceWorker from './serviceWorker';
4 | import { createStore, applyMiddleware } from 'redux';
5 | import { Provider } from 'react-redux';
6 | import thunk from 'redux-thunk';
7 |
8 | import reducer from './store/reducers/rootReducer';
9 | import './css/index.css';
10 | import App from './App';
11 |
12 |
13 | const store = createStore(reducer, applyMiddleware(thunk));
14 |
15 | ReactDOM.render( , document.getElementById('root'));
16 |
17 |
18 | // If you want your app to work offline and load faster, you can change
19 | // unregister() to register() below. Note this comes with some pitfalls.
20 | // Learn more about service workers: http://bit.ly/CRA-PWA
21 | serviceWorker.register();
22 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/store/actions/BooksAction.js:
--------------------------------------------------------------------------------
1 | import config from '../../config'
2 | export const FetchApiBooks = () => {
3 | return (dispatch) => {
4 | console.log(process.env)
5 | fetch(`https://api.cosmicjs.com/v1/${config.COSMIC_BUCKET ? config.COSMIC_BUCKET : 'book-app'}?pretty=true&hide_metafields=true&read_key=${config.COSMIC_READ_KEY}`)
6 | .then(response => response.json())
7 | .then(data => {
8 | console.log("Action creator from:", data);
9 | dispatch({type:"FETCH_BOOKS", data:data});
10 | })
11 | }
12 | }
--------------------------------------------------------------------------------
/src/store/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 |
2 | const initialState = {
3 | books: [],
4 | menu: [],
5 | value: '',
6 | search:[],
7 | author:[],
8 | likes: [],
9 | add:[]
10 |
11 | }
12 |
13 | const reducer = (state=initialState, action) => {
14 | switch(action.type) {
15 | case 'FETCH_BOOKS':
16 |
17 | return{
18 | ...state,
19 | books: action.data.bucket.objects.filter(item => {
20 | return(item.type_slug === "books")
21 | }),
22 | menu: action.data.bucket.objects.filter(item => {
23 | return(item.type_slug === "categories")
24 | }),
25 | author: action.data.bucket.objects.filter(item => {
26 | return(item.type_slug === "authors")
27 | })
28 |
29 | }
30 | case 'INPUT_CHANGE':
31 | console.log("Input", action);
32 | let newList = state.books.filter(item => {
33 | return item.slug.toLowerCase().search(action.input.toLowerCase()) !== -1 || item.metadata.author.title.toLowerCase().search(action.input.toLowerCase()) !== -1;
34 |
35 | });
36 |
37 | return{
38 | ...state,
39 | search: newList,
40 | value:action.input
41 | }
42 | case 'HANDLE_LIKE':
43 |
44 | return{
45 | ...state,
46 | likes: state.books[action.like].options.slug_field ++
47 | }
48 |
49 | default:
50 | return state;
51 |
52 | }
53 |
54 | }
55 | export default reducer;
--------------------------------------------------------------------------------
/src/strip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/react-book-app/2f415225e775fb722b69251da350ee5701c3c9fc/src/strip.png
--------------------------------------------------------------------------------