farheen_blog_app 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "farheen_blog_app",
3
3
  "private": false,
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -0,0 +1,85 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useState
6
+ } from "react";
7
+
8
+ const UserContext = createContext();
9
+
10
+ export const UserProvider = ({ children }) => {
11
+ const [user, setUser] = useState(null);
12
+ const [token, setToken] = useState(null);
13
+
14
+ useEffect(() => {
15
+
16
+ const storedUser =
17
+ localStorage.getItem(
18
+ "user"
19
+ );
20
+
21
+ const storedToken =
22
+ localStorage.getItem(
23
+ "token"
24
+ );
25
+
26
+ if (
27
+ storedUser &&
28
+ storedToken
29
+ ) {
30
+
31
+ setUser(JSON.parse(storedUser));
32
+ setToken(storedToken);
33
+ }
34
+
35
+ }, []);
36
+
37
+ const login = (userData, jwtToken) => {
38
+ setUser(userData);
39
+ setToken(jwtToken);
40
+
41
+ localStorage.setItem(
42
+ "user",
43
+ JSON.stringify(
44
+ userData
45
+ )
46
+ );
47
+
48
+ localStorage.setItem(
49
+ "token",
50
+ jwtToken
51
+ );
52
+ };
53
+
54
+ const logout = () => {
55
+
56
+ setUser(null);
57
+
58
+ setToken(null);
59
+
60
+ localStorage.removeItem(
61
+ "user"
62
+ );
63
+
64
+ localStorage.removeItem(
65
+ "token"
66
+ );
67
+ };
68
+
69
+ return (
70
+ <UserContext.Provider
71
+ value={{
72
+ user,
73
+ token,
74
+ login,
75
+ logout
76
+ }}
77
+ >
78
+ {children}
79
+ </UserContext.Provider>
80
+ );
81
+ };
82
+
83
+ export const useUser = () => {
84
+ return useContext(UserContext);
85
+ };
@@ -0,0 +1,35 @@
1
+ import {
2
+ useState,
3
+ useEffect
4
+ } from "react";
5
+
6
+ const useDebounce = (
7
+ value,
8
+ delay
9
+ ) => {
10
+
11
+ const [
12
+ debouncedValue,
13
+ setDebouncedValue
14
+ ] = useState(value);
15
+
16
+ useEffect(() => {
17
+
18
+ const timer =
19
+ setTimeout(() => {
20
+
21
+ setDebouncedValue(
22
+ value
23
+ );
24
+
25
+ }, delay);
26
+
27
+ return () =>
28
+ clearTimeout(timer);
29
+
30
+ }, [value, delay]);
31
+
32
+ return debouncedValue;
33
+ };
34
+
35
+ export default useDebounce;
@@ -1,13 +1,17 @@
1
1
  import {
2
- Navigate
3
- } from "react-router-dom";
2
+ Navigate,
3
+ Outlet
4
+ }
5
+ from "react-router-dom";
4
6
 
