├── .gitignore ├── README.md ├── craco.config.js ├── package-lock.json ├── package.json ├── public ├── _redirects ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── components │ ├── Categories │ │ ├── AddNewCategory.js │ │ ├── CategoryDropDown.js │ │ ├── CategoryList.js │ │ └── UpdateCategory.js │ ├── Comments │ │ ├── AddComment.js │ │ ├── CommentsList.js │ │ └── UpdateComment.js │ ├── HomePage │ │ └── HomePage.js │ ├── Navigation │ │ ├── Admin │ │ │ └── AdminNavbar.js │ │ ├── Alerts │ │ │ ├── AccountVerificationAlertWarning.js │ │ │ └── AccountVerificationSuccessAlert.js │ │ ├── Navbar.js │ │ ├── Private │ │ │ └── PrivateNavbar.js │ │ ├── ProtectedRoutes │ │ │ ├── AdminRoute.js │ │ │ └── PrivateProtectRoute.js │ │ └── Public │ │ │ └── PublicNavbar.js │ ├── Posts │ │ ├── CreatePost.js │ │ ├── PostDetails.js │ │ ├── PostsList.js │ │ └── UpdatePost.js │ └── Users │ │ ├── AccountVerification │ │ └── AccountVerified.js │ │ ├── Emailing │ │ └── SendEmail.js │ │ ├── Login │ │ └── Login.js │ │ ├── PasswordManagement │ │ ├── ResetPassword.js │ │ ├── ResetPasswordForm.js │ │ └── UpdatePassword.js │ │ ├── Profile │ │ ├── Profile.js │ │ ├── UpdateProfileForm.js │ │ └── UploadProfilePhoto.js │ │ ├── Register │ │ └── Register.js │ │ └── UsersList │ │ ├── UsersList.js │ │ ├── UsersListHeader.js │ │ └── UsersListItem.js ├── img │ ├── err.svg │ ├── poster.png │ └── svg1.svg ├── index.css ├── index.js ├── redux │ ├── slices │ │ ├── accountVerification │ │ │ └── accVerificationSlices.js │ │ ├── category │ │ │ └── categorySlice.js │ │ ├── comments │ │ │ └── commentSlices.js │ │ ├── email │ │ │ └── emailSlices.js │ │ ├── posts │ │ │ └── postSlices.js │ │ └── users │ │ │ └── usersSlices.js │ └── store │ │ └── store.js └── utils │ ├── DateFormatter.js │ ├── LoadingComponent.js │ └── baseURL.js ├── tailwind.config.js └── yarn.lock /.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 | .env 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | // craco.config.js 2 | module.exports = { 3 | style: { 4 | postcss: { 5 | plugins: [require("tailwindcss"), require("autoprefixer")], 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^6.2.0", 7 | "@emotion/react": "^11.4.0", 8 | "@headlessui/react": "^1.3.0", 9 | "@heroicons/react": "^1.0.3", 10 | "@reduxjs/toolkit": "^1.6.1", 11 | "@testing-library/jest-dom": "^5.11.4", 12 | "@testing-library/react": "^11.1.0", 13 | "@testing-library/user-event": "^12.1.10", 14 | "axios": "^0.21.1", 15 | "formik": "^2.2.9", 16 | "moment": "^2.29.1", 17 | "react": "^17.0.2", 18 | "react-dom": "^17.0.2", 19 | "react-dropzone": "^11.3.4", 20 | "react-moment": "^1.1.1", 21 | "react-redux": "^7.2.4", 22 | "react-router-dom": "^5.2.0", 23 | "react-scripts": "4.0.3", 24 | "react-select": "^4.3.1", 25 | "react-spinners": "^0.11.0", 26 | "styled-components": "^5.3.0", 27 | "web-vitals": "^1.0.1", 28 | "yup": "^0.32.9" 29 | }, 30 | "scripts": { 31 | "start": "craco start", 32 | "build": "craco build", 33 | "test": "craco test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "autoprefixer": "^9.8.6", 56 | "postcss": "^7.0.36", 57 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweneboah/blog-app-frontend/038afe8ccc3059b6a81bf6c0e8b715d17a876d67/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Bloog App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweneboah/blog-app-frontend/038afe8ccc3059b6a81bf6c0e8b715d17a876d67/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweneboah/blog-app-frontend/038afe8ccc3059b6a81bf6c0e8b715d17a876d67/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Switch, Route } from "react-router-dom"; 2 | import AddNewCategory from "./components/Categories/AddNewCategory"; 3 | import HomePage from "./components/HomePage/HomePage"; 4 | import Navbar from "./components/Navigation/Navbar"; 5 | import Login from "./components/Users/Login/Login"; 6 | import Register from "./components/Users/Register/Register"; 7 | import CategoryList from "./components/Categories/CategoryList"; 8 | import UpdateCategory from "./components/Categories/UpdateCategory"; 9 | import PrivateProtectRoute from "./components/Navigation/ProtectedRoutes/PrivateProtectRoute"; 10 | import AdminRoute from "./components/Navigation/ProtectedRoutes/AdminRoute"; 11 | import CreatePost from "./components/Posts/CreatePost"; 12 | import PostsList from "./components/Posts/PostsList"; 13 | import PostDetails from "./components/Posts/PostDetails"; 14 | import UpdatePost from "./components/Posts/UpdatePost"; 15 | import UpdateComment from "./components/Comments/UpdateComment"; 16 | import Profile from "./components/Users/Profile/Profile"; 17 | import UploadProfilePhoto from "./components/Users/Profile/UploadProfilePhoto"; 18 | import UpdateProfileForm from "./components/Users/Profile/UpdateProfileForm"; 19 | import SendEmail from "./components/Users/Emailing/SendEmail"; 20 | import AccountVerified from "./components/Users/AccountVerification/AccountVerified"; 21 | import UsersList from "./components/Users/UsersList/UsersList"; 22 | import UpdatePassword from "./components/Users/PasswordManagement/UpdatePassword"; 23 | import ResetPasswordForm from "./components/Users/PasswordManagement/ResetPasswordForm"; 24 | import ResetPassword from "./components/Users/PasswordManagement/ResetPassword"; 25 | 26 | function App() { 27 | return ( 28 | 29 | 30 | 31 | 36 | 41 | 42 | 43 | 48 | 49 | 54 | 55 | 60 | 61 | 66 | 71 | 72 | 73 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | } 89 | 90 | export default App; 91 | -------------------------------------------------------------------------------- /src/components/Categories/AddNewCategory.js: -------------------------------------------------------------------------------- 1 | import { PlusCircleIcon, BookOpenIcon } from "@heroicons/react/solid"; 2 | import { useFormik } from "formik"; 3 | import { Redirect } from "react-router-dom"; 4 | import * as Yup from "yup"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { createCategoryAction } from "../../redux/slices/category/categorySlice"; 7 | 8 | //Form schema 9 | const formSchema = Yup.object({ 10 | title: Yup.string().required("Title is required"), 11 | }); 12 | 13 | const AddNewCategory = () => { 14 | const dispatch = useDispatch(); 15 | //formik 16 | const formik = useFormik({ 17 | initialValues: { 18 | title: "", 19 | }, 20 | onSubmit: values => { 21 | //dispath the action 22 | dispatch(createCategoryAction(values)); 23 | }, 24 | validationSchema: formSchema, 25 | }); 26 | 27 | //get data from store 28 | const state = useSelector(state => state?.category); 29 | 30 | const { loading, appErr, serverErr, isCreated } = state; 31 | //redirect 32 | if (isCreated) return ; 33 | return ( 34 |
35 |
36 |
37 | 38 |

39 | Add New Category 40 |

41 |

42 |

43 | These are the categories user will select when creating a post 44 |

45 | {/* Display err */} 46 |
47 | {appErr || serverErr ? ( 48 |

49 | {serverErr} {appErr} 50 |

51 | ) : null} 52 |
53 |

