├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── home_page.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── App.css ├── context │ ├── blog-context.js │ ├── GlobalState.js │ └── reducers.js ├── App.js ├── index.css ├── components │ ├── PageLoader.js │ ├── Main.js │ ├── Home.js │ ├── BlogFormOld.js │ └── BlogForm.js ├── index.js ├── header.js ├── shared │ └── Header.js └── data │ └── blogs.json ├── static.json ├── .gitignore ├── package.json └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/reactBlog/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/reactBlog/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/reactBlog/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | .active { 3 | color: #d4de3c; 4 | border-bottom: 1px solid; 5 | } 6 | -------------------------------------------------------------------------------- /public/home_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/reactBlog/HEAD/public/home_page.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /static.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "build/", 3 | "clean_urls": false, 4 | "routes": { 5 | "/**": "index.html" 6 | } 7 | } -------------------------------------------------------------------------------- /src/context/blog-context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import blogs from "../data/blogs"; 3 | 4 | export default React.createContext({ 5 | blogs: blogs, 6 | createBlog: blog => {}, 7 | deleteBlog: blogId => {} 8 | }); 9 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router } from "react-router-dom"; 3 | import Main from "./components/Main"; 4 | import GlobalState from "./context/GlobalState"; 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 |
11 | 12 | 13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/PageLoader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Flex, Spinner } from "@chakra-ui/core"; 3 | const PageLoader = () => { 4 | return ( 5 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default PageLoader; 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Switch, Route, Redirect } from "react-router-dom"; 3 | import Header from "../shared/Header"; 4 | import BlogForm from "./BlogForm"; 5 | import Home from "./Home"; 6 | 7 | const Main = () => { 8 | return ( 9 | 10 |
11 | 12 | 13 | { 17 | return ; 18 | }} 19 | /> 20 | { 24 | return ; 25 | }} 26 | /> 27 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default Main; 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import { theme, ThemeProvider, CSSReset } from '@chakra-ui/core' 5 | 6 | const breakpoints = ['600px', '850px', '900px', '1440px'] 7 | breakpoints.sm = breakpoints[0] 8 | breakpoints.md = breakpoints[1] 9 | breakpoints.lg = breakpoints[2] 10 | breakpoints.xl = breakpoints[3] 11 | 12 | const newTheme = { 13 | ...theme, 14 | breakpoints, 15 | } 16 | 17 | ReactDOM.render( 18 | 19 | 20 | 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ) 26 | // function App() { 27 | // return ( 28 | // 29 | // 30 | //
31 | // 32 | // 33 | // ); 34 | // } 35 | 36 | // const rootElement = document.getElementById("root"); 37 | // ReactDOM.render(, rootElement); 38 | -------------------------------------------------------------------------------- /src/context/GlobalState.js: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from "react"; 2 | import BlogContext from "./blog-context"; 3 | import { blogReducer, CREATE_BLOG, EDIT_BLOG, DELETE_BLOG } from "./reducers"; 4 | import blogs_data from "../data/blogs"; 5 | 6 | const GlobalState = props => { 7 | const blogs = blogs_data; 8 | const [blogState, dispatch] = useReducer(blogReducer, { blogs: blogs }); 9 | 10 | const createBlog = blog => { 11 | dispatch({ type: CREATE_BLOG, blog: blog }); 12 | }; 13 | 14 | const editBlog = blog => { 15 | dispatch({ type: EDIT_BLOG, blog: blog }); 16 | }; 17 | 18 | const deleteBlog = blogId => { 19 | dispatch({ type: DELETE_BLOG, blogId: blogId }); 20 | }; 21 | 22 | return ( 23 | 31 | {props.children} 32 | 33 | ); 34 | }; 35 | 36 | export default GlobalState; 37 | -------------------------------------------------------------------------------- /src/context/reducers.js: -------------------------------------------------------------------------------- 1 | export const CREATE_BLOG = "CREATE_BLOG"; 2 | export const EDIT_BLOG = "EDIT_BLOG"; 3 | export const DELETE_BLOG = "DELETE_BLOG"; 4 | 5 | const createBlog = (blog, state) => { 6 | const state_blogs = [...state.blogs]; 7 | state_blogs.push({ ...blog, id: state_blogs.length + 1 }); 8 | return { ...state, blogs: state_blogs }; 9 | }; 10 | 11 | const editBlog = (blog, state) => { 12 | const state_blogs = [...state.blogs]; 13 | const blogIndex = getBlogIndex(state, blog.id); 14 | state_blogs[blogIndex] = blog; 15 | return { ...state, blogs: state_blogs }; 16 | }; 17 | 18 | const getBlogIndex = (state, blogId) => { 19 | return state.blogs.findIndex(blog => blog.id === blogId); 20 | }; 21 | 22 | const deleteBlog = (blogId, state) => { 23 | const state_blogs = [...state.blogs]; 24 | return { ...state, blogs: state_blogs.filter(blog => blog.id !== blogId) }; 25 | }; 26 | 27 | export const blogReducer = (state, action) => { 28 | switch (action.type) { 29 | case CREATE_BLOG: 30 | return createBlog(action.blog, state); 31 | case EDIT_BLOG: 32 | return editBlog(action.blog, state); 33 | case DELETE_BLOG: 34 | return deleteBlog(action.blogId, state); 35 | default: 36 | return state; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://ma-ahmad.github.io/reactBlog", 6 | "dependencies": { 7 | "@chakra-ui/core": "^0.8.0", 8 | "@emotion/core": "^10.0.28", 9 | "@emotion/styled": "^10.0.27", 10 | "@testing-library/jest-dom": "^4.2.4", 11 | "@testing-library/react": "^9.3.2", 12 | "@testing-library/user-event": "^7.1.2", 13 | "emotion-theming": "^10.0.27", 14 | "formik": "^2.1.5", 15 | "react": "^16.13.1", 16 | "react-animation-components": "^3.0.0", 17 | "react-dom": "^16.13.1", 18 | "react-dotdotdot": "^1.3.1", 19 | "react-icons": "^3.10.0", 20 | "react-image": "^4.0.3", 21 | "react-router-dom": "^5.2.0", 22 | "react-scripts": "3.4.1", 23 | "react-transition-group": "^4.4.1" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "predeploy": "npm run build", 28 | "deploy": "gh-pages -d build", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": "react-app" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "gh-pages": "^3.1.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

React Blogs

2 | 3 |
4 | 5 | [![Linkedin](https://img.shields.io/badge/-LinkedIn-blue?style=flat&logo=Linkedin&logoColor=white)](https://www.linkedin.com/in/muhammad-ahmad20/) 6 | [![Gmail](https://img.shields.io/badge/-Gmail-c14438?style=flat&logo=Gmail&logoColor=white)](mailto:muhammad.ahmad8043@gmail.com) 7 |
8 |

This app shows you how to create a CRUD app with react by using ChakraUI.

9 | 10 | ![Home Page](/public/home_page.png) 11 | 12 | ## What this App does? 13 | 14 | - By this app, you will be able to: 15 | 16 | - Create a new Blog. 17 | - Edit a Blog. 18 | - Delete a Blog. 19 | - Get list of all Blogs. 20 | 21 | ## Built with 22 | 23 | - Reactjs (react hooks, react router) 24 | - ChakraUI used for styling 25 | - Formik used for validations 26 | - react-transition-group for animation 27 | 28 | ## Setup 29 | 30 | - Clone the repository 31 | - Use `cd ` 32 | - Run `npm install` 33 | - Run `npm start` 34 | 35 | ## View and copy code of your favourite components 36 | [TemplatesKart website](https://templateskart.com/projects/blog-app) 37 | 38 | ## Author 39 | 40 | Muhammad Ahmad 41 | 42 | - Github: [@Ahmad](https://github.com/MA-Ahmad) 43 | 44 | - LinkedIn: [Muhammad Ahmad](https://www.linkedin.com/in/muhammad-ahmad20/) 45 | 46 | ## 🤝 Contributing 47 | 48 | Contributions, issues and feature requests are welcome! Start by: 49 | 50 | - Forking the project 51 | - Cloning the project to your local machine 52 | - `cd` into the project directory 53 | - Run `git checkout -b your-branch-name` 54 | - Make your contributions 55 | - Push your branch up to your forked repository 56 | - Open a Pull Request with a detailed description to the development branch of the original project for a review 57 | 58 | ## Show your support 59 | 60 | Give a ⭐️ if you like this project! 61 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, Heading, Flex, Text, Button } from "@chakra-ui/core"; 3 | 4 | const MenuItems = ({ children }) => ( 5 | 6 | {children} 7 | 8 | ); 9 | 10 | // Note: This code could be better, so I'd recommend you to understand how I solved and you could write yours better :) 11 | const Header = props => { 12 | const [show, setShow] = React.useState(false); 13 | const handleToggle = () => setShow(!show); 14 | 15 | return ( 16 | 26 | 27 | 28 | Blog 29 | 30 | 31 | 32 | 33 | 39 | Menu 40 | 41 | 42 | 43 | 44 | 50 | New 51 | List 52 | 53 | 54 | 55 | 59 | 62 | 63 | 64 | ); 65 | }; 66 | 67 | export default Header; 68 | -------------------------------------------------------------------------------- /src/shared/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Heading, Flex, Text, Button, Link } from '@chakra-ui/core' 3 | import { NavLink } from 'react-router-dom' 4 | import { FaGithub } from 'react-icons/fa' 5 | import '../App.css' 6 | 7 | const MenuItems = ({ children }) => ( 8 | 9 | {children} 10 | 11 | ) 12 | 13 | // Note: This code could be better, so I'd recommend you to understand how I solved and you could write yours better :) 14 | const Header = (props) => { 15 | const [show, setShow] = React.useState(false) 16 | const handleToggle = () => setShow(!show) 17 | 18 | return ( 19 | 20 | 32 | 33 | 34 | Blog 35 | 36 | 37 | 38 | 39 | 45 | Menu 46 | 47 | 48 | 49 | 50 | 56 | 57 | 58 | 59 | New 60 | 61 | 62 | 63 | 64 | 65 | 66 | List 67 | 68 | 69 | 70 | 71 | 72 | 73 | 78 | 81 | 82 | 83 | 84 | 85 | ) 86 | } 87 | 88 | export default Header 89 | -------------------------------------------------------------------------------- /src/data/blogs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "title": "Rails ActiveAdmin", 5 | "authorName": "Ali", 6 | "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." 7 | }, 8 | { 9 | "id": 2, 10 | "title": "Future of ROR", 11 | "authorName": "Ahmad", 12 | "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." 13 | }, 14 | { 15 | "id": 3, 16 | "title": "ReactJs with Chakra", 17 | "authorName": "Taimoor", 18 | "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." 19 | }, 20 | { 21 | "id": 4, 22 | "title": "ReactJS with Rails", 23 | "authorName": "Hassan", 24 | "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." 25 | }, 26 | { 27 | "id": 5, 28 | "title": "React on Rails", 29 | "authorName": "Umar", 30 | "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." 31 | } 32 | ] -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect } from 'react' 2 | import { Box, Badge, SimpleGrid, useToast } from '@chakra-ui/core' 3 | import Dotdotdot from 'react-dotdotdot' 4 | import BlogContext from '../context/blog-context' 5 | import { Link } from 'react-router-dom' 6 | import PageLoader from './PageLoader' 7 | import { Img } from 'react-image' 8 | 9 | const Home = () => { 10 | const context = useContext(BlogContext) 11 | const [blogId, setBlogId] = useState('') 12 | const toast = useToast() 13 | 14 | useEffect(() => { 15 | context.deleteBlog(blogId) 16 | }, [blogId]) 17 | 18 | const handleDelete = (id) => { 19 | toast({ 20 | position: 'bottom', 21 | title: 'Notification', 22 | description: 'Blog deleted successfully', 23 | status: 'success', 24 | duration: 2000, 25 | isClosable: true, 26 | }) 27 | setBlogId(id) 28 | } 29 | return ( 30 | 38 | 39 | {context.blogs.map((blog) => { 40 | return ( 41 | 42 | 50 | handleDelete(blog.id)} 56 | > 57 | Delete 58 | 59 | 60 | 61 | 69 | Blog image} 73 | /> 74 | 75 | 76 | 83 | {blog.title} 84 | 85 | 86 | 87 | 88 | 89 | {blog.authorName} 90 | 91 | 92 | 93 | 94 | 102 | {blog.content} 103 | 104 | 105 | 106 | 107 | 108 | 109 | ) 110 | })} 111 | 112 | 113 | ) 114 | } 115 | 116 | export default Home 117 | -------------------------------------------------------------------------------- /src/components/BlogFormOld.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { 3 | Box, 4 | Flex, 5 | FormControl, 6 | FormLabel, 7 | Input, 8 | Stack, 9 | Textarea, 10 | Button, 11 | Heading, 12 | useToast, 13 | FormErrorMessage 14 | } from "@chakra-ui/core"; 15 | import BlogContext from "../context/blog-context"; 16 | import { Formik, Field, Form, ErrorMessage, useFormik } from "formik"; 17 | import { red } from "color-name"; 18 | 19 | const BlogForm = () => { 20 | const context = useContext(BlogContext); 21 | const toast = useToast(); 22 | 23 | const [title, setTitle] = useState(""); 24 | const [authorName, setAuthorName] = useState(""); 25 | const [content, setContent] = useState(""); 26 | 27 | const handleSubmit = e => { 28 | e.preventDefault(); 29 | console.log(title); 30 | context.createBlog({ title: title, author: authorName, content: content }); 31 | toast({ 32 | position: "bottom", 33 | title: "Account created.", 34 | description: "We've created your account for you.", 35 | status: "success", 36 | duration: 5000, 37 | isClosable: true 38 | }); 39 | setTitle(""); 40 | setAuthorName(""); 41 | setContent(""); 42 | }; 43 | 44 | const handleNameChange = e => { 45 | setAuthorName(e.target.value); 46 | }; 47 | 48 | const handleTitleChange = e => { 49 | setTitle(e.target.value); 50 | }; 51 | 52 | const handleContentChange = e => { 53 | setContent(e.target.value); 54 | }; 55 | 56 | const validate = values => { 57 | const errors = {}; 58 | if (!values.title) { 59 | errors.title = "Required"; 60 | } else if (values.title.length < 5) { 61 | errors.title = "Must be 5 characters or more"; 62 | } 63 | 64 | if (!values.authorName) { 65 | errors.authorName = "Required"; 66 | } else if (values.authorName.length > 20) { 67 | errors.authorName = "Must be 20 characters or less"; 68 | } 69 | 70 | return errors; 71 | }; 72 | 73 | const formik = useFormik({ 74 | initialValues: { 75 | title: title, 76 | authorName: authorName 77 | }, 78 | validate, 79 | onSubmit: values => { 80 | alert(JSON.stringify(values, null, 2)); 81 | }, 82 | onChange: value => { 83 | console.log(value); 84 | setTitle(title); 85 | setAuthorName(authorName); 86 | } 87 | }); 88 | 89 | return ( 90 | 98 | 99 | 100 | Create a Blog 101 | 102 | 103 | 104 | 105 |
106 | 107 | Title 108 | 115 | {formik.errors.title ? ( 116 |
117 | {formik.errors.title} 118 | {formik.errors.name} 119 |
120 | ) : null} 121 |
122 | 123 | Author 124 | 131 | {formik.errors.authorName ? ( 132 |
{formik.errors.authorName}
133 | ) : null} 134 |
135 | 136 | Content 137 |