From 798967100bc74bda5c9e13e6e23d4d2035049355 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Wed, 8 Nov 2023 10:34:58 +0100
Subject: [PATCH 1/9] feat: add list of blog posts to admin panel

---
 src/screens/admin/component.js                |  6 ++
 .../components/add-blog-post/component.js     |  7 ++
 .../admin/components/add-blog-post/index.js   | 13 +++
 .../admin/components/add-blog-post/style.scss |  0
 .../admin/components/blog-posts/component.js  | 58 +++++++++++++
 .../admin/components/blog-posts/index.js      | 13 +++
 .../admin/components/blog-posts/style.scss    | 48 ++++++++++
 src/screens/admin/components/index.js         |  4 +
 src/screens/admin/style.scss                  | 29 +++++--
 src/services/blog.js                          | 87 +++++++++++++++++++
 src/services/index.js                         |  2 +
 11 files changed, 262 insertions(+), 5 deletions(-)
 create mode 100644 src/screens/admin/components/add-blog-post/component.js
 create mode 100644 src/screens/admin/components/add-blog-post/index.js
 create mode 100644 src/screens/admin/components/add-blog-post/style.scss
 create mode 100644 src/screens/admin/components/blog-posts/component.js
 create mode 100644 src/screens/admin/components/blog-posts/index.js
 create mode 100644 src/screens/admin/components/blog-posts/style.scss
 create mode 100644 src/services/blog.js

diff --git a/src/screens/admin/component.js b/src/screens/admin/component.js
index 75fe1beb..d6fb24d5 100644
--- a/src/screens/admin/component.js
+++ b/src/screens/admin/component.js
@@ -1,7 +1,9 @@
 import React, { useState } from 'react';
 
 import {
+  AddBlogPost,
   AddUser,
+  BlogPosts,
   ChangePassword,
   FileUpload,
   Login,
@@ -78,6 +80,10 @@ const Admin = (props) => {
               <p>{modelError}</p>
             </div>
           )}
+          <div className="blog-container">
+            <AddBlogPost />
+            <BlogPosts />
+          </div>
         </div>
         <ChangePassword
           close={() => setChangePasswordVisible(false)}
diff --git a/src/screens/admin/components/add-blog-post/component.js b/src/screens/admin/components/add-blog-post/component.js
new file mode 100644
index 00000000..e45a7e77
--- /dev/null
+++ b/src/screens/admin/components/add-blog-post/component.js
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const AddBlogPost = () => {
+  return <div className="add-blog-post-container">Add blog post</div>;
+};
+
+export default AddBlogPost;
diff --git a/src/screens/admin/components/add-blog-post/index.js b/src/screens/admin/components/add-blog-post/index.js
new file mode 100644
index 00000000..8239409a
--- /dev/null
+++ b/src/screens/admin/components/add-blog-post/index.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+
+import AddBlogPost from './component';
+
+const mapStateToProps = (state) => {
+  return {};
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {};
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(AddBlogPost);
diff --git a/src/screens/admin/components/add-blog-post/style.scss b/src/screens/admin/components/add-blog-post/style.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
new file mode 100644
index 00000000..49a04f81
--- /dev/null
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -0,0 +1,58 @@
+import React, { useEffect, useState } from 'react';
+import { deleteBlogPost, editBlogPost, getAllBlogPostsByAuthor } from '../../../../services/blog';
+
+import './style.scss';
+
+const BlogPost = ({ post }) => {
+  const date = new Date(post.date_created);
+  const dateToDisplay = `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`;
+
+  const onClickEdit = () => {
+    editBlogPost(
+      post.id,
+      { title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ulla.' },
+    );
+  };
+
+  const onClickDelete = () => {
+    deleteBlogPost(post.id);
+  };
+
+  return (
+    <div className="blog-post">
+      <div>
+        <div className="blog-post-title">
+          {post.title}
+        </div>
+        <div className="blog-post-date">
+          {dateToDisplay}
+        </div>
+      </div>
+      <div className="blog-post-action-buttons">
+        <button className="animated-button blog-post-button" type="button" onClick={onClickEdit}>Edit</button>
+        <button className="animated-button blog-post-button" type="button" onClick={onClickDelete}>Delete</button>
+      </div>
+    </div>
+  );
+};
+
+const BlogPosts = () => {
+  const [blogPosts, setBlogPosts] = useState([]);
+
+  useEffect(() => {
+    (async () => {
+      const blogPostsArray = await getAllBlogPostsByAuthor();
+      setBlogPosts(blogPostsArray);
+    })();
+  }, [setBlogPosts]);
+
+  console.log(blogPosts);
+  return (
+    <div className="blog-posts-container">
+      <div className="blog-posts-title">Your blog posts</div>
+      {blogPosts.map((post) => <BlogPost post={post} key={post.id} />)}
+    </div>
+  );
+};
+
+export default BlogPosts;
diff --git a/src/screens/admin/components/blog-posts/index.js b/src/screens/admin/components/blog-posts/index.js
new file mode 100644
index 00000000..9416c92b
--- /dev/null
+++ b/src/screens/admin/components/blog-posts/index.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+
+import BlogPosts from './component';
+
+const mapStateToProps = (state) => {
+  return {};
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {};
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(BlogPosts);
diff --git a/src/screens/admin/components/blog-posts/style.scss b/src/screens/admin/components/blog-posts/style.scss
new file mode 100644
index 00000000..d35b86ee
--- /dev/null
+++ b/src/screens/admin/components/blog-posts/style.scss
@@ -0,0 +1,48 @@
+.blog-post {
+    width: 100%;
+    box-sizing: border-box;
+    margin-bottom: 8px;
+    padding: 5px 16px;
+    border: none;
+    border-radius: 10px;
+    box-shadow: 0 6px 40px 0 rgba(0, 0, 0, 0.04);
+    background-color: #ffffff;
+    font-size: 15px;
+    line-height: 1.51;
+    color: #62697e;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    &-title {
+        font-weight: 600;
+        margin-right: 6px;
+        width: 250px;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+    }
+
+    &-date {
+        font-style: italic;
+    }
+
+    &-action-buttons {
+        border-left: 1px solid #c9cdd9;
+        padding-left: 6px;
+    }
+
+    &-button {
+        padding: 6px;
+        border-radius: 6px;
+        background-color: #c9cdd9;
+        margin-left: 6px;
+    }
+}
+
+.blog-posts-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #2f303a;
+    margin-bottom: 14px;
+}
\ No newline at end of file
diff --git a/src/screens/admin/components/index.js b/src/screens/admin/components/index.js
index 955c412a..1a14608a 100644
--- a/src/screens/admin/components/index.js
+++ b/src/screens/admin/components/index.js
@@ -1,11 +1,15 @@
+import AddBlogPost from './add-blog-post/component';
 import AddUser from './add-user';
+import BlogPosts from './blog-posts/component';
 import ChangePassword from './change-password';
 import FileUpload from './file-upload';
 import Login from './login';
 import Users from './users';
 
 export {
+  AddBlogPost,
   AddUser,
+  BlogPosts,
   ChangePassword,
   FileUpload,
   Login,
diff --git a/src/screens/admin/style.scss b/src/screens/admin/style.scss
index 3e50a22e..3bf69815 100644
--- a/src/screens/admin/style.scss
+++ b/src/screens/admin/style.scss
@@ -38,7 +38,7 @@
         }
     }
 
-    #dashboard-container{
+    #dashboard-container {
         padding: 37px;
         border-radius: 26px;
         box-shadow: 0 35px 90px -10px rgba(0, 0, 0, 0.06);
@@ -64,7 +64,7 @@
                     width: 400px;
                 }
 
-                * + * {
+                *+* {
                     margin-top: 15px;
                 }
             }
@@ -77,7 +77,8 @@
                 overflow: scroll;
                 margin-left: 20px;
 
-                #users-container, #add-users {
+                #users-container,
+                #add-users {
                     flex: 1 1 auto;
                     padding: 23px;
                     border-radius: 22px;
@@ -91,7 +92,7 @@
         }
 
         #rerun-button {
-            margin: 40px 346px 0 349px;
+            margin: 40px 346px 40px 349px;
             padding: 8px 34px 7px 35px;
             border-radius: 6px;
             background-color: #2f303a;
@@ -100,6 +101,24 @@
             line-height: 1.8;
             color: #ffffff;
             white-space: nowrap;
-          }
+        }
     }
 }
+
+.blog-container {
+    display: flex;
+}
+
+.add-blog-post-container,
+.blog-posts-container {
+    flex: 1 1 auto;
+    padding: 23px;
+    border-radius: 22px;
+    background-color: #f8f9ff;
+    width: 50%;
+}
+
+.blog-posts-container {
+    margin-left: 20px;
+    overflow: scroll;
+}
\ No newline at end of file
diff --git a/src/services/blog.js b/src/services/blog.js
new file mode 100644
index 00000000..a25d6e98
--- /dev/null
+++ b/src/services/blog.js
@@ -0,0 +1,87 @@
+import axios from 'axios';
+
+import {
+  getAuthTokenFromStorage, getUserIdFromStorage,
+} from '../utils';
+
+const SUBROUTE = 'blog';
+
+export const createBlogPost = async () => {};
+
+export const getAllBlogPosts = async () => {
+  const url = `${global.API_URL}/${SUBROUTE}`;
+
+  try {
+    console.log('req');
+    const { data: response } = await axios.get(url);
+    console.log('res');
+
+    const { data } = response;
+
+    return data;
+  } catch (error) {
+    console.error(error);
+    throw error;
+  }
+};
+
+export const getAllBlogPostsByAuthor = async () => {
+  const userId = getUserIdFromStorage();
+  const url = `${global.API_URL}/${SUBROUTE}/user/${userId}`;
+  const token = getAuthTokenFromStorage();
+
+  try {
+    const { data: response } = await axios.get(url, {
+      headers: {
+        authorization: `Bearer ${token}`,
+      },
+    });
+
+    const { data } = response;
+
+    return data;
+  } catch (error) {
+    console.error(error);
+    throw error;
+  }
+};
+
+export const editBlogPost = async (id, fields) => {
+  const url = `${global.API_URL}/${SUBROUTE}/${id}`;
+  const token = getAuthTokenFromStorage();
+
+  try {
+    const { data: response } = await axios.put(url, fields, {
+      headers: {
+        authorization: `Bearer ${token}`,
+      },
+    });
+
+    const { data } = response;
+
+    return data;
+  } catch (error) {
+    console.error(error);
+    throw error;
+  }
+};
+
+export const deleteBlogPost = async (id) => {
+  const url = `${global.API_URL}/${SUBROUTE}/${id}`;
+  const token = getAuthTokenFromStorage();
+
+  try {
+    const { data: response } = await axios.delete(url, {
+      headers: {
+        authorization: `Bearer ${token}`,
+      },
+    });
+
+    const { data } = response;
+
+    return data;
+  } catch (error) {
+    console.error(error);
+    throw error;
+  }
+};
diff --git a/src/services/index.js b/src/services/index.js
index befa82eb..97a82ca1 100644
--- a/src/services/index.js
+++ b/src/services/index.js
@@ -1,9 +1,11 @@
 import * as admin from './admin';
 import * as api from './api';