54 |
55 | {/* Form */} 56 |
57 | 58 |
59 |
60 | 63 | {/* Title */} 64 | 73 |
74 | {formik.touched.title && formik.errors.title} 75 |
76 |
77 |
78 | 79 |
80 |
81 | {/* Submit */} 82 | {loading ? ( 83 | 95 | ) : ( 96 | 108 | )} 109 |
110 |
111 |
112 |
113 |
114 | ); 115 | }; 116 | 117 | export default AddNewCategory; 118 | -------------------------------------------------------------------------------- /src/components/Categories/CategoryDropDown.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import Select from "react-select"; 4 | import { fetchCategoriesAction } from "../../redux/slices/category/categorySlice"; 5 | 6 | const options = [ 7 | { value: "chocolate", label: "Chocolate" }, 8 | { value: "strawberry", label: "Strawberry" }, 9 | { value: "vanilla", label: "Vanilla" }, 10 | ]; 11 | 12 | const CategoryDropDown = props => { 13 | //dispatch action 14 | const dispatch = useDispatch(); 15 | useEffect(() => { 16 | dispatch(fetchCategoriesAction()); 17 | }, [dispatch]); 18 | //select categories 19 | const category = useSelector(state => state?.category); 20 | const { categoryList, loading, appErr, serverErr } = category; 21 | 22 | const allCategories = categoryList?.map(category => { 23 | return { 24 | label: category?.title, 25 | value: category?._id, 26 | }; 27 | }); 28 | 29 | //handleChange 30 | const handleChange = value => { 31 | props.onChange("category", value); 32 | }; 33 | //handleBlur 34 | const handleBlur = () => { 35 | props.onBlur("category", true); 36 | }; 37 | return ( 38 |
39 | {loading ? ( 40 |

41 | Product categories list are loading please wait... 42 |

43 | ) : ( 44 | 76 |
77 |
78 | 81 | {/* Title */} 82 | 91 |
92 | {formik.touched.title && formik.errors.title} 93 |
94 |
95 |
96 | 97 |
98 |
99 | {/* Submit */} 100 | {loading ? ( 101 | 113 | ) : ( 114 | <> 115 | 127 | 134 | 135 | )} 136 |
137 |
138 | 139 |
140 | 141 | ); 142 | }; 143 | 144 | export default UpdateCategory; 145 | -------------------------------------------------------------------------------- /src/components/Comments/AddComment.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import * as Yup from "yup"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { createCommentAction } from "../../redux/slices/comments/commentSlices"; 6 | 7 | //Form schema 8 | const formSchema = Yup.object({ 9 | description: Yup.string().required("Description is required"), 10 | }); 11 | 12 | const AddComment = ({ postId }) => { 13 | //dispatch 14 | const dispatch = useDispatch(); 15 | //select data from store 16 | const comment = useSelector(state => state?.comment); 17 | const { loading, appErr, serverErr } = comment; 18 | 19 | const formik = useFormik({ 20 | initialValues: { 21 | description: "", 22 | }, 23 | onSubmit: values => { 24 | const data = { 25 | postId, 26 | description: values?.description, 27 | }; 28 | //dispatch action 29 | dispatch(createCommentAction(data)); 30 | }, 31 | validationSchema: formSchema, 32 | }); 33 | return ( 34 |
35 | {/* Err */} 36 | {serverErr || appErr ? ( 37 |

38 | {serverErr} {appErr} 39 |

40 | ) : null} 41 |
45 | 55 | {loading ? ( 56 | 62 | ) : ( 63 | 69 | )} 70 |
71 |
72 | {formik.touched.description && formik.errors.description} 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default AddComment; 79 | -------------------------------------------------------------------------------- /src/components/Comments/CommentsList.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { PencilAltIcon, TrashIcon } from "@heroicons/react/solid"; 3 | import Moment from "react-moment"; 4 | import { deleteCommentAction } from "../../redux/slices/comments/commentSlices"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | 7 | export default function CommentsList({ comments }) { 8 | const user = useSelector(state => state?.users); 9 | const { userAuth } = user; 10 | const isLoginuser = userAuth?._id; 11 | console.log(comments); 12 | //dispatch 13 | const dispatch = useDispatch(); 14 | return ( 15 |
16 | 80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/components/Comments/UpdateComment.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Redirect } from "react-router-dom"; 3 | import { useFormik } from "formik"; 4 | import * as Yup from "yup"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { 7 | updateCommentAction, 8 | fetchCommentAction, 9 | } from "../../redux/slices/comments/commentSlices"; 10 | 11 | //Form schema 12 | const formSchema = Yup.object({ 13 | description: Yup.string().required("Description is required"), 14 | }); 15 | 16 | const UpdateComment = ({ 17 | computedMatch: { 18 | params: { id }, 19 | }, 20 | }) => { 21 | //dispatch 22 | const dispatch = useDispatch(); 23 | //fetch comment 24 | useEffect(() => { 25 | dispatch(fetchCommentAction(id)); 26 | }, [dispatch, id]); 27 | //select comment from store 28 | const comment = useSelector(state => state?.comment); 29 | const { commentDetails, isUpdate } = comment; 30 | 31 | const formik = useFormik({ 32 | enableReinitialize: true, 33 | initialValues: { 34 | description: commentDetails?.description, 35 | }, 36 | onSubmit: values => { 37 | const data = { 38 | id, 39 | description: values?.description, 40 | }; 41 | //dispatch action 42 | dispatch(updateCommentAction(data)); 43 | }, 44 | validationSchema: formSchema, 45 | }); 46 | 47 | //redirect 48 | if (isUpdate) return ; 49 | return ( 50 |
51 |
52 |
56 | 143 | {/* Image component */} 144 | 150 | 151 | { 155 | formik.setFieldValue("image", acceptedFiles[0]); 156 | }} 157 | > 158 | {({ getRootProps, getInputProps }) => ( 159 |
160 |
event.stopPropagation(), 164 | })} 165 | > 166 | 167 |

168 | Click here to select image 169 |

170 |
171 |
172 | )} 173 |
174 |
175 | {/* Err msg */} 176 |
177 | {formik?.touched?.description && formik.errors?.description} 178 |
179 |
180 |
181 | {/* Submit btn */} 182 | {loading ? ( 183 | 189 | ) : ( 190 | 196 | )} 197 |
198 | 199 |
200 | 201 | 202 | 203 | ); 204 | } 205 | -------------------------------------------------------------------------------- /src/components/Posts/PostDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link, Redirect } from "react-router-dom"; 3 | import { PencilAltIcon, TrashIcon } from "@heroicons/react/solid"; 4 | import { 5 | deletePostAction, 6 | fetchPostDetailsAction, 7 | } from "../../redux/slices/posts/postSlices"; 8 | import { useDispatch, useSelector } from "react-redux"; 9 | import DateFormatter from "../../utils/DateFormatter"; 10 | import LoadingComponent from "../../utils/LoadingComponent"; 11 | import AddComment from "../Comments/AddComment"; 12 | import CommentsList from "../Comments/CommentsList"; 13 | 14 | const PostDetails = ({ 15 | match: { 16 | params: { id }, 17 | }, 18 | }) => { 19 | const dispatch = useDispatch(); 20 | 21 | //select post details from store 22 | const post = useSelector(state => state?.post); 23 | const { postDetails, loading, appErr, serverErr, isDeleted } = post; 24 | 25 | //comment 26 | const comment = useSelector(state => state.comment); 27 | const { commentCreated, commentDeleted } = comment; 28 | useEffect(() => { 29 | dispatch(fetchPostDetailsAction(id)); 30 | }, [id, dispatch, commentCreated, commentDeleted]); 31 | 32 | //Get login user 33 | const user = useSelector(state => state.users); 34 | const { userAuth } = user; 35 | 36 | const isCreatedBy = postDetails?.user?._id === userAuth?._id; 37 | console.log(isCreatedBy); 38 | //redirect 39 | if (isDeleted) return ; 40 | return ( 41 | <> 42 | {loading ? ( 43 |
44 | 45 |
46 | ) : appErr || serverErr ? ( 47 |

48 | {serverErr} {appErr} 49 |

50 | ) : ( 51 |
52 |
53 | {/* Post Image */} 54 | 59 |
60 |

61 | {postDetails?.title} 62 |

63 | 64 | {/* User */} 65 |
66 | 71 |
72 | 73 |

74 | 75 | {postDetails?.user?.firstName}{" "} 76 | {postDetails?.user?.lastName}{" "} 77 | 78 |

79 | 80 |

81 | {} 82 |

83 |
84 |
85 | {/* Post description */} 86 |
87 |

88 | {postDetails?.description} 89 | 90 | {/* Show delete and update if it was created by the user */} 91 | {isCreatedBy ? ( 92 |

93 | 94 | 95 | 96 | 104 |

105 | ) : null} 106 |

107 |
108 |
109 |
110 | {/* Add comment Form component here */} 111 | {userAuth ? : null} 112 |
113 | {/* */} 114 | 115 |
116 |
117 | )} 118 | 119 | ); 120 | }; 121 | 122 | export default PostDetails; 123 | -------------------------------------------------------------------------------- /src/components/Posts/PostsList.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { ThumbUpIcon, ThumbDownIcon, EyeIcon } from "@heroicons/react/solid"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { Link } from "react-router-dom"; 5 | import { 6 | fetchPostsAction, 7 | toggleAddLikesToPost, 8 | toggleAddDisLikesToPost, 9 | } from "../../redux/slices/posts/postSlices"; 10 | import DateFormatter from "../../utils/DateFormatter"; 11 | import { fetchCategoriesAction } from "../../redux/slices/category/categorySlice"; 12 | import LoadingComponent from "../../utils/LoadingComponent"; 13 | 14 | export default function PostsList() { 15 | //select post from store 16 | const post = useSelector(state => state?.post); 17 | const { postLists, loading, appErr, serverErr, likes, dislikes } = post; 18 | console.log(postLists); 19 | //select categories from store 20 | const category = useSelector(state => state?.category); 21 | const { 22 | categoryList, 23 | loading: catLoading, 24 | appErr: catAppErr, 25 | serverErr: catServerErr, 26 | } = category; 27 | //dispatch 28 | const dispatch = useDispatch(); 29 | //fetch post 30 | useEffect(() => { 31 | dispatch(fetchPostsAction("")); 32 | }, [dispatch, likes, dislikes]); 33 | //fetch categories 34 | useEffect(() => { 35 | dispatch(fetchCategoriesAction()); 36 | }, [dispatch]); 37 | 38 | return ( 39 | <> 40 |
41 |
42 |
43 |
44 |
45 | 46 | Latest Posts from our awesome authors 47 | 48 |

49 | Latest Post 50 |

51 |
52 |
53 | {/* View All */} 54 | 60 |
61 |
62 |
63 |
64 |
65 |

66 | Categories 67 |

68 |
    69 | {catLoading ? ( 70 | 71 | ) : catAppErr || catServerErr ? ( 72 |

    73 | {catServerErr} {catAppErr} 74 |

    75 | ) : categoryList?.length <= 0 ? ( 76 |

    77 | No Category Found 78 |

    79 | ) : ( 80 | categoryList?.map(category => ( 81 |
  • 82 |

    84 | dispatch(fetchPostsAction(category?.title)) 85 | } 86 | className="block cursor-pointer py-2 px-3 mb-4 rounded text-yellow-500 font-bold bg-gray-500" 87 | > 88 | {category?.title} 89 |

    90 |
  • 91 | )) 92 | )} 93 |