5
- export const ProtectedRoutes = ({ children }) => {
6
- const token = localStorage.getItem("token");
7
+ export function ProtectedRoute(){
7
8
 
8
- if (!token) {
9
- return <Navigate to="/login" />
10
- }
9
+ const token =
10
+ localStorage.getItem(
11
+ "token"
12
+ );
11
13
 
12
- return children;
14
+ return token
15
+ ? <Outlet />
16
+ : <Navigate to="/login"/>
13
17
  }
@@ -1,79 +1,125 @@
1
1
  import { useState } from "react";
2
-
3
2
  import { createPost } from "../services/postService";
4
3
 
5
4
  const CreatePost = () => {
6
- const [title, setTitle] = useState("");
7
-
8
- const [content, setContent] =
9
- useState("");
10
-
11
- const [image, setImage] =
12
- useState(null);
13
-
14
- const handleSubmit = async (e) => {
15
- e.preventDefault();
16
-
17
- const formData = new FormData();
18
-
19
- formData.append("title", title);
20
-
21
- formData.append("content", content);
22
-
23
- if (image) {
24
- formData.append("image", image);
25
- }
26
-
27
- try {
28
- await createPost(formData);
29
-
30
- alert("Post Created");
31
-
32
- setTitle("");
33
- setContent("");
34
- } catch (error) {
35
- console.log(error);
36
- }
37
- };
38
-
39
- return (
40
- <div className="max-w-xl mx-auto mt-10">
41
- <form
42
- onSubmit={handleSubmit}
43
- className="flex flex-col gap-4"
44
- >
45
- <input
46
- type="text"
47
- placeholder="Title"
48
- className="border p-3 rounded"
49
- value={title}
50
- onChange={(e) =>
51
- setTitle(e.target.value)
52
- }
53
- />
54
-
55
- <textarea
56
- placeholder="Content"
57
- className="border p-3 rounded h-40"
58
- value={content}
59
- onChange={(e) =>
60
- setContent(e.target.value)
61
- }
62
- />
63
-
64
- <input
65
- type="file"
66
- onChange={(e) =>
67
- setImage(e.target.files[0])
68
- }
69
- />
70
-
71
- <button className="bg-black text-white p-3 rounded">
72
- Create Post
73
- </button>
74
- </form>
75
- </div>
76
- );
5
+ const [formData, setFormData] = useState({
6
+ title: "",
7
+ content: "",
8
+ image: null
9
+ });
10
+
11
+ const [loading, setLoading] =
12
+ useState(false);
13
+
14
+ const handleChange = (e) => {
15
+ const { name, value, files } =
16
+ e.target;
17
+
18
+ setFormData({
19
+ ...formData,
20
+ [name]: files ? files[0] : value
21
+ });
22
+ };
23
+
24
+ const handleSubmit = async (e) => {
25
+ e.preventDefault();
26
+
27
+ try {
28
+ setLoading(true);
29
+
30
+ const data = new FormData();
31
+
32
+ data.append(
33
+ "title",
34
+ formData.title
35
+ );
36
+
37
+ data.append(
38
+ "content",
39
+ formData.content
40
+ );
41
+
42
+ if (formData.image) {
43
+ data.append(
44
+ "image",
45
+ formData.image
46
+ );
47
+ }
48
+
49
+ await createPost(data);
50
+
51
+ alert("Post Created");
52
+
53
+ setFormData({
54
+ title: "",
55
+ content: "",
56
+ image: null
57
+ });
58
+
59
+ } catch (error) {
60
+
61
+ console.log(error);
62
+
63
+ alert(
64
+ error.response?.data?.message ||
65
+ "Something went wrong"
66
+ );
67
+
68
+ } finally {
69
+ setLoading(false);
70
+ }
71
+ };
72
+
73
+ return (
74
+ <div className="max-w-xl mx-auto mt-10">
75
+
76
+ <form
77
+ onSubmit={handleSubmit}
78
+ className="flex flex-col gap-4"
79
+ >
80
+
81
+ <input
82
+ type="text"
83
+ name="title"
84
+ placeholder="Enter Title"
85
+ className="border p-3 rounded"
86
+ value={formData.title}
87
+ onChange={handleChange}
88
+ required
89
+ />
90
+
91
+ <textarea
92
+ name="content"
93
+ placeholder="Enter Content"
94
+ className="border p-3 rounded h-40"
95
+ value={formData.content}
96
+ onChange={handleChange}
97
+ required
98
+ />
99
+
100
+ <input
101
+ type="file"
102
+ name="image"
103
+ accept="image/*"
104
+ onChange={handleChange}
105
+ />
106
+
107
+ <button
108
+ type="submit"
109
+ disabled={loading}
110
+ className="bg-black text-white p-3 rounded"
111
+ >
112
+ {
113
+ loading
114
+ ? "Creating..."
115
+ : "Create Post"
116
+ }
117
+ </button>
118
+
119
+ </form>
120
+
121
+ </div>
122
+ );
77
123
  };
78
124
 
79
125
  export default CreatePost;
@@ -1,33 +1,43 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import api from "../../services/api";
3
+ import useDebounce from "../../hooks/useDebounce";
3
4
 
4
5
  const ManageBlogs = () => {
5
6
 
6
- const [blogs,setBlogs] = useState([]);
7
+ // for pagination and debounce search
8
+ const [page, setPage] = useState(1);
9
+ const [totalPage, setTotalPage] = useState(1);
10
+ const [search, setSearch] = useState("");
11
+
12
+ const debouncedSearch = useDebounce(search, 500);
13
+
14
+ const [blogs, setBlogs] = useState([]);
7
15
 
8
16
  const getBlogs = async () => {
9
- try{
17
+ try {
10
18
 
11
- const res = await api.get("/admin/blogs");
19
+ const res = await api.get(`/admin/blogs/posts?page=${page}&search=${debouncedSearch}`);
12
20
 
13
21
  setBlogs(res.data);
22
+ setTotalPage(res.data.totalPges);
14
23
 
15
- }catch(error){
24
+ } catch (error) {
16
25
  console.log(error);
17
26
  }
18
27
  };
19
28
 
20
- useEffect(()=>{
29
+ useEffect(() => {
21
30
  getBlogs();
22
- },[]);
31
+ }, [page, debouncedSearch]);
23
32
 
24
33
  return (
25
34
  <div>
35
+ <input type="text" value={search} placeholder="search" onChange={(e) => setSearch(e.target.value)} />
26
36
 
27
37
  <h1>Manage Blogs</h1>
28
38
 
29
39
  {
30
- blogs.map((blog)=>(
40
+ blogs.map((blog) => (
31
41
  <div key={blog._id}>
32
42
 
33
43
  <h3>{blog.title}</h3>
@@ -40,6 +50,43 @@ const ManageBlogs = () => {
40
50
  ))
41
51
  }
42
52
 
53
+ {/* pagination */}
54
+ <div>
55
+ <button
56
+ disabled={
57
+ page === 1
58
+ }
59
+ onClick={() =>
60
+ setPage(
61
+ page - 1
62
+ )
63
+ }
64
+ >
65
+ Prev
66
+ </button>
67
+
68
+ <span>
69
+ {" "}
70
+ {page} / {
71
+ totalPages
72
+ }{" "}
73
+ </span>
74
+
75
+ <button
76
+ disabled={
77
+ page ===
78
+ totalPages
79
+ }
80
+ onClick={() =>
81
+ setPage(
82
+ page + 1
83
+ )
84
+ }
85
+ >
86
+ Next
87
+ </button>
88
+ </div>
89
+
43
90
  </div>
44
91
  );
45
92
  };
@@ -13,8 +13,9 @@ import ManageBlogs from "../pages/admin/ManageBlogs";
13
13
  import AdminDashboard from "../pages/admin/AdminDashboard";
14
14
  import AdminLayout from "../layout/AdminLayout";
15
15
  import Register from "../pages/Register";
16
+ import { ProtectedRoute } from "../middleware/ProtectedRoute";
16
17
 
17
- const AppRoutes = () => {
18
+ function AppRoutes() {
18
19
  return (
19
20
  <BrowserRouter>
20
21
  {/* <Navbar /> */}
@@ -25,7 +26,7 @@ const AppRoutes = () => {
25
26
  element={<Home />}
26
27
  />
27
28
 
28
- <Route
29
+ <Route
29
30
  path="/register"
30
31
  element={<Register />}
31
32
  />
@@ -40,22 +41,28 @@ const AppRoutes = () => {
40
41
  element={<CreatePost />}
41
42
  />
42
43
 
43
- <Route path="/admin" element={<AdminLayout />}>
44
- <Route index element={<AdminDashboard />} />
45
- <Route
46
- path="users"
47
- element={<ManageUsers />}
48
- />
49
- <Route
50
- path="blogs"
51
- element={<ManageBlogs />}
52
- />
53
- {/* <Route
44
+ <Route
45
+ element={<ProtectedRoute />}
46
+ >
47
+
48
+ <Route path="/admin" element={<AdminLayout />}>
49
+ <Route index element={<AdminDashboard />} />
50
+ <Route
51
+ path="users"
52
+ element={<ManageUsers />}
53
+ />
54
+ <Route
55
+ path="blogs"
56
+ element={<ManageBlogs />}
57
+ />
58
+ {/* <Route
54
59
  path="settings"
55
60
  element={<Settings />}
56
61
  /> */}
62
+ </Route>
63
+
57
64
  </Route>
58
-
65
+
59
66
  </Routes>
60
67
  </BrowserRouter>
61
68
  );