diff --git a/src/components/blog/BlogForm.jsx b/src/components/blog/BlogForm.jsx index 885bd32..19d0725 100644 --- a/src/components/blog/BlogForm.jsx +++ b/src/components/blog/BlogForm.jsx @@ -1,30 +1,13 @@ import { Formik, Form } from "formik"; -import { useSelector } from "react-redux"; -import { toast } from "react-toastify"; + import { blogSchema } from "../../schema/blogSchemas"; -import { customFetch } from "../../utils/customFetch"; + import CustomInput from "../forms/CustomInput"; import CustomDropdownInput from "../forms/CustomDropdownInput"; import CustomTextArea from "../forms/CustomTextArea"; +import { sendPostRequest } from "../common/sendRequests"; const BlogForm = ({ setIsChanged }) => { - const { token } = useSelector((state) => state.userReducers); - const createNewBlogPost = async (values) => { - try { - await customFetch.post("blogs", values, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - setIsChanged(true); - toast.success("Blog Posted Successfully"); - } catch (error) { - toast.error("Failed to Post Blog"); - console.log(error); - } - }; - return (

Add Post

@@ -36,8 +19,15 @@ const BlogForm = ({ setIsChanged }) => { }} validationSchema={blogSchema} onSubmit={(values, actions) => { - createNewBlogPost(values); - actions.resetForm(); + sendPostRequest( + "blogs", + values, + "Blog Posted Successfully", + "Failed to Post Blog", + setIsChanged, + actions + ); + actions.setSubmitting(false); }} > {({ handleSubmit, isSubmitting }) => ( @@ -69,10 +59,10 @@ const BlogForm = ({ setIsChanged }) => { ]} />
-
+
- {visibility === "public" && ( - +
+ { + sendDeleteRequest( + "Are you sure you want to delete this post?", + `blogs/${_id}`, + "Blog post deleted successfully", + "Failed to delete blog post", + setIsChanged + ); + }} + btnText="Delete" + /> + {myBlog && visibility === "public" && ( + { + sendPatchRequest( + `blogs/${_id}`, + { + visibility: "private", + }, + "Are you sure you want to make this post private no one else you will be able to see the post?", + " post visibility converted to private successfully", + "Failed to Make blog post private", + setIsChanged + ); + }} + btnText="Make It Private" + /> )}
)} diff --git a/src/components/common/sendRequests.js b/src/components/common/sendRequests.js new file mode 100644 index 0000000..6baaba2 --- /dev/null +++ b/src/components/common/sendRequests.js @@ -0,0 +1,78 @@ +import { store } from "../../store/store"; +import { toast } from "react-toastify"; +import { customFetch } from "../../utils/customFetch"; + +export const sendPostRequest = async ( + endPoint, + data, + successMessage, + failedMessage, + setIsChanged, + actions +) => { + try { + await customFetch.post(endPoint, data, { + headers: { + Authorization: `Bearer ${store.getState().userReducers.token}`, + }, + }); + + setIsChanged(true); + actions.resetForm(); + toast.success(successMessage); + } catch (error) { + console.log(error); + toast.error(failedMessage); + } +}; + +export const sendPatchRequest = async ( + endPoint, + data, + confirmText, + successMessage, + failedMessage, + setIsChanged +) => { + const isConfirmed = window.confirm(confirmText); + if (!isConfirmed) return; + + try { + await customFetch.patch(endPoint, data, { + headers: { + Authorization: `Bearer ${store.getState().userReducers.token}`, + }, + }); + + setIsChanged(true); + toast.success(successMessage); + } catch (error) { + console.log(error.message); + toast.error(failedMessage); + } +}; + +export const sendDeleteRequest = async ( + confirmText, + endPoint, + successMessage, + failedMessage, + setIsChanged +) => { + const isConfirmed = window.confirm(confirmText); + if (!isConfirmed) return; + + try { + await customFetch.delete(endPoint, { + headers: { + Authorization: `Bearer ${store.getState().userReducers.token}`, + }, + }); + + setIsChanged(true); + toast.success(successMessage); + } catch (error) { + console.log(error.message); + toast.error(failedMessage); + } +}; diff --git a/src/components/userProfile/blogs/ActionButton.jsx b/src/components/userProfile/blogs/ActionButton.jsx new file mode 100644 index 0000000..bd2939e --- /dev/null +++ b/src/components/userProfile/blogs/ActionButton.jsx @@ -0,0 +1,18 @@ +const ActionButton = ({ btnType, onClick, btnText }) => { + const colorClass = + btnType === "danger" + ? "bg-red-500 hover:bg-red-800" + : btnType === "mainBlue" + ? "bg-blue-500 hover:bg-blue-800" + : ""; + return ( + + ); +}; + +export default ActionButton; diff --git a/src/components/userProfile/blogs/BlogCart.jsx b/src/components/userProfile/blogs/BlogCart.jsx index c1a8dad..c0cc879 100644 --- a/src/components/userProfile/blogs/BlogCart.jsx +++ b/src/components/userProfile/blogs/BlogCart.jsx @@ -1,84 +1,11 @@ -import { customFetch } from "../../../utils/customFetch"; -import { toast } from "react-toastify"; -import { useSelector } from "react-redux"; - +import ActionBUtton from "./ActionButton"; +import { sendDeleteRequest, sendPatchRequest } from "../../common/sendRequests"; const BlogCart = ({ blog, setIsChanged }) => { - const { token } = useSelector((state) => state.userReducers); const { _id, title, content, category, createdAt, published, visibility } = blog; const formattedDate = new Date(createdAt).toDateString(); const isPublished = published ? "published" : "Not published"; - const handelDeleteBlog = async () => { - const isConfirm = window.confirm( - "Are you sure you want to delete this post?" - ); - if (!isConfirm) return; - - try { - const response = await customFetch.delete(`blogs/${_id}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - if (response.status === 204) { - toast.success("Blog deleted successfully"); - setIsChanged(true); - } - } catch (error) { - toast.error(error.response.data.message); - } - }; - const handelMakeItPublic = async () => { - try { - const isConfirmed = window.confirm( - "Are you sure you want to make this post private no one else you will be able to see the post?" - ); - if (!isConfirmed) return; - await customFetch.patch( - `blogs/${_id}`, - { - visibility: "public", - }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - - setIsChanged(true); - toast.success(" post visibility converted to private successfully"); - } catch (error) { - toast.error("Failed to delete blog post"); - } - }; - - const handelMakeItPrivate = async () => { - try { - const isConfirmed = window.confirm( - "Are you sure you want to make this post private no one else you will be able to see the post?" - ); - if (!isConfirmed) return; - await customFetch.patch( - `blogs/${_id}`, - { - visibility: "private", - }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - - setIsChanged(true); - toast.success(" post visibility converted to private successfully"); - } catch (error) { - toast.error("Failed to delete blog post"); - } - }; - return (
@@ -116,27 +43,54 @@ const BlogCart = ({ blog, setIsChanged }) => {

{content}

- + { + sendDeleteRequest( + "Are you sure you want to delete this post?", + `blogs/${_id}`, + "Blog deleted successfully", + "Failed to delete blog post", + setIsChanged + ); + }} + btnText="Delete Blog" + /> {visibility === "private" && ( - + { + sendPatchRequest( + `blogs/${_id}`, + { + visibility: "public", + }, + "Are you sure you want to make this post private no one else you will be able to see the post?", + " post visibility converted to Public successfully", + "Failed to make blog post public", + setIsChanged + ); + }} + btnText="Make It Public" + /> )} {visibility === "public" && ( - + { + sendPatchRequest( + `blogs/${_id}`, + { + visibility: "private", + }, + "Are you sure you want to make this post private no one else you will be able to see the post?", + " post visibility converted to private successfully", + "Failed to Make blog post private", + setIsChanged + ); + }} + btnText="Make It Private" + /> )}
diff --git a/src/components/userProfile/financialAids/FinancialAidCard.jsx b/src/components/userProfile/financialAids/FinancialAidCard.jsx index cc74a1e..82de3bc 100644 --- a/src/components/userProfile/financialAids/FinancialAidCard.jsx +++ b/src/components/userProfile/financialAids/FinancialAidCard.jsx @@ -1,34 +1,10 @@ -import { customFetch } from "../../../utils/customFetch"; -import { toast } from "react-toastify"; -import { useSelector } from "react-redux"; import { Link } from "react-router-dom"; - +import { sendDeleteRequest } from "../../common/sendRequests"; const FinancialAidCard = ({ request, setIsChanged }) => { - const { token } = useSelector((state) => state.userReducers); const { _id, user, course, createdAt, updatedAt } = request; const { photo, title, category, duration, price, description } = course; const formatCreatedDate = new Date(createdAt).toDateString(); - const formatUpdatedDate = new Date(updatedAt).toDateString(); - - const handelCancelRequest = async () => { - try { - const isConfirmed = window.confirm( - "Are you sure you want to cancel the request?" - ); - if (!isConfirmed) return; - await customFetch.delete(`financialAidRequests/${_id}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setIsChanged(true); - toast.success("Course financial aid request canceled successfully"); - } catch (error) { - console.log(error); - toast.error("Failed to cancel financial aid request "); - } - }; return (
@@ -65,7 +41,15 @@ const FinancialAidCard = ({ request, setIsChanged }) => {
diff --git a/src/components/userProfile/wishlist/WishlistCard.jsx b/src/components/userProfile/wishlist/WishlistCard.jsx index 3fe5ffb..ebb9147 100644 --- a/src/components/userProfile/wishlist/WishlistCard.jsx +++ b/src/components/userProfile/wishlist/WishlistCard.jsx @@ -1,33 +1,10 @@ -import { customFetch } from "../../../utils/customFetch"; -import { toast } from "react-toastify"; -import { useSelector } from "react-redux"; import { Link } from "react-router-dom"; +import { sendDeleteRequest } from "../../common/sendRequests"; const WishlistCard = ({ wishlistItem, setIsChanged }) => { - const { token } = useSelector((state) => state.userReducers); - const { _id, course, createdAt, updatedAt } = wishlistItem; + const { _id, course } = wishlistItem; const { photo, title, category, duration, price, description } = course; - // const formatCreatedDate = new Date(createdAt).toDateString(); - // const formatUpdatedDate = new Date(updatedAt).toDateString(); - const handelDeleteWishlistItem = async () => { - try { - const isConfirmed = window.confirm( - "Are you sure you want to delete this note?" - ); - if (!isConfirmed) return; - await customFetch.delete(`wishlist/${_id}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - setIsChanged(true); - toast.success(" Course removed from wishlist successfully"); - } catch (error) { - toast.error("Failed to remove course from wishlist"); - } - }; return (
@@ -59,7 +36,15 @@ const WishlistCard = ({ wishlistItem, setIsChanged }) => {
diff --git a/src/pages/userProfile/MyNotes.jsx b/src/pages/userProfile/MyNotes.jsx index 663488d..19be5c9 100644 --- a/src/pages/userProfile/MyNotes.jsx +++ b/src/pages/userProfile/MyNotes.jsx @@ -10,7 +10,6 @@ import { LoadingSpinner, ProfilePageContainer, } from "../../components"; - const MyNotes = () => { const { token } = useSelector((state) => state.userReducers); const [isChanged, setIsChanged] = useState(false); diff --git a/src/pages/userProfile/MyTasks.jsx b/src/pages/userProfile/MyTasks.jsx index 9e75a46..80f5a4c 100644 --- a/src/pages/userProfile/MyTasks.jsx +++ b/src/pages/userProfile/MyTasks.jsx @@ -32,6 +32,7 @@ function MyTasks() { requestedData: "tasks", } ); + const fetchTasksStates = async () => { try { const response = await customFetch.get("tasks/stats", { diff --git a/src/store/adminSlice.js b/src/store/adminSlice.js deleted file mode 100644 index fdc1898..0000000 --- a/src/store/adminSlice.js +++ /dev/null @@ -1,197 +0,0 @@ -import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; -import axios from "axios"; -import { cleareStatus } from "./authHandler"; - -// const userUrl = "http://localhost:3000/api/v1/admin"; - -const userUrl = - "https://e-learning-backend-application.onrender.com/api/v1/admin"; - -const initialState = { - loading: false, - isSuccess: false, - error: null, - users: [], - userDocument: null, -}; - -//Create User -export const createUser = createAsyncThunk( - "admin/createUser", - async (userData, { rejectWithValue, getState, dispatch }) => { - try { - const token = getState().userReducers.token; - const res = await axios.post(`${userUrl}/createUser`, userData, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const data = await res.data; - return data; - } catch (error) { - if (error.response.status === 401) { - dispatch(cleareStatus()); - } - return rejectWithValue(error.response); - } - } -); - -//get all User -export const getAllUsers = createAsyncThunk( - "admin/getAllUsers", - async (_, { rejectWithValue, getState, dispatch }) => { - try { - const token = getState().userReducers.token; - const res = await axios.get(`${userUrl}/getAllUsers`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const data = await res.data; - return data; - } catch (error) { - if (error.response.status === 401) { - dispatch(cleareStatus()); - } - return rejectWithValue(error.response); - } - } -); - -//get User -export const getUser = createAsyncThunk( - "admin/getUser", - async (userID, { rejectWithValue, getState, dispatch }) => { - try { - const token = getState().userReducers.token; - const res = await axios.get(`${userUrl}/getUser/${userID}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const data = await res.data; - - return data; - } catch (error) { - if (error.response.status === 401) { - dispatch(cleareStatus()); - } - return rejectWithValue(error.response); - } - } -); - -//delete User -export const deleteUser = createAsyncThunk( - "admin/deleteUser", - async (userID, { rejectWithValue, getState, dispatch }) => { - try { - const token = getState().userReducers.token; - const res = await axios.delete(`${userUrl}/deleteUser/${userID}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const data = await res.data; - - return data; - } catch (error) { - if (error.response.status === 401) { - dispatch(cleareStatus()); - } - return rejectWithValue(error.response); - } - } -); -const adminSlice = createSlice({ - name: "admin", - initialState, - reducers: { - resetAdminState: (state) => { - state.loading = false; - state.isSuccess = false; - state.error = null; - state.users = []; - state.userDocument = null; - }, - clearSuccessState: (state) => { - state.isSuccess = false; - state.error = null; - }, - }, - extraReducers: (builder) => { - //Create User builder - builder - .addCase(createUser.pending, (state) => { - state.loading = true; - state.isSuccess = false; - state.error = null; - }) - .addCase(createUser.fulfilled, (state) => { - state.loading = false; - state.isSuccess = true; - state.error = null; - }) - .addCase(createUser.rejected, (state, action) => { - state.error = action.payload.data.message; - state.loading = false; - state.isSuccess = false; - }); - //Get all users builder - builder - .addCase(getAllUsers.pending, (state) => { - state.loading = true; - state.isSuccess = false; - state.error = null; - }) - .addCase(getAllUsers.fulfilled, (state, action) => { - state.loading = false; - state.isSuccess = true; - state.users = action.payload.data.doc; - }) - .addCase(getAllUsers.rejected, (state, action) => { - state.error = action.payload.data.message; - state.loading = false; - state.isSuccess = false; - }); - //Get user builder - builder - .addCase(getUser.pending, (state) => { - state.loading = true; - state.isSuccess = false; - state.error = null; - }) - .addCase(getUser.fulfilled, (state, action) => { - state.loading = false; - state.isSuccess = true; - state.userDocument = action.payload.data.doc; - }) - .addCase(getUser.rejected, (state, action) => { - state.error = action.payload.data.message; - state.loading = false; - state.isSuccess = false; - state.userDocument = null; - }); - - //delete user builder - builder - .addCase(deleteUser.pending, (state) => { - state.loading = true; - state.isSuccess = false; - state.error = null; - }) - .addCase(deleteUser.fulfilled, (state) => { - state.loading = false; - state.isSuccess = true; - }) - .addCase(deleteUser.rejected, (state, action) => { - state.error = action.payload.data.message; - state.loading = false; - state.isSuccess = false; - }); - }, -}); - -export const adminActions = adminSlice.actions; -export default adminSlice.reducer; diff --git a/src/store/authHandler.js b/src/store/authHandler.js index b594e3f..d904836 100644 --- a/src/store/authHandler.js +++ b/src/store/authHandler.js @@ -1,13 +1,11 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; -import { adminActions } from "./adminSlice"; import { userActions } from "./userSlice"; export const cleareStatus = createAsyncThunk( "auth/cleareStatus", async (_, { dispatch }) => { try { - dispatch(adminActions.resetAdminState()); dispatch(userActions.handelUnAuthorizedUser()); return true; } catch (error) { diff --git a/src/store/store.js b/src/store/store.js index b3c17d8..64b8e17 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -2,12 +2,10 @@ import { configureStore, combineReducers } from "@reduxjs/toolkit"; import { persistReducer, persistStore } from "redux-persist"; import storage from "redux-persist/lib/storage"; -import adminReducers from "./adminSlice"; import userReducers from "./userSlice"; const rootReducer = combineReducers({ userReducers, - adminReducers, }); const persistConfig = { diff --git a/src/store/userSlice.js b/src/store/userSlice.js index e56637a..062bd91 100644 --- a/src/store/userSlice.js +++ b/src/store/userSlice.js @@ -4,11 +4,11 @@ import { toast } from "react-toastify"; import { cleareStatus } from "./authHandler"; //Bas URL -// const authUrl = `http://localhost:3000/api/v1/auth`; -// const userUrl = `http://localhost:3000/api/v1/users`; +const authUrl = `http://localhost:3000/api/v1/auth`; +const userUrl = `http://localhost:3000/api/v1/users`; -const authUrl = `https://e-learning-backend-application.onrender.com/api/v1/auth`; -const userUrl = `https://e-learning-backend-application.onrender.com/api/v1/users`; +// const authUrl = `https://e-learning-backend-application.onrender.com/api/v1/auth`; +// const userUrl = `https://e-learning-backend-application.onrender.com/api/v1/users`; //SignUp new user to the application export const signUp = createAsyncThunk( diff --git a/src/utils/customFetch.js b/src/utils/customFetch.js index 6f3b98f..4d108a8 100644 --- a/src/utils/customFetch.js +++ b/src/utils/customFetch.js @@ -4,5 +4,5 @@ const productionUrl = "https://e-learning-backend-application.onrender.com/api/v1"; export const customFetch = axios.create({ - baseURL: productionUrl, + baseURL: devURL, });