94 |
95 |
96 |
97 | {/* Post goes here */} 98 | 99 | {appErr || serverErr ? ( 100 |

101 | {serverErr} {appErr} 102 |

103 | ) : postLists?.length <= 0 ? ( 104 |

105 | No Post Found 106 |

107 | ) : ( 108 | postLists?.map(post => ( 109 |
113 |
114 | 115 | {/* Post image */} 116 | 121 | 122 | {/* Likes, views dislikes */} 123 |
124 | {/* Likes */} 125 |
126 | {/* Togle like */} 127 |
128 | 130 | dispatch(toggleAddLikesToPost(post?._id)) 131 | } 132 | className="h-7 w-7 text-indigo-600 cursor-pointer" 133 | /> 134 |
135 |
136 | {post?.likes?.length} 137 |
138 |
139 | {/* Dislike */} 140 |
141 |
142 | 144 | dispatch(toggleAddDisLikesToPost(post?._id)) 145 | } 146 | className="h-7 w-7 cursor-pointer text-gray-600" 147 | /> 148 |
149 |
150 | {post?.disLikes?.length} 151 |
152 |
153 | {/* Views */} 154 |
155 |
156 | 157 |
158 |
159 | {post?.numViews} 160 |
161 |
162 |
163 |
164 |
165 | 166 |

167 | {/* {capitalizeWord(post?.title)} */} 168 | {post?.title} 169 |

170 | 171 |

{post?.description}

172 | {/* Read more */} 173 | 177 | Read More.. 178 | 179 | {/* User Avatar */} 180 |
181 |
182 | 183 | 188 | 189 |
190 |
191 |

192 | 196 | {post?.user?.firstName} {post?.user?.lastName} 197 | 198 |

199 |
200 | 203 | 204 |
205 |
206 |
207 | {/*

208 | Quisque id sagittis turpis. Nulla sollicitudin rutrum 209 | eros eu dictum... 210 |

*/} 211 |
212 |
213 | )) 214 | )} 215 |
216 |
217 |
218 |
219 |
220 |
221 | 226 | 227 | 228 |
229 |
230 | 235 | 236 | 237 |
238 |
239 |
240 | 241 | ); 242 | } 243 | -------------------------------------------------------------------------------- /src/components/Posts/UpdatePost.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useFormik } from "formik"; 3 | import { Redirect } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import * as Yup from "yup"; 6 | import CategoriesOptions from "../Categories/CategoryDropDown"; 7 | import { 8 | fetchPostDetailsAction, 9 | updatePostAction, 10 | } from "../../redux/slices/posts/postSlices"; 11 | 12 | //Validation 13 | const formSchema = Yup.object({ 14 | title: Yup.string().required("Title is required"), 15 | description: Yup.string().required("Description is required"), 16 | category: Yup.object().required("Category is required"), 17 | }); 18 | 19 | export default function UpdatePost(props) { 20 | const { 21 | computedMatch: { 22 | params: { id }, 23 | }, 24 | } = props; 25 | //Fetch the post in the url 26 | const dispatch = useDispatch(); 27 | 28 | useEffect(() => { 29 | dispatch(fetchPostDetailsAction(id)); 30 | }, [id, dispatch]); 31 | //selet post 32 | const postData = useSelector(state => state.post); 33 | const { postDetails } = postData; 34 | 35 | //select updated post from store; 36 | const postUpdate = useSelector(state => state.post); 37 | const { loading, appErr, serverErr, isUpdated } = postUpdate; 38 | //formik 39 | const formik = useFormik({ 40 | enableReinitialize: true, 41 | initialValues: { 42 | title: postDetails?.title, 43 | description: postDetails?.description, 44 | category: "", 45 | }, 46 | onSubmit: values => { 47 | const data = { 48 | title: values.title, 49 | description: values.description, 50 | id, 51 | }; 52 | dispatch(updatePostAction(data)); 53 | }, 54 | validationSchema: formSchema, 55 | }); 56 | 57 | //redirect 58 | if (isUpdated) return ; 59 | return ( 60 | <> 61 |
62 |
63 |

64 | Are you sure you want to edit {""} 65 | {postDetails?.title} 66 |

67 | {appErr || serverErr ? ( 68 |

69 | {serverErr} {appErr} 70 |

71 | ) : null} 72 |
73 | 74 |
75 |
76 |
77 |
78 | 84 |
85 | 95 |
96 |
97 | {formik.touched.title && formik.errors.title} 98 |
99 |
100 | 101 | 108 |
109 | 115 | 124 |
125 | {formik.touched.description && formik.errors.description} 126 |
127 |
128 | 129 |
130 | {loading ? ( 131 | 137 | ) : ( 138 | 144 | )} 145 |
146 | 147 |
148 |
149 |
150 | 151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /src/components/Users/AccountVerification/AccountVerified.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { CheckIcon } from "@heroicons/react/outline"; 3 | import { Link } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { verifyAccountAction } from "../../../redux/slices/accountVerification/accVerificationSlices"; 6 | import { logoutAction } from "../../../redux/slices/users/usersSlices"; 7 | export default function AccountVerified({ 8 | computedMatch: { 9 | params: { token }, 10 | }, 11 | }) { 12 | //dispatch 13 | const dispatch = useDispatch(); 14 | //verify account 15 | useEffect(() => { 16 | dispatch(verifyAccountAction(token)); 17 | }, [dispatch, token]); 18 | 19 | //store 20 | const accountVerification = useSelector(state => state.accountVerification); 21 | const { loading, appErr, serverErr, isVerified, verified } = 22 | accountVerification; 23 | 24 | return ( 25 | <> 26 | {verified ? ( 27 |
28 |
29 |
30 |
31 |
36 |
37 |
41 | Account Verified 42 |
43 |
44 |

45 | Your account is now verified. Logout and login back to see 46 | the changes 47 |

48 |
49 |
50 |
51 |
52 | 59 |
60 |
61 |
62 | ) : ( 63 |
64 | 69 | Go Home 70 | 71 |
72 | )} 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/Users/Emailing/SendEmail.js: -------------------------------------------------------------------------------- 1 | import { useFormik } from "formik"; 2 | import { Redirect } from "react-router-dom"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import * as Yup from "yup"; 5 | import { sendMailAction } from "../../../redux/slices/email/emailSlices"; 6 | 7 | //Form schema 8 | const formSchema = Yup.object({ 9 | recipientEmail: Yup.string().required("Recipent Email is required"), 10 | subject: Yup.string().required("Subject is required"), 11 | message: Yup.string().required("Message is required"), 12 | }); 13 | const SendEmail = ({ location: { state } }) => { 14 | console.log(state); 15 | //dispath 16 | const dispatch = useDispatch(); 17 | //formik 18 | const formik = useFormik({ 19 | initialValues: { 20 | recipientEmail: state?.email, 21 | subject: "", 22 | message: "", 23 | }, 24 | onSubmit: values => { 25 | //dispath the action 26 | dispatch(sendMailAction(values)); 27 | }, 28 | validationSchema: formSchema, 29 | }); 30 | //select data from store 31 | const sendMail = useSelector(state => state?.sendMail); 32 | const { mailSent, loading, appErr, serverErr, isMailSent } = sendMail; 33 | 34 | //redirect 35 | if (isMailSent) return ; 36 | return ( 37 |
38 |
39 |

40 | Send Mesage 41 | {/* Email title */} 42 | email title 43 |

44 | 45 |

46 | {serverErr || appErr ? ( 47 |

48 | {serverErr} {appErr} 49 |

50 | ) : null} 51 |

52 |

53 | {/* {emailSent &&

Sent
} */} 54 |

55 |
56 | 57 |
58 |
59 |
60 |
61 | 67 | {/* Email message */} 68 |
69 | 80 |
81 | {/* Err msg */} 82 |
83 | {formik.touched.recipientEmail && formik.errors.recipientEmail} 84 |
85 |
86 |
87 | 93 |
94 | {/* Subject */} 95 | 105 |
106 | {/* err msg */} 107 |
108 | {formik.touched.subject && formik.errors.subject} 109 |
110 |
111 |
112 | 118 | {/* email message */} 119 | 128 | {/* err here */} 129 |
130 | {formik.touched.message && formik.errors.message} 131 |
132 |
133 | {/* Submit btn */} 134 |
135 | {loading ? ( 136 | 142 | ) : ( 143 | 149 | )} 150 |
151 |
152 |
153 |
154 |
155 | ); 156 | }; 157 | 158 | export default SendEmail; 159 | -------------------------------------------------------------------------------- /src/components/Users/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import { Redirect, Link } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import * as Yup from "yup"; 6 | import poster from "../../../img/poster.png"; 7 | import { loginUserAction } from "../../../redux/slices/users/usersSlices"; 8 | 9 | //Form schema 10 | const formSchema = Yup.object({ 11 | email: Yup.string().required("Email is required"), 12 | password: Yup.string().required("Password is required"), 13 | }); 14 | 15 | const Login = () => { 16 | const dispatch = useDispatch(); 17 | //formik 18 | const formik = useFormik({ 19 | initialValues: { 20 | email: "", 21 | password: "", 22 | }, 23 | onSubmit: values => { 24 | //dispath the action 25 | dispatch(loginUserAction(values)); 26 | }, 27 | validationSchema: formSchema, 28 | }); 29 | 30 | //redirect 31 | const store = useSelector(state => state?.users); 32 | const { userAuth, loading, serverErr, appErr } = store; 33 | if (userAuth) return ; 34 | return ( 35 | <> 36 |
37 |
38 | 43 |
44 |
45 |
46 |
47 |
48 |
49 | {/* Form */} 50 |
51 |

52 | {/* Header */} 53 | Login to your Account 54 |

55 | {/* display err */} 56 | {serverErr || appErr ? ( 57 |

58 | {serverErr} - {appErr} 59 |

60 | ) : null} 61 |
62 | 63 | 71 | 75 | 79 | 80 | 81 | {/* Email */} 82 | 90 |
91 | {/* Err message */} 92 |
93 | {formik.touched.email && formik.errors.email} 94 |
95 |
96 | 97 | 105 | 109 | 113 | 114 | 115 | {/* Password */} 116 | 124 |
125 | {/* Err msg */} 126 |
127 | {formik.touched.password && formik.errors.password} 128 |
129 | {/* Login btn */} 130 | {loading ? ( 131 | 137 | ) : ( 138 | 144 | )} 145 |
146 |
147 | 151 | Forget Password ? 152 | 153 |
154 |
155 |
156 |
157 | 158 | 165 | 166 | 170 | 174 | 178 | 182 | 186 | 190 | 194 | 195 | 196 | 197 |

198 | Ready to start? Login Now. 199 |

