├── .babelrc
├── .gitignore
├── README.md
├── config.js
├── demo-dashboard.gif
├── dist
├── dfc0cda63aba4a842c70693be2325975.gif
├── index.html
└── main.js
├── index.pug
├── package-lock.json
├── package.json
├── pagination.gif
├── public
└── index.html
├── render-posts.gif
├── server.js
├── src
├── App.js
├── client-config.js
├── components
│ ├── Blogs.js
│ ├── Home.js
│ ├── Login.js
│ ├── NavLink.js
│ ├── Navbar.js
│ ├── Page.js
│ ├── Posts.js
│ ├── SinglePost.js
│ ├── content
│ │ └── Content.js
│ ├── context
│ │ ├── AppContext.js
│ │ └── AppProvider.js
│ ├── dashboard
│ │ ├── Dashboard.js
│ │ ├── pages
│ │ │ └── Pages.js
│ │ ├── posts
│ │ │ ├── CreatePost.js
│ │ │ └── Posts.js
│ │ └── sidebar
│ │ │ ├── SidebarMenu.js
│ │ │ ├── ToggleSidebarBtn.js
│ │ │ └── menus
│ │ │ ├── PageMenu.js
│ │ │ └── PostMenu.js
│ ├── functions.js
│ └── layouts
│ │ ├── DashboardLayout.js
│ │ ├── FeaturedImage.js
│ │ ├── Loader.js
│ │ ├── Pagination.js
│ │ ├── Post.js
│ │ └── PostLoader.js
├── index.js
├── loader.gif
├── style.css
└── utils
│ └── functions.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [ "@babel/preset-env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": [
7 | "last 2 Chrome versions",
8 | "last 2 Firefox versions",
9 | "last 2 Safari versions",
10 | "last 2 iOS versions",
11 | "last 1 Android version",
12 | "last 1 ChromeAndroid version",
13 | "ie 11"
14 | ]
15 | }
16 | } ],
17 | "@babel/preset-react"
18 | ],
19 | "plugins": [ "@babel/plugin-proposal-class-properties" ]
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.log
3 | npm-debug.log
4 | yarn-error.log
5 | .DS_Store
6 | node_modules
7 | .cache
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React with WordPress
2 |
3 | :fire: Example of react application with WordPress REST API
4 |
5 | ******* PLEASE STAR MY REPO TO SUPPORT MY WORK 🙏 ******
6 |
7 | Please follow me 🙏 on [twitter](https://twitter.com/imranhsayed)
8 |
9 | ## Dashboard Demo
10 |
11 | 
12 |
13 | ## Pagination :video_camera:
14 | 
15 |
16 | ## Post Listings Demo :video_camera:
17 |
18 | 
19 |
20 | ## [Live Demo](https://react-with-wordpress.netlify.com/)
21 |
22 | ## Features
23 |
24 | 1. CRUD operation with WordPress REST API
25 | 2. Authentication with JWT ( Login Logout )
26 | 3. Accessing public and private routes
27 | 4. Handing WordPress REST API custom end points.
28 | 5. Creating Dashboard with React for CRUD operation.
29 | 6. Pagination
30 |
31 | ## Installation
32 |
33 | 1. Clone this repo in `git clone https://github.com/imranhsayed/react-with-wordpress`
34 |
35 | 2. `git checkout branchname`
36 |
37 | 3. Run `npm install`
38 |
39 | ## Add [REST API ENDPOINTS WordPress Plugin](https://github.com/imranhsayed/rest-api-endpoints)
40 |
41 | * Clone the [REST API ENDPOINTS](https://github.com/imranhsayed/rest-api-endpoints) plugin in your WordPress plugin directory.
42 |
43 | `git clone git@github.com:imranhsayed/rest-api-endpoints.git`
44 |
45 | ## Configure
46 |
47 | Add your wordPress siteUrl in `src/client-config.js`
48 |
49 | ```ruby
50 | const clientConfig = {
51 | siteUrl: 'http://localhost:8888/wordpress'
52 | };
53 |
54 | export default clientConfig;
55 | ```
56 |
57 | ## Branches
58 |
59 | ### 1. [login-with-jwt-wordpress-plugin](https://github.com/imranhsayed/react-with-wordpress/tree/login-with-jwt-wordpress-plugin)
60 |
61 | > A React App where you can login using the endpoint provided by `JWT Authentication for WP-API` WordPress Plugin.
62 | So you need to have this plugin installed on WordPress. The plugin's endpoint returns the user object and a jwt-token on success,
63 | which we can then store in localstorage and login the user on front React Application
64 |
65 | ### Steps
66 | * You need to install and activate [JWT Authentication for WP REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/) plugin on you WordPress site
67 | * Then you need to configure it by adding these:
68 |
69 | i. Add the last three lines in your WordPress `.htaccess` file as shown:
70 | ```ruby
71 | # BEGIN WordPress
72 |
73 | RewriteEngine On
74 | RewriteBase /wordpress/
75 | RewriteRule ^index\.php$ - [L]
76 | RewriteCond %{REQUEST_FILENAME} !-f
77 | RewriteCond %{REQUEST_FILENAME} !-d
78 | RewriteRule . /wordpress/index.php [L]
79 |
80 |
81 | RewriteCond %{HTTP:Authorization} ^(.*)
82 | RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
83 | SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
84 |
85 |
86 | ```
87 | ii. Add the following in your `wp-config.php` Wordpress file. You can choose your own secret key.
88 |
89 | ```ruby
90 | define('JWT_AUTH_SECRET_KEY', '&BZd]N-ghz|hbH`=%~a5z(`mR=n%7#8-Iz@KoqtDhQ6(8h$og%-IbI#>N*T`s9Dg');
91 | define('JWT_AUTH_CORS_ENABLE', true);
92 | ```
93 |
94 | iii. Now you can make a request to `/wp-json/jwt-auth/v1/token` REST API provided by the plugin. You need to pass
95 | username and password and it returns a user object and token . You can save the token in localstorage and send it in the headers
96 | of your protected route requests ( e.g. [Create Post](https://developer.wordpress.org/rest-api/reference/posts/#create-a-post) `/wp-json/wp/v2/posts` )
97 |
98 | iiv. So whenever you send a request to WordPress REST API for your protected routes, you send the token received in the headers of
99 | your request
100 | ```
101 | {
102 | 'Accept': 'application/json',
103 | 'Content-Type': 'application/json',
104 | 'Authorization': `Bearer putTokenReceivedHere`
105 | }
106 |
107 | ```
108 |
109 | This repo also demonstrates how to create posts in React Application by sending request to protected endpoints ( passing the token in the header )
110 |
111 | ### 2. [jwt-verify-with-node](https://github.com/imranhsayed/react-with-wordpress/tree/jwt-verify-with-node)
112 |
113 | > A React(front end) + Node(back end) application. It uses `jwt.sign()` ( from `jwtwebtoken` npm package ) to generate a token using the username and password
114 | sent from front end( React ) and returns it as a response, which we then store in localstorage to login the user.
115 | This token received by frond end, will be sent with all further request for protected routes, which will then be verified in node route
116 | using `jwt.verify()`
117 | Besides generating the token, the end point in node also accesses the WordPress rest api to confirm the credentials and returns the user object
118 | or errors if any.
119 |
120 | > It also has functionality to create post where we make a request from front end along with token( React ) to a node end point.
121 | The node endpoint verifies the token and then makes a request to WordPress REST API endpoint to create the post and then returns the
122 | new post id, or error if any.
123 |
124 | ## Commands
125 |
126 | 1. Branch [master](https://github.com/imranhsayed/react-with-wordpress) and [build-app-for-heroku](https://github.com/imranhsayed/react-with-wordpress/tree/build-app-for-heroku)
127 | - `start` Runs node server for development ( in watch mode ). The server.js sends all front end route request to index.html and then all front end route requests is handled by reach router
128 |
129 | 2. Branch [jwt-verify-with-node](https://github.com/imranhsayed/react-with-wordpress/tree/jwt-verify-with-node) and
130 | [login-with-jwt-wordpress-plugin](https://github.com/imranhsayed/react-with-wordpress/tree/login-with-jwt-wordpress-plugin)
131 |
132 | - `dev` Runs webpack dev server for development ( in watch mode )
133 |
134 | Common
135 | - `prod` Runs webpack in production mode
136 |
137 | ## Free Courses
138 |
139 | [Codeytek](https://codeytek.com/)
140 |
141 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tokenSecret: 'SECRET',
3 | wordPressUrl: 'https://orionhive.com',
4 | wordPressRestNameSpace: '/wp-json/wp/v2/rae'
5 | };
6 |
--------------------------------------------------------------------------------
/demo-dashboard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imranhsayed/react-with-wordpress/36282df72aef5d668832502fe67945fe831bf887/demo-dashboard.gif
--------------------------------------------------------------------------------
/dist/dfc0cda63aba4a842c70693be2325975.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imranhsayed/react-with-wordpress/36282df72aef5d668832502fe67945fe831bf887/dist/dfc0cda63aba4a842c70693be2325975.gif
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 | React App
11 |
12 |
13 | Please enable JavaScript
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/index.pug:
--------------------------------------------------------------------------------
1 | html
2 | head
3 | meta(charset="UTF-8")
4 | meta(name="viewport", content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0")
5 | link(rel="stylesheet", href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css")
6 | link(rel="stylesheet", href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css")
7 | link(rel="stylesheet", href="static/bundle.css")
8 | |
9 | body
10 | div(id="root")
11 | script(src="static/bundle.js")
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-workshop",
3 | "version": "1.0.0",
4 | "description": ":fire: This is a workshop for learning how to build React Applications.",
5 | "main": "index.js",
6 | "scripts": {
7 | "webpack-dev-server": "webpack-dev-server",
8 | "dev": "webpack-dev-server --mode=development",
9 | "prod": "webpack --mode=production"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/imranhsayed/react-workshop.git"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/imranhsayed/react-workshop/issues"
20 | },
21 | "homepage": "https://github.com/imranhsayed/react-workshop#readme",
22 | "dependencies": {
23 | "@reach/router": "^1.2.1",
24 | "axios": "^0.21.1",
25 | "cors": "^2.8.5",
26 | "dompurify": "^2.1.1",
27 | "jsonwebtoken": "^9.0.0",
28 | "moment": "^2.29.4",
29 | "pug": "^3.0.2",
30 | "qs": "^6.7.3",
31 | "querystringify": "^2.0.0",
32 | "react": "^16.13.1",
33 | "react-dom": "^16.13.1",
34 | "react-moment": "^0.9.2",
35 | "react-navigation": "^3.9.1",
36 | "react-render-html": "^0.6.0"
37 | },
38 | "devDependencies": {
39 | "@babel/core": "^7.4.3",
40 | "@babel/plugin-proposal-class-properties": "^7.4.0",
41 | "@babel/preset-env": "^7.4.3",
42 | "@babel/preset-react": "^7.0.0",
43 | "babel-loader": "^8.0.5",
44 | "css-loader": "^2.1.1",
45 | "file-loader": "^3.0.1",
46 | "html-webpack-plugin": "^5.5.0",
47 | "image-webpack-loader": "^4.6.0",
48 | "path": "^0.12.7",
49 | "style-loader": "^0.23.1",
50 | "url-loader": "^1.1.2",
51 | "webpack": "^4.29.6",
52 | "webpack-cli": "^3.3.0",
53 | "webpack-dev-server": "^3.2.1"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/pagination.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imranhsayed/react-with-wordpress/36282df72aef5d668832502fe67945fe831bf887/pagination.gif
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 | React App
11 |
12 |
13 | Please enable JavaScript
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/render-posts.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imranhsayed/react-with-wordpress/36282df72aef5d668832502fe67945fe831bf887/render-posts.gif
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require( 'express' );
2 | const cors = require( 'cors' );
3 | const jwt = require('jsonwebtoken');
4 | const bodyParser = require('body-parser');
5 | const config = require( './config' );
6 | const axios = require( 'axios' );
7 | const webpack = require('webpack');
8 | const webpackDevMiddleware = require('webpack-dev-middleware');
9 | const webpackConfig = require('./webpack.config');
10 | const path = require('path')
11 |
12 | const wordPressRestUrl = `${config.wordPressUrl}/${config.wordPressRestNameSpace}`;
13 |
14 | console.log(wordPressRestUrl, 'wordpressurl')
15 |
16 | const app = express();
17 | app.use( cors() );
18 |
19 | // Setting Paths
20 | app.use('/static', express.static(path.join(__dirname, '/')));
21 |
22 | // Setting views of the app
23 | app.set('views', path.join(__dirname, './'));
24 | app.set('view engine', 'pug');
25 |
26 | // Parse application/x-www-form-urlencoded
27 | app.use( bodyParser.urlencoded( { extended: false } ) );
28 |
29 | // Parse application/json
30 | app.use(bodyParser.json());
31 |
32 | // Adding webpack build
33 | // Middleware of webpack
34 | if (process.env.NODE_ENV === 'development') {
35 | console.log('in webpack hot middleware');
36 | const compiler = webpack(webpackConfig);
37 |
38 | app.use(webpackDevMiddleware(compiler, {
39 | noInfo: true,
40 | publicPath: webpackConfig.output.publicPath,
41 | }));
42 | }
43 |
44 |
45 | /**
46 | * Sign in user
47 | *
48 | * @route http://localhost:5000/sign-in
49 | */
50 | app.post( '/sign-in', ( req, res ) => {
51 |
52 | jwt.sign( req.body ,config.tokenSecret , { expiresIn: 3600 }, ( err, token ) => {
53 | if ( ! token ) {
54 | res.json({ success: false, error: 'Token could not be generated' });
55 | } else {
56 |
57 | // Make a login request.
58 | axios.post( `${wordPressRestUrl}/user/login`, req.body )
59 | .then( response => {
60 | res.json( {
61 | success: true,
62 | status: 200,
63 | token,
64 | userData: response.data.user.data
65 | } );
66 | } )
67 | .catch( err => {
68 | const responseReceived = err.response.data;
69 | res.status(404).json({ success: false, status: 400, errorMessage: responseReceived.message });
70 | } );
71 | }
72 |
73 | } );
74 | } );
75 |
76 | /**
77 | * Create a new post
78 | *
79 | * @route http://localhost:5000/create-post
80 | */
81 | app.post( '/create-post', ( req, res ) => {
82 |
83 | jwt.verify(req.body.token, config.tokenSecret, function ( err, decoded ) {
84 | if ( undefined !== decoded ) {
85 |
86 | const postData = {
87 | user_id: req.body.userID,
88 | title: req.body.title,
89 | content: req.body.content,
90 | };
91 |
92 | // Make a create post request.
93 | axios.post( `${wordPressRestUrl}/post/create`, postData )
94 | .then( response => {
95 |
96 | res.json( {
97 | success: true,
98 | status: 200,
99 | userData: response.data
100 | } );
101 | } )
102 | .catch( err => {
103 | const responseReceived = err.response.data;
104 | res.status(404).json({ success: false, status: 400, errorMessage: responseReceived.message });
105 | } );
106 | } else {
107 | res.status( 400 ).json( { success: false, status: 400, errorMessage: 'Authorization failed'} );
108 | }
109 | });
110 | } );
111 |
112 | app.get('/', (req, res) => {
113 | res.render('index')
114 | });
115 |
116 | app.get('/login', (req, res) => {
117 | res.render('index')
118 | });
119 |
120 | app.get('/dashboard/:userName', (req, res) => {
121 | res.render('index')
122 | });
123 |
124 | app.get('/post/:id', (req, res) => {
125 | res.render('index')
126 | });
127 |
128 |
129 | app.listen( process.env.PORT || 5000, () => console.log( 'Listening on port 5000' ) );
130 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import { Router } from "@reach/router";
4 | import Login from "./components/Login";
5 | import Dashboard from "./components/dashboard/Dashboard";
6 | import Home from "./components/Home";
7 | import SinglePost from "./components/SinglePost";
8 | import CreatePost from "./components/dashboard/posts/CreatePost";
9 | import AppProvider from "./components/context/AppProvider";
10 | import Posts from "./components/dashboard/posts/Posts";
11 | import Pages from "./components/dashboard/pages/Pages";
12 | import Blogs from "./components/Blogs";
13 | import Page from "./components/Page";
14 |
15 | class App extends React.Component {
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | export default App;
37 |
--------------------------------------------------------------------------------
/src/client-config.js:
--------------------------------------------------------------------------------
1 | const clientConfig = {
2 | siteUrl: 'https://codeytek.com',
3 | };
4 |
5 | export default clientConfig;
6 |
--------------------------------------------------------------------------------
/src/components/Blogs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Navbar from "./Navbar";
3 | import { Posts } from "./Posts";
4 |
5 | const Blogs = () => {
6 |
7 | return (
8 |
9 |
10 |
11 |
12 | )
13 | };
14 |
15 | export default Blogs;
16 |
--------------------------------------------------------------------------------
/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Navbar from "./Navbar";
3 | import axios from 'axios';
4 | import Loader from "../loader.gif";
5 | import renderHTML from 'react-render-html';
6 | import Moment from 'react-moment';
7 | import { Link } from '@reach/router';
8 | import clientConfig from '../client-config';
9 |
10 | class Home extends React.Component {
11 |
12 | constructor( props ) {
13 | super( props );
14 |
15 | this.state = {
16 | loading : false,
17 | posts: [],
18 | error: ''
19 | };
20 | }
21 |
22 | createMarkup = ( data ) => ({
23 | __html: data
24 | });
25 |
26 | componentDidMount() {
27 | const wordPressSiteURL = clientConfig.siteUrl;
28 |
29 | this.setState( { loading: true }, () => {
30 | axios.get( `${wordPressSiteURL}/wp-json/wp/v2/posts/` )
31 | .then( res => {
32 | if ( 200 === res.status ) {
33 | if ( res.data.length ) {
34 | this.setState( { loading: false, posts: res.data } );
35 | } else {
36 | this.setState( { loading: false, error: 'No Posts Found' } );
37 | }
38 | }
39 |
40 | } )
41 | .catch( err => this.setState( { loading: false, error: err } ) );
42 | } )
43 | }
44 |
45 | render() {
46 |
47 | const { loading, posts, error } = this.state;
48 |
49 | return(
50 |
51 |
52 | { error &&
}
53 | { posts.length ? (
54 |
55 | { posts.map( post => (
56 |
57 |
58 |
59 | {renderHTML( post.title.rendered )}
60 |
61 |
62 |
63 |
{ renderHTML( post.excerpt.rendered ) }
64 |
65 |
66 | {post.date}
67 |
68 | Read More...
69 |
70 |
71 |
72 | ) ) }
73 |
74 | ) : '' }
75 | { loading && }
76 |
77 | );
78 | }
79 | }
80 |
81 | export default Home;
82 |
--------------------------------------------------------------------------------
/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from 'react';
2 | import Navbar from "./Navbar";
3 | import { Redirect } from "@reach/router";
4 | import Loader from "../loader.gif";
5 | import axios from 'axios';
6 | import clientConfig from '../client-config';
7 | import AppContext from "./context/AppContext";
8 |
9 | const Login = () => {
10 |
11 | const [ store, setStore ] = useContext( AppContext );
12 |
13 | const [ loginFields, setLoginFields ] = useState({
14 | username: '',
15 | password: '',
16 | userNiceName: '',
17 | userEmail: '',
18 | loading: false,
19 | error: ''
20 | });
21 |
22 | const createMarkup = ( data ) => ({
23 | __html: data
24 | });
25 |
26 | const onFormSubmit = ( event ) => {
27 | event.preventDefault();
28 |
29 | const siteUrl = clientConfig.siteUrl;
30 |
31 | const loginData = {
32 | username: loginFields.username,
33 | password: loginFields.password,
34 | };
35 |
36 | setLoginFields( { ...loginFields, loading: true } );
37 |
38 | axios.post( `${siteUrl}/wp-json/jwt-auth/v1/token`, loginData )
39 | .then( res => {
40 |
41 | if ( undefined === res.data.token ) {
42 | setLoginFields( {
43 | ...loginFields,
44 | error: res.data.message,
45 | loading: false }
46 | );
47 | return;
48 | }
49 |
50 | const { token, user_nicename, user_email } = res.data;
51 |
52 | localStorage.setItem( 'token', token );
53 | localStorage.setItem( 'userName', user_nicename );
54 |
55 | setStore({
56 | ...store,
57 | userName: user_nicename,
58 | token: token
59 | });
60 |
61 | setLoginFields( {
62 | ...loginFields,
63 | loading: false,
64 | token: token,
65 | userNiceName: user_nicename,
66 | userEmail: user_email,
67 | } )
68 | } )
69 | .catch( err => {
70 | setLoginFields( { ...loginFields, error: err.response.data.message, loading: false } );
71 | } )
72 | };
73 |
74 | const handleOnChange = ( event ) => {
75 | setLoginFields( { ...loginFields, [event.target.name]: event.target.value } );
76 | };
77 |
78 |
79 | const { username, password, userNiceName, error, loading } = loginFields;
80 |
81 | if ( store.token ) {
82 | return ( )
83 | } else {
84 | return (
85 |
86 |
87 |
88 |
Login
89 | { error &&
}
90 |
116 |
117 |
118 | )
119 | }
120 | };
121 |
122 | export default Login;
123 |
--------------------------------------------------------------------------------
/src/components/NavLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from "@reach/router";
3 |
4 | /**
5 | * Reach Routers gives us access to a function called getProps.
6 | * Whatever is returned by getProps(), in this case style,
7 | * will be applied to the Link attribute as props.
8 | * So here {...props} will be replaced by style: {}
9 | *
10 | * @param props
11 | * @return {*}
12 | * @constructor
13 | */
14 | const NavLink = props => (
15 | ( { style: { color: isCurrent ? '#fff' : '#fffc' } } )}
18 | className="nav-link"
19 | />
20 | );
21 |
22 | export default NavLink;
23 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import NavLink from './NavLink';
3 | import { isLoggedIn } from "./functions";
4 | import ToggleSidebarBtn from "./dashboard/sidebar/ToggleSidebarBtn";
5 | import AppContext from "./context/AppContext";
6 |
7 | const Navbar = () => {
8 |
9 | const [ store, setStore ] = useContext( AppContext );
10 |
11 | const handleLogout = () => {
12 | localStorage.removeItem( 'token' );
13 | localStorage.removeItem( 'useName' );
14 |
15 | setStore( {
16 | ...store,
17 | token: '',
18 | useName: ''
19 | } );
20 | window.location.href = '/';
21 | };
22 |
23 | return (
24 |
25 |
26 |
27 |
28 | Home
29 |
30 |
31 | Blogs
32 |
33 | { isLoggedIn() ? (
34 |
35 |
36 | Dashboard
37 |
38 |
39 | Logout
40 |
41 |
42 | ) : (
43 |
44 | Login
45 |
46 | ) }
47 |
48 |
49 |
50 | {/* If on dashboard page */}
51 | { window.location.pathname.includes( 'dashboard' ) ? (
52 |
53 | ) : ''}
54 |
55 | );
56 | };
57 |
58 | export default Navbar;
59 |
--------------------------------------------------------------------------------
/src/components/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Navbar from "./Navbar";
3 | import Posts from "./Posts";
4 |
5 | const Page = ( props ) => {
6 |
7 | const { id } = props;
8 |
9 | return (
10 |
11 |
12 |
13 |
14 | )
15 | };
16 |
17 | export default Page;
18 |
--------------------------------------------------------------------------------
/src/components/Posts.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import clientConfig from "../client-config";
3 | import axios from "axios";
4 | import Loader from "./layouts/Loader";
5 | import { Post } from "./layouts/Post";
6 | import { Pagination } from "./layouts/Pagination";
7 | import PostLoader from "./layouts/PostLoader";
8 |
9 | export const Posts = ( props ) => {
10 |
11 | const pageId = parseInt( props.pageId );
12 |
13 | const [currentPage, setCurrentPage] = useState( pageId );
14 |
15 | const [totalPages, setTotalPages] = useState( 1 );
16 | const [loading, setLoading] = useState( false );
17 | const [errMessage, setError] = useState( '' );
18 | const [posts, setPosts] = useState( null );
19 |
20 | useEffect( () => {
21 |
22 | const wordPressSiteURL = clientConfig.siteUrl;
23 |
24 | setLoading( true );
25 |
26 | axios.get( `${ wordPressSiteURL }/wp-json/rae/v1/posts?page_no=${ currentPage }` )
27 | .then( res => {
28 |
29 | setLoading( false );
30 |
31 | if ( 200 === res.data.status ) {
32 | setPosts( res.data.posts_data );
33 | setTotalPages( res.data.page_count )
34 | } else {
35 | setError( 'No posts found' );
36 | }
37 | } )
38 | .catch( err => {
39 | setError( err.response.data.message );
40 | } );
41 |
42 | }, [currentPage] );
43 |
44 | const getPosts = ( posts ) => {
45 | return posts.map( post => );
46 | };
47 |
48 | return (
49 |
50 | { loading ? : '' }
51 |
52 | { ( !loading && null !== posts && posts.length ) ? (
53 |
54 | { getPosts( posts ) }
55 |
60 |
61 | ) :
{ errMessage }
}
62 |
63 |
64 | )
65 |
66 | };
67 |
68 | export default Posts;
69 |
--------------------------------------------------------------------------------
/src/components/SinglePost.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Navbar from "./Navbar";
3 | import renderHTML from "react-render-html";
4 | import Moment from "react-moment";
5 | import Loader from "../loader.gif";
6 | import axios from "axios";
7 | import clientConfig from "../client-config";
8 |
9 | class SinglePost extends React.Component {
10 |
11 | constructor( props ) {
12 | super( props );
13 |
14 | this.state = {
15 | loading : false,
16 | post: {},
17 | error: ''
18 | };
19 | }
20 |
21 | createMarkup = ( data ) => ({
22 | __html: data
23 | });
24 |
25 | componentDidMount() {
26 | const wordPressSiteURL = clientConfig.siteUrl;
27 |
28 | this.setState( { loading: true }, () => {
29 | axios.get( `${wordPressSiteURL}/wp-json/wp/v2/posts/${this.props.id}` )
30 | .then( res => {
31 |
32 | if ( Object.keys( res.data ).length ) {
33 | this.setState( { loading: false, post: res.data } );
34 | } else {
35 | this.setState( { loading: false, error: 'No Posts Found' } );
36 | }
37 | } )
38 | .catch( err => this.setState( { loading: false, error: err.response.data.message } ) );
39 | } )
40 | }
41 |
42 | render() {
43 |
44 | const { loading, post, error } = this.state;
45 |
46 | return(
47 |
48 |
49 | { error &&
}
50 | { Object.keys( post ).length ? (
51 |
52 |
53 |
54 | {renderHTML( post.title.rendered )}
55 |
56 |
57 |
{ renderHTML( post.content.rendered ) }
58 |
59 |
{post.date}
60 |
61 |
62 | ) : '' }
63 | { loading && }
64 |
65 | )
66 | }
67 | }
68 |
69 | export default SinglePost;
70 |
--------------------------------------------------------------------------------
/src/components/content/Content.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import Navbar from "../Navbar";
3 | import AppContext from "../context/AppContext";
4 |
5 | const Content = ( props ) => {
6 |
7 | const [ store, setStore ] = useContext( AppContext );
8 |
9 | return (
10 |
11 | {/* Top Navbar */}
12 |
13 | {/* Main Content */}
14 |
15 | { props.children }
16 |
17 |
18 | )
19 | };
20 |
21 | export default Content;
22 |
--------------------------------------------------------------------------------
/src/components/context/AppContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext } from 'react';
2 |
3 | const AppContext = createContext([
4 | {},
5 | () => {}
6 | ]);
7 |
8 | export default AppContext;
9 |
--------------------------------------------------------------------------------
/src/components/context/AppProvider.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import AppContext from "./AppContext";
3 |
4 | const AppProvider = ( props ) => {
5 |
6 | const [ store, setStore ] = useState( {
7 | userName: '',
8 | token: '',
9 | activeMenu: {},
10 | sidebarActive: true
11 | } );
12 |
13 | useEffect( () => {
14 | const token = localStorage.getItem( 'token' );
15 | const userName = localStorage.getItem( 'userName' );
16 |
17 | setStore( { ...store, token, userName } );
18 |
19 | }, [] );
20 |
21 | return (
22 |
23 | { props.children }
24 |
25 | )
26 |
27 | };
28 |
29 | export default AppProvider;
30 |
--------------------------------------------------------------------------------
/src/components/dashboard/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DashboardLayout from "./../layouts/DashboardLayout";
3 | import { getUserName } from "../functions";
4 |
5 | const Dashboard = ( props ) => {
6 |
7 | const userName = ( getUserName() ) ? getUserName() : '';
8 |
9 | return(
10 |
11 | { userName ? Welcome { userName }!! : '' }
12 |
13 | )
14 | };
15 |
16 | export default Dashboard;
17 |
--------------------------------------------------------------------------------
/src/components/dashboard/pages/Pages.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DashboardLayout from "../../layouts/DashboardLayout";
3 |
4 | const Pages = () => {
5 | return (
6 |
7 | Pages
8 |
9 | )
10 | };
11 |
12 | export default Pages;
13 |
--------------------------------------------------------------------------------
/src/components/dashboard/posts/CreatePost.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DashboardLayout from "../../layouts/DashboardLayout";
3 | import Loader from '../../../loader.gif';
4 | import clientConfig from "../../../client-config";
5 | import axios from 'axios';
6 |
7 | class CreatePost extends React.Component {
8 |
9 | constructor( props ) {
10 | super( props );
11 |
12 | this.state = {
13 | title: '',
14 | content: '',
15 | postCreated: false,
16 | loading: false,
17 | message: '',
18 | }
19 |
20 | }
21 |
22 | createMarkup = ( data ) => ({
23 | __html: data
24 | });
25 |
26 | handleFormSubmit = ( event ) => {
27 | event.preventDefault();
28 |
29 | this.setState( { loading: true } );
30 |
31 | const formData = {
32 | title: this.state.title,
33 | content: this.state.content,
34 | status: 'publish'
35 | };
36 |
37 | const wordPressSiteUrl = clientConfig.siteUrl;
38 | const authToken = localStorage.getItem( 'token' );
39 |
40 | // Post request to create a post
41 | axios.post( `${ wordPressSiteUrl }/wp-json/wp/v2/posts`, formData, {
42 | headers: {
43 | 'Content-Type': 'application/json',
44 | 'Authorization': `Bearer ${ authToken }`
45 | }
46 | } )
47 | .then( res => {
48 |
49 | this.setState( {
50 | loading: false,
51 | postCreated: !! res.data.id,
52 | message: res.data.id ? 'New post created' : ''
53 | } )
54 | } )
55 | .catch( err => {{
56 |
57 | this.setState( { loading: false, message: err.response.data.message } )
58 | }} )
59 | };
60 |
61 | handleInputChange = ( event ) => {
62 |
63 | this.setState( { [ event.target.name ]: event.target.value } );
64 |
65 | };
66 |
67 | render() {
68 |
69 | const { loading, message, postCreated } = this.state;
70 |
71 | return(
72 |
73 |
94 |
95 | )
96 | }
97 | }
98 | export default CreatePost;
99 |
--------------------------------------------------------------------------------
/src/components/dashboard/posts/Posts.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DashboardLayout from "../../layouts/DashboardLayout";
3 |
4 | const Posts = () => {
5 | return (
6 |
7 | Posts
8 |
9 | )
10 | };
11 |
12 | export default Posts;
13 |
--------------------------------------------------------------------------------
/src/components/dashboard/sidebar/SidebarMenu.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import NavLink from "../../NavLink";
3 | import AppContext from "../../context/AppContext";
4 | import PostMenu from "./menus/PostMenu";
5 | import PageMenu from "./menus/PageMenu";
6 |
7 | const SidebarMenu = () => {
8 |
9 | const [ store, setStore ] = useContext( AppContext );
10 |
11 | return (
12 |
13 |
29 |
30 | )
31 | };
32 |
33 | export default SidebarMenu;
34 |
--------------------------------------------------------------------------------
/src/components/dashboard/sidebar/ToggleSidebarBtn.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import AppContext from "../../context/AppContext";
3 |
4 | const ToggleSidebarBtn = () => {
5 |
6 | const [ store, setStore ] = useContext( AppContext );
7 |
8 | return (
9 |
15 | )
16 | };
17 |
18 | export default ToggleSidebarBtn;
19 |
--------------------------------------------------------------------------------
/src/components/dashboard/sidebar/menus/PageMenu.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import NavLink from "../../../NavLink";
3 | import AppContext from "../../../context/AppContext";
4 |
5 | const PageMenu = () => {
6 |
7 | const [ store, setStore ] = useContext( AppContext );
8 |
9 | return(
10 |
11 | setStore({
17 | ...store,
18 | activeMenu: { pageMenuActive: ! store.activeMenu.pageMenuActive }
19 | }) }
20 | >
21 | Pages
22 |
23 |
31 |
32 | )
33 | };
34 |
35 | export default PageMenu;
36 |
--------------------------------------------------------------------------------
/src/components/dashboard/sidebar/menus/PostMenu.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import NavLink from "../../../NavLink";
3 | import AppContext from "../../../context/AppContext";
4 |
5 | const PostMenu = () => {
6 |
7 | const [ store, setStore ] = useContext( AppContext );
8 |
9 | return(
10 |
11 | setStore({
17 | ...store,
18 | activeMenu: { postMenuActive: ! store.activeMenu.postMenuActive }
19 | }) }
20 | >
21 | Posts
22 |
23 |
31 |
32 | )
33 | };
34 |
35 | export default PostMenu;
36 |
--------------------------------------------------------------------------------
/src/components/functions.js:
--------------------------------------------------------------------------------
1 | export const isLoggedIn = () => {
2 | return localStorage.getItem( 'token' );
3 | };
4 |
5 | export const getUserName = () => (
6 | localStorage.getItem( 'userName' )
7 | );
8 |
--------------------------------------------------------------------------------
/src/components/layouts/DashboardLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SidebarMenu from "./../dashboard/sidebar/SidebarMenu";
3 | import Content from "./../content/Content";
4 |
5 | const DashboardLayout = ( props ) => {
6 |
7 | return(
8 |
9 |
10 |
11 | { props.children }
12 |
13 |
14 | )
15 | };
16 |
17 | export default DashboardLayout;
18 |
--------------------------------------------------------------------------------
/src/components/layouts/FeaturedImage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const FeaturedImage = ( props ) => {
4 |
5 | const { img_sizes, img_src, img_srcset } = props.image;
6 | const { title } = props.title;
7 |
8 | return (
9 |
16 | )
17 | };
18 |
19 | export default FeaturedImage;
20 |
--------------------------------------------------------------------------------
/src/components/layouts/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LoaderImg from './../../loader.gif';
3 |
4 | const Loader = () => (
5 |
6 | );
7 |
8 | export default Loader;
9 |
--------------------------------------------------------------------------------
/src/components/layouts/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from '@reach/router';
3 | import { createPaginationArray } from "../../utils/functions";
4 |
5 | export const Pagination = ( props ) => {
6 |
7 | const { currentPage, totalPages, setCurrentPage } = props;
8 |
9 | const isThereNextPage = currentPage < totalPages;
10 | const isTherePreviousPage = currentPage > 1;
11 | const paginationArray = createPaginationArray( currentPage, totalPages );
12 |
13 | const getPageLink = ( pageNo ) => {
14 | return `/page/${pageNo}`;
15 | };
16 |
17 | return (
18 |
19 | { isTherePreviousPage && setCurrentPage( currentPage - 1 ) } className="prev">Previous }
20 |
21 | { paginationArray &&
22 | paginationArray.map( ( item, index ) => {
23 | // If item is not equal to '...' and the item value is not equal to current page.
24 | if ( '...' !== item && currentPage !== item ) {
25 |
26 | return (
27 | // Page number links.
28 |
29 | setCurrentPage( item ) }
32 | >
33 | { item }
34 |
35 |
36 | );
37 | } else {
38 | return (
39 | // if its '...' or the current page, it should not be clickable ( should not be a link )
40 |
41 | { item }
42 |
43 | );
44 | }
45 | } ) }
46 | { isThereNextPage && setCurrentPage( currentPage + 1 ) } className="next">Next }
47 |
48 | )
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/layouts/Post.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FeaturedImage from "./FeaturedImage";
3 | import { Link } from '@reach/router';
4 | import { sanitize } from "../../utils/functions";
5 |
6 | export const Post = ( props ) => {
7 |
8 | const { post } = props;
9 |
10 | return (
11 |
12 |
13 | {/*TItle*/}
14 | { post.title ?
{ post.title } : '' }
15 |
16 | {/*Featured Image*/}
17 | { post.attachment_image.img_sizes ?
: '' }
18 |
19 | {/*Excerpt*/}
20 | { post.title ?
: '' }
21 |
22 | {/*Post meta*/}
23 |
24 | {/*Author*/}
25 |
26 |
27 |
{ post.meta.author_name }
28 |
29 | {/*Date*/}
30 |
31 |
32 | { post.date }
33 |
34 |
35 | {/* Categories*/}
36 | { post.categories.length ? (
37 |
38 |
39 | {
40 | post.categories.map( ( category, index ) => {
41 | return (
42 | // If its not the last item.
43 | index !== ( post.categories.length - 1 ) ?
44 | { category.name },
45 | :
46 | { category.name }
47 | );
48 | } )
49 | }
50 |
51 | ) : '' }
52 |
53 |
54 | )
55 | };
56 |
--------------------------------------------------------------------------------
/src/components/layouts/PostLoader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PostLoader = () => (
4 |
16 | );
17 |
18 | export default PostLoader;
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from "./App";
4 |
5 | ReactDOM.render( , document.getElementById('root') );
6 |
--------------------------------------------------------------------------------
/src/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imranhsayed/react-with-wordpress/36282df72aef5d668832502fe67945fe831bf887/src/loader.gif
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700";
2 |
3 | body {
4 | font-family: 'Poppins', sans-serif;
5 | background: #fafafa;
6 | }
7 |
8 | p {
9 | font-family: 'Poppins', sans-serif;
10 | font-size: 1.1em;
11 | font-weight: 300;
12 | line-height: 1.7em;
13 | color: #999;
14 | }
15 |
16 | a,
17 | a:hover,
18 | a:focus {
19 | color: inherit;
20 | text-decoration: none;
21 | transition: all 0.3s;
22 | }
23 |
24 | .container.blog {
25 | max-width: 960px;
26 | }
27 |
28 | .navbar {
29 | padding: 15px 10px;
30 | background: #eee;
31 | border: none;
32 | border-radius: 0;
33 | margin-bottom: 40px;
34 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
35 | }
36 |
37 | .navbar-btn {
38 | box-shadow: none;
39 | outline: none !important;
40 | border: none;
41 | }
42 |
43 | .line {
44 | width: 100%;
45 | height: 1px;
46 | border-bottom: 1px dashed #ddd;
47 | margin: 40px 0;
48 | }
49 |
50 | /* ---------------------------------------------------
51 | SIDEBAR STYLE
52 | ----------------------------------------------------- */
53 |
54 | .wrapper {
55 | display: flex;
56 | width: 100%;
57 | }
58 |
59 | #sidebar {
60 | width: 250px;
61 | position: fixed;
62 | top: 0;
63 | left: 0;
64 | height: 100vh;
65 | z-index: 999;
66 | background: #24404d;
67 | color: #fff;
68 | transition: all 0.3s;
69 | margin-left: -250px;
70 | }
71 |
72 | #sidebar.active {
73 | margin-left: 0;
74 | }
75 |
76 | #sidebar .sidebar-header {
77 | padding: 8px 20px;
78 | background: #50b8dc;
79 | }
80 |
81 | #sidebar ul.components {
82 | padding: 20px 0;
83 | border-bottom: 1px solid #304d5a;
84 | }
85 |
86 | #sidebar ul p {
87 | color: #fff;
88 | padding: 10px;
89 | }
90 |
91 | #sidebar ul li a {
92 | padding: 10px;
93 | font-size: 1.1em;
94 | display: block;
95 | }
96 |
97 | #sidebar ul li a:hover {
98 | color: #304d5a !important;
99 | background: #fff;
100 | }
101 |
102 | #sidebar ul li.active>a,
103 | a[aria-expanded="true"] {
104 | color: #fff;
105 | background: #50b8dc;
106 | }
107 |
108 | a[data-toggle="collapse"] {
109 | position: relative;
110 | }
111 |
112 | .dropdown-toggle::after {
113 | display: block;
114 | position: absolute;
115 | top: 50%;
116 | right: 20px;
117 | transform: translateY(-50%);
118 | }
119 |
120 | ul ul a {
121 | font-size: 0.9em !important;
122 | padding-left: 30px !important;
123 | background: #385868
124 | }
125 |
126 | ul.CTAs {
127 | padding: 20px;
128 | }
129 |
130 | ul.CTAs a {
131 | text-align: center;
132 | font-size: 0.9em !important;
133 | display: block;
134 | border-radius: 5px;
135 | margin-bottom: 5px;
136 | }
137 |
138 | a.download {
139 | background: #fff;
140 | color: #7386D5;
141 | }
142 |
143 | a.article,
144 | a.article:hover {
145 | background: #50b8dc !important;
146 | color: #fff !important;
147 | }
148 |
149 | /**
150 | * Content.
151 | */
152 | #content {
153 | width: calc(100% - 250px);
154 | min-height: 100vh;
155 | transition: all 0.3s;
156 | position: absolute;
157 | top: 0;
158 | right: 0;
159 | }
160 |
161 | #content.active {
162 | width: 100%;
163 | }
164 |
165 | /**
166 | * Media Query.
167 | */
168 | @media (max-width: 768px) {
169 | #sidebar {
170 | margin-left: -250px;
171 | }
172 | #sidebar.active {
173 | margin-left: 0;
174 | }
175 | #content {
176 | width: 100%;
177 | }
178 | #content.active {
179 | width: calc(100% - 250px);
180 | }
181 | #sidebarCollapse span {
182 | display: none;
183 | }
184 | }
185 |
186 | .loader {
187 | position: absolute;
188 | left: 0;
189 | right: 0;
190 | top: 0;
191 | bottom: 0;
192 | margin: auto;
193 | width: 100px;
194 | }
195 |
196 | .posts-container {
197 | margin-left: 16px;
198 | }
199 |
200 | .post-content img{
201 | width: 100%;
202 | }
203 |
204 | .main-navbar {
205 | background-color: #50b8dc;
206 | display: flex;
207 | justify-content: flex-end;
208 | }
209 |
210 | .main-content {
211 | padding: 0 24px;
212 | }
213 |
214 | .sidebar-header .nav-link {
215 | font-size: 24px !important;
216 | }
217 |
218 | @media only screen and (max-width: 1020px) {
219 |
220 | .navbar-expand-lg .navbar-nav {
221 | flex-direction: row;
222 | }
223 |
224 | .navbar-expand-lg .nav-item {
225 | padding-right: 16px;
226 | }
227 | }
228 |
229 | /* posts */
230 | .post-wrapper {
231 | background-color: #fff;
232 | margin-bottom: 36px;
233 | padding: 24px 36px;
234 | }
235 |
236 | .post-title {
237 | margin-bottom: 16px;
238 | color: #00528a;
239 | font-size: 36px;
240 | display: inline-block;
241 | }
242 |
243 | .post-wrapper img {
244 | width: 100%;
245 | height: auto;
246 | }
247 |
248 | .post-excerpt {
249 | color: #4a4a4a;
250 | }
251 |
252 | .post-meta {
253 | display: flex;
254 | padding: 24px 0;
255 | }
256 |
257 | .post-meta > * {
258 | padding-right: 16px;
259 | }
260 |
261 | .post-meta > * > i {
262 | padding-right: 10px;
263 | }
264 |
265 | .pagination {
266 | display: flex;
267 | justify-content: flex-start;
268 | align-items: center;
269 | margin: 36px 0 64px 0;
270 | }
271 |
272 | .page-no {
273 | padding: 8px 10px;
274 | background-color: #fff;
275 | border: 2px solid #f3f4f4;
276 | margin: 0 8px;
277 | width: 50px;
278 | height: 50px;
279 | display: inline-flex;
280 | justify-content: center;
281 | align-items: center;
282 | transition: 0.3s;
283 | }
284 |
285 | .page-no.active {
286 | font-weight: 800;
287 | background-color: #00528a;
288 | color: #fff;
289 | }
290 |
291 | .prev,
292 | .next {
293 | padding: 8px 10px;
294 | background-color: #00528a;
295 | color: #fff;
296 | border: 2px solid #f3f4f4;
297 | margin: 0 8px;
298 | width: 100px;
299 | height: 50px;
300 | display: inline-flex;
301 | justify-content: center;
302 | align-items: center;
303 | }
304 |
305 | .page-no:hover {
306 | background-color: #00528a;
307 | color: #fff;
308 | }
309 |
310 | .prev:hover,
311 | .next:hover {
312 | color: #50b8dc;
313 | }
314 |
315 | /*Post Loader css*/
316 | @keyframes gradientBG {
317 | 0% {
318 | background-position: 0% 50%;
319 | }
320 | 50% {
321 | background-position: 100% 50%;
322 | }
323 | 100% {
324 | background-position: 0% 50%;
325 | }
326 | }
327 |
328 | .loader-title {
329 | display: block;
330 | height: 54px;
331 | background-image: linear-gradient(-45deg, #fff, #f3f4f4, #fff, #f3f4f4 );
332 | background-size: 400% 400%;
333 | animation: gradientBG 3s ease infinite;
334 | }
335 |
336 | .loader-img {
337 | display: block;
338 | height: 300px;
339 | background-image: linear-gradient(-45deg, #fff, #f3f4f4, #fff, #f3f4f4 );
340 | background-size: 400% 400%;
341 | animation: gradientBG 3s ease infinite;
342 | }
343 |
344 | .loader-excerpt {
345 | display: block;
346 | height: 100px;
347 | background-image: linear-gradient(-45deg, #fff, #f3f4f4, #fff, #f3f4f4 );
348 | background-size: 400% 400%;
349 | animation: gradientBG 3s ease infinite;
350 | }
351 |
352 | .loader-author,
353 | .loader-date,
354 | .loader-category {
355 | display: block;
356 | width: 100px;
357 | height: 24px;
358 | background-image: linear-gradient(-45deg, #fff, #f3f4f4, #fff, #f3f4f4 );
359 | background-size: 400% 400%;
360 | animation: gradientBG 3s ease infinite;
361 | margin-right: 20px;
362 | }
363 |
--------------------------------------------------------------------------------
/src/utils/functions.js:
--------------------------------------------------------------------------------
1 | import DOMPurify from 'dompurify';
2 |
3 | /**
4 | * Create pagination array.
5 | *
6 | * Example: [1, "...", 521, 522, 523, 524, 525, "...", 529]
7 | *
8 | * @param {int} currentPage Current page no.
9 | * @param {int} totalPages Count of total no of pages.
10 | * @return {Array} Array containing the indexes to be looped through to create pagination
11 | */
12 | export const createPaginationArray = ( currentPage, totalPages ) => {
13 |
14 | let loopableArray = [];
15 | let countOfDotItems = 0;
16 |
17 | // If there is only one page, return an empty array.
18 | if ( 1 === totalPages ) {
19 | return loopableArray;
20 | }
21 |
22 | /**
23 | * Push the two index items before the current page.
24 | */
25 | if ( 0 < currentPage - 2 ) {
26 | loopableArray.push( currentPage - 2 );
27 | }
28 |
29 | if ( 0 < currentPage - 1 ) {
30 | loopableArray.push( currentPage - 1 );
31 | }
32 |
33 | // Push the current page index item.
34 | loopableArray.push( currentPage );
35 |
36 | /**
37 | * Push the two index items after the current page.
38 | */
39 | if ( totalPages >= currentPage + 1 ) {
40 | loopableArray.push( currentPage + 1 );
41 | }
42 |
43 | if ( totalPages >= currentPage + 2 ) {
44 | loopableArray.push( currentPage + 2 );
45 | }
46 |
47 | /**
48 | * Push the '...' at the beginning of the array
49 | * only if the difference of between the 1st and 2nd index item is greater than 1.
50 | */
51 | if ( 1 < loopableArray[ 0 ] - 1 ) {
52 | loopableArray.unshift( '...' );
53 | countOfDotItems += 1;
54 | }
55 |
56 | /**
57 | * Push the '...' at the end of the array.
58 | * only if the difference of between the last and 2nd last item is greater than 1.
59 | * We remove the count of dot items from the array to get the actual indexes, while checking the condition.
60 | */
61 | if (
62 | 1 <
63 | totalPages - loopableArray[ loopableArray.length - ( 2 - countOfDotItems ) ]
64 | ) {
65 | loopableArray.push( '...' );
66 | }
67 |
68 | // Push first index item in the array if it does not already exists.
69 | if ( -1 === loopableArray.indexOf( 1 ) ) {
70 | loopableArray.unshift( 1 );
71 | }
72 |
73 | // Push last index item in the array if it does not already exists.
74 | if ( -1 === loopableArray.indexOf( totalPages ) ) {
75 | loopableArray.push( totalPages );
76 | }
77 |
78 | return loopableArray;
79 | };
80 |
81 | /**
82 | * Sanitize markup or text when used inside dangerouslysetInnerHTML
83 | *
84 | * @param {string} content Plain or html string.
85 | *
86 | * @return {string} Sanitized string
87 | */
88 | export const sanitize = (content) => {
89 | return process.browser ? DOMPurify.sanitize(content) : content
90 | }
91 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebPackPlugin = require( 'html-webpack-plugin' );
2 | const path = require( 'path' );
3 | module.exports = {
4 | context: __dirname,
5 | entry: './src/index.js',
6 | output: {
7 | path: path.resolve( __dirname, 'dist' ),
8 | filename: 'main.js',
9 | publicPath: "/"
10 | },
11 | devServer: {
12 | historyApiFallback: true
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | use: 'babel-loader',
19 | },
20 | {
21 | test: /\.css$/,
22 | use: ['style-loader', 'css-loader'],
23 | },
24 | {
25 | test: /\.(png|jp?g|svg|gif)$/,
26 | use: [{
27 | loader: "file-loader"
28 | }]
29 | }
30 | ]
31 | },
32 | plugins: [
33 | new HtmlWebPackPlugin({
34 | template: path.resolve( __dirname, 'public/index.html' ),
35 | filename: 'index.html'
36 | })
37 | ]
38 | };
39 |
--------------------------------------------------------------------------------