+import * as blog from './blog';
 import * as user from './user';
 
 export {
   admin,
   api,
+  blog,
   user,
 };

From 74d98d1604773a77b19fd369626f1ac400ee7647 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Fri, 10 Nov 2023 14:46:45 +0100
Subject: [PATCH 2/9] feat: use redux for blog posts

---
 .../admin/components/blog-posts/component.js  | 27 ++++----
 .../admin/components/blog-posts/index.js      | 23 ++++++-
 .../admin/components/blog-posts/style.scss    |  2 +-
 src/screens/admin/components/index.js         |  4 +-
 src/services/blog.js                          |  4 +-
 src/state/actions/blog.js                     | 63 +++++++++++++++++++
 src/state/actions/index.js                    | 11 ++++
 src/state/reducers/blog.js                    | 27 ++++++++
 src/state/reducers/index.js                   |  2 +
 9 files changed, 140 insertions(+), 23 deletions(-)
 create mode 100644 src/state/actions/blog.js
 create mode 100644 src/state/reducers/blog.js

diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index 49a04f81..cb8f522f 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -1,21 +1,20 @@
-import React, { useEffect, useState } from 'react';
-import { deleteBlogPost, editBlogPost, getAllBlogPostsByAuthor } from '../../../../services/blog';
+import React, { useEffect } from 'react';
 
 import './style.scss';
 
-const BlogPost = ({ post }) => {
+const BlogPost = ({ post, onEdit, onDelete }) => {
   const date = new Date(post.date_created);
   const dateToDisplay = `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`;
 
   const onClickEdit = () => {
-    editBlogPost(
+    onEdit(
       post.id,
-      { title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ulla.' },
+      { title: 'New title 40' },
     );
   };
 
   const onClickDelete = () => {
-    deleteBlogPost(post.id);
+    onDelete(post.id);
   };
 
   return (
@@ -36,21 +35,19 @@ const BlogPost = ({ post }) => {
   );
 };
 
-const BlogPosts = () => {
-  const [blogPosts, setBlogPosts] = useState([]);
+const BlogPosts = (props) => {
+  const {
+    blogPosts, getAllBlogPostsByAuthor, editBlogPost, deleteBlogPost,
+  } = props;
 
   useEffect(() => {
-    (async () => {
-      const blogPostsArray = await getAllBlogPostsByAuthor();
-      setBlogPosts(blogPostsArray);
-    })();
-  }, [setBlogPosts]);
+    getAllBlogPostsByAuthor();
+  }, [getAllBlogPostsByAuthor]);
 
-  console.log(blogPosts);
   return (
     <div className="blog-posts-container">
       <div className="blog-posts-title">Your blog posts</div>
-      {blogPosts.map((post) => <BlogPost post={post} key={post.id} />)}
+      {blogPosts && blogPosts.map((post) => <BlogPost post={post} onEdit={editBlogPost} onDelete={deleteBlogPost} key={post.id} />)}
     </div>
   );
 };
diff --git a/src/screens/admin/components/blog-posts/index.js b/src/screens/admin/components/blog-posts/index.js
index 9416c92b..13240202 100644
--- a/src/screens/admin/components/blog-posts/index.js
+++ b/src/screens/admin/components/blog-posts/index.js
@@ -1,13 +1,32 @@
 import { connect } from 'react-redux';
 
 import BlogPosts from './component';
+import { deleteBlogPost, editBlogPost, getAllBlogPostsByAuthor } from '../../../../state/actions';
 
 const mapStateToProps = (state) => {
-  return {};
+  const {
+    blog: {
+      blogPostsByUser: blogPosts,
+    },
+  } = state;
+
+  return {
+    blogPosts,
+  };
 };
 
 const mapDispatchToProps = (dispatch) => {
-  return {};
+  return {
+    getAllBlogPostsByAuthor: (onSuccess, onError) => {
+      dispatch(getAllBlogPostsByAuthor(onSuccess, onError));
+    },
+    editBlogPost: (id, fields) => {
+      dispatch(editBlogPost(id, fields));
+    },
+    deleteBlogPost: (id) => {
+      dispatch(deleteBlogPost(id));
+    },
+  };
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(BlogPosts);
diff --git a/src/screens/admin/components/blog-posts/style.scss b/src/screens/admin/components/blog-posts/style.scss
index d35b86ee..7368a2df 100644
--- a/src/screens/admin/components/blog-posts/style.scss
+++ b/src/screens/admin/components/blog-posts/style.scss
@@ -45,4 +45,4 @@
     font-weight: 600;
     color: #2f303a;
     margin-bottom: 14px;
-}
\ No newline at end of file
+}
diff --git a/src/screens/admin/components/index.js b/src/screens/admin/components/index.js
index 1a14608a..a3e30039 100644
--- a/src/screens/admin/components/index.js
+++ b/src/screens/admin/components/index.js
@@ -1,6 +1,6 @@
-import AddBlogPost from './add-blog-post/component';
+import AddBlogPost from './add-blog-post';
 import AddUser from './add-user';
-import BlogPosts from './blog-posts/component';
+import BlogPosts from './blog-posts';
 import ChangePassword from './change-password';
 import FileUpload from './file-upload';
 import Login from './login';
diff --git a/src/services/blog.js b/src/services/blog.js
index a25d6e98..d93efc37 100644
--- a/src/services/blog.js
+++ b/src/services/blog.js
@@ -77,9 +77,7 @@ export const deleteBlogPost = async (id) => {
       },
     });
 