200 |
201 |
202 |
203 |
204 |
205 | 206 | ); 207 | }; 208 | 209 | export default Login; 210 | -------------------------------------------------------------------------------- /src/components/Users/PasswordManagement/ResetPassword.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useFormik } from "formik"; 3 | import * as Yup from "yup"; 4 | import { Redirect, Link } from "react-router-dom"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { LockClosedIcon } from "@heroicons/react/solid"; 7 | import { passwordResetAction } from "../../../redux/slices/users/usersSlices"; 8 | 9 | //Form schema 10 | const formSchema = Yup.object({ 11 | password: Yup.string().required("Password is required"), 12 | }); 13 | 14 | const ResetPassword = props => { 15 | const token = props.match.params.token; 16 | 17 | const dispatch = useDispatch(); 18 | //formik 19 | const formik = useFormik({ 20 | initialValues: { 21 | pasword: "", 22 | }, 23 | onSubmit: values => { 24 | //dispath the action 25 | const data = { 26 | password: values?.password, 27 | token, 28 | }; 29 | dispatch(passwordResetAction(data)); 30 | }, 31 | validationSchema: formSchema, 32 | }); 33 | 34 | //select data from store 35 | const users = useSelector(state => state?.users); 36 | const { passwordReset, loading, appErr, serverErr } = users; 37 | 38 | //Redirect 39 | 40 | useEffect(() => { 41 | setTimeout(() => { 42 | if (passwordReset) props.history.push("/login"); 43 | }, 5000); 44 | }, [passwordReset]); 45 | 46 | return ( 47 |
48 |
49 |
50 |

51 | Password Reset 52 |

53 |

54 | 55 | Reset your password if you have forgotten 56 | 57 |

58 |
59 | {/* Err msg */} 60 |
61 | {appErr || serverErr ? ( 62 |

63 | {serverErr} {appErr} 64 |

65 | ) : null} 66 |
67 | 68 | {/* Sucess msg */} 69 |
70 | {passwordReset && ( 71 |

72 | Password Reset Successfully. You will be redirected to login with 73 | 5 seconds 74 |

75 | )} 76 |
77 |
78 | 79 |
80 |
81 | 84 | 93 | {/* Err msg */} 94 |
95 | {formik.touched.password && formik.errors.password} 96 |
97 |
98 |
99 | 100 |
101 | 102 |
103 | {loading ? ( 104 | 116 | ) : ( 117 | 129 | )} 130 |
131 |
132 |
133 |
134 | ); 135 | }; 136 | 137 | export default ResetPassword; 138 | -------------------------------------------------------------------------------- /src/components/Users/PasswordManagement/ResetPasswordForm.js: -------------------------------------------------------------------------------- 1 | import { useFormik } from "formik"; 2 | import * as Yup from "yup"; 3 | import { Redirect, Link } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { LockClosedIcon } from "@heroicons/react/solid"; 6 | import { passwordResetTokenAction } from "../../../redux/slices/users/usersSlices"; 7 | 8 | //Form schema 9 | const formSchema = Yup.object({ 10 | email: Yup.string().required("Email is required"), 11 | }); 12 | 13 | const ResetPasswordForm = () => { 14 | const dispatch = useDispatch(); 15 | //formik 16 | const formik = useFormik({ 17 | initialValues: { 18 | email: "", 19 | }, 20 | onSubmit: values => { 21 | //dispath the action 22 | dispatch(passwordResetTokenAction(values?.email)); 23 | }, 24 | validationSchema: formSchema, 25 | }); 26 | 27 | //select data from store 28 | const users = useSelector(state => state?.users); 29 | const { passwordToken, loading, appErr, serverErr } = users; 30 | return ( 31 |
32 |
33 |
34 |

35 | Password Reset Form 36 |

37 |

38 | 39 | Reset your password if you have forgotten 40 | 41 |

42 |
43 | {/* Err msg */} 44 |
45 | {appErr || serverErr ? ( 46 |

47 | {serverErr} {appErr} 48 |

49 | ) : null} 50 |
51 | 52 | {/* Sucess msg */} 53 |
54 | {passwordToken && ( 55 |

56 | Email is successfully sent to your email. Verify it within 10 57 | minutes. 58 |

59 | )} 60 |
61 |
62 | 63 |
64 |
65 | 68 | 77 | {/* Err msg */} 78 |
79 | {formik.touched.email && formik.errors.email} 80 |
81 |
82 |
83 | 84 |
85 |
86 | 90 | Or Update Your Password ? 91 | 92 |
93 |
94 | 95 |
96 | {loading ? ( 97 | 109 | ) : ( 110 | 122 | )} 123 |
124 |
125 |
126 |
127 | ); 128 | }; 129 | 130 | export default ResetPasswordForm; 131 | -------------------------------------------------------------------------------- /src/components/Users/PasswordManagement/UpdatePassword.js: -------------------------------------------------------------------------------- 1 | import { useFormik } from "formik"; 2 | import * as Yup from "yup"; 3 | import { Redirect } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { updatePasswordAction } from "../../../redux/slices/users/usersSlices"; 6 | 7 | //Form schema 8 | const formSchema = Yup.object({ 9 | password: Yup.string().required("Password is required"), 10 | }); 11 | 12 | const UpdatePassword = () => { 13 | const dispatch = useDispatch(); 14 | //formik 15 | const formik = useFormik({ 16 | initialValues: { 17 | password: "", 18 | }, 19 | onSubmit: values => { 20 | //dispath the action 21 | dispatch(updatePasswordAction(values?.password)); 22 | }, 23 | validationSchema: formSchema, 24 | }); 25 | 26 | const users = useSelector(state => state?.users); 27 | const { isPasswordUpdated, loading, appErr, serverErr, userAuth } = users; 28 | 29 | //redirect 30 | if (isPasswordUpdated) return ; 31 | return ( 32 |
33 |
34 |

35 | Change your password 36 |

37 |

38 | {serverErr || appErr ? ( 39 |

40 | {serverErr} {appErr} 41 |

42 | ) : null} 43 |

44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | 59 | 63 | 67 | 68 | 69 | {/* Password */} 70 | 71 | 79 |
80 | {/* Err msg */} 81 |
82 | {formik.touched.password && formik.errors.password} 83 |
84 |
85 | {/* Submit btn */} 86 | {loading ? ( 87 | 93 | ) : ( 94 | 100 | )} 101 |
102 |
103 |
104 |
105 |
106 | ); 107 | }; 108 | 109 | export default UpdatePassword; 110 | -------------------------------------------------------------------------------- /src/components/Users/Profile/UpdateProfileForm.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useFormik } from "formik"; 3 | import { Redirect } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import * as Yup from "yup"; 6 | import { 7 | updateUserAction, 8 | fetchUserDetailsAction, 9 | } from "../../../redux/slices/users/usersSlices"; 10 | 11 | //Form schema 12 | const formSchema = Yup.object({ 13 | firstName: Yup.string().required("First Name is required"), 14 | lastName: Yup.string().required("Last Name is required"), 15 | email: Yup.string().required("Email is required"), 16 | bio: Yup.string().required("Bio is required"), 17 | }); 18 | 19 | const UpdateProfileForm = ({ 20 | computedMatch: { 21 | params: { id }, 22 | }, 23 | }) => { 24 | const dispatch = useDispatch(); 25 | //fetch user details 26 | useEffect(() => { 27 | dispatch(fetchUserDetailsAction(id)); 28 | }, [dispatch, id]); 29 | 30 | //get user from store 31 | const users = useSelector(state => state.users); 32 | const { userDetails, isUpdated, loading, appErr, serverErr } = users; 33 | 34 | //formik 35 | const formik = useFormik({ 36 | enableReinitialize: true, 37 | initialValues: { 38 | firstName: userDetails?.firstName, 39 | lastName: userDetails?.lastName, 40 | email: userDetails?.email, 41 | bio: userDetails?.bio, 42 | }, 43 | onSubmit: values => { 44 | //dispath the action 45 | dispatch(updateUserAction(values)); 46 | }, 47 | validationSchema: formSchema, 48 | }); 49 | 50 | //redirect 51 | 52 | if (isUpdated) return ; 53 | return ( 54 |
55 |
56 |

57 | Hei buddy{" "} 58 | 59 | {userDetails?.firstName} {userDetails?.lastName} 60 | {" "} 61 | Do want to update your profile? 62 |

63 | {/* ERR */} 64 | {serverErr || appErr ? ( 65 |

66 | {serverErr} {appErr} 67 |

68 | ) : null} 69 |
70 | 71 |
72 |
73 |
74 |
75 | 81 |
82 | {/* First name */} 83 | 93 |
94 |
95 | {formik.touched.firstName && formik.errors.firstName} 96 |
97 |
98 |
99 | 105 |
106 | {/* Last Name */} 107 | 117 |
118 | {/* Err msg */} 119 |
120 | {formik.touched.lastName && formik.errors.lastName} 121 |
122 |
123 |
124 | 130 |
131 | {/* Email */} 132 | 142 |
143 | {/* err msg */} 144 |
145 | {formik.touched.email && formik.errors.email} 146 |
147 |
148 |
149 | 155 | 164 | {/* Err msg */} 165 |
166 | {formik.touched.bio && formik.errors.bio} 167 |
168 |
169 |
170 | {/* submit btn */} 171 | {loading ? ( 172 | 178 | ) : ( 179 | 185 | )} 186 |
187 |
188 | 189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | ); 202 | }; 203 | 204 | export default UpdateProfileForm; 205 | -------------------------------------------------------------------------------- /src/components/Users/Profile/UploadProfilePhoto.js: -------------------------------------------------------------------------------- 1 | import { UploadIcon } from "@heroicons/react/outline"; 2 | import Dropzone from "react-dropzone"; 3 | import { Redirect } from "react-router"; 4 | import { useFormik } from "formik"; 5 | import styled from "styled-components"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import * as Yup from "yup"; 8 | import { uploadProfilePhototAction } from "../../../redux/slices/users/usersSlices"; 9 | 10 | //Css for dropzone 11 | const Container = styled.div` 12 | flex: 1; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | padding: 20px; 17 | border-width: 2px; 18 | border-radius: 2px; 19 | border-style: dashed; 20 | background-color: #fafafa; 21 | color: #bdbdbd; 22 | outline: none; 23 | transition: border 0.24s ease-in-out; 24 | `; 25 | 26 | const formSchema = Yup.object({ 27 | image: Yup.string().required("Image is required"), 28 | }); 29 | 30 | export default function UploadProfilePhoto() { 31 | const dispatch = useDispatch(); 32 | //formik 33 | const formik = useFormik({ 34 | initialValues: { 35 | image: "", 36 | }, 37 | onSubmit: values => { 38 | dispatch(uploadProfilePhototAction(values)); 39 | }, 40 | validationSchema: formSchema, 41 | }); 42 | //store data 43 | const users = useSelector(state => state?.users); 44 | const { profilePhoto, loading, appErr, serverErr, userAuth } = users; 45 | //redirect 46 | if (profilePhoto) return ; 47 | return ( 48 |
49 |
50 |

