├── .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 | You need to enable JavaScript to run this app.
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 |
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 |
51 | )}
52 | {/* Display */}
53 | {props?.error && (
54 |
{props?.error}
55 | )}
56 |
57 | );
58 | };
59 |
60 | export default CategoryDropDown;
61 |
--------------------------------------------------------------------------------
/src/components/Categories/CategoryList.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { useEffect } from "react";
4 | import { PencilAltIcon } from "@heroicons/react/outline";
5 | import { fetchCategoriesAction } from "../../redux/slices/category/categorySlice";
6 | import DateFormatter from "../../utils/DateFormatter";
7 | import LoadingComponent from "../../utils/LoadingComponent";
8 |
9 | const CategoryList = () => {
10 | const dispatch = useDispatch();
11 | useEffect(() => {
12 | dispatch(fetchCategoriesAction());
13 | }, [dispatch]);
14 | const category = useSelector(state => state?.category);
15 |
16 | const { categoryList, loading, appErr, serverErr } = category;
17 |
18 | return (
19 | <>
20 | {loading ? (
21 | <>
22 |
23 | >
24 | ) : appErr || serverErr ? (
25 |
26 | {serverErr} {serverErr}
27 |
28 | ) : categoryList?.length <= 0 ? (
29 |
30 | No category Found
31 |
32 | ) : (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 | Author
45 |
46 |
50 | Title
51 |
52 |
56 | Created At
57 |
58 |
62 | Edit
63 |
64 |
65 |
66 |
67 | {categoryList?.map(category => (
68 |
69 |
70 |
71 |
72 |
77 |
78 |
79 |
80 | {category?.user?.firstName}{" "}
81 | {category?.user?.lastName}
82 |
83 |
84 | {category?.user?.email}
85 |
86 |
87 |
88 |
89 |
90 | {category.title}
91 |
92 |
93 | { }
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | ))}
102 |
103 |
104 |
105 |
106 |
107 |
108 | )}
109 | >
110 | );
111 | };
112 |
113 | export default CategoryList;
114 |
--------------------------------------------------------------------------------
/src/components/Categories/UpdateCategory.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import { PlusCircleIcon, BookOpenIcon } from "@heroicons/react/solid";
4 | import { useFormik } from "formik";
5 | import * as Yup from "yup";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import {
8 | fetchCategoryAction,
9 | updateCategoriesAction,
10 | deleteCategoriesAction,
11 | } from "../../redux/slices/category/categorySlice";
12 |
13 | //Form schema
14 | const formSchema = Yup.object({
15 | title: Yup.string().required("Title is required"),
16 | });
17 |
18 | const UpdateCategory = ({
19 | computedMatch: {
20 | params: { id },
21 | },
22 | }) => {
23 | const dispatch = useDispatch();
24 | //fetch single category
25 | useEffect(() => {
26 | dispatch(fetchCategoryAction(id));
27 | }, []);
28 |
29 | //get data from store
30 | const state = useSelector(state => state?.category);
31 |
32 | const { loading, appErr, serverErr, category, isEdited, isDeleted } = state;
33 |
34 | //formik
35 | const formik = useFormik({
36 | enableReinitialize: true,
37 | initialValues: {
38 | title: category?.title,
39 | },
40 | onSubmit: values => {
41 | //build up the date for update
42 |
43 | //dispath the action
44 | dispatch(updateCategoriesAction({ title: values.title, id }));
45 | },
46 | validationSchema: formSchema,
47 | });
48 |
49 | //redirect
50 | if (isEdited || isDeleted) return ;
51 | return (
52 |
53 |
54 |
55 |
56 |
57 | Update Category
58 |
59 |
60 |
61 | These are the categories user will select when creating a post
62 |
63 | {/* Display err */}
64 |
65 | {appErr || serverErr ? (
66 |
67 | {serverErr} {appErr}
68 |
69 | ) : null}
70 |
71 |
72 |
73 | {/* Form */}
74 |
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 |
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 |
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 |
74 |
75 | {formik.touched.description && formik.errors.description}
76 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default UpdateComment;
83 |
--------------------------------------------------------------------------------
/src/components/HomePage/HomePage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import poster from "../../img/poster.png";
3 | const HomePage = () => {
4 | return (
5 | <>
6 |
7 |
8 |
9 |
10 |
11 | Create posts to educate
12 |
13 |
14 | Pen down your ideas{" "}
15 | By creating a post
16 |
17 |
18 | Your post must be free from racism and unhealthy words
19 |
20 |
24 | Buy This Course
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | >
34 | );
35 | };
36 |
37 | export default HomePage;
38 |
--------------------------------------------------------------------------------
/src/components/Navigation/Admin/AdminNavbar.js:
--------------------------------------------------------------------------------
1 | /* This example requires Tailwind CSS v2.0+ */
2 | import { Fragment } from "react";
3 | import { Disclosure, Menu, Transition } from "@headlessui/react";
4 | import { useDispatch } from "react-redux";
5 |
6 | import { Link } from "react-router-dom";
7 | import {
8 | BellIcon,
9 | MenuIcon,
10 | XIcon,
11 | BookOpenIcon,
12 | LogoutIcon,
13 | } from "@heroicons/react/outline";
14 | import { PlusIcon } from "@heroicons/react/solid";
15 | import { logoutAction } from "../../../redux/slices/users/usersSlices";
16 |
17 | const navigation = [
18 | { name: "Home", href: "/", current: true },
19 | { name: "Create", href: "/create-post", current: false },
20 | { name: "Posts", href: "/posts", current: false },
21 | { name: "Authors", href: "/users", current: false },
22 | { name: "Add Category", href: "/add-category", current: false },
23 | { name: "Category List", href: "/category-list", current: false },
24 | ];
25 |
26 | function classNames(...classes) {
27 | return classes.filter(Boolean).join(" ");
28 | }
29 |
30 | const AdminNavbar = ({ isLogin }) => {
31 | //Navigation
32 | const userNavigation = [
33 | { name: "Your Profile", href: `/profile/${isLogin?._id}` },
34 | { name: "Change your password", href: "/update-password" },
35 | { name: "Settings", href: "/update-password" },
36 | ];
37 | //logout
38 | const dispatch = useDispatch();
39 | return (
40 |
41 | {({ open }) => (
42 | <>
43 |
44 |
45 |
46 |
47 | {/* Mobile menu button */}
48 |
49 | Open main menu
50 | {open ? (
51 |
52 | ) : (
53 |
54 | )}
55 |
56 |
57 |
58 | {/* Logo */}
59 |
60 |
61 |
62 | {navigation.map(item => (
63 |
74 | {item.name}
75 |
76 | ))}
77 |
78 |
79 |
80 |
81 | {/* New post */}
82 |
87 |
91 |
New Post
92 |
93 | {/* Logout */}
94 |
dispatch(logoutAction())}
96 | type="button"
97 | className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-red-500 hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-indigo-500"
98 | >
99 |
103 | Logout
104 |
105 |
106 |
107 | {/* Profile dropdown */}
108 |
109 | {({ open }) => (
110 | <>
111 |
112 |
113 | Open user menu
114 |
119 |
120 |
121 |
131 |
135 | {userNavigation.map(item => (
136 |
137 | {({ active }) => (
138 |
145 | {item.name}
146 |
147 | )}
148 |
149 | ))}
150 |
151 |
152 | >
153 | )}
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | {navigation.map(item => (
163 |
174 | {item.name}
175 |
176 | ))}
177 |
178 | {/* Mobile */}
179 |
180 |
181 |
182 | {/* Image */}
183 |
188 |
189 |
190 |
191 | {isLogin?.firstName} {isLogin?.lastName}
192 |
193 |
194 | {isLogin?.email}
195 |
196 |
197 |
198 |
209 |
210 |
211 | >
212 | )}
213 |
214 | );
215 | };
216 |
217 | export default AdminNavbar;
218 |
--------------------------------------------------------------------------------
/src/components/Navigation/Alerts/AccountVerificationAlertWarning.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import { ExclamationIcon } from "@heroicons/react/solid";
3 | import { accVerificationSendTokenAction } from "../../../redux/slices/accountVerification/accVerificationSlices";
4 |
5 | export default function AccountVerificationAlertWarning() {
6 | const dispatch = useDispatch();
7 | return (
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 | Your account is not verified.{" "}
19 | dispatch(accVerificationSendTokenAction())}
21 | className="font-medium underline text-green-200 hover:text-yellow-600"
22 | >
23 | Click this link to verify
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Navigation/Alerts/AccountVerificationSuccessAlert.js:
--------------------------------------------------------------------------------
1 | /* This example requires Tailwind CSS v2.0+ */
2 | import { CheckCircleIcon } from "@heroicons/react/solid";
3 |
4 | export default function AccountVerificationSuccessAlert() {
5 | return (
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 | Email is successfully sent to your Email
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Navigation/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import AdminNavbar from "./Admin/AdminNavbar";
4 | import AccountVerificationAlertWarning from "./Alerts/AccountVerificationAlertWarning";
5 | import AccountVerificationSuccessAlert from "./Alerts/AccountVerificationSuccessAlert";
6 |
7 | import PrivateNavbar from "./Private/PrivateNavbar";
8 | import PublicNavbar from "./Public/PublicNavbar";
9 |
10 | const Navbar = () => {
11 | //get user from store
12 | const state = useSelector(state => state.users);
13 | const { userAuth, profile } = state;
14 | const isAdmin = userAuth?.isAdmin;
15 |
16 | //account verification
17 | const account = useSelector(state => state?.accountVerification);
18 | const { loading, appErr, serverErr, token } = account;
19 | return (
20 | <>
21 | {isAdmin ? (
22 |
23 | ) : userAuth ? (
24 |
25 | ) : (
26 |
27 | )}
28 | {/* Display alert */}
29 | {userAuth && !userAuth.isVerified && }
30 | {/* display success msg */}
31 | {loading && Loading please wait... }
32 | {token && }
33 | {appErr || serverErr ? (
34 |
35 | {serverErr} {appErr}
36 |
37 | ) : null}
38 | >
39 | );
40 | };
41 |
42 | export default Navbar;
43 |
--------------------------------------------------------------------------------
/src/components/Navigation/Private/PrivateNavbar.js:
--------------------------------------------------------------------------------
1 | /* This example requires Tailwind CSS v2.0+ */
2 | import { Fragment } from "react";
3 | import { Disclosure, Menu, Transition } from "@headlessui/react";
4 | import { Link } from "react-router-dom";
5 | import {
6 | BellIcon,
7 | MenuIcon,
8 | XIcon,
9 | BookOpenIcon,
10 | } from "@heroicons/react/outline";
11 | import { PlusIcon, LogoutIcon } from "@heroicons/react/solid";
12 | import { useDispatch } from "react-redux";
13 | import { logoutAction } from "../../../redux/slices/users/usersSlices";
14 |
15 | const navigation = [
16 | { name: "Home", href: "/", current: true },
17 | { name: "Create", href: "/create-post", current: false },
18 | { name: "Posts", href: "/posts", current: false },
19 | { name: "Profile", href: "/users", current: false },
20 | ];
21 |
22 | function classNames(...classes) {
23 | return classes.filter(Boolean).join(" ");
24 | }
25 |
26 | const PrivateNavbar = ({ isLogin }) => {
27 | const userNavigation = [
28 | { name: "Your Profile", href: `/profile/${isLogin?._id}` },
29 | { name: "Change your password", href: "/update-password" },
30 | ];
31 |
32 | //logout
33 | const dispatch = useDispatch();
34 | return (
35 |
36 | {({ open }) => (
37 | <>
38 |
39 |
40 |
41 |
42 | {/* Mobile menu button */}
43 |
44 | Open main menu
45 | {open ? (
46 |
47 | ) : (
48 |
49 | )}
50 |
51 |
52 |
53 | {/* Logo */}
54 |
55 |
56 |
57 | {navigation.map(item => (
58 |
69 | {item.name}
70 |
71 | ))}
72 |
73 |
74 |
75 |
76 |
80 |
84 |
New Post
85 |
86 |
87 |
dispatch(logoutAction())}
89 | type="button"
90 | className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-red-500 hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-indigo-500"
91 | >
92 |
96 | Logout
97 |
98 |
99 |
100 | {/* Profile dropdown */}
101 |
102 | {({ open }) => (
103 | <>
104 |
105 |
106 | Open user menu
107 |
112 |
113 |
114 |
124 |
128 | {userNavigation.map(item => (
129 |
130 | {({ active }) => (
131 |
138 | {item.name}
139 |
140 | )}
141 |
142 | ))}
143 |
144 |
145 | >
146 | )}
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
171 | {/* Mobile */}
172 |
173 |
174 |
175 |
180 |
181 |
182 |
183 | {isLogin?.firstName} {isLogin?.lastName}
184 |
185 |
186 | {isLogin?.email}
187 |
188 |
189 |
190 |
201 |
202 |
203 | >
204 | )}
205 |
206 | );
207 | };
208 |
209 | export default PrivateNavbar;
210 |
--------------------------------------------------------------------------------
/src/components/Navigation/ProtectedRoutes/AdminRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { Route, Redirect } from "react-router-dom";
4 |
5 | const AdminRoute = ({ component: Component, ...rest }) => {
6 | //check if user is loggin
7 | const user = useSelector(state => state?.users);
8 | const { userAuth } = user;
9 | return (
10 |
13 | userAuth?.isAdmin ? :
14 | }
15 | />
16 | );
17 | };
18 |
19 | export default AdminRoute;
20 |
--------------------------------------------------------------------------------
/src/components/Navigation/ProtectedRoutes/PrivateProtectRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { Route, Redirect } from "react-router-dom";
4 |
5 | const PrivateProtectRoute = ({ component: Component, ...rest }) => {
6 | //check if user is loggin
7 | const user = useSelector(state => state?.users);
8 | const { userAuth } = user;
9 | return (
10 |
13 | userAuth ? :
14 | }
15 | />
16 | );
17 | };
18 |
19 | export default PrivateProtectRoute;
20 |
--------------------------------------------------------------------------------
/src/components/Navigation/Public/PublicNavbar.js:
--------------------------------------------------------------------------------
1 | import { Disclosure } from "@headlessui/react";
2 | import { Link } from "react-router-dom";
3 | import {
4 | MenuIcon,
5 | XIcon,
6 | LoginIcon,
7 | BookOpenIcon,
8 | } from "@heroicons/react/outline";
9 | import { PlusIcon } from "@heroicons/react/solid";
10 |
11 | const navigation = [
12 | { name: "Home", href: "/", current: true },
13 | { name: "Create", href: "/create-post", current: false },
14 | { name: "Posts", href: "/posts", current: false },
15 | { name: "Register", href: "/register", current: false },
16 | { name: "Login", href: "/login", current: false },
17 | ];
18 |
19 | function classNames(...classes) {
20 | return classes.filter(Boolean).join(" ");
21 | }
22 |
23 | const PublicNavbar = () => {
24 | return (
25 |
26 | {({ open }) => (
27 | <>
28 |
29 |
30 |
31 |
32 | {/* Mobile menu button */}
33 |
34 | Open main menu
35 | {open ? (
36 |
37 | ) : (
38 |
39 | )}
40 |
41 |
42 |
43 | {/* Logo */}
44 |
45 |
46 |
47 | {navigation.map(item => (
48 |
59 | {item.name}
60 |
61 | ))}
62 |
63 |
64 |
65 |
66 |
71 |
75 | Login
76 |
77 |
78 |
79 |
83 |
87 |
New Post
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
112 |
113 | >
114 | )}
115 |
116 | );
117 | };
118 |
119 | export default PublicNavbar;
120 |
--------------------------------------------------------------------------------
/src/components/Posts/CreatePost.js:
--------------------------------------------------------------------------------
1 | import { useFormik } from "formik";
2 | import * as Yup from "yup";
3 | import Dropzone from "react-dropzone";
4 | import { Redirect } from "react-router-dom";
5 | import styled from "styled-components";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { createpostAction } from "../../redux/slices/posts/postSlices";
8 | import CategoryDropDown from "../Categories/CategoryDropDown";
9 |
10 | //Form schema
11 | const formSchema = Yup.object({
12 | title: Yup.string().required("Title is required"),
13 | description: Yup.string().required("Description is required"),
14 | category: Yup.object().required("Category is required"),
15 | image: Yup.string().required("Image is required"),
16 | });
17 | //css for dropzone
18 | const Container = styled.div`
19 | flex: 1;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | padding: 20px;
24 | border-width: 2px;
25 | border-radius: 2px;
26 | border-style: dashed;
27 | background-color: #fafafa;
28 | color: #bdbdbd;
29 | border-color:'red'
30 | transition: border 0.24s ease-in-out;
31 | `;
32 |
33 | export default function CreatePost() {
34 | const dispatch = useDispatch();
35 |
36 | //select store data
37 | const post = useSelector(state => state?.post);
38 | const { isCreated, loading, appErr, serverErr } = post;
39 | //formik
40 | const formik = useFormik({
41 | initialValues: {
42 | title: "",
43 | description: "",
44 | category: "",
45 | image: "",
46 | },
47 | onSubmit: values => {
48 | //dispath the action
49 |
50 | const data = {
51 | category: values?.category?.label,
52 | title: values?.title,
53 | description: values?.description,
54 | image: values?.image,
55 | };
56 | dispatch(createpostAction(data));
57 | },
58 | validationSchema: formSchema,
59 | });
60 |
61 | //redirect
62 | if (isCreated) return ;
63 | return (
64 | <>
65 |
66 |
67 |
68 | Create Post
69 |
70 |
71 |
72 |
73 | Share your ideas to the word. Your post must be free from
74 | profanity
75 |
76 |
77 |
78 | {appErr || serverErr ? (
79 |
80 | {serverErr} {appErr}
81 |
82 | ) : null}
83 |
84 |
85 |
86 |
87 |
88 |
92 | Title
93 |
94 |
95 | {/* Title */}
96 |
106 |
107 | {/* Err msg */}
108 |
109 | {formik?.touched?.title && formik?.errors?.title}
110 |
111 |
112 | {/* Category input goes here */}
113 |
117 | Select Category
118 |
119 |
126 |
127 |
131 | Description
132 |
133 | {/* Description */}
134 |
143 | {/* Image component */}
144 |
148 | Select image to upload
149 |
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 |
187 | Loading please wait...
188 |
189 | ) : (
190 |
194 | Create
195 |
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 |
98 | dispatch(deletePostAction(postDetails?._id))
99 | }
100 | class="ml-3"
101 | >
102 |
103 |
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 | dispatch(fetchPostsAction(""))}
56 | className="inline-block py-2 px-6 rounded-l-xl rounded-t-xl bg-green-600 hover:bg-green-700 text-gray-50 font-bold leading-loose transition duration-200"
57 | >
58 | View All Posts
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Categories
67 |
68 |
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 |
201 |
202 |
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 |
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 |
82 | Title
83 |
84 |
85 |
95 |
96 |
97 | {formik.touched.title && formik.errors.title}
98 |
99 |
100 |
101 |
108 |
109 |
113 | Description
114 |
115 |
124 |
125 | {formik.touched.description && formik.errors.description}
126 |
127 |
128 |
129 |
130 | {loading ? (
131 |
135 | Loading please wait...
136 |
137 | ) : (
138 |
142 | Update
143 |
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 |
35 |
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 | dispatch(logoutAction())}
54 | type="button"
55 | className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm"
56 | >
57 | Logout
58 |
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 |
65 | Recipient Email
66 |
67 | {/* Email message */}
68 |
69 |
80 |
81 | {/* Err msg */}
82 |
83 | {formik.touched.recipientEmail && formik.errors.recipientEmail}
84 |
85 |
86 |
87 |
91 | Subject
92 |
93 |
94 | {/* Subject */}
95 |
105 |
106 | {/* err msg */}
107 |
108 | {formik.touched.subject && formik.errors.subject}
109 |
110 |
111 |
112 |
116 | Message
117 |
118 | {/* email message */}
119 |
128 | {/* err here */}
129 |
130 | {formik.touched.message && formik.errors.message}
131 |
132 |
133 | {/* Submit btn */}
134 |
135 | {loading ? (
136 |
140 | Loading please wait...
141 |
142 | ) : (
143 |
147 | Send
148 |
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 |
135 | Loading...
136 |
137 | ) : (
138 |
142 | Login
143 |
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 |
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 |
82 | Enter Your New Password
83 |
84 |
93 | {/* Err msg */}
94 |
95 | {formik.touched.password && formik.errors.password}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | {loading ? (
104 |
108 |
109 |
113 |
114 | Loading please wait...
115 |
116 | ) : (
117 |
121 |
122 |
126 |
127 | Reset Password
128 |
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 |
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 |
66 | Enter Your Email Address
67 |
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 |
101 |
102 |
106 |
107 | Loading please wait...
108 |
109 | ) : (
110 |
114 |
115 |
119 |
120 | Reset Password
121 |
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 |
91 | Loading Please wait...
92 |
93 | ) : (
94 |
98 | Update Password
99 |
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 |
79 | First Name
80 |
81 |
82 | {/* First name */}
83 |
93 |
94 |
95 | {formik.touched.firstName && formik.errors.firstName}
96 |
97 |
98 |
99 |
103 | Last Name
104 |
105 |
106 | {/* Last Name */}
107 |
117 |
118 | {/* Err msg */}
119 |
120 | {formik.touched.lastName && formik.errors.lastName}
121 |
122 |
123 |
124 |
128 | Email
129 |
130 |
131 | {/* Email */}
132 |
142 |
143 | {/* err msg */}
144 |
145 | {formik.touched.email && formik.errors.email}
146 |
147 |
148 |
149 |
153 | Bio
154 |
155 |
164 | {/* Err msg */}
165 |
166 | {formik.touched.bio && formik.errors.bio}
167 |
168 |
169 |
170 | {/* submit btn */}
171 | {loading ? (
172 |
176 | Loading please wait...
177 |
178 | ) : (
179 |
183 | Update
184 |
185 | )}
186 |
187 |
188 |
189 |
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 |
104 |
108 | Loading Please wait...
109 |
110 | ) : (
111 |
115 |
119 | Upload Photo
120 |
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 |
276 | loading please wait...
277 |
278 | ) : (
279 |
283 | Register
284 |
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 |
16 |
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 |
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 |
dispatch(unBlockUserAction(user?.user?._id))}
72 | className="inline-block py-1 px-2 text-center bg-gray-500 text-gray-300 mr-2 mb-1 lg:mb-0 text-xs border rounded"
73 | >
74 | unblock
75 |
76 | ) : (
77 |
dispatch(blockUserAction(user?.user?._id))}
79 | className="inline-block py-1 px-2 text-center bg-red-600 text-gray-300 mr-2 mb-1 lg:mb-0 text-xs border rounded"
80 | >
81 | Block
82 |
83 | )}
84 |
85 |
89 |
93 |
94 | Message
95 |
96 |
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 |
--------------------------------------------------------------------------------