-    const { data } = response;
-
-    return data;
+    return response;
   } catch (error) {
     console.error(error);
     throw error;
diff --git a/src/state/actions/blog.js b/src/state/actions/blog.js
new file mode 100644
index 00000000..ad5c4b89
--- /dev/null
+++ b/src/state/actions/blog.js
@@ -0,0 +1,63 @@
+import { blog as BlogService } from '../../services';
+
+export const ActionTypes = {
+  API_ERROR: 'API_ERROR',
+  SET_BLOG_POSTS_BY_USER_DATA: 'SET_BLOG_POSTS_BY_USER_DATA',
+  EDIT_BLOG_POST: 'EDIT_BLOG_POST',
+  DELETE_BLOG_POST: 'DELETE_BLOG_POST',
+};
+
+export const getAllBlogPostsByAuthor = (onSuccess = () => {}, onError = () => {}) => {
+  return async (dispatch) => {
+    try {
+      const blogPosts = await BlogService.getAllBlogPostsByAuthor();
+      dispatch({ type: ActionTypes.SET_BLOG_POSTS_BY_USER_DATA, payload: blogPosts });
+      onSuccess();
+    } catch (error) {
+      dispatch({
+        type: ActionTypes.API_ERROR,
+        payload: {
+          action: 'GET ALL BLOG POSTS',
+          error,
+        },
+      });
+      onError(error);
+    }
+  };
+};
+
+export const editBlogPost = (id, fields) => {
+  return async (dispatch) => {
+    try {
+      const editedBlogPost = await BlogService.editBlogPost(id, fields);
+      dispatch({ type: ActionTypes.EDIT_BLOG_POST, payload: editedBlogPost });
+    } catch (error) {
+      dispatch({
+        type: ActionTypes.API_ERROR,
+        payload: {
+          action: 'EDIT BLOG POST',
+          error,
+        },
+      });
+    }
+  };
+};
+
+export const deleteBlogPost = (id) => {
+  return async (dispatch) => {
+    try {
+      const response = await BlogService.deleteBlogPost(id);
+      if (response.status === 200) {
+        dispatch({ type: ActionTypes.DELETE_BLOG_POST, payload: { _id: id } });
+      }
+    } catch (error) {
+      dispatch({
+        type: ActionTypes.API_ERROR,
+        payload: {
+          action: 'DELETE BLOG POST',
+          error,
+        },
+      });
+    }
+  };
+};
diff --git a/src/state/actions/index.js b/src/state/actions/index.js
index 9d88071c..8578b9d0 100644
--- a/src/state/actions/index.js
+++ b/src/state/actions/index.js
@@ -35,10 +35,18 @@ import {
   runCustomPrediction,
 } from './data';
 
+import {
+  ActionTypes as blogActionTypes,
+  getAllBlogPostsByAuthor,
+  editBlogPost,
+  deleteBlogPost,
+} from './blog';
+
 const ActionTypes = {
   ...dataActionTypes,
   ...selectionActionTypes,
   ...userActionTypes,
+  ...blogActionTypes,
 };
 
 export {
@@ -46,9 +54,12 @@ export {
   clearCustomPredictionError,
   clearData,
   clearSelections,
+  deleteBlogPost,
+  editBlogPost,
   getAggregateLocationData,
   getAggregateStateData,
   getAggregateYearData,
+  getAllBlogPostsByAuthor,
   getAvailableStates,
   getAvailableSublocations,
   getAvailableYears,
diff --git a/src/state/reducers/blog.js b/src/state/reducers/blog.js
new file mode 100644
index 00000000..aaf2dd8c
--- /dev/null
+++ b/src/state/reducers/blog.js
@@ -0,0 +1,27 @@
+import { ActionTypes } from '../actions';
+
+const initialState = {
+  blogPostsByUser: [],
+};
+
+const BlogReducer = (state = initialState, action) => {
+  switch (action.type) {
+    case ActionTypes.SET_BLOG_POSTS_BY_USER_DATA:
+      return { ...state, blogPostsByUser: action.payload };
+
+    case ActionTypes.EDIT_BLOG_POST: {
+      const updatedBlogPosts = state.blogPostsByUser.map((post) => (post._id === action.payload._id ? action.payload : post));
+      return { ...state, blogPostsByUser: updatedBlogPosts };
+    }
+
+    case ActionTypes.DELETE_BLOG_POST: {
+      const filteredBlogPosts = state.blogPostsByUser.filter((post) => post._id !== action.payload._id);
+      return { ...state, blogPostsByUser: filteredBlogPosts };
+    }
+
+    default:
+      return state;
+  }
+};
+
+export default BlogReducer;
diff --git a/src/state/reducers/index.js b/src/state/reducers/index.js
index 3ca302a8..da0e76bd 100644
--- a/src/state/reducers/index.js
+++ b/src/state/reducers/index.js
@@ -1,11 +1,13 @@
 import { combineReducers } from 'redux';
 
+import BlogReducer from './blog';
 import ErrorReducer from './error';
 import DataReducer from './data';
 import SelectionsReducer from './selections';
 import UserReducer from './user';
 
 const rootReducer = combineReducers({
+  blog: BlogReducer,
   data: DataReducer,
   error: ErrorReducer,
   selections: SelectionsReducer,

From d84b8647dbf440ebf4f79fe448b095cde62a9567 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Fri, 17 Nov 2023 12:01:51 +0100
Subject: [PATCH 3/9] feat: create FileInput component using UploadFile
 component

---
 .../input-components/file-input/index.js      | 121 ++++++++++++++++++
 .../input-components/file-input}/style.scss   |   0
 src/components/input-components/index.js      |   2 +
 .../admin/components/file-upload/component.js | 108 +---------------
 4 files changed, 128 insertions(+), 103 deletions(-)
 create mode 100644 src/components/input-components/file-input/index.js
 rename src/{screens/admin/components/file-upload => components/input-components/file-input}/style.scss (100%)

diff --git a/src/components/input-components/file-input/index.js b/src/components/input-components/file-input/index.js
new file mode 100644
index 00000000..26e8d2b0
--- /dev/null
+++ b/src/components/input-components/file-input/index.js
@@ -0,0 +1,121 @@
+import React, { useState } from 'react';
+
+import './style.scss';
+
+const FileInput = (props) => {
+  const {
+    guideURL, component, onResetFiles, fileFormat = '.csv',
+  } = props;
+
+  const [isUploadingFile, setIsUploadingFile] = useState(false);
+  const [uploadingFileError, setUploadingFileError] = useState('');
+  const [successMessage, setSuccessMessage] = useState({});
+
+  const clearSuccessMessage = () => setSuccessMessage({});
+
+  const clearError = () => {
+    setUploadingFileError('');
+    onResetFiles();
+    setIsUploadingFile(false);
+    setSuccessMessage({});
+  };
+
+  if (isUploadingFile) {
+    return (
+      <div className="uploading-message-container">
+        <h3>Uploading File...</h3>
+      </div>
+    );
+  }
+
+  if (uploadingFileError) {
+    return (
+      <div id="uploading-error-container" className="uploading-message-container">
+        {
+            guideURL
+              ? <h3>{uploadingFileError} Please read <a href={guideURL} target="_blank" rel="noopener noreferrer">this guide</a> for uploading data.</h3>
+              : <h3>{uploadingFileError}</h3>
+          }
+        <button
+          type="button"
+          onClick={clearError}
+        >Try Again
+        </button>
+      </div>
+    );
+  }
+
+  /**
+   * @description uploads given file
+   * @param {Function} uploadFunction function to upload file
+   * @param {File} file file object
+   * @param {Function} clearFile function to clear the file
+   * @param {String} id file id
+   */
+  const uploadFile = async (uploadFunction, file, clearFile, id) => {
+    setIsUploadingFile(true);
+
+    try {
+      await uploadFunction(file);
+      clearFile();
+      setSuccessMessage({ [id]: 'Successfully uploaded file' });
+      setTimeout(clearSuccessMessage, 1000 * 7);
+    } catch (err) {
+      const { data, status } = err?.response || {};
+
+      const strippedError = data?.error.toString().replace('Error: ', '');
+
+      const badRequest = status === 400;
+      const badColumnNames = strippedError.includes('missing fields in csv');
+      const wrongFileFormat = strippedError.includes('Invalid file type');
+
+      if (badColumnNames) setUploadingFileError('Incorrect column names. Please upload a different CSV.');
+      else if (wrongFileFormat) setUploadingFileError('Invalid file type. Only PNG, JPG, and JPEG files are allowed! Please, choose a different file.');
+      else if (badRequest) setUploadingFileError(`Bad request: ${strippedError}`);
+      else setUploadingFileError(strippedError || data?.error.toString() || 'We encountered an error. Please try again.');
+    } finally {
+      setIsUploadingFile(false);
+    }
+  };
+
+  return (
+    <div id={component.id} key={component.id}>
+      <p>{component.name}</p>
+      <p id="file-selected">
+        {component.file ? component.file.name : ''}
+      </p>
+      {component.file && component.uploadFile ? (
+        <button
+          id="upload-button"
+          className="custom-file-upload"
+          type="button"
+          onClick={() => uploadFile(
+            component.uploadFile,
+            component.file,
+            component.selectFile,
+            component.id,
+          )}
+        >
+          Upload File
+        </button>
+      ) : (
+        <>
+          {successMessage[component.id] && (
+          <p id="success-message">{successMessage[component.id]}</p>
+          )}
+          <label htmlFor={`file-upload-${component.id}`} className="custom-file-upload">
+            <input
+              id={`file-upload-${component.id}`}
+              type="file"
+              accept={fileFormat}
+              onChange={(e) => component.selectFile(e.target.files[0]) && clearSuccessMessage()}
+            />
+            Select File
+          </label>
+        </>
+      )}
+    </div>
+  );
+};
+
+export default FileInput;
diff --git a/src/screens/admin/components/file-upload/style.scss b/src/components/input-components/file-input/style.scss
similarity index 100%
rename from src/screens/admin/components/file-upload/style.scss
rename to src/components/input-components/file-input/style.scss
diff --git a/src/components/input-components/index.js b/src/components/input-components/index.js
index 24598b34..2eb9c75f 100644
--- a/src/components/input-components/index.js
+++ b/src/components/input-components/index.js
@@ -1,9 +1,11 @@
 import ChoiceInput from './choice-input';
 import TextInput from './text-input';
 import MultiSelectInput from './multi-select-input';
+import FileInput from './file-input';
 
 export {
   ChoiceInput,
   TextInput,
   MultiSelectInput,
+  FileInput,
 };
diff --git a/src/screens/admin/components/file-upload/component.js b/src/screens/admin/components/file-upload/component.js
index edad2028..0df4838e 100644
--- a/src/screens/admin/components/file-upload/component.js
+++ b/src/screens/admin/components/file-upload/component.js
@@ -1,13 +1,12 @@
 import React, { useState } from 'react';
 
+import { FileInput } from '../../../../components/input-components';
 import {
   uploadCountySpotCsv,
   uploadRangerDistrictSpotCsv,
   uploadSurvey123UnsummarizedCsv,
 } from '../../../../services/admin';
 
-import './style.scss';
-
 const FileUpload = (props) => {
   const { guideURL } = props;
 
@@ -15,127 +14,30 @@ const FileUpload = (props) => {
   const [rdSpotFile, setRdSpotFile] = useState();
   const [unsummarizedFile, setUnsummarizedFile] = useState();
 
-  const [isUploadingFile, setIsUploadingFile] = useState(false);
-  const [uploadingFileError, setUploadingFileError] = useState('');
-  const [successMessage, setSuccessMessage] = useState({});
-
-  const clearSuccessMessage = () => setSuccessMessage({});
-
-  const clearError = () => {
-    setUploadingFileError('');
-    setCountySpotFile();
-    setRdSpotFile();
-    setUnsummarizedFile();
-    setIsUploadingFile(false);
-    setSuccessMessage({});
-  };
-
-  /**
-   * @description uploads given file
-   * @param {Function} uploadFunction function to upload file
-   * @param {File} file file object
-   * @param {Function} clearFile function to clear the file
-   * @param {String} id file id
-   */
-  const uploadFile = async (uploadFunction, file, clearFile, id) => {
-    setIsUploadingFile(true);
-
-    try {
-      await uploadFunction(file);
-      clearFile();
-      setSuccessMessage({ [id]: 'Successfully uploaded file' });
-      setTimeout(clearSuccessMessage, 1000 * 7);
-    } catch (err) {
-      const { data, status } = err?.response || {};
-
-      const strippedError = data?.error.toString().replace('Error: ', '');
-
-      const badRequest = status === 400;
-      const badColumnNames = strippedError.includes('missing fields in csv');
-
-      if (badColumnNames) setUploadingFileError('Incorrect column names. Please upload a different CSV.');
-      else if (badRequest) setUploadingFileError(`Bad request: ${strippedError}`);
-      else setUploadingFileError(strippedError || data?.error.toString() || 'We encountered an error. Please try again.');
-    } finally {
-      setIsUploadingFile(false);
-    }
-  };
-
   const componentsToRender = [{
     file: countySpotFile,
     id: 'county-spot',
     name: 'Upload File for County Spot Data',
     selectFile: setCountySpotFile,
-    uploadFile: () => uploadFile(uploadCountySpotCsv, countySpotFile, setCountySpotFile, 'county-spot'),
+    uploadFile: uploadCountySpotCsv,
   }, {
     file: rdSpotFile,
     id: 'rd-spot',
     name: 'Upload File for Ranger District Spot Data',
     selectFile: setRdSpotFile,
-    uploadFile: () => uploadFile(uploadRangerDistrictSpotCsv, rdSpotFile, setRdSpotFile, 'rd-spot'),
+    uploadFile: uploadRangerDistrictSpotCsv,
   }, {
     file: unsummarizedFile,
     id: 'unsummarized',
     name: 'Upload File for Survey123 Unsummarized Data',
     selectFile: setUnsummarizedFile,
-    uploadFile: () => uploadFile(uploadSurvey123UnsummarizedCsv, unsummarizedFile, setUnsummarizedFile, 'unsummarized'),
+    uploadFile: uploadSurvey123UnsummarizedCsv,
   }];
 
-  if (isUploadingFile) {
-    return (
-      <div className="uploading-message-container">
-        <h3>Uploading File...</h3>
-      </div>
-    );
-  }
-
-  if (uploadingFileError) {
-    return (
-      <div id="uploading-error-container" className="uploading-message-container">
-        <h3>{uploadingFileError} Please read <a href={guideURL} target="_blank" rel="noopener noreferrer">this guide</a> for uploading data.</h3>
-        <button
-          type="button"
-          onClick={clearError}
-        >Try Again
-        </button>
-      </div>
-    );
-  }
-
   return (
     <>
       {componentsToRender.map((component) => (
-        <div id={component.id} key={component.id}>
-          <p>{component.name}</p>
-          <p id="file-selected">
-            {component.file ? component.file.name : ''}
-          </p>
-          {component.file ? (
-            <button
-              id="upload-button"
-              className="custom-file-upload"
-              type="button"
-              onClick={component.uploadFile}
-            >
-              Upload File
-            </button>
-          ) : (
-            <>
-              {successMessage[component.id] && (
-              <p id="success-message">{successMessage[component.id]}</p>
-              )}
-              <label htmlFor={`file-upload-${component.id}`} className="custom-file-upload">
-                <input
-                  id={`file-upload-${component.id}`}
-                  type="file"
-                  accept=".csv"
-                  onChange={(e) => component.selectFile(e.target.files[0]) && clearSuccessMessage()}
-                />
-                Select File
-              </label>
-            </>
-          )}
-        </div>
+        <FileInput component={component} onResetFiles={() => component.selectFile()} guideURL={guideURL} />
       ))}
     </>
   );

From 03dc2050892cf8543849b5c2cbfb6d9aec819ac2 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Fri, 17 Nov 2023 12:09:58 +0100
Subject: [PATCH 4/9] feat: add BlogPostForm component and AddBlogPost form

---
 .../components/add-blog-post/component.js     | 11 ++-
 .../admin/components/add-blog-post/index.js   |  7 +-
 .../components/blog-post-form/component.js    | 83 +++++++++++++++++++
 .../admin/components/blog-post-form/index.js  | 13 +++
 .../components/blog-post-form/style.scss      | 61 ++++++++++++++
 .../admin/components/blog-posts/component.js  | 13 ++-
 src/screens/admin/style.scss                  |  1 +
 src/services/blog.js                          | 20 ++++-
 src/state/actions/blog.js                     | 18 ++++
 src/state/actions/index.js                    |  2 +
 src/state/reducers/blog.js                    |  5 ++
 src/style.scss                                |  4 +
 12 files changed, 231 insertions(+), 7 deletions(-)
 create mode 100644 src/screens/admin/components/blog-post-form/component.js
 create mode 100644 src/screens/admin/components/blog-post-form/index.js
 create mode 100644 src/screens/admin/components/blog-post-form/style.scss

diff --git a/src/screens/admin/components/add-blog-post/component.js b/src/screens/admin/components/add-blog-post/component.js
index e45a7e77..42d30602 100644
--- a/src/screens/admin/components/add-blog-post/component.js
+++ b/src/screens/admin/components/add-blog-post/component.js
@@ -1,7 +1,14 @@
 import React from 'react';
 
-const AddBlogPost = () => {
-  return <div className="add-blog-post-container">Add blog post</div>;
+import BlogPostForm from '../blog-post-form';
+import './style.scss';
+
+const AddBlogPost = (props) => {
+  const { createBlogPost } = props;
+
+  return (
+    <BlogPostForm onSubmit={createBlogPost} />
+  );
 };
 
 export default AddBlogPost;
diff --git a/src/screens/admin/components/add-blog-post/index.js b/src/screens/admin/components/add-blog-post/index.js
index 8239409a..625e9257 100644
--- a/src/screens/admin/components/add-blog-post/index.js
+++ b/src/screens/admin/components/add-blog-post/index.js
@@ -1,5 +1,6 @@
 import { connect } from 'react-redux';
 
+import { createBlogPost } from '../../../../state/actions';
 import AddBlogPost from './component';
 
 const mapStateToProps = (state) => {
@@ -7,7 +8,11 @@ const mapStateToProps = (state) => {
 };
 
 const mapDispatchToProps = (dispatch) => {
-  return {};
+  return {
+    createBlogPost: (fields) => {
+      dispatch(createBlogPost(fields));
+    },
+  };
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(AddBlogPost);
diff --git a/src/screens/admin/components/blog-post-form/component.js b/src/screens/admin/components/blog-post-form/component.js
new file mode 100644
index 00000000..4c38d236
--- /dev/null
+++ b/src/screens/admin/components/blog-post-form/component.js
@@ -0,0 +1,83 @@
+import React, { useState } from 'react';
+
+import { FileInput } from '../../../../components/input-components';
+
+import './style.scss';
+
+const BlogPostForm = (props) => {
+  const { onSubmit, formTitle } = props;
+
+  const [formData, setFormData] = useState({
+    title: '',
+    body: '',
+    image: null,
+  });
+
+  const handleInputChange = (ev) => {
+    return setFormData({
+      ...formData,
+      [ev.target.name]: ev.target.value,
+    });
+  };
+
+  const handleFileChange = (file) => {
+    setFormData({
+      ...formData,
+      image: file,
+    });
+  };
+
+  const handleSubmit = async (ev) => {
+    ev.preventDefault();
+
+    const data = new FormData();
+    data.append('title', formData.title);
+    data.append('body', formData.body);
+
+    if (formData.image) {
+      data.append('image', formData.image);
+    }
+
+    await onSubmit(data);
+  };
+
+  const uploadImageComponent = {
+    file: formData.image,
+    id: 'blog-post-image',
+    name: 'Upload image for your blog post',
+    selectFile: handleFileChange,
+  };
+
+  const resetImage = () => setFormData({
+    ...formData,
+    image: null,
+  });
+
+  return (
+    <div className="add-blog-post-container">
+      <div className="blog-posts-form-title">
+        {formTitle}
+      </div>
+      <form>
+        <label htmlFor="title" className="input-label">
+          Title
+          <div className="input-container">
+            <input name="title" type="text" placeholder="Title" onChange={handleInputChange} />
+          </div>
+        </label>
+        <div className="image-input-container">
+          <FileInput component={uploadImageComponent} onResetFiles={resetImage} fileFormat="image/png, image/jpg, image/jpeg, image/pjpeg" />
+        </div>
+        <label htmlFor="body" className="input-label">
+          Enter blog post
+          <div className="input-container">
+            <textarea name="body" onChange={handleInputChange} />
+          </div>
+        </label>
+        <button type="submit" className="blog-form-button animated-button" onClick={handleSubmit}>Submit</button>
+      </form>
+    </div>
+  );
+};
+
+export default BlogPostForm;
diff --git a/src/screens/admin/components/blog-post-form/index.js b/src/screens/admin/components/blog-post-form/index.js
new file mode 100644
index 00000000..e2476aed
--- /dev/null
+++ b/src/screens/admin/components/blog-post-form/index.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+
+import BlogPostForm from './component';
+
+const mapStateToProps = (state) => {
+  return {};
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {};
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(BlogPostForm);
diff --git a/src/screens/admin/components/blog-post-form/style.scss b/src/screens/admin/components/blog-post-form/style.scss
new file mode 100644
index 00000000..f8d2b44b
--- /dev/null
+++ b/src/screens/admin/components/blog-post-form/style.scss
@@ -0,0 +1,61 @@
+.blog-form-button {
+    width: 114px;
+    margin: 19px 67px 0 0;
+    padding: 4px 0;
+    border-radius: 6px;
+    background-color: #c9cdd9;
+    margin-top: 20px;
+    font-size: 18px;
+    font-weight: 600;
+    line-height: 1.8;
+    color: #2f303a;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.blog-posts-form-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #2f303a;
+    margin-bottom: 14px;
+}
+
+.input-container {
+
+    margin: 10px 0;
+
+    input,
+    textarea {
+        width: 100%;
+        box-sizing: border-box;
+        margin-bottom: 8px;
+        padding: 5px 16px;
+        border: none;
+        border-radius: 10px;
+        box-shadow: 0 6px 40px 0 rgba(0, 0, 0, 0.04);
+        background-color: #ffffff;
+        font-size: 15px;
+        line-height: 1.51;
+        color: #62697e;
+    }
+
+    textarea {
+        resize: vertical;
+        min-height: 150px;
+
+    }
+}
+
+.input-label {
+    margin-bottom: 10px;
+}
+
+.image-input-container {
+    margin: 10px 0 18px;
+
+    label {
+        font-size: 13.3333px;
+        padding: 6px;
+        border-radius: 6px;
+    }
+}
\ No newline at end of file
diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index cb8f522f..c36b6628 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -44,10 +44,21 @@ const BlogPosts = (props) => {
     getAllBlogPostsByAuthor();
   }, [getAllBlogPostsByAuthor]);
 
+  // Sort posts from the newest to the oldest
+  const sortedBlogPosts = blogPosts.sort((a, b) => {
+    const dateA = new Date(a.date_created);
+    const dateB = new Date(b.date_created);
+
+    return dateB - dateA;
+  });
+
   return (
     <div className="blog-posts-container">
       <div className="blog-posts-title">Your blog posts</div>
-      {blogPosts && blogPosts.map((post) => <BlogPost post={post} onEdit={editBlogPost} onDelete={deleteBlogPost} key={post.id} />)}
+      {sortedBlogPosts.length > 0
+      && sortedBlogPosts.map(
+        (post) => <BlogPost post={post} onEdit={editBlogPost} onDelete={deleteBlogPost} key={post.id} />,
+      )}
     </div>
   );
 };
diff --git a/src/screens/admin/style.scss b/src/screens/admin/style.scss
index 3bf69815..41fa8b2a 100644
--- a/src/screens/admin/style.scss
+++ b/src/screens/admin/style.scss
@@ -121,4 +121,5 @@
 .blog-posts-container {
     margin-left: 20px;
     overflow: scroll;
+    max-height: 500px;
 }
\ No newline at end of file
diff --git a/src/services/blog.js b/src/services/blog.js
index d93efc37..2ca16a0b 100644
--- a/src/services/blog.js
+++ b/src/services/blog.js
@@ -6,15 +6,29 @@ import {
 
 const SUBROUTE = 'blog';
 
-export const createBlogPost = async () => {};
+export const createBlogPost = async (fields) => {
+  const url = `${global.API_URL}/${SUBROUTE}/create`;
+  const token = getAuthTokenFromStorage();
+  try {
+    const { data: response } = await axios.post(url, fields, {
+      headers: {
+        authorization: `Bearer ${token}`,
+      },
+    });
+
+    const { data } = response;
+
+    return data.blogPost;
+  } catch (error) {
+    console.error(error); throw error;
+  }
+};
 
 export const getAllBlogPosts = async () => {
   const url = `${global.API_URL}/${SUBROUTE}`;
 
   try {
-    console.log('req');
     const { data: response } = await axios.get(url);
-    console.log('res');
 
     const { data } = response;
 
diff --git a/src/state/actions/blog.js b/src/state/actions/blog.js
index ad5c4b89..b7ae1769 100644
--- a/src/state/actions/blog.js
+++ b/src/state/actions/blog.js
@@ -5,6 +5,24 @@ export const ActionTypes = {
   SET_BLOG_POSTS_BY_USER_DATA: 'SET_BLOG_POSTS_BY_USER_DATA',
   EDIT_BLOG_POST: 'EDIT_BLOG_POST',
   DELETE_BLOG_POST: 'DELETE_BLOG_POST',
+  CREATE_BLOG_POST: 'CREATE_BLOG_POST',
+};
+
+export const createBlogPost = (fields) => {
+  return async (dispatch) => {
+    try {
+      const createdBlogPost = await BlogService.createBlogPost(fields);
+      dispatch({ type: ActionTypes.CREATE_BLOG_POST, payload: createdBlogPost });
+    } catch (error) {
+      dispatch({
+        type: ActionTypes.API_ERROR,
+        payload: {
+          action: 'CREATE BLOG POST',
+          error,
+        },
+      });
+    }
+  };
 };
 
 export const getAllBlogPostsByAuthor = (onSuccess = () => {}, onError = () => {}) => {
diff --git a/src/state/actions/index.js b/src/state/actions/index.js
index 8578b9d0..dab15c0b 100644
--- a/src/state/actions/index.js
+++ b/src/state/actions/index.js
@@ -37,6 +37,7 @@ import {
 
 import {
   ActionTypes as blogActionTypes,
+  createBlogPost,
   getAllBlogPostsByAuthor,
   editBlogPost,
   deleteBlogPost,
@@ -54,6 +55,7 @@ export {
   clearCustomPredictionError,
   clearData,
   clearSelections,
+  createBlogPost,
   deleteBlogPost,
   editBlogPost,
   getAggregateLocationData,
diff --git a/src/state/reducers/blog.js b/src/state/reducers/blog.js
index aaf2dd8c..6e8245ec 100644
--- a/src/state/reducers/blog.js
+++ b/src/state/reducers/blog.js
@@ -6,6 +6,11 @@ const initialState = {
 
 const BlogReducer = (state = initialState, action) => {
   switch (action.type) {
+    case ActionTypes.CREATE_BLOG_POST: {
+      const updatedBlogPostsByUser = state.blogPostsByUser.concat([action.payload]);
+      return { ...state, blogPostsByUser: updatedBlogPostsByUser };
+    }
+
     case ActionTypes.SET_BLOG_POSTS_BY_USER_DATA:
       return { ...state, blogPostsByUser: action.payload };
 
diff --git a/src/style.scss b/src/style.scss
index b4331932..87e15ff8 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -40,6 +40,10 @@ body img {
     border-radius: 0px;
 }
 
+body input, textarea {
+    font-family: 'Inter';
+}
+
 .container {
     width: auto;
     height: auto;

From 4c510f8311d2e446ea9d4588d70172b441812ed5 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Tue, 21 Nov 2023 15:52:27 +0100
Subject: [PATCH 5/9] feat: add edit form for blog posts

---
 .../components/add-blog-post/component.js     |  2 +-
 .../components/blog-post-form/component.js    | 45 +++++++++++++++----
 .../admin/components/blog-post-form/index.js  | 12 +----
 .../components/blog-post-form/style.scss      | 20 +++++++++
 .../admin/components/blog-posts/component.js  | 24 +++++-----
 .../components/edit-blog-post/component.js    | 37 +++++++++++++++
 .../admin/components/edit-blog-post/index.js  |  3 ++
 .../components/edit-blog-post/style.scss      | 16 +++++++
 .../admin/components/file-upload/component.js |  8 +++-
 src/state/actions/blog.js                     |  1 +
 src/state/reducers/blog.js                    |  6 ++-
 11 files changed, 142 insertions(+), 32 deletions(-)
 create mode 100644 src/screens/admin/components/edit-blog-post/component.js
 create mode 100644 src/screens/admin/components/edit-blog-post/index.js
 create mode 100644 src/screens/admin/components/edit-blog-post/style.scss

diff --git a/src/screens/admin/components/add-blog-post/component.js b/src/screens/admin/components/add-blog-post/component.js
index 42d30602..9c483cd6 100644
--- a/src/screens/admin/components/add-blog-post/component.js
+++ b/src/screens/admin/components/add-blog-post/component.js
@@ -7,7 +7,7 @@ const AddBlogPost = (props) => {
   const { createBlogPost } = props;
 
   return (
-    <BlogPostForm onSubmit={createBlogPost} />
+    <BlogPostForm onSubmit={createBlogPost} formTitle="Create blog post" formType="create" />
   );
 };
 
diff --git a/src/screens/admin/components/blog-post-form/component.js b/src/screens/admin/components/blog-post-form/component.js
index 4c38d236..7e50c9b7 100644
--- a/src/screens/admin/components/blog-post-form/component.js
+++ b/src/screens/admin/components/blog-post-form/component.js
@@ -5,9 +5,11 @@ import { FileInput } from '../../../../components/input-components';
 import './style.scss';
 
 const BlogPostForm = (props) => {
-  const { onSubmit, formTitle } = props;
+  const {
+    onSubmit, formTitle, formValues, formType,
+  } = props;
 
-  const [formData, setFormData] = useState({
+  const [formData, setFormData] = useState(formValues || {
     title: '',
     body: '',
     image: null,
@@ -43,8 +45,10 @@ const BlogPostForm = (props) => {
 
   const uploadImageComponent = {
     file: formData.image,
-    id: 'blog-post-image',
-    name: 'Upload image for your blog post',
+    id: `${formType}-blog-post-image`,
+    name: formType === 'edit'
+      ? 'Change image for your blog post'
+      : 'Upload image for your blog post',
     selectFile: handleFileChange,
   };
 
@@ -62,19 +66,44 @@ const BlogPostForm = (props) => {
         <label htmlFor="title" className="input-label">
           Title
           <div className="input-container">
-            <input name="title" type="text" placeholder="Title" onChange={handleInputChange} />
+            <input
+              name="title"
+              type="text"
+              placeholder="Title"
+              onChange={handleInputChange}
+              value={formData.title}
+            />
           </div>
         </label>
         <div className="image-input-container">
-          <FileInput component={uploadImageComponent} onResetFiles={resetImage} fileFormat="image/png, image/jpg, image/jpeg, image/pjpeg" />
+          <FileInput
+            component={uploadImageComponent}
+            onResetFiles={resetImage}
+            fileFormat="image/png, image/jpg, image/jpeg, image/pjpeg"
+          />
+          {typeof formData.image === 'string' && (
+          <div className="image-preview-container">
+            <img src={formData.image} alt="blog post illustration" />
+          </div>
+          )}
         </div>
         <label htmlFor="body" className="input-label">
           Enter blog post
           <div className="input-container">
-            <textarea name="body" onChange={handleInputChange} />
+            <textarea
+              name="body"
+              onChange={handleInputChange}
+              value={formData.body}
+            />
           </div>
         </label>
-        <button type="submit" className="blog-form-button animated-button" onClick={handleSubmit}>Submit</button>
+        <button
+          type="submit"
+          className="blog-form-button animated-button"
+          onClick={handleSubmit}
+        >
+          Submit
+        </button>
       </form>
     </div>
   );
diff --git a/src/screens/admin/components/blog-post-form/index.js b/src/screens/admin/components/blog-post-form/index.js
index e2476aed..1d648a3a 100644
--- a/src/screens/admin/components/blog-post-form/index.js
+++ b/src/screens/admin/components/blog-post-form/index.js
@@ -1,13 +1,3 @@
-import { connect } from 'react-redux';
-
 import BlogPostForm from './component';
 
-const mapStateToProps = (state) => {
-  return {};
-};
-
-const mapDispatchToProps = (dispatch) => {
-  return {};
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(BlogPostForm);
+export default BlogPostForm;
diff --git a/src/screens/admin/components/blog-post-form/style.scss b/src/screens/admin/components/blog-post-form/style.scss
index f8d2b44b..096c002f 100644
--- a/src/screens/admin/components/blog-post-form/style.scss
+++ b/src/screens/admin/components/blog-post-form/style.scss
@@ -52,10 +52,30 @@
 
 .image-input-container {
     margin: 10px 0 18px;
+    display: flex;
+    justify-content: space-between;
 
     label {
         font-size: 13.3333px;
         padding: 6px;
         border-radius: 6px;
     }
+}
+
+.image-preview-container {
+    width: 50%;
+    height: 150px;
+    overflow: hidden;
+    position: relative;
+
+    img {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        min-width: 100%;
+        min-height: 100%;
+        width: auto;
+        height: auto;
+    }
 }
\ No newline at end of file
diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index c36b6628..1116eca9 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -1,18 +1,12 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
 
+import EditBlogPost from '../edit-blog-post';
 import './style.scss';
 
-const BlogPost = ({ post, onEdit, onDelete }) => {
+const BlogPost = ({ post, onClickEdit, onDelete }) => {
   const date = new Date(post.date_created);
   const dateToDisplay = `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`;
 
-  const onClickEdit = () => {
-    onEdit(
-      post.id,
-      { title: 'New title 40' },
-    );
-  };
-
   const onClickDelete = () => {
     onDelete(post.id);
   };
@@ -40,6 +34,14 @@ const BlogPosts = (props) => {
     blogPosts, getAllBlogPostsByAuthor, editBlogPost, deleteBlogPost,
   } = props;
 
+  const [selectedBlogPost, setSelectedBlogPost] = useState({ title: '', body: '', image: null });
+  const [showEditForm, setShowEditForm] = useState(false);
+
+  const openEditForm = (post) => {
+    setShowEditForm(true);
+    setSelectedBlogPost(post);
+  };
+
   useEffect(() => {
     getAllBlogPostsByAuthor();
   }, [getAllBlogPostsByAuthor]);
@@ -57,8 +59,10 @@ const BlogPosts = (props) => {
       <div className="blog-posts-title">Your blog posts</div>
       {sortedBlogPosts.length > 0
       && sortedBlogPosts.map(
-        (post) => <BlogPost post={post} onEdit={editBlogPost} onDelete={deleteBlogPost} key={post.id} />,
+        (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={deleteBlogPost} key={post.id} />,
       )}
+      <EditBlogPost isOpen={showEditForm} setIsOpen={setShowEditForm} post={selectedBlogPost} onSubmit={editBlogPost} />
+
     </div>
   );
 };
diff --git a/src/screens/admin/components/edit-blog-post/component.js b/src/screens/admin/components/edit-blog-post/component.js
new file mode 100644
index 00000000..60a175e9
--- /dev/null
+++ b/src/screens/admin/components/edit-blog-post/component.js
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import Modal from 'react-modal';
+import BlogPostForm from '../blog-post-form';
+
+import './style.scss';
+
+const EditBlogPost = (props) => {
+  const {
+    isOpen, setIsOpen, post, onSubmit,
+  } = props;
+  const handleClose = () => setIsOpen(false);
+  const handleOpen = () => setIsOpen(true);
+
+  const handleSubmit = (data) => { onSubmit(post.id, data); handleClose(); };
+
+  return (
+    <Modal isOpen={isOpen}
+      onAfterOpen={handleOpen}
+      onRequestClose={handleClose}
+      className="blog-post-edit-modal"
+      ariaHideApp={false}
+    >
+      <BlogPostForm onSubmit={handleSubmit}
+        formTitle="Edit your blog post"
+        formValues={{
+          title: post.title,
+          body: post.body,
+          image: post.image,
+        }}
+        formType="edit"
+      />
+    </Modal>
+  );
+};
+
+export default EditBlogPost;
diff --git a/src/screens/admin/components/edit-blog-post/index.js b/src/screens/admin/components/edit-blog-post/index.js
new file mode 100644
index 00000000..9e525327
--- /dev/null
+++ b/src/screens/admin/components/edit-blog-post/index.js
@@ -0,0 +1,3 @@
+import EditBlogPost from './component';
+
+export default EditBlogPost;
diff --git a/src/screens/admin/components/edit-blog-post/style.scss b/src/screens/admin/components/edit-blog-post/style.scss
new file mode 100644
index 00000000..d94cf7e2
--- /dev/null
+++ b/src/screens/admin/components/edit-blog-post/style.scss
@@ -0,0 +1,16 @@
+.blog-post-edit-modal {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 55vw;
+    height: 70vh;
+    padding: 50px;
+    border-radius: 20px;
+    background-color: #ffffff;
+    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+
+    .add-blog-post-container {
+        width: auto;
+    }
+}
\ No newline at end of file
diff --git a/src/screens/admin/components/file-upload/component.js b/src/screens/admin/components/file-upload/component.js
index 0df4838e..5f43e299 100644
--- a/src/screens/admin/components/file-upload/component.js
+++ b/src/screens/admin/components/file-upload/component.js
@@ -37,7 +37,13 @@ const FileUpload = (props) => {
   return (
     <>
       {componentsToRender.map((component) => (
-        <FileInput component={component} onResetFiles={() => component.selectFile()} guideURL={guideURL} />
+        <FileInput
+          component={component}
+          onResetFiles={() => component.selectFile()}
+          guideURL={guideURL}
+          key={component.id}
+
+        />
       ))}
     </>
   );
diff --git a/src/state/actions/blog.js b/src/state/actions/blog.js
index b7ae1769..6c8cb6da 100644
--- a/src/state/actions/blog.js
+++ b/src/state/actions/blog.js
@@ -47,6 +47,7 @@ export const getAllBlogPostsByAuthor = (onSuccess = () => {}, onError = () => {}
 export const editBlogPost = (id, fields) => {
   return async (dispatch) => {
     try {
+      console.log('id', id, 'fields', fields);
       const editedBlogPost = await BlogService.editBlogPost(id, fields);
       dispatch({ type: ActionTypes.EDIT_BLOG_POST, payload: editedBlogPost });
     } catch (error) {
diff --git a/src/state/reducers/blog.js b/src/state/reducers/blog.js
index 6e8245ec..2e2b9331 100644
--- a/src/state/reducers/blog.js
+++ b/src/state/reducers/blog.js
@@ -15,7 +15,11 @@ const BlogReducer = (state = initialState, action) => {
       return { ...state, blogPostsByUser: action.payload };
 
     case ActionTypes.EDIT_BLOG_POST: {
-      const updatedBlogPosts = state.blogPostsByUser.map((post) => (post._id === action.payload._id ? action.payload : post));
+      const updatedBlogPosts = state.blogPostsByUser.map(
+        (post) => (post._id === action.payload._id
+          ? action.payload
+          : post),
+      );
       return { ...state, blogPostsByUser: updatedBlogPosts };
     }
 

From 37e581455d588e95cd05a92ec4cdb75eced7e6a8 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Thu, 23 Nov 2023 16:11:15 +0100
Subject: [PATCH 6/9] feat: add error handling to blog post forms

---
 .../admin/components/add-blog-post/index.js   |  4 ++--
 .../components/blog-post-form/component.js    | 23 +++++++++++++++----
 .../admin/components/blog-post-form/index.js  | 18 ++++++++++++++-
 .../components/blog-post-form/style.scss      | 11 +++++++++
 .../admin/components/blog-posts/component.js  | 11 +++++++--
 .../admin/components/blog-posts/index.js      |  4 ++--
 .../components/edit-blog-post/component.js    |  4 +---
 .../components/edit-blog-post/style.scss      |  1 +
 src/state/actions/blog.js                     | 11 ++++++---
 src/state/reducers/blog.js                    |  7 ++++++
 10 files changed, 77 insertions(+), 17 deletions(-)

diff --git a/src/screens/admin/components/add-blog-post/index.js b/src/screens/admin/components/add-blog-post/index.js
index 625e9257..02d6e67b 100644
--- a/src/screens/admin/components/add-blog-post/index.js
+++ b/src/screens/admin/components/add-blog-post/index.js
@@ -9,8 +9,8 @@ const mapStateToProps = (state) => {
 
 const mapDispatchToProps = (dispatch) => {
   return {
-    createBlogPost: (fields) => {
-      dispatch(createBlogPost(fields));
+    createBlogPost: (fields, onSuccess) => {
+      dispatch(createBlogPost(fields, onSuccess));
     },
   };
 };
diff --git a/src/screens/admin/components/blog-post-form/component.js b/src/screens/admin/components/blog-post-form/component.js
index 7e50c9b7..381c8de7 100644
--- a/src/screens/admin/components/blog-post-form/component.js
+++ b/src/screens/admin/components/blog-post-form/component.js
@@ -6,7 +6,7 @@ import './style.scss';
 
 const BlogPostForm = (props) => {
   const {
-    onSubmit, formTitle, formValues, formType,
+    onSubmit, formTitle, formValues, formType, error,
   } = props;
 
   const [formData, setFormData] = useState(formValues || {
@@ -39,8 +39,15 @@ const BlogPostForm = (props) => {
     if (formData.image) {
       data.append('image', formData.image);
     }
-
-    await onSubmit(data);
+    if (formType === 'create') {
+      await onSubmit(data, () => setFormData({
+        title: '',
+        body: '',
+        image: null,
+      }));
+    } else {
+      onSubmit(data);
+    }
   };
 
   const uploadImageComponent = {
@@ -57,6 +64,10 @@ const BlogPostForm = (props) => {
     image: null,
   });
 
+  const isButtonDisabled = !formData.title.length || !formData.body.length;
+
+  const shouldErrorDisplay = error?.action && error.action.toLowerCase().includes(formType);
+
   return (
     <div className="add-blog-post-container">
       <div className="blog-posts-form-title">
@@ -72,6 +83,7 @@ const BlogPostForm = (props) => {
               placeholder="Title"
               onChange={handleInputChange}
               value={formData.title}
+              required
             />
           </div>
         </label>
@@ -94,16 +106,19 @@ const BlogPostForm = (props) => {
               name="body"
               onChange={handleInputChange}
               value={formData.body}
+              required
             />
           </div>
         </label>
         <button
           type="submit"
-          className="blog-form-button animated-button"
+          className={`blog-form-button ${isButtonDisabled ? '' : 'animated-button'}`}
           onClick={handleSubmit}
+          disabled={isButtonDisabled}
         >
           Submit
         </button>
+        <div className="blog-form-error">{shouldErrorDisplay && error.message}</div>
       </form>
     </div>
   );
diff --git a/src/screens/admin/components/blog-post-form/index.js b/src/screens/admin/components/blog-post-form/index.js
index 1d648a3a..741a461e 100644
--- a/src/screens/admin/components/blog-post-form/index.js
+++ b/src/screens/admin/components/blog-post-form/index.js
@@ -1,3 +1,19 @@
+import { connect } from 'react-redux';
+
 import BlogPostForm from './component';
 
-export default BlogPostForm;
+const mapStateToProps = (state) => {
+  const {
+    blog: {
+      error,
+    },
+  } = state;
+
+  return { error };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {};
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(BlogPostForm);
diff --git a/src/screens/admin/components/blog-post-form/style.scss b/src/screens/admin/components/blog-post-form/style.scss
index 096c002f..7f75180f 100644
--- a/src/screens/admin/components/blog-post-form/style.scss
+++ b/src/screens/admin/components/blog-post-form/style.scss
@@ -11,6 +11,17 @@
     color: #2f303a;
     margin-left: auto;
     margin-right: auto;
+
+    &:disabled {
+        border: none;
+        opacity: 0.5;
+        cursor: not-allowed;
+    }
+}
+
+.blog-form-error {
+    margin-top: 10px;
+    color: red
 }
 
 .blog-posts-form-title {
diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index 1116eca9..8f8b2d04 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -34,7 +34,9 @@ const BlogPosts = (props) => {
     blogPosts, getAllBlogPostsByAuthor, editBlogPost, deleteBlogPost,
   } = props;
 
-  const [selectedBlogPost, setSelectedBlogPost] = useState({ title: '', body: '', image: null });
+  const [selectedBlogPost, setSelectedBlogPost] = useState({
+    id: null, title: '', body: '', image: null,
+  });
   const [showEditForm, setShowEditForm] = useState(false);
 
   const openEditForm = (post) => {
@@ -54,6 +56,11 @@ const BlogPosts = (props) => {
     return dateB - dateA;
   });
 
+  const handleFormSubmit = async (formData) => {
+    const closeModal = () => { setShowEditForm(false); };
+    await editBlogPost(selectedBlogPost.id, formData, closeModal);
+  };
+
   return (
     <div className="blog-posts-container">
       <div className="blog-posts-title">Your blog posts</div>
@@ -61,7 +68,7 @@ const BlogPosts = (props) => {
       && sortedBlogPosts.map(
         (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={deleteBlogPost} key={post.id} />,
       )}
-      <EditBlogPost isOpen={showEditForm} setIsOpen={setShowEditForm} post={selectedBlogPost} onSubmit={editBlogPost} />
+      <EditBlogPost isOpen={showEditForm} setIsOpen={setShowEditForm} post={selectedBlogPost} onSubmit={handleFormSubmit} />
 
     </div>
   );
diff --git a/src/screens/admin/components/blog-posts/index.js b/src/screens/admin/components/blog-posts/index.js
index 13240202..2d66e067 100644
--- a/src/screens/admin/components/blog-posts/index.js
+++ b/src/screens/admin/components/blog-posts/index.js
@@ -20,8 +20,8 @@ const mapDispatchToProps = (dispatch) => {
     getAllBlogPostsByAuthor: (onSuccess, onError) => {
       dispatch(getAllBlogPostsByAuthor(onSuccess, onError));
     },
-    editBlogPost: (id, fields) => {
-      dispatch(editBlogPost(id, fields));
+    editBlogPost: (id, fields, onSuccess) => {
+      dispatch(editBlogPost(id, fields, onSuccess));
     },
     deleteBlogPost: (id) => {
       dispatch(deleteBlogPost(id));
diff --git a/src/screens/admin/components/edit-blog-post/component.js b/src/screens/admin/components/edit-blog-post/component.js
index 60a175e9..4e6b9a64 100644
--- a/src/screens/admin/components/edit-blog-post/component.js
+++ b/src/screens/admin/components/edit-blog-post/component.js
@@ -12,8 +12,6 @@ const EditBlogPost = (props) => {
   const handleClose = () => setIsOpen(false);
   const handleOpen = () => setIsOpen(true);
 
-  const handleSubmit = (data) => { onSubmit(post.id, data); handleClose(); };
-
   return (
     <Modal isOpen={isOpen}
       onAfterOpen={handleOpen}
@@ -21,7 +19,7 @@ const EditBlogPost = (props) => {
       className="blog-post-edit-modal"
       ariaHideApp={false}
     >
-      <BlogPostForm onSubmit={handleSubmit}
+      <BlogPostForm onSubmit={onSubmit}
         formTitle="Edit your blog post"
         formValues={{
           title: post.title,
diff --git a/src/screens/admin/components/edit-blog-post/style.scss b/src/screens/admin/components/edit-blog-post/style.scss
index d94cf7e2..8e03dc9d 100644
--- a/src/screens/admin/components/edit-blog-post/style.scss
+++ b/src/screens/admin/components/edit-blog-post/style.scss
@@ -9,6 +9,7 @@
     border-radius: 20px;
     background-color: #ffffff;
     box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+    overflow: scroll;
 
     .add-blog-post-container {
         width: auto;
diff --git a/src/state/actions/blog.js b/src/state/actions/blog.js
index 6c8cb6da..b82de719 100644
--- a/src/state/actions/blog.js
+++ b/src/state/actions/blog.js
@@ -2,17 +2,20 @@ import { blog as BlogService } from '../../services';
 
 export const ActionTypes = {
   API_ERROR: 'API_ERROR',
+  CLEAR_API_ERROR: 'CLEAR_API_ERROR',
   SET_BLOG_POSTS_BY_USER_DATA: 'SET_BLOG_POSTS_BY_USER_DATA',
   EDIT_BLOG_POST: 'EDIT_BLOG_POST',
   DELETE_BLOG_POST: 'DELETE_BLOG_POST',
   CREATE_BLOG_POST: 'CREATE_BLOG_POST',
 };
 
-export const createBlogPost = (fields) => {
+export const createBlogPost = (fields, onSuccess) => {
   return async (dispatch) => {
     try {
+      dispatch({ type: ActionTypes.CLEAR_API_ERROR });
       const createdBlogPost = await BlogService.createBlogPost(fields);
       dispatch({ type: ActionTypes.CREATE_BLOG_POST, payload: createdBlogPost });
+      onSuccess();
     } catch (error) {
       dispatch({
         type: ActionTypes.API_ERROR,
@@ -44,12 +47,14 @@ export const getAllBlogPostsByAuthor = (onSuccess = () => {}, onError = () => {}
   };
 };
 
-export const editBlogPost = (id, fields) => {
+export const editBlogPost = (id, fields, onSuccess) => {
   return async (dispatch) => {
     try {
-      console.log('id', id, 'fields', fields);
+      dispatch({ type: ActionTypes.CLEAR_API_ERROR });
+
       const editedBlogPost = await BlogService.editBlogPost(id, fields);
       dispatch({ type: ActionTypes.EDIT_BLOG_POST, payload: editedBlogPost });
+      onSuccess();
     } catch (error) {
       dispatch({
         type: ActionTypes.API_ERROR,
diff --git a/src/state/reducers/blog.js b/src/state/reducers/blog.js
index 2e2b9331..cbfdb55c 100644
--- a/src/state/reducers/blog.js
+++ b/src/state/reducers/blog.js
@@ -2,6 +2,7 @@ import { ActionTypes } from '../actions';
 
 const initialState = {
   blogPostsByUser: [],
+  error: null,
 };
 
 const BlogReducer = (state = initialState, action) => {
@@ -28,6 +29,12 @@ const BlogReducer = (state = initialState, action) => {
       return { ...state, blogPostsByUser: filteredBlogPosts };
     }
 
+    case ActionTypes.API_ERROR:
+      return { ...state, error: { message: action.payload.error.response?.data?.error, action: action.payload.action } };
+
+    case ActionTypes.CLEAR_API_ERROR:
+      return { ...state, error: null };
+
     default:
       return state;
   }

From 74c941e929f3e145676228c808f569d415b23c39 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Thu, 23 Nov 2023 16:44:59 +0100
Subject: [PATCH 7/9] feat: add delete modal to blog post list

---
 .../admin/components/blog-posts/component.js  | 39 ++++++++++++++++++-
 .../admin/components/blog-posts/index.js      |  4 +-
 .../admin/components/blog-posts/style.scss    | 31 +++++++++++++++
 src/state/actions/blog.js                     |  3 +-
 4 files changed, 73 insertions(+), 4 deletions(-)

diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index 8f8b2d04..a7f151b1 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -1,4 +1,5 @@
 import React, { useEffect, useState } from 'react';
+import Modal from 'react-modal';
 
 import EditBlogPost from '../edit-blog-post';
 import './style.scss';
@@ -29,6 +30,30 @@ const BlogPost = ({ post, onClickEdit, onDelete }) => {
   );
 };
 
+const DeleteModal = ({
+  handleDelete, isOpen, setIsOpen,
+}) => {
+  const handleClose = () => setIsOpen(false);
+  const handleOpen = () => setIsOpen(true);
+
+  return (
+    <Modal isOpen={isOpen}
+      onAfterOpen={handleOpen}
+      onRequestClose={handleClose}
+      className="delete-blog-post-modal"
+      ariaHideApp={false}
+    >
+      <div>
+        Are you sure you want to delete?
+        <div className="delete-blog-post-buttons">
+          <button type="button" className="blog-post-button animated-button" onClick={handleDelete}>Yes</button>
+          <button type="button" className="blog-post-button animated-button" onClick={handleClose}>No</button>
+        </div>
+      </div>
+    </Modal>
+  );
+};
+
 const BlogPosts = (props) => {
   const {
     blogPosts, getAllBlogPostsByAuthor, editBlogPost, deleteBlogPost,
@@ -38,12 +63,18 @@ const BlogPosts = (props) => {
     id: null, title: '', body: '', image: null,
   });
   const [showEditForm, setShowEditForm] = useState(false);
+  const [showDeleteModal, setShowDeleteModal] = useState(false);
 
   const openEditForm = (post) => {
     setShowEditForm(true);
     setSelectedBlogPost(post);
   };
 
+  const openDeleteModal = (post) => {
+    setShowDeleteModal(true);
+    setSelectedBlogPost(post);
+  };
+
   useEffect(() => {
     getAllBlogPostsByAuthor();
   }, [getAllBlogPostsByAuthor]);
@@ -61,14 +92,20 @@ const BlogPosts = (props) => {
     await editBlogPost(selectedBlogPost.id, formData, closeModal);
   };
 
+  const handleDeleteBlogPost = async () => {
+    const closeModal = () => setShowDeleteModal(false);
+    await deleteBlogPost(selectedBlogPost.id, closeModal);
+  };
+
   return (
     <div className="blog-posts-container">
       <div className="blog-posts-title">Your blog posts</div>
       {sortedBlogPosts.length > 0
       && sortedBlogPosts.map(
-        (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={deleteBlogPost} key={post.id} />,
+        (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={() => openDeleteModal(post)} key={post.id} />,
       )}
       <EditBlogPost isOpen={showEditForm} setIsOpen={setShowEditForm} post={selectedBlogPost} onSubmit={handleFormSubmit} />
+      <DeleteModal handleDelete={handleDeleteBlogPost} isOpen={showDeleteModal} setIsOpen={setShowDeleteModal} />
 
     </div>
   );
diff --git a/src/screens/admin/components/blog-posts/index.js b/src/screens/admin/components/blog-posts/index.js
index 2d66e067..ec49528b 100644
--- a/src/screens/admin/components/blog-posts/index.js
+++ b/src/screens/admin/components/blog-posts/index.js
@@ -23,8 +23,8 @@ const mapDispatchToProps = (dispatch) => {
     editBlogPost: (id, fields, onSuccess) => {
       dispatch(editBlogPost(id, fields, onSuccess));
     },
-    deleteBlogPost: (id) => {
-      dispatch(deleteBlogPost(id));
+    deleteBlogPost: (id, onSuccess) => {
+      dispatch(deleteBlogPost(id, onSuccess));
     },
   };
 };
diff --git a/src/screens/admin/components/blog-posts/style.scss b/src/screens/admin/components/blog-posts/style.scss
index 7368a2df..d12b1c2b 100644
--- a/src/screens/admin/components/blog-posts/style.scss
+++ b/src/screens/admin/components/blog-posts/style.scss
@@ -46,3 +46,34 @@
     color: #2f303a;
     margin-bottom: 14px;
 }
+
+.delete-blog-post-modal {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 20vw;
+    height: 10vh;
+    padding: 50px;
+    border-radius: 20px;
+    background-color: #ffffff;
+    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+    overflow: scroll;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.delete-blog-post-buttons {
+    display: flex;
+    width: 100%;
+    justify-content: space-between;
+    box-sizing: border-box;
+    padding: 0 50px;
+    margin-top: 20px;
+
+    .blog-post-button {
+        padding-left: 20px;
+        padding-right: 20px;
+    }
+}
\ No newline at end of file
diff --git a/src/state/actions/blog.js b/src/state/actions/blog.js
index b82de719..c89d3366 100644
--- a/src/state/actions/blog.js
+++ b/src/state/actions/blog.js
@@ -67,12 +67,13 @@ export const editBlogPost = (id, fields, onSuccess) => {
   };
 };
 
-export const deleteBlogPost = (id) => {
+export const deleteBlogPost = (id, onSuccess) => {
   return async (dispatch) => {
     try {
       const response = await BlogService.deleteBlogPost(id);
       if (response.status === 200) {
         dispatch({ type: ActionTypes.DELETE_BLOG_POST, payload: { _id: id } });
+        onSuccess();
       }
     } catch (error) {
       dispatch({

From d5287d3d6311c0f537cc96ed8eba2e661ed79d36 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Fri, 24 Nov 2023 12:48:02 +0100
Subject: [PATCH 8/9] feat: add post title to delete modal

---
 .gitignore                                           | 1 +
 src/screens/admin/components/blog-posts/component.js | 6 +++---
 src/screens/admin/components/blog-posts/style.scss   | 4 ++++
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7db80cb6..3698ee4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ yarn-error.log
 .env
 
 .eslintcache
+.vscode
diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index a7f151b1..55f6077f 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -31,7 +31,7 @@ const BlogPost = ({ post, onClickEdit, onDelete }) => {
 };
 
 const DeleteModal = ({
-  handleDelete, isOpen, setIsOpen,
+  handleDelete, isOpen, setIsOpen, title,
 }) => {
   const handleClose = () => setIsOpen(false);
   const handleOpen = () => setIsOpen(true);
@@ -44,7 +44,7 @@ const DeleteModal = ({
       ariaHideApp={false}
     >
       <div>
-        Are you sure you want to delete?
+        Are you sure you want to delete <span className="delete-blog-post-title">{`"${title}" `}</span>?
         <div className="delete-blog-post-buttons">
           <button type="button" className="blog-post-button animated-button" onClick={handleDelete}>Yes</button>
           <button type="button" className="blog-post-button animated-button" onClick={handleClose}>No</button>
@@ -105,7 +105,7 @@ const BlogPosts = (props) => {
         (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={() => openDeleteModal(post)} key={post.id} />,
       )}
       <EditBlogPost isOpen={showEditForm} setIsOpen={setShowEditForm} post={selectedBlogPost} onSubmit={handleFormSubmit} />
-      <DeleteModal handleDelete={handleDeleteBlogPost} isOpen={showDeleteModal} setIsOpen={setShowDeleteModal} />
+      <DeleteModal handleDelete={handleDeleteBlogPost} isOpen={showDeleteModal} setIsOpen={setShowDeleteModal} title={selectedBlogPost.title} />
 
     </div>
   );
diff --git a/src/screens/admin/components/blog-posts/style.scss b/src/screens/admin/components/blog-posts/style.scss
index d12b1c2b..93da0cb1 100644
--- a/src/screens/admin/components/blog-posts/style.scss
+++ b/src/screens/admin/components/blog-posts/style.scss
@@ -76,4 +76,8 @@
         padding-left: 20px;
         padding-right: 20px;
     }
+}
+
+.delete-blog-post-title {
+    font-style: italic;
 }
\ No newline at end of file

From 7229df88d50b5292d576d1797369dd9c22cd3351 Mon Sep 17 00:00:00 2001
From: Weronika Ciesielska <weronika.ciesielska@lunarlogic.io>
Date: Fri, 24 Nov 2023 13:58:18 +0100
Subject: [PATCH 9/9] style: adjustments after self-review

---
 src/screens/admin/components/blog-posts/component.js | 6 +++---
 src/state/actions/blog.js                            | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/screens/admin/components/blog-posts/component.js b/src/screens/admin/components/blog-posts/component.js
index 55f6077f..c7343823 100644
--- a/src/screens/admin/components/blog-posts/component.js
+++ b/src/screens/admin/components/blog-posts/component.js
@@ -101,9 +101,9 @@ const BlogPosts = (props) => {
     <div className="blog-posts-container">
       <div className="blog-posts-title">Your blog posts</div>
       {sortedBlogPosts.length > 0
-      && sortedBlogPosts.map(
-        (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={() => openDeleteModal(post)} key={post.id} />,
-      )}
+        ? sortedBlogPosts.map(
+          (post) => <BlogPost post={post} onClickEdit={() => openEditForm(post)} onDelete={() => openDeleteModal(post)} key={post.id} />,
+        ) : <div>You haven&#39;t written any blog posts yet!</div>}
       <EditBlogPost isOpen={showEditForm} setIsOpen={setShowEditForm} post={selectedBlogPost} onSubmit={handleFormSubmit} />
       <DeleteModal handleDelete={handleDeleteBlogPost} isOpen={showDeleteModal} setIsOpen={setShowDeleteModal} title={selectedBlogPost.title} />
 
diff --git a/src/state/actions/blog.js b/src/state/actions/blog.js
index c89d3366..be9f7a33 100644
--- a/src/state/actions/blog.js
+++ b/src/state/actions/blog.js
@@ -9,7 +9,7 @@ export const ActionTypes = {
   CREATE_BLOG_POST: 'CREATE_BLOG_POST',
 };
 
-export const createBlogPost = (fields, onSuccess) => {
+export const createBlogPost = (fields, onSuccess = () => {}) => {
   return async (dispatch) => {
     try {
       dispatch({ type: ActionTypes.CLEAR_API_ERROR });
@@ -47,7 +47,7 @@ export const getAllBlogPostsByAuthor = (onSuccess = () => {}, onError = () => {}
   };
 };
 
-export const editBlogPost = (id, fields, onSuccess) => {
+export const editBlogPost = (id, fields, onSuccess = () => {}) => {
   return async (dispatch) => {
     try {
       dispatch({ type: ActionTypes.CLEAR_API_ERROR });
@@ -67,7 +67,7 @@ export const editBlogPost = (id, fields, onSuccess) => {
   };
 };
 
-export const deleteBlogPost = (id, onSuccess) => {
+export const deleteBlogPost = (id, onSuccess = () => {}) => {
   return async (dispatch) => {
     try {
       const response = await BlogService.deleteBlogPost(id);