51 | Upload profile photo 52 |

53 | {/* Displya err here */} 54 |
55 | 56 |
57 |
58 |
59 | {/* Image container here thus Dropzone */} 60 | {appErr || serverErr ? ( 61 |

62 | {serverErr} {appErr} 63 |

64 | ) : null} 65 | 66 | { 70 | formik.setFieldValue("image", acceptedFiles[0]); 71 | }} 72 | > 73 | {({ getRootProps, getInputProps }) => ( 74 |
75 |
event.stopPropagation(), 79 | })} 80 | > 81 | 82 |

83 | Click here to select image 84 |

85 |
86 |
87 | )} 88 |
89 |
90 | 91 |
92 | {formik.touched.image && formik.errors.image} 93 |
94 |

95 | PNG, JPG, GIF minimum size 400kb uploaded only 1 image 96 |

97 | 98 |
99 | {loading ? ( 100 | 110 | ) : ( 111 | 121 | )} 122 |
123 |
124 |
125 |
126 |
127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/components/Users/Register/Register.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import { Redirect } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import * as Yup from "yup"; 6 | import { registerUserAction } from "../../../redux/slices/users/usersSlices"; 7 | 8 | //Form schema 9 | const formSchema = Yup.object({ 10 | firstName: Yup.string().required("First Name is required"), 11 | lastName: Yup.string().required("Last Name is required"), 12 | email: Yup.string().required("Email is required"), 13 | password: Yup.string().required("Password is required"), 14 | }); 15 | //------------------------------- 16 | //Register 17 | //------------------------------- 18 | const Register = () => { 19 | //dispath 20 | const dispatch = useDispatch(); 21 | 22 | //formik 23 | const formik = useFormik({ 24 | initialValues: { 25 | firstName: "", 26 | lastName: "", 27 | email: "", 28 | password: "", 29 | }, 30 | onSubmit: values => { 31 | //dispath the action 32 | dispatch(registerUserAction(values)); 33 | console.log(values); 34 | }, 35 | validationSchema: formSchema, 36 | }); 37 | 38 | //select state from store 39 | const storeData = useSelector(store => store?.users); 40 | const { loading, appErr, serverErr, registered } = storeData; 41 | 42 | //redirect 43 | if (registered) { 44 | return ; 45 | } 46 | 47 | return ( 48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 | Register Account 56 | 57 |

58 | Create an account and start pending down your ideas 59 |

60 |
61 |
62 |
63 |
64 |
65 |

66 | Register Account 67 | {/* display error message*/} 68 | {appErr || serverErr ? ( 69 |
70 | {serverErr} {appErr} 71 |
72 | ) : null} 73 |

