├── .gitignore
├── static
├── .DS_Store
├── images
│ ├── nextjs.jpg
│ ├── nodejs.jpg
│ ├── reactjs.jpg
│ ├── seoblog.jpg
│ └── mountain.jpg
└── css
│ └── styles.css
├── components
├── Layout.js
├── auth
│ ├── Private.js
│ ├── Admin.js
│ ├── LoginGoogle.js
│ ├── LoginFacebook.js
│ ├── SigninComponent.js
│ ├── SignupComponent.js
│ └── ProfileUpdate.js
├── DisqusThread.js
├── blog
│ ├── SmallCard.js
│ ├── Card.js
│ └── Search.js
├── crud
│ ├── BlogRead.js
│ ├── Tag.js
│ ├── Category.js
│ ├── BlogCreate.js
│ └── BlogUpdate.js
├── Header.js
└── form
│ └── ContactForm.js
├── pages
├── user
│ ├── update.js
│ ├── crud
│ │ ├── [slug].js
│ │ ├── blog.js
│ │ └── blogs.js
│ └── index.js
├── signup.js
├── contact.js
├── admin
│ ├── crud
│ │ ├── blogs.js
│ │ ├── [slug].js
│ │ ├── blog.js
│ │ └── category-tag.js
│ └── index.js
├── signin.js
├── _document.js
├── search.js
├── auth
│ ├── account
│ │ └── activate
│ │ │ └── [id].js
│ └── password
│ │ ├── forgot.js
│ │ └── reset
│ │ └── [id].js
├── tags
│ └── [slug].js
├── categories
│ └── [slug].js
├── profile
│ └── [username].js
├── blogs
│ ├── index.js
│ └── [slug].js
└── index.js
├── config.js
├── actions
├── form.js
├── user.js
├── tag.js
├── category.js
├── blog.js
└── auth.js
├── helpers
└── quill.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | next.config.js
4 | .env
--------------------------------------------------------------------------------
/static/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/seoblog/HEAD/static/.DS_Store
--------------------------------------------------------------------------------
/static/images/nextjs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/seoblog/HEAD/static/images/nextjs.jpg
--------------------------------------------------------------------------------
/static/images/nodejs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/seoblog/HEAD/static/images/nodejs.jpg
--------------------------------------------------------------------------------
/static/images/reactjs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/seoblog/HEAD/static/images/reactjs.jpg
--------------------------------------------------------------------------------
/static/images/seoblog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/seoblog/HEAD/static/images/seoblog.jpg
--------------------------------------------------------------------------------
/static/images/mountain.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/seoblog/HEAD/static/images/mountain.jpg
--------------------------------------------------------------------------------
/components/Layout.js:
--------------------------------------------------------------------------------
1 | import Header from './Header';
2 |
3 | const Layout = ({ children }) => {
4 | return (
5 |
6 |
7 | {children}
8 |
9 | );
10 | };
11 |
12 | export default Layout;
13 |
--------------------------------------------------------------------------------
/components/auth/Private.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Router from 'next/router';
3 | import { isAuth } from '../../actions/auth';
4 |
5 | const Private = ({ children }) => {
6 | useEffect(() => {
7 | if (!isAuth()) {
8 | Router.push(`/signin`);
9 | }
10 | }, []);
11 | return {children};
12 | };
13 |
14 | export default Private;
15 |
--------------------------------------------------------------------------------
/components/auth/Admin.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Router from 'next/router';
3 | import { isAuth } from '../../actions/auth';
4 |
5 | const Admin = ({ children }) => {
6 | useEffect(() => {
7 | if (!isAuth()) {
8 | Router.push(`/signin`);
9 | } else if (isAuth().role !== 1) {
10 | Router.push(`/`);
11 | }
12 | }, []);
13 | return {children};
14 | };
15 |
16 | export default Admin;
17 |
--------------------------------------------------------------------------------
/pages/user/update.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/Layout';
2 | import Private from '../../components/auth/Private';
3 | import ProfileUpdate from '../../components/auth/ProfileUpdate';
4 | import Link from 'next/link';
5 |
6 | const UserProfileUpdate = () => {
7 | return (
8 |
9 |
10 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default UserProfileUpdate;
21 |
--------------------------------------------------------------------------------
/pages/signup.js:
--------------------------------------------------------------------------------
1 | import Layout from '../components/Layout';
2 | import SignupComponent from '../components/auth/SignupComponent';
3 | import Link from 'next/link';
4 |
5 | const Signup = () => {
6 | return (
7 |
8 |
16 |
17 | );
18 | };
19 |
20 | export default Signup;
21 |
--------------------------------------------------------------------------------
/pages/contact.js:
--------------------------------------------------------------------------------
1 | import Layout from '../components/Layout';
2 | import Link from 'next/link';
3 | import ContactForm from '../components/form/ContactForm';
4 |
5 | const Contact = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
Contact form
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Contact;
22 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | import getConfig from 'next/config';
2 | const { publicRuntimeConfig } = getConfig();
3 |
4 | export const API = publicRuntimeConfig.PRODUCTION
5 | ? publicRuntimeConfig.API_PRODUCTION
6 | : publicRuntimeConfig.API_DEVELOPMENT;
7 |
8 | export const APP_NAME = publicRuntimeConfig.APP_NAME;
9 |
10 | export const DOMAIN = publicRuntimeConfig.PRODUCTION
11 | ? publicRuntimeConfig.DOMAIN_PRODUCTION
12 | : publicRuntimeConfig.DOMAIN_DEVELOPMENT;
13 |
14 | export const FB_APP_ID = publicRuntimeConfig.FB_APP_ID;
15 | export const DISQUS_SHORTNAME = publicRuntimeConfig.DISQUS_SHORTNAME;
16 | export const GOOGLE_CLIENT_ID = publicRuntimeConfig.GOOGLE_CLIENT_ID;
17 |
--------------------------------------------------------------------------------
/actions/form.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import { API } from '../config';
3 |
4 | export const emailContactForm = data => {
5 | let emailEndpoint;
6 |
7 | if (data.authorEmail) {
8 | emailEndpoint = `${API}/contact-blog-author`;
9 | } else {
10 | emailEndpoint = `${API}/contact`;
11 | }
12 |
13 | return fetch(`${emailEndpoint}`, {
14 | method: 'POST',
15 | headers: {
16 | Accept: 'application/json',
17 | 'Content-Type': 'application/json'
18 | },
19 | body: JSON.stringify(data)
20 | })
21 | .then(response => {
22 | return response.json();
23 | })
24 | .catch(err => console.log(err));
25 | };
26 |
--------------------------------------------------------------------------------
/helpers/quill.js:
--------------------------------------------------------------------------------
1 | export const QuillModules = {
2 | toolbar: [
3 | [{ header: '1' }, { header: '2' }, { header: [3, 4, 5, 6] }, { font: [] }],
4 | [{ size: [] }],
5 | ['bold', 'italic', 'underline', 'strike', 'blockquote'],
6 | [{ list: 'ordered' }, { list: 'bullet' }],
7 | ['link', 'video'],
8 | // ['link', 'image', 'video'],
9 | ['clean'],
10 | ['code-block']
11 | ]
12 | };
13 |
14 | export const QuillFormats = [
15 | 'header',
16 | 'font',
17 | 'size',
18 | 'bold',
19 | 'italic',
20 | 'underline',
21 | 'strike',
22 | 'blockquote',
23 | 'list',
24 | 'bullet',
25 | 'link',
26 | // 'image',
27 | 'video',
28 | 'code-block'
29 | ];
30 |
--------------------------------------------------------------------------------
/pages/admin/crud/blogs.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Admin from '../../../components/auth/Admin';
3 | import BlogRead from '../../../components/crud/BlogRead';
4 | import Link from 'next/link';
5 |
6 | const Blog = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
Manage blogs
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Blog;
26 |
--------------------------------------------------------------------------------
/pages/admin/crud/[slug].js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Admin from '../../../components/auth/Admin';
3 | import BlogUpdate from '../../../components/crud/BlogUpdate';
4 | import Link from 'next/link';
5 |
6 | const Blog = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
Update blog
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Blog;
26 |
--------------------------------------------------------------------------------
/pages/admin/crud/blog.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Admin from '../../../components/auth/Admin';
3 | import BlogCreate from '../../../components/crud/BlogCreate';
4 | import Link from 'next/link';
5 |
6 | const Blog = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
Create a new blog
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Blog;
26 |
--------------------------------------------------------------------------------
/pages/user/crud/[slug].js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Private from '../../../components/auth/Private';
3 | import BlogUpdate from '../../../components/crud/BlogUpdate';
4 | import Link from 'next/link';
5 |
6 | const Blog = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
Update blog
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Blog;
26 |
--------------------------------------------------------------------------------
/pages/user/crud/blog.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Private from '../../../components/auth/Private';
3 | import BlogCreate from '../../../components/crud/BlogCreate';
4 | import Link from 'next/link';
5 |
6 | const CreateBlog = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
Create a new blog
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default CreateBlog;
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "next",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@zeit/next-css": "^1.0.1",
16 | "i": "^0.3.6",
17 | "isomorphic-fetch": "^2.2.1",
18 | "js-cookie": "^2.2.1",
19 | "jsonwebtoken": "^8.5.1",
20 | "moment": "^2.24.0",
21 | "next": "^9.0.5",
22 | "npm": "^6.11.3",
23 | "nprogress": "^0.2.0",
24 | "prop-types": "^15.7.2",
25 | "query-string": "^6.8.3",
26 | "react": "^16.9.0",
27 | "react-dom": "^16.9.0",
28 | "react-facebook-login": "^4.1.1",
29 | "react-google-login": "^5.0.5",
30 | "react-quill": "^1.3.3",
31 | "react-render-html": "^0.6.0",
32 | "reactstrap": "^8.0.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pages/user/crud/blogs.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Private from '../../../components/auth/Private';
3 | import BlogRead from '../../../components/crud/BlogRead';
4 | import Link from 'next/link';
5 | import { isAuth } from '../../../actions/auth';
6 |
7 | const Blog = () => {
8 | const username = isAuth() && isAuth().username;
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
Manage blogs
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Blog;
28 |
--------------------------------------------------------------------------------
/pages/admin/crud/category-tag.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../../components/Layout';
2 | import Admin from '../../../components/auth/Admin';
3 | import Category from '../../../components/crud/Category';
4 | import Tag from '../../../components/crud/Tag';
5 | import Link from 'next/link';
6 |
7 | const CategoryTag = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
Manage Categories and Tags
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default CategoryTag;
30 |
--------------------------------------------------------------------------------
/pages/signin.js:
--------------------------------------------------------------------------------
1 | import Layout from '../components/Layout';
2 | import { withRouter } from 'next/router';
3 | import SigninComponent from '../components/auth/SigninComponent';
4 |
5 | const Signin = ({ router }) => {
6 | const showRedirectMessage = () => {
7 | if (router.query.message) {
8 | return
{router.query.message}
;
9 | } else {
10 | return;
11 | }
12 | };
13 |
14 | return (
15 |
16 |
17 |
Signin
18 |
19 |
20 |
{showRedirectMessage()}
21 |
22 |
23 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default withRouter(Signin);
34 |
--------------------------------------------------------------------------------
/actions/user.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import { API } from '../config';
3 | import { handleResponse } from './auth';
4 |
5 | export const userPublicProfile = username => {
6 | return fetch(`${API}/user/${username}`, {
7 | method: 'GET',
8 | headers: {
9 | Accept: 'application/json'
10 | }
11 | })
12 | .then(response => {
13 | return response.json();
14 | })
15 | .catch(err => console.log(err));
16 | };
17 |
18 | export const getProfile = token => {
19 | return fetch(`${API}/user/profile`, {
20 | method: 'GET',
21 | headers: {
22 | Accept: 'application/json',
23 | Authorization: `Bearer ${token}`
24 | }
25 | })
26 | .then(response => {
27 | return response.json();
28 | })
29 | .catch(err => console.log(err));
30 | };
31 |
32 | export const update = (token, user) => {
33 | return fetch(`${API}/user/update`, {
34 | method: 'PUT',
35 | headers: {
36 | Accept: 'application/json',
37 | Authorization: `Bearer ${token}`
38 | },
39 | body: user
40 | })
41 | .then(response => {
42 | handleResponse(response);
43 | return response.json();
44 | })
45 | .catch(err => console.log(err));
46 | };
47 |
--------------------------------------------------------------------------------
/components/auth/LoginGoogle.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { useState, useEffect } from 'react';
3 | import Router from 'next/router';
4 | import GoogleLogin from 'react-google-login';
5 | import { loginWithGoogle, authenticate, isAuth } from '../../actions/auth';
6 | import { GOOGLE_CLIENT_ID } from '../../config';
7 |
8 | const LoginGoogle = () => {
9 | const responseGoogle = response => {
10 | // console.log(response);
11 | const tokenId = response.tokenId;
12 | const user = { tokenId };
13 |
14 | loginWithGoogle(user).then(data => {
15 | if (data.error) {
16 | console.log(data.error);
17 | } else {
18 | authenticate(data, () => {
19 | if (isAuth() && isAuth().role === 1) {
20 | Router.push(`/admin`);
21 | } else {
22 | Router.push(`/user`);
23 | }
24 | });
25 | }
26 | });
27 | };
28 |
29 | return (
30 |
31 |
38 |
39 | );
40 | };
41 |
42 | export default LoginGoogle;
43 |
--------------------------------------------------------------------------------
/components/auth/LoginFacebook.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import Router from 'next/router';
3 | import FacebookLogin from 'react-facebook-login';
4 | import { loginWithFacebook, authenticate, isAuth } from '../../actions/auth';
5 | import { FB_APP_ID } from '../../config';
6 |
7 | const LoginFacebook = () => {
8 | const responseFacebook = response => {
9 | // console.log(response); // {access_token, email, id, userID, name, signed_request}
10 |
11 | loginWithFacebook(response).then(data => {
12 | if (data.error) {
13 | console.log(data.error);
14 | } else {
15 | // console.log('repsonse on fb login', data);
16 | authenticate(data, () => {
17 | if (isAuth() && isAuth().role === 1) {
18 | Router.push(`/admin`);
19 | } else {
20 | Router.push(`/user`);
21 | }
22 | });
23 | }
24 | });
25 | };
26 |
27 | return (
28 |
29 |
37 |
38 | );
39 | };
40 |
41 | export default LoginFacebook;
42 |
--------------------------------------------------------------------------------
/components/DisqusThread.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { DISQUS_SHORTNAME, DOMAIN } from '../config';
3 |
4 | const SHORTNAME = DISQUS_SHORTNAME;
5 | const WEBSITE_URL = DOMAIN;
6 |
7 | function renderDisqus() {
8 | if (window.DISQUS === undefined) {
9 | var script = document.createElement('script');
10 | script.async = true;
11 | script.src = 'https://' + SHORTNAME + '.disqus.com/embed.js';
12 | document.getElementsByTagName('head')[0].appendChild(script);
13 | } else {
14 | window.DISQUS.reset({ reload: true });
15 | }
16 | }
17 |
18 | class DisqusThread extends React.Component {
19 | static propTypes = {
20 | id: PropTypes.string.isRequired,
21 | title: PropTypes.string.isRequired,
22 | path: PropTypes.string.isRequired
23 | };
24 |
25 | shouldComponentUpdate(nextProps) {
26 | return this.props.id !== nextProps.id || this.props.title !== nextProps.title || this.props.path !== nextProps.path;
27 | }
28 |
29 | componentDidMount() {
30 | renderDisqus();
31 | }
32 |
33 | componentDidUpdate() {
34 | renderDisqus();
35 | }
36 |
37 | render() {
38 | let { id, title, path, ...other } = this.props;
39 |
40 | if (process.env.BROWSER) {
41 | window.disqus_shortname = SHORTNAME;
42 | window.disqus_identifier = id;
43 | window.disqus_title = title;
44 | window.disqus_url = WEBSITE_URL + path;
45 | }
46 |
47 | return ;
48 | }
49 | }
50 |
51 | export default DisqusThread;
52 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 | import getConfig from 'next/config';
3 | const { publicRuntimeConfig } = getConfig();
4 |
5 | class MyDocument extends Document {
6 | setGoogleTags() {
7 | if (publicRuntimeConfig.PRODUCTION) {
8 | return {
9 | __html: `
10 | window.dataLayer = window.dataLayer || [];
11 | function gtag(){dataLayer.push(arguments);}
12 | gtag('js', new Date());
13 |
14 | gtag('config', 'UA-147955896-1');
15 | `
16 | };
17 | }
18 | }
19 |
20 | render() {
21 | return (
22 |
23 |
24 |
25 |
26 |
30 | {/**/}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | export default MyDocument;
45 |
--------------------------------------------------------------------------------
/components/blog/SmallCard.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import renderHTML from 'react-render-html';
3 | import moment from 'moment';
4 | import { API } from '../../config';
5 |
6 | const SmallCard = ({ blog }) => {
7 | return (
8 |
9 |
21 |
22 |
32 |
33 |
39 |
40 | );
41 | };
42 |
43 | export default SmallCard;
44 |
--------------------------------------------------------------------------------
/pages/user/index.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/Layout';
2 | import Private from '../../components/auth/Private';
3 | import Link from 'next/link';
4 |
5 | const UserIndex = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
User Dashboard
13 |
14 |
31 |
right
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default UserIndex;
40 |
--------------------------------------------------------------------------------
/actions/tag.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import { API } from '../config';
3 | import { handleResponse } from './auth';
4 |
5 | export const create = (tag, token) => {
6 | return fetch(`${API}/tag`, {
7 | method: 'POST',
8 | headers: {
9 | Accept: 'application/json',
10 | 'Content-Type': 'application/json',
11 | Authorization: `Bearer ${token}`
12 | },
13 | body: JSON.stringify(tag)
14 | })
15 | .then(response => {
16 | handleResponse(response);
17 | return response.json();
18 | })
19 | .catch(err => console.log(err));
20 | };
21 |
22 | export const getTags = () => {
23 | return fetch(`${API}/tags`, {
24 | method: 'GET'
25 | })
26 | .then(response => {
27 | return response.json();
28 | })
29 | .catch(err => console.log(err));
30 | };
31 |
32 | export const singleTag = slug => {
33 | return fetch(`${API}/tag/${slug}`, {
34 | method: 'GET'
35 | })
36 | .then(response => {
37 | return response.json();
38 | })
39 | .catch(err => console.log(err));
40 | };
41 |
42 | export const removeTag = (slug, token) => {
43 | return fetch(`${API}/tag/${slug}`, {
44 | method: 'DELETE',
45 | headers: {
46 | Accept: 'application/json',
47 | 'Content-Type': 'application/json',
48 | Authorization: `Bearer ${token}`
49 | }
50 | })
51 | .then(response => {
52 | handleResponse(response);
53 | return response.json();
54 | })
55 | .catch(err => console.log(err));
56 | };
57 |
--------------------------------------------------------------------------------
/pages/search.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import { withRouter } from 'next/router';
4 | import Layout from '../components/Layout';
5 | import { useState, useEffect } from 'react';
6 | import { listSearch } from '../actions/blog';
7 | import Card from '../components/blog/Card';
8 | import { API, DOMAIN, APP_NAME, FB_APP_ID } from '../config';
9 |
10 | const Search = ({ router }) => {
11 | const [results, setResults] = useState([]);
12 |
13 | useEffect(() => {
14 | // console.log('router query in search page', router.query.searchQuery);
15 | listSearch({ search: router.query.searchQuery }).then(data => {
16 | setResults(data);
17 | });
18 | }, [router.query.searchQuery]);
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Search
27 |
28 |
29 |
30 |
show search results here
31 | {JSON.stringify(results)}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default withRouter(Search);
43 |
--------------------------------------------------------------------------------
/actions/category.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import { API } from '../config';
3 | import { handleResponse } from './auth';
4 |
5 | export const create = (category, token) => {
6 | return fetch(`${API}/category`, {
7 | method: 'POST',
8 | headers: {
9 | Accept: 'application/json',
10 | 'Content-Type': 'application/json',
11 | Authorization: `Bearer ${token}`
12 | },
13 | body: JSON.stringify(category)
14 | })
15 | .then(response => {
16 | handleResponse(response);
17 | return response.json();
18 | })
19 | .catch(err => console.log(err));
20 | };
21 |
22 | export const getCategories = () => {
23 | return fetch(`${API}/categories`, {
24 | method: 'GET'
25 | })
26 | .then(response => {
27 | return response.json();
28 | })
29 | .catch(err => console.log(err));
30 | };
31 |
32 | export const singleCategory = slug => {
33 | return fetch(`${API}/category/${slug}`, {
34 | method: 'GET'
35 | })
36 | .then(response => {
37 | return response.json();
38 | })
39 | .catch(err => console.log(err));
40 | };
41 |
42 | export const removeCategory = (slug, token) => {
43 | return fetch(`${API}/category/${slug}`, {
44 | method: 'DELETE',
45 | headers: {
46 | Accept: 'application/json',
47 | 'Content-Type': 'application/json',
48 | Authorization: `Bearer ${token}`
49 | }
50 | })
51 | .then(response => {
52 | handleResponse(response);
53 | return response.json();
54 | })
55 | .catch(err => console.log(err));
56 | };
57 |
--------------------------------------------------------------------------------
/pages/auth/account/activate/[id].js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import jwt from 'jsonwebtoken';
3 | import Layout from '../../../../components/Layout';
4 | import { withRouter } from 'next/router';
5 | import { signup } from '../../../../actions/auth';
6 |
7 | const ActivateAccount = ({ router }) => {
8 | const [values, setValues] = useState({
9 | name: '',
10 | token: '',
11 | error: '',
12 | loading: false,
13 | success: false,
14 | showButton: true
15 | });
16 |
17 | const { name, token, error, loading, success, showButton } = values;
18 |
19 | useEffect(() => {
20 | let token = router.query.id;
21 | if (token) {
22 | const { name } = jwt.decode(token);
23 | setValues({ ...values, name, token });
24 | }
25 | }, [router]);
26 |
27 | const clickSubmit = e => {
28 | e.preventDefault();
29 | setValues({ ...values, loading: true, error: false });
30 | signup({ token }).then(data => {
31 | if (data.error) {
32 | setValues({ ...values, error: data.error, loading: false, showButton: false });
33 | } else {
34 | setValues({ ...values, loading: false, success: true, showButton: false });
35 | }
36 | });
37 | };
38 |
39 | const showLoading = () => (loading ? Loading...
: '');
40 |
41 | return (
42 |
43 |
44 |
Hey {name}, Ready to activate your account?
45 | {showLoading()}
46 | {error && error}
47 | {success && 'You have successfully activated your account. Please signin.'}
48 | {showButton && (
49 |
52 | )}
53 |
54 |
55 | );
56 | };
57 |
58 | export default withRouter(ActivateAccount);
59 |
--------------------------------------------------------------------------------
/pages/auth/password/forgot.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Layout from '../../../components/Layout';
3 | import { forgotPassword } from '../../../actions/auth';
4 |
5 | const ForgotPassword = () => {
6 | const [values, setValues] = useState({
7 | email: '',
8 | message: '',
9 | error: '',
10 | showForm: true
11 | });
12 |
13 | const { email, message, error, showForm } = values;
14 |
15 | const handleChange = name => e => {
16 | setValues({ ...values, message: '', error: '', [name]: e.target.value });
17 | };
18 |
19 | const handleSubmit = e => {
20 | e.preventDefault();
21 | setValues({ ...values, message: '', error: '' });
22 | forgotPassword({ email }).then(data => {
23 | if (data.error) {
24 | setValues({ ...values, error: data.error });
25 | } else {
26 | setValues({ ...values, message: data.message, email: '', showForm: false });
27 | }
28 | });
29 | };
30 |
31 | const showError = () => (error ? {error}
: '');
32 | const showMessage = () => (message ? {message}
: '');
33 |
34 | const passwordForgotForm = () => (
35 |
50 | );
51 |
52 | return (
53 |
54 |
55 |
Forgot password
56 |
57 | {showError()}
58 | {showMessage()}
59 | {showForm && passwordForgotForm()}
60 |
61 |
62 | );
63 | };
64 |
65 | export default ForgotPassword;
66 |
--------------------------------------------------------------------------------
/pages/admin/index.js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/Layout';
2 | import Admin from '../../components/auth/Admin';
3 | import Link from 'next/link';
4 |
5 | const AdminIndex = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
Admin Dashboard
13 |
14 |
45 |
right
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default AdminIndex;
54 |
--------------------------------------------------------------------------------
/pages/auth/password/reset/[id].js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Layout from '../../../../components/Layout';
3 | import { withRouter } from 'next/router';
4 | import { resetPassword } from '../../../../actions/auth';
5 |
6 | const ResetPassword = ({ router }) => {
7 | const [values, setValues] = useState({
8 | name: '',
9 | newPassword: '',
10 | error: '',
11 | message: '',
12 | showForm: true
13 | });
14 |
15 | const { showForm, name, newPassword, error, message } = values;
16 |
17 | const handleSubmit = e => {
18 | e.preventDefault();
19 | resetPassword({
20 | newPassword,
21 | resetPasswordLink: router.query.id
22 | }).then(data => {
23 | if (data.error) {
24 | setValues({ ...values, error: data.error, showForm: false, newPassword: '' });
25 | } else {
26 | setValues({ ...values, message: data.message, showForm: false, newPassword: '', error: false });
27 | }
28 | });
29 | };
30 |
31 | const passwordResetForm = () => (
32 |
47 | );
48 |
49 | const showError = () => (error ? {error}
: '');
50 | const showMessage = () => (message ? {message}
: '');
51 |
52 | return (
53 |
54 |
55 |
Reset password
56 |
57 | {showError()}
58 | {showMessage()}
59 | {passwordResetForm()}
60 |
61 |
62 | );
63 | };
64 |
65 | export default withRouter(ResetPassword);
66 |
--------------------------------------------------------------------------------
/components/blog/Card.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import renderHTML from 'react-render-html';
3 | import moment from 'moment';
4 | import { API } from '../../config';
5 |
6 | const Card = ({ blog }) => {
7 | const showBlogCategories = blog =>
8 | blog.categories.map((c, i) => (
9 |
10 | {c.name}
11 |
12 | ));
13 |
14 | const showBlogTags = blog =>
15 | blog.tags.map((t, i) => (
16 |
17 | {t.name}
18 |
19 | ));
20 |
21 | return (
22 |
23 |
30 |
31 |
32 | Written by{' '}
33 |
34 | {blog.postedBy.name}
35 | {' '}
36 | | Published {moment(blog.updatedAt).fromNow()}
37 |
38 |
39 |
40 | {showBlogCategories(blog)}
41 | {showBlogTags(blog)}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
57 |
58 |
59 | {renderHTML(blog.excerpt)}
60 |
61 | Read more
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default Card;
71 |
--------------------------------------------------------------------------------
/pages/tags/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import Layout from '../../components/Layout';
4 | import { singleTag } from '../../actions/tag';
5 | import { API, DOMAIN, APP_NAME, FB_APP_ID } from '../../config';
6 | import renderHTML from 'react-render-html';
7 | import moment from 'moment';
8 | import Card from '../../components/blog/Card';
9 |
10 | const Tag = ({ tag, blogs, query }) => {
11 | const head = () => (
12 |
13 |
14 | {tag.name} | {APP_NAME}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
31 | return (
32 |
33 | {head()}
34 |
35 |
36 |
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | Tag.getInitialProps = ({ query }) => {
56 | return singleTag(query.slug).then(data => {
57 | if (data.error) {
58 | console.log(data.error);
59 | } else {
60 | return { tag: data.tag, blogs: data.blogs, query };
61 | }
62 | });
63 | };
64 |
65 | export default Tag;
66 |
--------------------------------------------------------------------------------
/static/css/styles.css:
--------------------------------------------------------------------------------
1 | /**
2 | * social login fb
3 | */
4 |
5 | .metro .fa-facebook {
6 | padding-right: 10px;
7 | }
8 |
9 | #nprogress .bar {
10 | height: 4px !important;
11 | }
12 |
13 | .ql-editor {
14 | min-height: 300px;
15 | }
16 |
17 | .featured-image {
18 | width: 100%;
19 | max-height: 500px;
20 | object-fit: cover;
21 | }
22 |
23 | /**
24 | * card
25 | */
26 |
27 | .flip {
28 | position: relative;
29 | }
30 | .flip > .front,
31 | .flip > .back {
32 | display: block;
33 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
34 | transition-duration: 0.5s;
35 | transition-property: transform, opacity;
36 | }
37 | .flip > .front {
38 | transform: rotateY(0deg);
39 | }
40 | .flip > .back {
41 | position: absolute;
42 | opacity: 0;
43 | top: 0px;
44 | left: 0px;
45 | width: 100%;
46 | height: 100%;
47 | transform: rotateY(-180deg);
48 | }
49 | .flip:hover > .front {
50 | transform: rotateY(180deg);
51 | }
52 | .flip:hover > .back {
53 | opacity: 1;
54 | transform: rotateY(0deg);
55 | }
56 | .flip.flip-vertical > .back {
57 | transform: rotateX(-180deg);
58 | }
59 | .flip.flip-vertical:hover > .front {
60 | transform: rotateX(180deg);
61 | }
62 | .flip.flip-vertical:hover > .back {
63 | transform: rotateX(0deg);
64 | }
65 | .flip {
66 | position: relative;
67 | display: inline-block;
68 | margin-right: 2px;
69 | margin-bottom: 1em;
70 | width: 100%;
71 | /*width: 400px;*/
72 | }
73 | .flip > .front,
74 | .flip > .back {
75 | display: block;
76 | color: white;
77 | width: inherit;
78 | background-size: cover !important;
79 | background-position: center !important;
80 | height: 220px;
81 | padding: 1em 2em;
82 | background: #313131;
83 | border-radius: 10px;
84 | }
85 | .flip > .front p,
86 | .flip > .back p {
87 | font-size: 0.9125rem;
88 | line-height: 160%;
89 | color: #999;
90 | }
91 | .text-shadow {
92 | text-shadow: 1px 1px rgba(0, 0, 0, 0.04), 2px 2px rgba(0, 0, 0, 0.04), 3px 3px rgba(0, 0, 0, 0.04),
93 | 4px 4px rgba(0, 0, 0, 0.04), 0.125rem 0.125rem rgba(0, 0, 0, 0.04), 6px 6px rgba(0, 0, 0, 0.04),
94 | 7px 7px rgba(0, 0, 0, 0.04), 8px 8px rgba(0, 0, 0, 0.04), 9px 9px rgba(0, 0, 0, 0.04),
95 | 0.3125rem 0.3125rem rgba(0, 0, 0, 0.04), 11px 11px rgba(0, 0, 0, 0.04), 12px 12px rgba(0, 0, 0, 0.04),
96 | 13px 13px rgba(0, 0, 0, 0.04), 14px 14px rgba(0, 0, 0, 0.04), 0.625rem 0.625rem rgba(0, 0, 0, 0.04),
97 | 16px 16px rgba(0, 0, 0, 0.04), 17px 17px rgba(0, 0, 0, 0.04), 18px 18px rgba(0, 0, 0, 0.04),
98 | 19px 19px rgba(0, 0, 0, 0.04), 1.25rem 1.25rem rgba(0, 0, 0, 0.04);
99 | }
100 |
--------------------------------------------------------------------------------
/pages/categories/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import Layout from '../../components/Layout';
4 | import { singleCategory } from '../../actions/category';
5 | import { API, DOMAIN, APP_NAME, FB_APP_ID } from '../../config';
6 | import renderHTML from 'react-render-html';
7 | import moment from 'moment';
8 | import Card from '../../components/blog/Card';
9 |
10 | const Category = ({ category, blogs, query }) => {
11 | const head = () => (
12 |
13 |
14 | {category.name} | {APP_NAME}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
31 | return (
32 |
33 | {head()}
34 |
35 |
36 |
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | Category.getInitialProps = ({ query }) => {
56 | return singleCategory(query.slug).then(data => {
57 | if (data.error) {
58 | console.log(data.error);
59 | } else {
60 | return { category: data.category, blogs: data.blogs, query };
61 | }
62 | });
63 | };
64 |
65 | export default Category;
66 |
--------------------------------------------------------------------------------
/components/blog/Search.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import renderHTML from 'react-render-html';
3 | import { useState, useEffect } from 'react';
4 | import { listSearch } from '../../actions/blog';
5 | import Router, { withRouter } from 'next/router';
6 |
7 | const Search = ({ router }) => {
8 | const [values, setValues] = useState({
9 | search: undefined,
10 | results: [],
11 | searched: false,
12 | message: ''
13 | });
14 |
15 | const { search, results, searched, message } = values;
16 |
17 | const searchSubmit = e => {
18 | e.preventDefault();
19 | listSearch({ search }).then(data => {
20 | setValues({ ...values, results: data, searched: true, message: `${data.length} blogs found` });
21 | });
22 | // show search result on different page
23 | // https://www.udemy.com/instructor/communication/qa/8593208/detail/
24 |
25 | // Router.push({
26 | // pathname: '/search',
27 | // query: { searchQuery: search }
28 | // });
29 | };
30 |
31 | const handleChange = e => {
32 | // console.log(e.target.value);
33 | setValues({ ...values, search: e.target.value, searched: false, results: [] });
34 | };
35 |
36 | const searchedBlogs = (results = []) => {
37 | return (
38 |
39 | {message &&
{message}
}
40 |
41 | {results.map((blog, i) => {
42 | return (
43 |
48 | );
49 | })}
50 |
51 | );
52 | };
53 |
54 | const searchForm = () => (
55 |
68 | );
69 |
70 | return (
71 |
72 |
{searchForm()}
73 | {searched &&
{searchedBlogs(results)}
}
74 |
75 | );
76 | };
77 |
78 | export default withRouter(Search);
79 |
--------------------------------------------------------------------------------
/components/crud/BlogRead.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { useState, useEffect } from 'react';
3 | import Router from 'next/router';
4 | import { getCookie, isAuth } from '../../actions/auth';
5 | import { list, removeBlog } from '../../actions/blog';
6 | import moment from 'moment';
7 |
8 | const BlogRead = ({ username }) => {
9 | const [blogs, setBlogs] = useState([]);
10 | const [message, setMessage] = useState('');
11 | const token = getCookie('token');
12 |
13 | useEffect(() => {
14 | loadBlogs();
15 | }, []);
16 |
17 | const loadBlogs = () => {
18 | list(username).then(data => {
19 | if (data.error) {
20 | console.log(data.error);
21 | } else {
22 | setBlogs(data);
23 | }
24 | });
25 | };
26 |
27 | const deleteBlog = slug => {
28 | removeBlog(slug, token).then(data => {
29 | if (data.error) {
30 | console.log(data.error);
31 | } else {
32 | setMessage(data.message);
33 | loadBlogs();
34 | }
35 | });
36 | };
37 |
38 | const deleteConfirm = slug => {
39 | let answer = window.confirm('Are you sure you want to delete your blog?');
40 | if (answer) {
41 | deleteBlog(slug);
42 | }
43 | };
44 |
45 | const showUpdateButton = blog => {
46 | if (isAuth() && isAuth().role === 0) {
47 | return (
48 |
49 | Update
50 |
51 | );
52 | } else if (isAuth() && isAuth().role === 1) {
53 | return (
54 |
55 | Update
56 |
57 | );
58 | }
59 | };
60 |
61 | const showAllBlogs = () => {
62 | return blogs.map((blog, i) => {
63 | return (
64 |
65 |
{blog.title}
66 |
67 | Written by {blog.postedBy.name} | Published on {moment(blog.updatedAt).fromNow()}
68 |
69 |
72 | {showUpdateButton(blog)}
73 |
74 | );
75 | });
76 | };
77 |
78 | return (
79 |
80 |
81 |
82 | {message &&
{message}
}
83 | {showAllBlogs()}
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default BlogRead;
91 |
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Link from "next/link";
3 | import Router from "next/router";
4 | import NProgress from "nprogress";
5 | import { APP_NAME } from "../config";
6 | import { signout, isAuth } from "../actions/auth";
7 | import {
8 | Collapse,
9 | Navbar,
10 | NavbarToggler,
11 | NavbarBrand,
12 | Nav,
13 | NavItem,
14 | NavLink,
15 | UncontrolledDropdown,
16 | DropdownToggle,
17 | DropdownMenu,
18 | DropdownItem
19 | } from "reactstrap";
20 | import ".././node_modules/nprogress/nprogress.css";
21 | import Search from "./blog/Search";
22 |
23 | Router.onRouteChangeStart = url => NProgress.start();
24 | Router.onRouteChangeComplete = url => NProgress.done();
25 | Router.onRouteChangeError = url => NProgress.done();
26 |
27 | const Header = () => {
28 | const [isOpen, setIsOpen] = useState(false);
29 |
30 | const toggle = () => {
31 | setIsOpen(!isOpen);
32 | };
33 |
34 | return (
35 |
36 |
37 |
38 | {APP_NAME}
39 |
40 |
41 |
42 |
105 |
106 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default Header;
113 |
--------------------------------------------------------------------------------
/components/form/ContactForm.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Link from 'next/link';
3 | import { emailContactForm } from '../../actions/form';
4 | import { isAuth } from '../../actions/auth';
5 |
6 | const ContactForm = ({ authorEmail }) => {
7 | const [values, setValues] = useState({
8 | message: '',
9 | name: '',
10 | email: '',
11 | sent: false,
12 | buttonText: isAuth() ? 'Send Message' : 'Login to Send Message',
13 | success: false,
14 | error: false
15 | });
16 |
17 | const { message, name, email, sent, buttonText, success, error } = values;
18 |
19 | const clickSubmit = e => {
20 | e.preventDefault();
21 | setValues({ ...values, buttonText: 'Sending...' });
22 | emailContactForm({ authorEmail, name, email, message }).then(data => {
23 | if (data.error) {
24 | setValues({ ...values, error: data.error });
25 | } else {
26 | setValues({
27 | ...values,
28 | sent: true,
29 | name: '',
30 | email: '',
31 | message: '',
32 | buttonText: 'Sent',
33 | success: data.success
34 | });
35 | }
36 | });
37 | };
38 |
39 | const handleChange = name => e => {
40 | setValues({ ...values, [name]: e.target.value, error: false, success: false, buttonText: 'Send Message' });
41 | };
42 |
43 | const showSuccessMessage = () => success && Thank you for contacting us.
;
44 |
45 | const showErrorMessage = () => (
46 |
47 | {error}
48 |
49 | );
50 |
51 | const contactForm = () => {
52 | return (
53 |
88 | );
89 | };
90 |
91 | return (
92 |
93 | {showSuccessMessage()}
94 | {showErrorMessage()}
95 | {contactForm()}
96 |
97 | );
98 | };
99 |
100 | export default ContactForm;
101 |
--------------------------------------------------------------------------------
/components/auth/SigninComponent.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { signin, authenticate, isAuth } from '../../actions/auth';
3 | import Router from 'next/router';
4 | import Link from 'next/link';
5 | import LoginGoogle from './LoginGoogle';
6 | import LoginFacebook from './LoginFacebook';
7 |
8 | const SigninComponent = () => {
9 | const [values, setValues] = useState({
10 | email: '',
11 | password: '',
12 | error: '',
13 | loading: false,
14 | message: '',
15 | showForm: true
16 | });
17 |
18 | const { email, password, error, loading, message, showForm } = values;
19 |
20 | useEffect(() => {
21 | isAuth() && Router.push(`/`);
22 | }, []);
23 |
24 | const handleSubmit = e => {
25 | e.preventDefault();
26 | // console.table({ name, email, password, error, loading, message, showForm });
27 | setValues({ ...values, loading: true, error: false });
28 | const user = { email, password };
29 |
30 | signin(user).then(data => {
31 | if (data.error) {
32 | setValues({ ...values, error: data.error, loading: false });
33 | } else {
34 | // save user token to cookie
35 | // save user info to localstorage
36 | // authenticate user
37 | authenticate(data, () => {
38 | if (isAuth() && isAuth().role === 1) {
39 | Router.push(`/admin`);
40 | } else {
41 | Router.push(`/user`);
42 | }
43 | });
44 | }
45 | });
46 | };
47 |
48 | const handleChange = name => e => {
49 | setValues({ ...values, error: false, [name]: e.target.value });
50 | };
51 |
52 | const showLoading = () => (loading ? Loading...
: '');
53 | const showError = () => (error ? {error}
: '');
54 | const showMessage = () => (message ? {message}
: '');
55 |
56 | const signinForm = () => {
57 | return (
58 |
83 | );
84 | };
85 |
86 | return (
87 |
88 | {showError()}
89 | {showLoading()}
90 | {showMessage()}
91 |
92 |
93 | {showForm && signinForm()}
94 |
95 |
96 | Forgot password
97 |
98 |
99 | );
100 | };
101 |
102 | export default SigninComponent;
103 |
--------------------------------------------------------------------------------
/components/auth/SignupComponent.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { signup, isAuth, preSignup } from '../../actions/auth';
3 | import Router from 'next/router';
4 | import Link from 'next/link';
5 | import LoginGoogle from './LoginGoogle';
6 | import LoginFacebook from './LoginFacebook';
7 |
8 | const SignupComponent = () => {
9 | const [values, setValues] = useState({
10 | name: '',
11 | email: '',
12 | password: '',
13 | error: '',
14 | loading: false,
15 | message: '',
16 | showForm: true
17 | });
18 |
19 | const { name, email, password, error, loading, message, showForm } = values;
20 |
21 | useEffect(() => {
22 | isAuth() && Router.push(`/`);
23 | }, []);
24 |
25 | const handleSubmit = e => {
26 | e.preventDefault();
27 | // console.table({ name, email, password, error, loading, message, showForm });
28 | setValues({ ...values, loading: true, error: false });
29 | const user = { name, email, password };
30 |
31 | preSignup(user).then(data => {
32 | if (data.error) {
33 | setValues({ ...values, error: data.error, loading: false });
34 | } else {
35 | setValues({
36 | ...values,
37 | name: '',
38 | email: '',
39 | password: '',
40 | error: '',
41 | loading: false,
42 | message: data.message,
43 | showForm: false
44 | });
45 | }
46 | });
47 | };
48 |
49 | const handleChange = name => e => {
50 | setValues({ ...values, error: false, [name]: e.target.value });
51 | };
52 |
53 | const showLoading = () => (loading ? Loading...
: '');
54 | const showError = () => (error ? {error}
: '');
55 | const showMessage = () => (message ? {message}
: '');
56 |
57 | const signupForm = () => {
58 | return (
59 |
94 | );
95 | };
96 |
97 | return (
98 |
99 | {showError()}
100 | {showLoading()}
101 | {showMessage()}
102 |
103 |
104 | {showForm && signupForm()}
105 |
106 |
107 | Forgot password
108 |
109 |
110 | );
111 | };
112 |
113 | export default SignupComponent;
114 |
--------------------------------------------------------------------------------
/components/crud/Tag.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import Link from 'next/link';
3 | import Router from 'next/router';
4 | import { getCookie } from '../../actions/auth';
5 | import { create, getTags, removeTag } from '../../actions/tag';
6 |
7 | const Tag = () => {
8 | const [values, setValues] = useState({
9 | name: '',
10 | error: false,
11 | success: false,
12 | tags: [],
13 | removed: false,
14 | reload: false
15 | });
16 |
17 | const { name, error, success, tags, removed, reload } = values;
18 | const token = getCookie('token');
19 |
20 | useEffect(() => {
21 | loadTags();
22 | }, [reload]);
23 |
24 | const loadTags = () => {
25 | getTags().then(data => {
26 | if (data.error) {
27 | console.log(data.error);
28 | } else {
29 | setValues({ ...values, tags: data });
30 | }
31 | });
32 | };
33 |
34 | const showTags = () => {
35 | return tags.map((t, i) => {
36 | return (
37 |
45 | );
46 | });
47 | };
48 |
49 | const deleteConfirm = slug => {
50 | let answer = window.confirm('Are you sure you want to delete this tag?');
51 | if (answer) {
52 | deleteTag(slug);
53 | }
54 | };
55 |
56 | const deleteTag = slug => {
57 | // console.log('delete', slug);
58 | removeTag(slug, token).then(data => {
59 | if (data.error) {
60 | console.log(data.error);
61 | } else {
62 | setValues({ ...values, error: false, success: false, name: '', removed: !removed, reload: !reload });
63 | }
64 | });
65 | };
66 |
67 | const clickSubmit = e => {
68 | e.preventDefault();
69 | // console.log('create category', name);
70 | create({ name }, token).then(data => {
71 | if (data.error) {
72 | setValues({ ...values, error: data.error, success: false });
73 | } else {
74 | // setValues({ ...values, error: false, success: false, name: '', removed: !removed, reload: !reload });
75 | setValues({ ...values, error: false, success: true, name: '', reload: !reload });
76 | }
77 | });
78 | };
79 |
80 | const handleChange = e => {
81 | setValues({ ...values, name: e.target.value, error: false, success: false, removed: '' });
82 | };
83 |
84 | const showSuccess = () => {
85 | if (success) {
86 | return Tag is created
;
87 | }
88 | };
89 |
90 | const showError = () => {
91 | if (error) {
92 | return Tag already exist
;
93 | }
94 | };
95 |
96 | const showRemoved = () => {
97 | if (removed) {
98 | return Tag is removed
;
99 | }
100 | };
101 |
102 | const mouseMoveHandler = e => {
103 | setValues({ ...values, error: false, success: false, removed: '' });
104 | };
105 |
106 | const newTagFom = () => (
107 |
118 | );
119 |
120 | return (
121 |
122 | {showSuccess()}
123 | {showError()}
124 | {showRemoved()}
125 |
126 | {newTagFom()}
127 | {showTags()}
128 |
129 |
130 | );
131 | };
132 |
133 | export default Tag;
134 |
--------------------------------------------------------------------------------
/components/crud/Category.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import Link from 'next/link';
3 | import Router from 'next/router';
4 | import { getCookie } from '../../actions/auth';
5 | import { create, getCategories, removeCategory } from '../../actions/category';
6 |
7 | const Category = () => {
8 | const [values, setValues] = useState({
9 | name: '',
10 | error: false,
11 | succe1ss: false,
12 | categories: [],
13 | removed: false,
14 | reload: false
15 | });
16 |
17 | const { name, error, success, categories, removed, reload } = values;
18 | const token = getCookie('token');
19 |
20 | useEffect(() => {
21 | loadCategories();
22 | }, [reload]);
23 |
24 | const loadCategories = () => {
25 | getCategories().then(data => {
26 | if (data.error) {
27 | console.log(data.error);
28 | } else {
29 | setValues({ ...values, categories: data });
30 | }
31 | });
32 | };
33 |
34 | const showCategories = () => {
35 | return categories.map((c, i) => {
36 | return (
37 |
45 | );
46 | });
47 | };
48 |
49 | const deleteConfirm = slug => {
50 | let answer = window.confirm('Are you sure you want to delete this category?');
51 | if (answer) {
52 | deleteCategory(slug);
53 | }
54 | };
55 |
56 | const deleteCategory = slug => {
57 | // console.log('delete', slug);
58 | removeCategory(slug, token).then(data => {
59 | if (data.error) {
60 | console.log(data.error);
61 | } else {
62 | setValues({ ...values, error: false, success: false, name: '', removed: !removed, reload: !reload });
63 | }
64 | });
65 | };
66 |
67 | const clickSubmit = e => {
68 | e.preventDefault();
69 | // console.log('create category', name);
70 | create({ name }, token).then(data => {
71 | if (data.error) {
72 | setValues({ ...values, error: data.error, success: false });
73 | } else {
74 | // setValues({ ...values, error: false, success: false, name: '', removed: !removed, reload: !reload });
75 | setValues({ ...values, error: false, success: true, name: '', reload: !reload });
76 | }
77 | });
78 | };
79 |
80 | const handleChange = e => {
81 | setValues({ ...values, name: e.target.value, error: false, success: false, removed: '' });
82 | };
83 |
84 | const showSuccess = () => {
85 | if (success) {
86 | return Category is created
;
87 | }
88 | };
89 |
90 | const showError = () => {
91 | if (error) {
92 | return Category already exist
;
93 | }
94 | };
95 |
96 | const showRemoved = () => {
97 | if (removed) {
98 | return Category is removed
;
99 | }
100 | };
101 |
102 | const mouseMoveHandler = e => {
103 | setValues({ ...values, error: false, success: false, removed: '' });
104 | };
105 |
106 | const newCategoryFom = () => (
107 |
118 | );
119 |
120 | return (
121 |
122 | {showSuccess()}
123 | {showError()}
124 | {showRemoved()}
125 |
126 | {newCategoryFom()}
127 | {showCategories()}
128 |
129 |
130 | );
131 | };
132 |
133 | export default Category;
134 |
--------------------------------------------------------------------------------
/actions/blog.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import { API } from '../config';
3 | import queryString from 'query-string';
4 | import { isAuth, handleResponse } from './auth';
5 |
6 | export const createBlog = (blog, token) => {
7 | let createBlogEndpoint;
8 |
9 | if (isAuth() && isAuth().role === 1) {
10 | createBlogEndpoint = `${API}/blog`;
11 | } else if (isAuth() && isAuth().role === 0) {
12 | createBlogEndpoint = `${API}/user/blog`;
13 | }
14 |
15 | return fetch(`${createBlogEndpoint}`, {
16 | method: 'POST',
17 | headers: {
18 | Accept: 'application/json',
19 | Authorization: `Bearer ${token}`
20 | },
21 | body: blog
22 | })
23 | .then(response => {
24 | handleResponse(response);
25 | return response.json();
26 | })
27 | .catch(err => console.log(err));
28 | };
29 |
30 | export const listBlogsWithCategoriesAndTags = (skip, limit) => {
31 | const data = {
32 | limit,
33 | skip
34 | };
35 | return fetch(`${API}/blogs-categories-tags`, {
36 | method: 'POST',
37 | headers: {
38 | Accept: 'application/json',
39 | 'Content-Type': 'application/json'
40 | },
41 | body: JSON.stringify(data)
42 | })
43 | .then(response => {
44 | return response.json();
45 | })
46 | .catch(err => console.log(err));
47 | };
48 |
49 | export const singleBlog = (slug = undefined) => {
50 | return fetch(`${API}/blog/${slug}`, {
51 | method: 'GET'
52 | })
53 | .then(response => {
54 | return response.json();
55 | })
56 | .catch(err => console.log(err));
57 | };
58 |
59 | export const listRelated = blog => {
60 | return fetch(`${API}/blogs/related`, {
61 | method: 'POST',
62 | headers: {
63 | Accept: 'application/json',
64 | 'Content-Type': 'application/json'
65 | },
66 | body: JSON.stringify(blog)
67 | })
68 | .then(response => {
69 | return response.json();
70 | })
71 | .catch(err => console.log(err));
72 | };
73 |
74 | export const list = username => {
75 | let listBlogsEndpoint;
76 |
77 | if (username) {
78 | listBlogsEndpoint = `${API}/${username}/blogs`;
79 | } else {
80 | listBlogsEndpoint = `${API}/blogs`;
81 | }
82 |
83 | return fetch(`${listBlogsEndpoint}`, {
84 | method: 'GET'
85 | })
86 | .then(response => {
87 | return response.json();
88 | })
89 | .catch(err => console.log(err));
90 | };
91 |
92 | export const removeBlog = (slug, token) => {
93 | let deleteBlogEndpoint;
94 |
95 | if (isAuth() && isAuth().role === 1) {
96 | deleteBlogEndpoint = `${API}/blog/${slug}`;
97 | } else if (isAuth() && isAuth().role === 0) {
98 | deleteBlogEndpoint = `${API}/user/blog/${slug}`;
99 | }
100 |
101 | return fetch(`${deleteBlogEndpoint}`, {
102 | method: 'DELETE',
103 | headers: {
104 | Accept: 'application/json',
105 | 'Content-Type': 'application/json',
106 | Authorization: `Bearer ${token}`
107 | }
108 | })
109 | .then(response => {
110 | handleResponse(response);
111 | return response.json();
112 | })
113 | .catch(err => console.log(err));
114 | };
115 |
116 | export const updateBlog = (blog, token, slug) => {
117 | let updateBlogEndpoint;
118 |
119 | if (isAuth() && isAuth().role === 1) {
120 | updateBlogEndpoint = `${API}/blog/${slug}`;
121 | } else if (isAuth() && isAuth().role === 0) {
122 | updateBlogEndpoint = `${API}/user/blog/${slug}`;
123 | }
124 |
125 | return fetch(`${updateBlogEndpoint}`, {
126 | method: 'PUT',
127 | headers: {
128 | Accept: 'application/json',
129 | Authorization: `Bearer ${token}`
130 | },
131 | body: blog
132 | })
133 | .then(response => {
134 | handleResponse(response);
135 | return response.json();
136 | })
137 | .catch(err => console.log(err));
138 | };
139 |
140 | export const listSearch = params => {
141 | console.log('search params', params);
142 | let query = queryString.stringify(params);
143 | console.log('query params', query);
144 | return fetch(`${API}/blogs/search?${query}`, {
145 | method: 'GET'
146 | })
147 | .then(response => {
148 | return response.json();
149 | })
150 | .catch(err => console.log(err));
151 | };
152 |
--------------------------------------------------------------------------------
/pages/profile/[username].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import Layout from '../../components/Layout';
4 | import { userPublicProfile } from '../../actions/user';
5 | import { API, DOMAIN, APP_NAME, FB_APP_ID } from '../../config';
6 | import moment from 'moment';
7 | import ContactForm from '../../components/form/ContactForm';
8 |
9 | const UserProfile = ({ user, blogs, query }) => {
10 | const head = () => (
11 |
12 |
13 | {user.username} | {APP_NAME}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | const showUserBlogs = () => {
31 | return blogs.map((blog, i) => {
32 | return (
33 |
38 | );
39 | });
40 | };
41 |
42 | return (
43 |
44 | {head()}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
{user.name}
54 |
55 |
Joined {moment(user.createdAt).fromNow()}
56 |
57 |
58 |

64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Recent blogs by {user.name}
81 |
82 |
83 | {showUserBlogs()}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Message {user.name}
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | UserProfile.getInitialProps = ({ query }) => {
107 | // console.log(query);
108 | return userPublicProfile(query.username).then(data => {
109 | if (data.error) {
110 | console.log(data.error);
111 | } else {
112 | // console.log(data);
113 | return { user: data.user, blogs: data.blogs, query };
114 | }
115 | });
116 | };
117 |
118 | export default UserProfile;
119 |
--------------------------------------------------------------------------------
/pages/blogs/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import { withRouter } from 'next/router';
4 | import Layout from '../../components/Layout';
5 | import { useState } from 'react';
6 | import { listBlogsWithCategoriesAndTags } from '../../actions/blog';
7 | import Card from '../../components/blog/Card';
8 | import { API, DOMAIN, APP_NAME, FB_APP_ID } from '../../config';
9 |
10 | const Blogs = ({ blogs, categories, tags, totalBlogs, blogsLimit, blogSkip, router }) => {
11 | const head = () => (
12 |
13 | Programming blogs | {APP_NAME}
14 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 |
35 | const [limit, setLimit] = useState(blogsLimit);
36 | const [skip, setSkip] = useState(0);
37 | const [size, setSize] = useState(totalBlogs);
38 | const [loadedBlogs, setLoadedBlogs] = useState([]);
39 |
40 | const loadMore = () => {
41 | let toSkip = skip + limit;
42 | listBlogsWithCategoriesAndTags(toSkip, limit).then(data => {
43 | if (data.error) {
44 | console.log(data.error);
45 | } else {
46 | setLoadedBlogs([...loadedBlogs, ...data.blogs]);
47 | setSize(data.size);
48 | setSkip(toSkip);
49 | }
50 | });
51 | };
52 |
53 | const loadMoreButton = () => {
54 | return (
55 | size > 0 &&
56 | size >= limit && (
57 |
60 | )
61 | );
62 | };
63 |
64 | const showAllBlogs = () => {
65 | return blogs.map((blog, i) => {
66 | // ()
67 | return (
68 |
69 |
70 |
71 |
72 | );
73 | });
74 | };
75 |
76 | const showAllCategories = () => {
77 | return categories.map((c, i) => (
78 |
79 | {c.name}
80 |
81 | ));
82 | };
83 |
84 | const showAllTags = () => {
85 | return tags.map((t, i) => (
86 |
87 | {t.name}
88 |
89 | ));
90 | };
91 |
92 | const showLoadedBlogs = () => {
93 | return loadedBlogs.map((blog, i) => (
94 |
95 |
96 |
97 | ));
98 | };
99 |
100 | return (
101 |
102 | {head()}
103 |
104 |
105 |
121 | {showAllBlogs()}
122 | {showLoadedBlogs()}
123 | {loadMoreButton()}
124 |
125 |
126 |
127 | );
128 | };
129 |
130 | Blogs.getInitialProps = () => {
131 | let skip = 0;
132 | let limit = 10;
133 | return listBlogsWithCategoriesAndTags(skip, limit).then(data => {
134 | if (data.error) {
135 | console.log(data.error);
136 | } else {
137 | return {
138 | blogs: data.blogs,
139 | categories: data.categories,
140 | tags: data.tags,
141 | totalBlogs: data.size,
142 | blogsLimit: limit,
143 | blogSkip: skip
144 | };
145 | }
146 | });
147 | };
148 |
149 | export default withRouter(Blogs);
150 |
--------------------------------------------------------------------------------
/actions/auth.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import cookie from 'js-cookie';
3 | import { API } from '../config';
4 | import Router from 'next/router';
5 |
6 | export const handleResponse = response => {
7 | if (response.status === 401) {
8 | signout(() => {
9 | Router.push({
10 | pathname: '/signin',
11 | query: {
12 | message: 'Your session is expired. Please signin'
13 | }
14 | });
15 | });
16 | }
17 | };
18 |
19 | export const preSignup = user => {
20 | return fetch(`${API}/pre-signup`, {
21 | method: 'POST',
22 | headers: {
23 | Accept: 'application/json',
24 | 'Content-Type': 'application/json'
25 | },
26 | body: JSON.stringify(user)
27 | })
28 | .then(response => {
29 | return response.json();
30 | })
31 | .catch(err => console.log(err));
32 | };
33 |
34 | export const signup = user => {
35 | return fetch(`${API}/signup`, {
36 | method: 'POST',
37 | headers: {
38 | Accept: 'application/json',
39 | 'Content-Type': 'application/json'
40 | },
41 | body: JSON.stringify(user)
42 | })
43 | .then(response => {
44 | return response.json();
45 | })
46 | .catch(err => console.log(err));
47 | };
48 |
49 | export const signin = user => {
50 | return fetch(`${API}/signin`, {
51 | method: 'POST',
52 | headers: {
53 | Accept: 'application/json',
54 | 'Content-Type': 'application/json'
55 | },
56 | body: JSON.stringify(user)
57 | })
58 | .then(response => {
59 | return response.json();
60 | })
61 | .catch(err => console.log(err));
62 | };
63 |
64 | export const signout = next => {
65 | removeCookie('token');
66 | removeLocalStorage('user');
67 | next();
68 |
69 | return fetch(`${API}/signout`, {
70 | method: 'GET'
71 | })
72 | .then(response => {
73 | console.log('signout success');
74 | })
75 | .catch(err => console.log(err));
76 | };
77 |
78 | // set cookie
79 | export const setCookie = (key, value) => {
80 | if (process.browser) {
81 | cookie.set(key, value, {
82 | expires: 1
83 | });
84 | }
85 | };
86 |
87 | export const removeCookie = key => {
88 | if (process.browser) {
89 | cookie.remove(key, {
90 | expires: 1
91 | });
92 | }
93 | };
94 | // get cookie
95 | export const getCookie = key => {
96 | if (process.browser) {
97 | return cookie.get(key);
98 | }
99 | };
100 | // localstorage
101 | export const setLocalStorage = (key, value) => {
102 | if (process.browser) {
103 | localStorage.setItem(key, JSON.stringify(value));
104 | }
105 | };
106 |
107 | export const removeLocalStorage = key => {
108 | if (process.browser) {
109 | localStorage.removeItem(key);
110 | }
111 | };
112 | // autheticate user by pass data to cookie and localstorage
113 | export const authenticate = (data, next) => {
114 | setCookie('token', data.token);
115 | setLocalStorage('user', data.user);
116 | next();
117 | };
118 |
119 | export const isAuth = () => {
120 | if (process.browser) {
121 | const cookieChecked = getCookie('token');
122 | if (cookieChecked) {
123 | if (localStorage.getItem('user')) {
124 | return JSON.parse(localStorage.getItem('user'));
125 | } else {
126 | return false;
127 | }
128 | }
129 | }
130 | };
131 |
132 | export const updateUser = (user, next) => {
133 | if (process.browser) {
134 | if (localStorage.getItem('user')) {
135 | let auth = JSON.parse(localStorage.getItem('user'));
136 | auth = user;
137 | localStorage.setItem('user', JSON.stringify(auth));
138 | next();
139 | }
140 | }
141 | };
142 |
143 | export const forgotPassword = email => {
144 | return fetch(`${API}/forgot-password`, {
145 | method: 'PUT',
146 | headers: {
147 | Accept: 'application/json',
148 | 'Content-Type': 'application/json'
149 | },
150 | body: JSON.stringify(email)
151 | })
152 | .then(response => {
153 | return response.json();
154 | })
155 | .catch(err => console.log(err));
156 | };
157 |
158 | export const resetPassword = resetInfo => {
159 | return fetch(`${API}/reset-password`, {
160 | method: 'PUT',
161 | headers: {
162 | Accept: 'application/json',
163 | 'Content-Type': 'application/json'
164 | },
165 | body: JSON.stringify(resetInfo)
166 | })
167 | .then(response => {
168 | return response.json();
169 | })
170 | .catch(err => console.log(err));
171 | };
172 |
173 | export const loginWithGoogle = user => {
174 | return fetch(`${API}/google-login`, {
175 | method: 'POST',
176 | headers: {
177 | Accept: 'application/json',
178 | 'Content-Type': 'application/json'
179 | },
180 | body: JSON.stringify(user)
181 | })
182 | .then(response => {
183 | return response.json();
184 | })
185 | .catch(err => console.log(err));
186 | };
187 |
188 | export const loginWithFacebook = user => {
189 | return fetch(`${API}/facebook-login`, {
190 | method: 'POST',
191 | headers: {
192 | Accept: 'application/json',
193 | 'Content-Type': 'application/json'
194 | },
195 | body: JSON.stringify(user)
196 | })
197 | .then(response => {
198 | return response.json();
199 | })
200 | .catch(err => console.log(err));
201 | };
202 |
--------------------------------------------------------------------------------
/components/auth/ProfileUpdate.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { useState, useEffect } from 'react';
3 | import Router from 'next/router';
4 | import { getCookie, isAuth, updateUser } from '../../actions/auth';
5 | import { getProfile, update } from '../../actions/user';
6 | import { API } from '../../config';
7 |
8 | const ProfileUpdate = () => {
9 | const [values, setValues] = useState({
10 | username: '',
11 | name: '',
12 | email: '',
13 | about: '',
14 | password: '',
15 | error: false,
16 | success: false,
17 | loading: false,
18 | photo: '',
19 | userData: ''
20 | });
21 |
22 | const token = getCookie('token');
23 | const { username, name, email, about, password, error, success, loading, photo, userData } = values;
24 |
25 | const init = () => {
26 | getProfile(token).then(data => {
27 | if (data.error) {
28 | setValues({ ...values, error: data.error });
29 | } else {
30 | setValues({
31 | ...values,
32 | username: data.username,
33 | name: data.name,
34 | email: data.email,
35 | about: data.about
36 | });
37 | }
38 | });
39 | };
40 |
41 | useEffect(() => {
42 | init();
43 | }, []);
44 |
45 | const handleChange = name => e => {
46 | // console.log(e.target.value);
47 | const value = name === 'photo' ? e.target.files[0] : e.target.value;
48 | let userFormData = new FormData();
49 | userFormData.set(name, value);
50 | setValues({ ...values, [name]: value, userData: userFormData, error: false, success: false });
51 | };
52 |
53 | const handleSubmit = e => {
54 | e.preventDefault();
55 | setValues({ ...values, loading: true });
56 | update(token, userData).then(data => {
57 | if (data.error) {
58 | console.log('data.error', data.error);
59 | setValues({ ...values, error: data.error, loading: false });
60 | } else {
61 | updateUser(data, () => {
62 | setValues({
63 | ...values,
64 | username: data.username,
65 | name: data.name,
66 | email: data.email,
67 | about: data.about,
68 | password: '',
69 | success: true,
70 | loading: false
71 | });
72 | });
73 | }
74 | });
75 | };
76 |
77 | const profileUpdateForm = () => (
78 |
116 | );
117 |
118 | const showError = () => (
119 |
120 | {error}
121 |
122 | );
123 |
124 | const showSuccess = () => (
125 |
126 | Profile updated
127 |
128 | );
129 |
130 | const showLoading = () => (
131 |
132 | Loading...
133 |
134 | );
135 |
136 | return (
137 |
138 |
139 |
140 |
141 |

147 |
148 |
{profileUpdateForm()}
149 |
150 |
151 |
152 | );
153 | };
154 |
155 | export default ProfileUpdate;
156 |
--------------------------------------------------------------------------------
/pages/blogs/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import Layout from '../../components/Layout';
4 | import { useState, useEffect } from 'react';
5 | import { singleBlog, listRelated } from '../../actions/blog';
6 | import { API, DOMAIN, APP_NAME, FB_APP_ID } from '../../config';
7 | import renderHTML from 'react-render-html';
8 | import moment from 'moment';
9 | import SmallCard from '../../components/blog/SmallCard';
10 | import DisqusThread from '../../components/DisqusThread';
11 |
12 | const SingleBlog = ({ blog, query }) => {
13 | const [related, setRelated] = useState([]);
14 |
15 | const loadRelated = () => {
16 | listRelated({ blog }).then(data => {
17 | if (data.error) {
18 | console.log(data.error);
19 | } else {
20 | setRelated(data);
21 | }
22 | });
23 | };
24 |
25 | useEffect(() => {
26 | loadRelated();
27 | }, []);
28 |
29 | const head = () => (
30 |
31 |
32 | {blog.title} | {APP_NAME}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 |
49 | const showBlogCategories = blog =>
50 | blog.categories.map((c, i) => (
51 |
52 | {c.name}
53 |
54 | ));
55 |
56 | const showBlogTags = blog =>
57 | blog.tags.map((t, i) => (
58 |
59 | {t.name}
60 |
61 | ));
62 |
63 | const showRelatedBlog = () => {
64 | return related.map((blog, i) => (
65 |
70 | ));
71 | };
72 |
73 | const showComments = () => {
74 | return (
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | return (
82 |
83 | {head()}
84 |
85 |
86 |
87 |
88 |
89 |
90 |

95 |
96 |
97 |
98 |
99 |
100 |
{blog.title}
101 |
102 | Written by{' '}
103 |
104 | {blog.postedBy.name}
105 | {' '}
106 | | Published {moment(blog.updatedAt).fromNow()}
107 |
108 |
109 |
110 | {showBlogCategories(blog)}
111 | {showBlogTags(blog)}
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | {renderHTML(blog.body)}
122 |
123 |
124 |
125 |
126 |
RELATED BLOGS
127 |
{showRelatedBlog()}
128 |
129 |
130 | {showComments()}
131 |
132 |
133 |
134 |
135 | );
136 | };
137 |
138 | SingleBlog.getInitialProps = ({ query }) => {
139 | return singleBlog(query.slug).then(data => {
140 | if (data.error) {
141 | console.log(data.error);
142 | } else {
143 | // console.log('GET INITIAL PROPS IN SINGLE BLOG', data);
144 | return { blog: data, query };
145 | }
146 | });
147 | };
148 |
149 | export default SingleBlog;
150 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Layout from '../components/Layout';
2 | import Link from 'next/link';
3 | import Head from 'next/head';
4 | import { withRouter } from 'next/router';
5 | import { APP_NAME, DOMAIN, FB_APP_ID } from '../config';
6 |
7 | const Index = ({ router }) => {
8 | const head = () => (
9 |
10 | Programming blogs | {APP_NAME}
11 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | return (
32 |
33 | {head()}
34 |
35 |
36 |
37 |
38 |
39 |
PROGRAMMING & WEB DEVELOPMENT BLOGS/TUTORIALS
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Best programming and web development blogs and tutorials on React Node NextJs and
49 | JavaScript
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
64 |
React
65 |
66 |
67 |
68 |
69 | React Js
70 |
71 |
72 |
73 | The world's most popular frontend web development library
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
87 |
Node
88 |
89 |
90 |
91 |
92 | Node Js
93 |
94 |
95 |
96 | The worlds most popular backend development tool for JavaScript Ninjas
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
110 |
Next
111 |
112 |
113 |
114 |
115 | Next Js
116 |
117 |
118 |
119 | A Production ready web framework for building SEO React apps
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | );
130 | };
131 |
132 | export default withRouter(Index);
133 |
--------------------------------------------------------------------------------
/components/crud/BlogCreate.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { useState, useEffect } from "react";
3 | import Router from "next/router";
4 | import dynamic from "next/dynamic";
5 | import { withRouter } from "next/router";
6 | import { getCookie, isAuth } from "../../actions/auth";
7 | import { getCategories } from "../../actions/category";
8 | import { getTags } from "../../actions/tag";
9 | import { createBlog } from "../../actions/blog";
10 | const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
11 | import "../../node_modules/react-quill/dist/quill.snow.css";
12 | import { QuillModules, QuillFormats } from "../../helpers/quill";
13 |
14 | const CreateBlog = ({ router }) => {
15 | const blogFromLS = () => {
16 | if (typeof window === "undefined") {
17 | return false;
18 | }
19 |
20 | if (localStorage.getItem("blog")) {
21 | return JSON.parse(localStorage.getItem("blog"));
22 | } else {
23 | return false;
24 | }
25 | };
26 |
27 | const [categories, setCategories] = useState([]);
28 | const [tags, setTags] = useState([]);
29 |
30 | const [checked, setChecked] = useState([]); // categories
31 | const [checkedTag, setCheckedTag] = useState([]); // tags
32 |
33 | const [body, setBody] = useState(blogFromLS());
34 | const [values, setValues] = useState({
35 | error: "",
36 | sizeError: "",
37 | success: "",
38 | formData: "",
39 | title: "",
40 | hidePublishButton: false,
41 | loading: false
42 | });
43 |
44 | const {
45 | error,
46 | sizeError,
47 | success,
48 | formData,
49 | title,
50 | hidePublishButton,
51 | loading
52 | } = values;
53 | const token = getCookie("token");
54 |
55 | useEffect(() => {
56 | setValues({ ...values, formData: new FormData() });
57 | initCategories();
58 | initTags();
59 | }, [router]);
60 |
61 | const initCategories = () => {
62 | getCategories().then(data => {
63 | if (data.error) {
64 | setValues({ ...values, error: data.error });
65 | } else {
66 | setCategories(data);
67 | }
68 | });
69 | };
70 |
71 | const initTags = () => {
72 | getTags().then(data => {
73 | if (data.error) {
74 | setValues({ ...values, error: data.error });
75 | } else {
76 | setTags(data);
77 | }
78 | });
79 | };
80 |
81 | const publishBlog = e => {
82 | setValues({ ...values, loading: true });
83 | e.preventDefault();
84 | // console.log('ready to publishBlog');
85 | createBlog(formData, token).then(data => {
86 | if (data.error) {
87 | setValues({ ...values, error: data.error, loading: false });
88 | } else {
89 | setValues({
90 | ...values,
91 | loading: false,
92 | title: "",
93 | error: "",
94 | success: `A new blog titled "${data.title}" is created`
95 | });
96 | setBody("");
97 | setCategories([]);
98 | setTags([]);
99 | }
100 | });
101 | };
102 |
103 | const handleChange = name => e => {
104 | // console.log(e.target.value);
105 | const value = name === "photo" ? e.target.files[0] : e.target.value;
106 | formData.set(name, value);
107 | setValues({ ...values, [name]: value, formData, error: "" });
108 | };
109 |
110 | const handleBody = e => {
111 | // console.log(e);
112 | setBody(e);
113 | formData.set("body", e);
114 | if (typeof window !== "undefined") {
115 | localStorage.setItem("blog", JSON.stringify(e));
116 | }
117 | };
118 |
119 | const handleToggle = c => () => {
120 | setValues({ ...values, error: "" });
121 | // return the first index or -1
122 | const clickedCategory = checked.indexOf(c);
123 | const all = [...checked];
124 |
125 | if (clickedCategory === -1) {
126 | all.push(c);
127 | } else {
128 | all.splice(clickedCategory, 1);
129 | }
130 | console.log(all);
131 | setChecked(all);
132 | formData.set("categories", all);
133 | };
134 |
135 | const handleTagsToggle = t => () => {
136 | setValues({ ...values, error: "" });
137 | // return the first index or -1
138 | const clickedTag = checked.indexOf(t);
139 | const all = [...checkedTag];
140 |
141 | if (clickedTag === -1) {
142 | all.push(t);
143 | } else {
144 | all.splice(clickedTag, 1);
145 | }
146 | console.log(all);
147 | setCheckedTag(all);
148 | formData.set("tags", all);
149 | };
150 |
151 | const showCategories = () => {
152 | return (
153 | categories &&
154 | categories.map((c, i) => (
155 |
156 |
161 |
162 |
163 | ))
164 | );
165 | };
166 |
167 | const showTags = () => {
168 | return (
169 | tags &&
170 | tags.map((t, i) => (
171 |
172 |
177 |
178 |
179 | ))
180 | );
181 | };
182 |
183 | const showError = () => (
184 |
188 | {error}
189 |
190 | );
191 |
192 | const showSuccess = () => (
193 |
197 | {success}
198 |
199 | );
200 |
201 | const showLoading = () => (
202 |
206 | Loading...
207 |
208 | );
209 |
210 | const createBlogForm = () => {
211 | return (
212 |
239 | );
240 | };
241 |
242 | return (
243 |
244 |
245 |
246 | {createBlogForm()}
247 |
248 | {showError()}
249 | {showSuccess()}
250 | {showLoading()}
251 |
252 |
253 |
254 |
255 |
256 |
257 |
Featured image
258 |
259 |
260 | Max size: 1mb
261 |
262 |
271 |
272 |
273 |
274 |
Categories
275 |
276 |
277 |
278 | {showCategories()}
279 |
280 |
281 |
282 |
Tags
283 |
284 |
287 |
288 |
289 |
290 |
291 | );
292 | };
293 |
294 | export default withRouter(CreateBlog);
295 |
--------------------------------------------------------------------------------
/components/crud/BlogUpdate.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { useState, useEffect } from 'react';
3 | import Router from 'next/router';
4 | import dynamic from 'next/dynamic';
5 | import { withRouter } from 'next/router';
6 | import { getCookie, isAuth } from '../../actions/auth';
7 | import { getCategories } from '../../actions/category';
8 | import { getTags } from '../../actions/tag';
9 | import { singleBlog, updateBlog } from '../../actions/blog';
10 | const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
11 | import '../../node_modules/react-quill/dist/quill.snow.css';
12 | import { QuillModules, QuillFormats } from '../../helpers/quill';
13 | import { API } from '../../config';
14 |
15 | const BlogUpdate = ({ router }) => {
16 | const [body, setBody] = useState('');
17 |
18 | const [categories, setCategories] = useState([]);
19 | const [tags, setTags] = useState([]);
20 |
21 | const [checked, setChecked] = useState([]); // categories
22 | const [checkedTag, setCheckedTag] = useState([]); // tags
23 |
24 | const [values, setValues] = useState({
25 | title: '',
26 | error: '',
27 | success: '',
28 | formData: '',
29 | title: '',
30 | body: '',
31 | loading: false
32 | });
33 |
34 | const { error, success, formData, title, loading } = values;
35 | const token = getCookie('token');
36 |
37 | useEffect(() => {
38 | setValues({ ...values, formData: new FormData() });
39 | initBlog();
40 | initCategories();
41 | initTags();
42 | }, [router]);
43 |
44 | const initBlog = () => {
45 | if (router.query.slug) {
46 | singleBlog(router.query.slug).then(data => {
47 | if (data.error) {
48 | console.log(data.error);
49 | } else {
50 | setValues({ ...values, title: data.title });
51 | setBody(data.body);
52 | setCategoriesArray(data.categories);
53 | setTagsArray(data.tags);
54 | }
55 | });
56 | }
57 | };
58 |
59 | const setCategoriesArray = blogCategories => {
60 | let ca = [];
61 | blogCategories.map((c, i) => {
62 | ca.push(c._id);
63 | });
64 | setChecked(ca);
65 | };
66 |
67 | const setTagsArray = blogTags => {
68 | let ta = [];
69 | blogTags.map((t, i) => {
70 | ta.push(t._id);
71 | });
72 | setCheckedTag(ta);
73 | };
74 |
75 | const initCategories = () => {
76 | getCategories().then(data => {
77 | if (data.error) {
78 | setValues({ ...values, error: data.error });
79 | } else {
80 | setCategories(data);
81 | }
82 | });
83 | };
84 |
85 | const initTags = () => {
86 | getTags().then(data => {
87 | if (data.error) {
88 | setValues({ ...values, error: data.error });
89 | } else {
90 | setTags(data);
91 | }
92 | });
93 | };
94 |
95 | const handleToggle = c => () => {
96 | setValues({ ...values, error: '' });
97 | // return the first index or -1
98 | const clickedCategory = checked.indexOf(c);
99 | const all = [...checked];
100 |
101 | if (clickedCategory === -1) {
102 | all.push(c);
103 | } else {
104 | all.splice(clickedCategory, 1);
105 | }
106 | console.log(all);
107 | setChecked(all);
108 | formData.set('categories', all);
109 | };
110 |
111 | const handleTagsToggle = t => () => {
112 | setValues({ ...values, error: '' });
113 | // return the first index or -1
114 | const clickedTag = checkedTag.indexOf(t);
115 | const all = [...checkedTag];
116 |
117 | if (clickedTag === -1) {
118 | all.push(t);
119 | } else {
120 | all.splice(clickedTag, 1);
121 | }
122 | console.log(all);
123 | setCheckedTag(all);
124 | formData.set('tags', all);
125 | };
126 |
127 | const findOutCategory = c => {
128 | const result = checked.indexOf(c);
129 | if (result !== -1) {
130 | return true;
131 | } else {
132 | return false;
133 | }
134 | };
135 |
136 | const findOutTag = t => {
137 | const result = checkedTag.indexOf(t);
138 | if (result !== -1) {
139 | return true;
140 | } else {
141 | return false;
142 | }
143 | };
144 |
145 | const showCategories = () => {
146 | return (
147 | categories &&
148 | categories.map((c, i) => (
149 |
150 |
156 |
157 |
158 | ))
159 | );
160 | };
161 |
162 | const showTags = () => {
163 | return (
164 | tags &&
165 | tags.map((t, i) => (
166 |
167 |
173 |
174 |
175 | ))
176 | );
177 | };
178 |
179 | const handleChange = name => e => {
180 | // console.log(e.target.value);
181 | const value = name === 'photo' ? e.target.files[0] : e.target.value;
182 | formData.set(name, value);
183 | setValues({ ...values, [name]: value, formData, error: '' });
184 | };
185 |
186 | const handleBody = e => {
187 | setBody(e);
188 | formData.set('body', e);
189 | };
190 |
191 | const editBlog = e => {
192 | e.preventDefault();
193 | updateBlog(formData, token, router.query.slug).then(data => {
194 | if (data.error) {
195 | setValues({ ...values, error: data.error, loading: false });
196 | } else {
197 | setValues({
198 | ...values,
199 | loading: false,
200 | title: '',
201 | success: `Blog titled "${data.title}" is successfully updated`
202 | });
203 | if (isAuth() && isAuth().role === 1) {
204 | // Router.replace(`/admin/crud/${router.query.slug}`);
205 | Router.replace(`/admin`);
206 | } else if (isAuth() && isAuth().role === 0) {
207 | // Router.replace(`/user/crud/${router.query.slug}`);
208 | Router.replace(`/user`);
209 | }
210 | }
211 | });
212 | };
213 |
214 | const showError = () => (
215 |
216 | {error}
217 |
218 | );
219 |
220 | const showSuccess = () => (
221 |
222 | {success}
223 |
224 | );
225 |
226 | const showLoading = () => (
227 |
228 | Loading...
229 |
230 | );
231 |
232 | const updateBlogForm = () => {
233 | return (
234 |
256 | );
257 | };
258 |
259 | return (
260 |
261 |
262 |
263 | {updateBlogForm()}
264 |
265 |
266 | {showSuccess()}
267 | {showError()}
268 | {showLoading()}
269 |
270 |
271 | {body && (
272 |

273 | )}
274 |
275 |
276 |
277 |
278 |
279 |
Featured image
280 |
281 |
282 | Max size: 1mb
283 |
284 |
288 |
289 |
290 |
291 |
Categories
292 |
293 |
294 |
295 |
296 |
297 |
Tags
298 |
299 |
300 |
301 |
302 |
303 |
304 | );
305 | };
306 |
307 | export default withRouter(BlogUpdate);
308 |
--------------------------------------------------------------------------------