74 | 75 | {/* First name */} 76 |
77 | 78 | 86 | 92 | 96 | 104 | 113 | 114 | 115 | 123 |
124 | {/* Err msg*/} 125 |
126 | {formik.touched.firstName && formik.errors.firstName} 127 |
128 | {/* Last name */} 129 |
130 | 131 | 139 | 145 | 149 | 157 | 166 | 167 | 168 | 176 |
177 | {/* Err msg*/} 178 |
179 | {formik.touched.lastName && formik.errors.lastName} 180 |
181 | {/* Email */} 182 |
183 | 184 | 192 | 198 | 202 | 210 | 219 | 220 | 221 | 229 |
230 | {/* Err msg*/} 231 |
232 | {formik.touched.email && formik.errors.email} 233 |
234 |
235 | 236 | 244 | 248 | 252 | 253 | 254 | 262 |
263 | {/* Err msg*/} 264 |
265 | {formik.touched.password && formik.errors.password} 266 |
267 | 268 |
269 | 270 | {/* Check for loading */} 271 | {loading ? ( 272 | 278 | ) : ( 279 | 285 | )} 286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 | ); 294 | }; 295 | 296 | export default Register; 297 | -------------------------------------------------------------------------------- /src/components/Users/UsersList/UsersList.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { fetchUsersAction } from "../../../redux/slices/users/usersSlices"; 4 | import LoadingComponent from "../../../utils/LoadingComponent"; 5 | 6 | import UsersListHeader from "./UsersListHeader"; 7 | import UsersListItem from "./UsersListItem"; 8 | 9 | const UsersList = () => { 10 | //dispatch 11 | const dispatch = useDispatch(); 12 | //data from store 13 | const users = useSelector(state => state?.users); 14 | const { usersList, appErr, serverErr, loading, block, unblock } = users; 15 | //fetch all users 16 | useEffect(() => { 17 | dispatch(fetchUsersAction()); 18 | }, [block, unblock]); 19 | 20 | return ( 21 | <> 22 |
23 | {loading ? ( 24 | 25 | ) : appErr || serverErr ? ( 26 |

27 | {serverErr} {appErr} 28 |

29 | ) : usersList?.length <= 0 ? ( 30 |

No User Found

31 | ) : ( 32 | usersList?.map(user => ( 33 | <> 34 | 35 | 36 | )) 37 | )} 38 |
39 | 40 | ); 41 | }; 42 | 43 | export default UsersList; 44 | -------------------------------------------------------------------------------- /src/components/Users/UsersList/UsersListHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const UsersListHeader = users => { 4 | return ( 5 |
6 |
7 | 12 | 17 |
18 |

19 | Authors List - {users?.users?.length} 20 |

21 |

22 | Mattis amet hendrerit dolor, quisque lorem pharetra. Pellentesque 23 | lacus nisi urna, arcu sociis eu. Orci vel lectus nisl eget eget ut 24 | consectetur. Sit justo viverra non adipisicing elit distinctio. 25 |

26 |
27 |
28 | ); 29 | }; 30 | 31 | export default UsersListHeader; 32 | -------------------------------------------------------------------------------- /src/components/Users/UsersList/UsersListItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, useHistory } from "react-router-dom"; 3 | import { MailIcon } from "@heroicons/react/solid"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { 6 | blockUserAction, 7 | unBlockUserAction, 8 | } from "../../../redux/slices/users/usersSlices"; 9 | 10 | const UsersListItem = user => { 11 | //dispatch 12 | const dispatch = useDispatch(); 13 | //history 14 | const history = useHistory(); 15 | 16 | const sendMailNavigator = () => { 17 | history.push({ 18 | pathname: "/send-mail", 19 | state: { 20 | email: user?.user?.email, 21 | id: user?.user?._id, 22 | }, 23 | }); 24 | }; 25 | return ( 26 | <> 27 |
28 |
29 |
30 | profile 35 |
36 |

37 | {user?.user?.firstName} {user?.user?.lastName} 38 |

39 |

{user?.user?.email}

40 |
41 |
42 |
43 |

44 | {user?.user?.accountType} 45 | {/* {user?.user?.isBlocked && "Blocked"} */} 46 |

47 |
48 |
49 |

50 | 51 | {user.user?.followers?.length} 52 | 53 | followers 54 |

55 |
56 |
57 |

58 | 59 | {user.user?.posts?.length} - Posts 60 | 61 |

62 | 66 | Profile 67 | 68 | 69 | {user?.user?.isBlocked ? ( 70 | 76 | ) : ( 77 | 83 | )} 84 | 85 | 97 |
98 |
99 |
100 | {/* Send Mail */} 101 |
102 |
103 |
104 |
105 |
106 | 107 | ); 108 | }; 109 | 110 | export default UsersListItem; 111 | -------------------------------------------------------------------------------- /src/img/poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tweneboah/blog-app-frontend/038afe8ccc3059b6a81bf6c0e8b715d17a876d67/src/img/poster.png -------------------------------------------------------------------------------- /src/img/svg1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | 5 | import "./index.css"; 6 | import App from "./App"; 7 | import store from "./redux/store/store"; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | -------------------------------------------------------------------------------- /src/redux/slices/accountVerification/accVerificationSlices.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, createAction } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import baseUrl from "../../../utils/baseURL"; 4 | 5 | //action for redirect 6 | const resetAcc = createAction("account/verify-reset"); 7 | 8 | //create verification token 9 | export const accVerificationSendTokenAction = createAsyncThunk( 10 | "account/token", 11 | async (email, { rejectWithValue, getState, dispatch }) => { 12 | //get user token 13 | const user = getState()?.users; 14 | const { userAuth } = user; 15 | const config = { 16 | headers: { 17 | Authorization: `Bearer ${userAuth?.token}`, 18 | }, 19 | }; 20 | //http call 21 | try { 22 | const { data } = await axios.post( 23 | `${baseUrl}/api/users/generate-verify-email-token`, 24 | {}, 25 | config 26 | ); 27 | 28 | return data; 29 | } catch (error) { 30 | if (!error?.response) { 31 | throw error; 32 | } 33 | return rejectWithValue(error?.response?.data); 34 | } 35 | } 36 | ); 37 | 38 | //Verify Account 39 | export const verifyAccountAction = createAsyncThunk( 40 | "account/verify", 41 | async (token, { rejectWithValue, getState, dispatch }) => { 42 | //get user token 43 | const user = getState()?.users; 44 | const { userAuth } = user; 45 | const config = { 46 | headers: { 47 | Authorization: `Bearer ${userAuth?.token}`, 48 | }, 49 | }; 50 | //http call 51 | try { 52 | const { data } = await axios.put( 53 | `${baseUrl}/api/users/verify-account`, 54 | { token }, 55 | config 56 | ); 57 | //dispatch 58 | dispatch(resetAcc()); 59 | return data; 60 | } catch (error) { 61 | if (!error?.response) { 62 | throw error; 63 | } 64 | return rejectWithValue(error?.response?.data); 65 | } 66 | } 67 | ); 68 | 69 | //slices 70 | 71 | const accountVericationSlices = createSlice({ 72 | name: "account", 73 | initialState: {}, 74 | extraReducers: builder => { 75 | //create 76 | builder.addCase(accVerificationSendTokenAction.pending, (state, action) => { 77 | state.loading = true; 78 | }); 79 | builder.addCase( 80 | accVerificationSendTokenAction.fulfilled, 81 | (state, action) => { 82 | state.token = action?.payload; 83 | state.loading = false; 84 | state.appErr = undefined; 85 | state.serverErr = undefined; 86 | } 87 | ); 88 | builder.addCase( 89 | accVerificationSendTokenAction.rejected, 90 | (state, action) => { 91 | state.loading = false; 92 | state.appErr = action?.payload?.message; 93 | state.serverErr = action?.error?.message; 94 | } 95 | ); 96 | 97 | //Verify account 98 | builder.addCase(verifyAccountAction.pending, (state, action) => { 99 | state.loading = true; 100 | }); 101 | builder.addCase(resetAcc, (state, action) => { 102 | state.isVerified = true; 103 | }); 104 | builder.addCase(verifyAccountAction.fulfilled, (state, action) => { 105 | state.verified = action?.payload; 106 | state.loading = false; 107 | state.isVerified = false; 108 | state.appErr = undefined; 109 | state.serverErr = undefined; 110 | }); 111 | builder.addCase(verifyAccountAction.rejected, (state, action) => { 112 | state.loading = false; 113 | state.appErr = action?.payload?.message; 114 | state.serverErr = action?.error?.message; 115 | }); 116 | }, 117 | }); 118 | 119 | export default accountVericationSlices.reducer; 120 | -------------------------------------------------------------------------------- /src/redux/slices/category/categorySlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, createAction } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import baseUrl from "../../../utils/baseURL"; 4 | 5 | //action to redirect 6 | const resetEditAction = createAction("category/reset"); 7 | const resetDeleteAction = createAction("category/delete-reset"); 8 | const resetCategoryAction = createAction("category/created-reset"); 9 | 10 | //action 11 | export const createCategoryAction = createAsyncThunk( 12 | "category/create", 13 | async (category, { rejectWithValue, getState, dispatch }) => { 14 | //get user token 15 | const user = getState()?.users; 16 | const { userAuth } = user; 17 | const config = { 18 | headers: { 19 | Authorization: `Bearer ${userAuth?.token}`, 20 | }, 21 | }; 22 | //http call 23 | try { 24 | const { data } = await axios.post( 25 | `${baseUrl}/api/category`, 26 | { 27 | title: category?.title, 28 | }, 29 | config 30 | ); 31 | //disoatch action 32 | dispatch(resetCategoryAction()); 33 | return data; 34 | } catch (error) { 35 | if (!error?.response) { 36 | throw error; 37 | } 38 | return rejectWithValue(error?.response?.data); 39 | } 40 | } 41 | ); 42 | 43 | //fetch all action 44 | export const fetchCategoriesAction = createAsyncThunk( 45 | "category/fetch", 46 | async (category, { rejectWithValue, getState, dispatch }) => { 47 | //get user token 48 | const user = getState()?.users; 49 | const { userAuth } = user; 50 | const config = { 51 | headers: { 52 | Authorization: `Bearer ${userAuth?.token}`, 53 | }, 54 | }; 55 | //http call 56 | try { 57 | const { data } = await axios.get(`${baseUrl}/api/category`, config); 58 | return data; 59 | } catch (error) { 60 | if (!error?.response) { 61 | throw error; 62 | } 63 | return rejectWithValue(error?.response?.data); 64 | } 65 | } 66 | ); 67 | 68 | //Update 69 | export const updateCategoriesAction = createAsyncThunk( 70 | "category/update", 71 | async (category, { rejectWithValue, getState, dispatch }) => { 72 | //get user token 73 | const user = getState()?.users; 74 | const { userAuth } = user; 75 | const config = { 76 | headers: { 77 | Authorization: `Bearer ${userAuth?.token}`, 78 | }, 79 | }; 80 | //http call 81 | try { 82 | const { data } = await axios.put( 83 | `${baseUrl}/api/category/${category?.id}`, 84 | { title: category?.title }, 85 | config 86 | ); 87 | //dispatch ation to reset the updated data 88 | dispatch(resetEditAction()); 89 | return data; 90 | } catch (error) { 91 | if (!error?.response) { 92 | throw error; 93 | } 94 | return rejectWithValue(error?.response?.data); 95 | } 96 | } 97 | ); 98 | 99 | //delete 100 | export const deleteCategoriesAction = createAsyncThunk( 101 | "category/delete", 102 | async (id, { rejectWithValue, getState, dispatch }) => { 103 | //get user token 104 | const user = getState()?.users; 105 | const { userAuth } = user; 106 | const config = { 107 | headers: { 108 | Authorization: `Bearer ${userAuth?.token}`, 109 | }, 110 | }; 111 | //http call 112 | try { 113 | const { data } = await axios.delete( 114 | `${baseUrl}/api/category/${id}`, 115 | config 116 | ); 117 | //dispatch action 118 | dispatch(resetDeleteAction()); 119 | return data; 120 | } catch (error) { 121 | if (!error?.response) { 122 | throw error; 123 | } 124 | return rejectWithValue(error?.response?.data); 125 | } 126 | } 127 | ); 128 | 129 | //fetch details 130 | export const fetchCategoryAction = createAsyncThunk( 131 | "category/details", 132 | async (id, { rejectWithValue, getState, dispatch }) => { 133 | //get user token 134 | const user = getState()?.users; 135 | const { userAuth } = user; 136 | const config = { 137 | headers: { 138 | Authorization: `Bearer ${userAuth?.token}`, 139 | }, 140 | }; 141 | //http call 142 | try { 143 | const { data } = await axios.get(`${baseUrl}/api/category/${id}`, config); 144 | return data; 145 | } catch (error) { 146 | if (!error?.response) { 147 | throw error; 148 | } 149 | return rejectWithValue(error?.response?.data); 150 | } 151 | } 152 | ); 153 | //slices 154 | 155 | const categorySlices = createSlice({ 156 | name: "category", 157 | initialState: {}, 158 | extraReducers: builder => { 159 | //create 160 | builder.addCase(createCategoryAction.pending, (state, action) => { 161 | state.loading = true; 162 | }); 163 | //dispatch action to redirect 164 | builder.addCase(resetCategoryAction, (state, action) => { 165 | state.isCreated = true; 166 | }); 167 | builder.addCase(createCategoryAction.fulfilled, (state, action) => { 168 | state.category = action?.payload; 169 | state.isCreated = false; 170 | state.loading = false; 171 | state.appErr = undefined; 172 | state.serverErr = undefined; 173 | }); 174 | builder.addCase(createCategoryAction.rejected, (state, action) => { 175 | state.loading = false; 176 | state.appErr = action?.payload?.message; 177 | state.serverErr = action?.error?.message; 178 | }); 179 | //fetch all 180 | builder.addCase(fetchCategoriesAction.pending, (state, action) => { 181 | state.loading = true; 182 | }); 183 | builder.addCase(fetchCategoriesAction.fulfilled, (state, action) => { 184 | state.categoryList = action?.payload; 185 | state.loading = false; 186 | state.appErr = undefined; 187 | state.serverErr = undefined; 188 | }); 189 | builder.addCase(fetchCategoriesAction.rejected, (state, action) => { 190 | state.loading = false; 191 | state.appErr = action?.payload?.message; 192 | state.serverErr = action?.error?.message; 193 | }); 194 | //update 195 | builder.addCase(updateCategoriesAction.pending, (state, action) => { 196 | state.loading = true; 197 | }); 198 | //Dispatch action 199 | builder.addCase(resetEditAction, (state, action) => { 200 | state.isEdited = true; 201 | }); 202 | builder.addCase(updateCategoriesAction.fulfilled, (state, action) => { 203 | state.updateCategory = action?.payload; 204 | state.isEdited = false; 205 | state.loading = false; 206 | state.appErr = undefined; 207 | state.serverErr = undefined; 208 | }); 209 | builder.addCase(updateCategoriesAction.rejected, (state, action) => { 210 | state.loading = false; 211 | state.appErr = action?.payload?.message; 212 | state.serverErr = action?.error?.message; 213 | }); 214 | 215 | //delete 216 | builder.addCase(deleteCategoriesAction.pending, (state, action) => { 217 | state.loading = true; 218 | }); 219 | //dispatch for redirect 220 | builder.addCase(resetDeleteAction, (state, action) => { 221 | state.isDeleted = true; 222 | }); 223 | builder.addCase(deleteCategoriesAction.fulfilled, (state, action) => { 224 | state.deletedCategory = action?.payload; 225 | state.isDeleted = false; 226 | state.loading = false; 227 | state.appErr = undefined; 228 | state.serverErr = undefined; 229 | }); 230 | builder.addCase(deleteCategoriesAction.rejected, (state, action) => { 231 | state.loading = false; 232 | state.appErr = action?.payload?.message; 233 | state.serverErr = action?.error?.message; 234 | }); 235 | 236 | //fetch details 237 | builder.addCase(fetchCategoryAction.pending, (state, action) => { 238 | state.loading = true; 239 | }); 240 | builder.addCase(fetchCategoryAction.fulfilled, (state, action) => { 241 | state.category = action?.payload; 242 | state.loading = false; 243 | state.appErr = undefined; 244 | state.serverErr = undefined; 245 | }); 246 | builder.addCase(fetchCategoryAction.rejected, (state, action) => { 247 | state.loading = false; 248 | state.appErr = action?.payload?.message; 249 | state.serverErr = action?.error?.message; 250 | }); 251 | }, 252 | }); 253 | 254 | export default categorySlices.reducer; 255 | -------------------------------------------------------------------------------- /src/redux/slices/comments/commentSlices.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, createAction } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import baseUrl from "../../../utils/baseURL"; 4 | 5 | //action to redirect 6 | const resetCommentAction = createAction("comment/reset"); 7 | //create 8 | export const createCommentAction = createAsyncThunk( 9 | "comment/create", 10 | async (comment, { rejectWithValue, getState, dispatch }) => { 11 | //get user token 12 | const user = getState()?.users; 13 | const { userAuth } = user; 14 | const config = { 15 | headers: { 16 | Authorization: `Bearer ${userAuth?.token}`, 17 | }, 18 | }; 19 | //http call 20 | try { 21 | const { data } = await axios.post( 22 | `${baseUrl}/api/comments`, 23 | { 24 | description: comment?.description, 25 | postId: comment?.postId, 26 | }, 27 | config 28 | ); 29 | return data; 30 | } catch (error) { 31 | if (!error?.response) { 32 | throw error; 33 | } 34 | return rejectWithValue(error?.response?.data); 35 | } 36 | } 37 | ); 38 | 39 | //delete 40 | export const deleteCommentAction = createAsyncThunk( 41 | "comment/delete", 42 | async (commentId, { rejectWithValue, getState, dispatch }) => { 43 | //get user token 44 | const user = getState()?.users; 45 | const { userAuth } = user; 46 | const config = { 47 | headers: { 48 | Authorization: `Bearer ${userAuth?.token}`, 49 | }, 50 | }; 51 | //http call 52 | try { 53 | const { data } = await axios.delete( 54 | `${baseUrl}/api/comments/${commentId}`, 55 | config 56 | ); 57 | return data; 58 | } catch (error) { 59 | if (!error?.response) { 60 | throw error; 61 | } 62 | return rejectWithValue(error?.response?.data); 63 | } 64 | } 65 | ); 66 | 67 | //Update 68 | export const updateCommentAction = createAsyncThunk( 69 | "comment/update", 70 | async (comment, { rejectWithValue, getState, dispatch }) => { 71 | //get user token 72 | const user = getState()?.users; 73 | const { userAuth } = user; 74 | const config = { 75 | headers: { 76 | Authorization: `Bearer ${userAuth?.token}`, 77 | }, 78 | }; 79 | //http call 80 | try { 81 | const { data } = await axios.put( 82 | `${baseUrl}/api/comments/${comment?.id}`, 83 | { description: comment?.description }, 84 | config 85 | ); 86 | //dispatch 87 | dispatch(resetCommentAction()); 88 | return data; 89 | } catch (error) { 90 | if (!error?.response) { 91 | throw error; 92 | } 93 | return rejectWithValue(error?.response?.data); 94 | } 95 | } 96 | ); 97 | 98 | //fetch comment details 99 | export const fetchCommentAction = createAsyncThunk( 100 | "comment/fetch-details", 101 | async (id, { rejectWithValue, getState, dispatch }) => { 102 | //get user token 103 | const user = getState()?.users; 104 | const { userAuth } = user; 105 | const config = { 106 | headers: { 107 | Authorization: `Bearer ${userAuth?.token}`, 108 | }, 109 | }; 110 | //http call 111 | try { 112 | const { data } = await axios.get(`${baseUrl}/api/comments/${id}`, config); 113 | return data; 114 | } catch (error) { 115 | if (!error?.response) { 116 | throw error; 117 | } 118 | return rejectWithValue(error?.response?.data); 119 | } 120 | } 121 | ); 122 | const commentSlices = createSlice({ 123 | name: "comment", 124 | initialState: {}, 125 | extraReducers: builder => { 126 | //create 127 | builder.addCase(createCommentAction.pending, (state, action) => { 128 | state.loading = true; 129 | }); 130 | builder.addCase(createCommentAction.fulfilled, (state, action) => { 131 | state.loading = false; 132 | state.commentCreated = action?.payload; 133 | state.appErr = undefined; 134 | state.serverErr = undefined; 135 | }); 136 | builder.addCase(createCommentAction.rejected, (state, action) => { 137 | state.loading = false; 138 | state.commentCreated = undefined; 139 | state.appErr = action?.payload?.message; 140 | state.serverErr = action?.error?.message; 141 | }); 142 | 143 | //delete 144 | builder.addCase(deleteCommentAction.pending, (state, action) => { 145 | state.loading = true; 146 | }); 147 | builder.addCase(deleteCommentAction.fulfilled, (state, action) => { 148 | state.loading = false; 149 | state.commentDeleted = action?.payload; 150 | state.appErr = undefined; 151 | state.serverErr = undefined; 152 | }); 153 | builder.addCase(deleteCommentAction.rejected, (state, action) => { 154 | state.loading = false; 155 | state.commentCreated = undefined; 156 | state.appErr = action?.payload?.message; 157 | state.serverErr = action?.error?.message; 158 | }); 159 | 160 | //update 161 | builder.addCase(updateCommentAction.pending, (state, action) => { 162 | state.loading = true; 163 | }); 164 | builder.addCase(resetCommentAction, (state, action) => { 165 | state.isUpdate = true; 166 | }); 167 | builder.addCase(updateCommentAction.fulfilled, (state, action) => { 168 | state.loading = false; 169 | state.commentUpdated = action?.payload; 170 | state.isUpdate = false; 171 | state.appErr = undefined; 172 | state.serverErr = undefined; 173 | }); 174 | builder.addCase(updateCommentAction.rejected, (state, action) => { 175 | state.loading = false; 176 | state.commentCreated = undefined; 177 | state.appErr = action?.payload?.message; 178 | state.serverErr = action?.error?.message; 179 | }); 180 | 181 | //fetch details 182 | builder.addCase(fetchCommentAction.pending, (state, action) => { 183 | state.loading = true; 184 | }); 185 | builder.addCase(fetchCommentAction.fulfilled, (state, action) => { 186 | state.loading = false; 187 | state.commentDetails = action?.payload; 188 | state.appErr = undefined; 189 | state.serverErr = undefined; 190 | }); 191 | builder.addCase(fetchCommentAction.rejected, (state, action) => { 192 | state.loading = false; 193 | state.commentCreated = undefined; 194 | state.appErr = action?.payload?.message; 195 | state.serverErr = action?.error?.message; 196 | }); 197 | }, 198 | }); 199 | 200 | export default commentSlices.reducer; 201 | -------------------------------------------------------------------------------- /src/redux/slices/email/emailSlices.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, createAction } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import baseUrl from "../../../utils/baseURL"; 4 | 5 | //custion action for redirect 6 | 7 | const resetEmailAction = createAction("mail/reset"); 8 | 9 | //action 10 | export const sendMailAction = createAsyncThunk( 11 | "mail/sent", 12 | async (email, { rejectWithValue, getState, dispatch }) => { 13 | //get user token 14 | const user = getState()?.users; 15 | const { userAuth } = user; 16 | const config = { 17 | headers: { 18 | Authorization: `Bearer ${userAuth?.token}`, 19 | }, 20 | }; 21 | //http call 22 | try { 23 | const { data } = await axios.post( 24 | `${baseUrl}/api/email`, 25 | { 26 | to: email?.recipientEmail, 27 | subject: email?.subject, 28 | message: email?.message, 29 | }, 30 | config 31 | ); 32 | dispatch(resetEmailAction()); 33 | return data; 34 | } catch (error) { 35 | if (!error?.response) { 36 | throw error; 37 | } 38 | return rejectWithValue(error?.response?.data); 39 | } 40 | } 41 | ); 42 | 43 | //slices 44 | 45 | const sendMailSlices = createSlice({ 46 | name: "mail", 47 | initialState: {}, 48 | extraReducers: builder => { 49 | //create 50 | builder.addCase(sendMailAction.pending, (state, action) => { 51 | state.loading = true; 52 | }); 53 | //disoatch redirect action 54 | builder.addCase(resetEmailAction, (state, action) => { 55 | state.isMailSent = true; 56 | }); 57 | builder.addCase(sendMailAction.fulfilled, (state, action) => { 58 | state.mailSent = action?.payload; 59 | state.isMailSent = false; 60 | state.loading = false; 61 | state.appErr = undefined; 62 | state.serverErr = undefined; 63 | }); 64 | builder.addCase(sendMailAction.rejected, (state, action) => { 65 | state.loading = false; 66 | state.appErr = action?.payload?.message; 67 | state.serverErr = action?.error?.message; 68 | }); 69 | }, 70 | }); 71 | 72 | export default sendMailSlices.reducer; 73 | -------------------------------------------------------------------------------- /src/redux/slices/posts/postSlices.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, createAction } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import baseUrl from "../../../utils/baseURL"; 4 | //Create Post action 5 | 6 | //action to redirect 7 | const resetPost = createAction("category/reset"); 8 | const resetPostEdit = createAction("post/reset"); 9 | const resetPostDelete = createAction("post/delete"); 10 | 11 | //Create 12 | export const createpostAction = createAsyncThunk( 13 | "post/created", 14 | async (post, { rejectWithValue, getState, dispatch }) => { 15 | // console.log(post); 16 | //get user token 17 | const user = getState()?.users; 18 | const { userAuth } = user; 19 | const config = { 20 | headers: { 21 | Authorization: `Bearer ${userAuth?.token}`, 22 | }, 23 | }; 24 | try { 25 | //http call 26 | const formData = new FormData(); 27 | formData.append("title", post?.title); 28 | formData.append("description", post?.description); 29 | formData.append("category", post?.category); 30 | formData.append("image", post?.image); 31 | 32 | const { data } = await axios.post( 33 | `${baseUrl}/api/posts`, 34 | formData, 35 | config 36 | ); 37 | //dispatch action 38 | dispatch(resetPost()); 39 | return data; 40 | } catch (error) { 41 | if (!error?.response) throw error; 42 | return rejectWithValue(error?.response?.data); 43 | } 44 | } 45 | ); 46 | 47 | //Update 48 | export const updatePostAction = createAsyncThunk( 49 | "post/updated", 50 | async (post, { rejectWithValue, getState, dispatch }) => { 51 | console.log(post); 52 | //get user token 53 | const user = getState()?.users; 54 | const { userAuth } = user; 55 | const config = { 56 | headers: { 57 | Authorization: `Bearer ${userAuth?.token}`, 58 | }, 59 | }; 60 | try { 61 | //http call 62 | const { data } = await axios.put( 63 | `${baseUrl}/api/posts/${post?.id}`, 64 | post, 65 | config 66 | ); 67 | //dispatch 68 | dispatch(resetPostEdit()); 69 | return data; 70 | } catch (error) { 71 | if (!error?.response) throw error; 72 | return rejectWithValue(error?.response?.data); 73 | } 74 | } 75 | ); 76 | 77 | //Delete 78 | export const deletePostAction = createAsyncThunk( 79 | "post/delete", 80 | async (postId, { rejectWithValue, getState, dispatch }) => { 81 | //get user token 82 | const user = getState()?.users; 83 | const { userAuth } = user; 84 | const config = { 85 | headers: { 86 | Authorization: `Bearer ${userAuth?.token}`, 87 | }, 88 | }; 89 | try { 90 | //http call 91 | const { data } = await axios.delete( 92 | `${baseUrl}/api/posts/${postId}`, 93 | config 94 | ); 95 | //dispatch 96 | dispatch(resetPostDelete()); 97 | return data; 98 | } catch (error) { 99 | if (!error?.response) throw error; 100 | return rejectWithValue(error?.response?.data); 101 | } 102 | } 103 | ); 104 | 105 | //fetch all posts 106 | export const fetchPostsAction = createAsyncThunk( 107 | "post/list", 108 | async (category, { rejectWithValue, getState, dispatch }) => { 109 | try { 110 | const { data } = await axios.get( 111 | `${baseUrl}/api/posts?category=${category}` 112 | ); 113 | return data; 114 | } catch (error) { 115 | if (!error?.response) throw error; 116 | return rejectWithValue(error?.response?.data); 117 | } 118 | } 119 | ); 120 | //fetch Post details 121 | export const fetchPostDetailsAction = createAsyncThunk( 122 | "post/detail", 123 | async (id, { rejectWithValue, getState, dispatch }) => { 124 | try { 125 | const { data } = await axios.get(`${baseUrl}/api/posts/${id}`); 126 | return data; 127 | } catch (error) { 128 | if (!error?.response) throw error; 129 | return rejectWithValue(error?.response?.data); 130 | } 131 | } 132 | ); 133 | 134 | //Add Likes to post 135 | export const toggleAddLikesToPost = createAsyncThunk( 136 | "post/like", 137 | async (postId, { rejectWithValue, getState, dispatch }) => { 138 | //get user token 139 | const user = getState()?.users; 140 | const { userAuth } = user; 141 | const config = { 142 | headers: { 143 | Authorization: `Bearer ${userAuth?.token}`, 144 | }, 145 | }; 146 | try { 147 | const { data } = await axios.put( 148 | `http://localhost:5000/api/posts/likes`, 149 | { postId }, 150 | config 151 | ); 152 | 153 | return data; 154 | } catch (error) { 155 | if (!error?.response) throw error; 156 | return rejectWithValue(error?.response?.data); 157 | } 158 | } 159 | ); 160 | 161 | //Add DisLikes to post 162 | export const toggleAddDisLikesToPost = createAsyncThunk( 163 | "post/dislike", 164 | async (postId, { rejectWithValue, getState, dispatch }) => { 165 | //get user token 166 | const user = getState()?.users; 167 | const { userAuth } = user; 168 | const config = { 169 | headers: { 170 | Authorization: `Bearer ${userAuth?.token}`, 171 | }, 172 | }; 173 | try { 174 | const { data } = await axios.put( 175 | `http://localhost:5000/api/posts/dislikes`, 176 | { postId }, 177 | config 178 | ); 179 | 180 | return data; 181 | } catch (error) { 182 | if (!error?.response) throw error; 183 | return rejectWithValue(error?.response?.data); 184 | } 185 | } 186 | ); 187 | 188 | //slice 189 | const postSlice = createSlice({ 190 | name: "post", 191 | initialState: {}, 192 | extraReducers: builder => { 193 | //create post 194 | builder.addCase(createpostAction.pending, (state, action) => { 195 | state.loading = true; 196 | }); 197 | builder.addCase(resetPost, (state, action) => { 198 | state.isCreated = true; 199 | }); 200 | builder.addCase(createpostAction.fulfilled, (state, action) => { 201 | state.postCreated = action?.payload; 202 | state.loading = false; 203 | state.isCreated = false; 204 | state.appErr = undefined; 205 | state.serverErr = undefined; 206 | }); 207 | builder.addCase(createpostAction.rejected, (state, action) => { 208 | state.loading = false; 209 | state.appErr = action?.payload?.message; 210 | state.serverErr = action?.error?.message; 211 | }); 212 | 213 | //Update post 214 | builder.addCase(updatePostAction.pending, (state, action) => { 215 | state.loading = true; 216 | }); 217 | builder.addCase(resetPostEdit, (state, action) => { 218 | state.isUpdated = true; 219 | }); 220 | builder.addCase(updatePostAction.fulfilled, (state, action) => { 221 | state.postUpdated = action?.payload; 222 | state.loading = false; 223 | state.appErr = undefined; 224 | state.serverErr = undefined; 225 | state.isUpdated = false; 226 | }); 227 | builder.addCase(updatePostAction.rejected, (state, action) => { 228 | state.loading = false; 229 | state.appErr = action?.payload?.message; 230 | state.serverErr = action?.error?.message; 231 | }); 232 | 233 | //Delete post 234 | builder.addCase(deletePostAction.pending, (state, action) => { 235 | state.loading = true; 236 | }); 237 | builder.addCase(resetPostDelete, (state, action) => { 238 | state.isDeleted = true; 239 | }); 240 | builder.addCase(deletePostAction.fulfilled, (state, action) => { 241 | state.postUpdated = action?.payload; 242 | state.isDeleted = false; 243 | state.loading = false; 244 | state.appErr = undefined; 245 | state.serverErr = undefined; 246 | }); 247 | builder.addCase(deletePostAction.rejected, (state, action) => { 248 | state.loading = false; 249 | state.appErr = action?.payload?.message; 250 | state.serverErr = action?.error?.message; 251 | }); 252 | 253 | //fetch posts 254 | builder.addCase(fetchPostsAction.pending, (state, action) => { 255 | state.loading = true; 256 | }); 257 | builder.addCase(fetchPostsAction.fulfilled, (state, action) => { 258 | state.postLists = action?.payload; 259 | state.loading = false; 260 | state.appErr = undefined; 261 | state.serverErr = undefined; 262 | }); 263 | builder.addCase(fetchPostsAction.rejected, (state, action) => { 264 | state.loading = false; 265 | state.appErr = action?.payload?.message; 266 | state.serverErr = action?.error?.message; 267 | }); 268 | 269 | //fetch post Details 270 | builder.addCase(fetchPostDetailsAction.pending, (state, action) => { 271 | state.loading = true; 272 | }); 273 | builder.addCase(fetchPostDetailsAction.fulfilled, (state, action) => { 274 | state.postDetails = action?.payload; 275 | state.loading = false; 276 | state.appErr = undefined; 277 | state.serverErr = undefined; 278 | }); 279 | builder.addCase(fetchPostDetailsAction.rejected, (state, action) => { 280 | state.loading = false; 281 | state.appErr = action?.payload?.message; 282 | state.serverErr = action?.error?.message; 283 | }); 284 | //Likes 285 | builder.addCase(toggleAddLikesToPost.pending, (state, action) => { 286 | state.loading = true; 287 | }); 288 | builder.addCase(toggleAddLikesToPost.fulfilled, (state, action) => { 289 | state.likes = action?.payload; 290 | state.loading = false; 291 | state.appErr = undefined; 292 | state.serverErr = undefined; 293 | }); 294 | builder.addCase(toggleAddLikesToPost.rejected, (state, action) => { 295 | state.loading = false; 296 | state.appErr = action?.payload?.message; 297 | state.serverErr = action?.error?.message; 298 | }); 299 | //DisLikes 300 | builder.addCase(toggleAddDisLikesToPost.pending, (state, action) => { 301 | state.loading = true; 302 | }); 303 | builder.addCase(toggleAddDisLikesToPost.fulfilled, (state, action) => { 304 | state.dislikes = action?.payload; 305 | state.loading = false; 306 | state.appErr = undefined; 307 | state.serverErr = undefined; 308 | }); 309 | builder.addCase(toggleAddDisLikesToPost.rejected, (state, action) => { 310 | state.loading = false; 311 | state.appErr = action?.payload?.message; 312 | state.serverErr = action?.error?.message; 313 | }); 314 | }, 315 | }); 316 | 317 | export default postSlice.reducer; 318 | -------------------------------------------------------------------------------- /src/redux/store/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import usersReducer from "../slices/users/usersSlices"; 3 | import categoriesReducer from "../slices/category/categorySlice"; 4 | import post from "../slices/posts/postSlices"; 5 | import comment from "../slices/comments/commentSlices"; 6 | import sendMail from "../slices/email/emailSlices"; 7 | import accountVerification from "../slices/accountVerification/accVerificationSlices"; 8 | const store = configureStore({ 9 | reducer: { 10 | users: usersReducer, 11 | category: categoriesReducer, 12 | post, 13 | comment, 14 | sendMail, 15 | accountVerification, 16 | }, 17 | }); 18 | 19 | export default store; 20 | -------------------------------------------------------------------------------- /src/utils/DateFormatter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Moment from "react-moment"; 3 | 4 | const DateFormatter = ({ date }) => { 5 | return ( 6 | 7 | {date} 8 | 9 | ); 10 | }; 11 | 12 | export default DateFormatter; 13 | -------------------------------------------------------------------------------- /src/utils/LoadingComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { css } from "@emotion/react"; 3 | import RiseLoader from "react-spinners/CircleLoader"; 4 | 5 | //css 6 | const override = css` 7 | display: block; 8 | margin: 0 auto; 9 | border-color: red; 10 | `; 11 | const LoadingComponent = () => { 12 | return ; 13 | }; 14 | 15 | export default LoadingComponent; 16 | -------------------------------------------------------------------------------- /src/utils/baseURL.js: -------------------------------------------------------------------------------- 1 | const baseUrlLocal = "http://localhost:5000"; 2 | 3 | const baseUrl = process.env.REACT_APP_API_URL; 4 | export default baseUrl; 5 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | --------------------------------------------------------------------------------