Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

minor changes and feat: create Post #15

Merged
merged 2 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions admin-panel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"cmdk": "^1.0.4",
"date-fns": "^3.6.0",
"framer-motion": "^11.16.4",
"jodit-react": "^4.1.2",
"lucide-react": "^0.468.0",
"moment": "^2.30.1",
"nepali-date-converter": "^3.3.2",
Expand Down
20 changes: 12 additions & 8 deletions admin-panel/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import UsersPage from "@/pages/UsersPage";
import PostsPage from "@/pages/PostsPage";
import LogoutPage from "@/pages/LogoutPage";
import CategoriesPage from "@/pages/CategoriesPage";
import CreatePost from "@/pages/createPostPage";

function App() {
const { isCheckingAuth, checkAuth, isAuthenticated } = useAuthStore();
Expand Down Expand Up @@ -66,18 +67,21 @@ function App() {
path="/categories"
element={
<PrivateRoute>
<CategoriesPage/>
<CategoriesPage />
</PrivateRoute>
}
/>
<Route path="/logout" element={<LogoutPage />} />


<Route
path="/create-post"
element={
<PrivateRoute>
<CreatePost />
</PrivateRoute>
}
/>
<Route path="/logout" element={<LogoutPage />} />
</Routes>
<Toaster
richColors={true}
position="top-right"
/>
<Toaster richColors={true} position="top-right" />
</>
);
}
Expand Down
19 changes: 13 additions & 6 deletions admin-panel/src/components/Sidebar/Routes.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Paperclip, Users, Home, LogOut, List } from "lucide-react";
import { Paperclip, Users, Home, LogOut, List, CirclePlus } from "lucide-react";
import { Link, useLocation } from "react-router-dom"; // Import useLocation hook

const Routes = () => {
Expand All @@ -18,9 +18,9 @@ const Routes = () => {
title="Users"
to="/users"
/>
<Route
<Route
Icon={List}
selected={location.pathname === "/categories"}
selected={location.pathname === "/categories"}
title="Categories"
to="/categories"
/>
Expand All @@ -30,7 +30,13 @@ const Routes = () => {
title="Posts"
to="/posts"
/>

<Route
Icon={CirclePlus}
selected={location.pathname === "/create-post"}
title="Add Post"
to="/create-post"
/>

<Route
Icon={LogOut}
selected={location.pathname === "/logout"}
Expand All @@ -55,10 +61,11 @@ const Route = ({
return (
<Link to={to} className="w-full">
<button
className={`flex items-center justify-start gap-3 w-full rounded px-2 py-1.5 text-sm transition-[box-shadow,_background-color,_color] ${selected
className={`flex items-center justify-start gap-3 w-full rounded px-2 py-1.5 text-sm transition-[box-shadow,_background-color,_color] ${
selected
? "bg-white text-stone-950 shadow"
: "hover:bg-stone-200 bg-transparent text-stone-500 shadow-none"
}`}
}`}
>
<Icon className={selected ? "text-violet-500" : ""} />
<span>{title}</span>
Expand Down
24 changes: 24 additions & 0 deletions admin-panel/src/hooks/useCreatePost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useState } from "react";
import { createPost } from "@/services/postsService";

export const useCreatePost = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const handleCreatePost = async (postData: any) => {

try {
setIsLoading(true);
setError(null);
const data = await createPost(postData);
return data;
} catch (err: any) {
setError(err.response?.data?.message || "Failed to create post.");
throw err;
} finally {
setIsLoading(false);
}
};

return { handleCreatePost, isLoading, error };
};
8 changes: 5 additions & 3 deletions admin-panel/src/hooks/usePosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fetchPosts } from "@/services/postsService";
*/
export const usePosts = (page: number, limit: number) => {
const [posts, setPosts] = useState([]);
const [totalPosts, setTotalPosts] = useState(0)
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

Expand All @@ -18,8 +19,9 @@ export const usePosts = (page: number, limit: number) => {
try {
setLoading(true);
setError(null);
const data = await fetchPosts(page, limit);
setPosts(data.posts);
const { posts, total } = await fetchPosts(page, limit);
setPosts(posts);
setTotalPosts(total);
} catch (err: any) {
setError(err.message || "An error occurred while fetching users");
} finally {
Expand All @@ -30,6 +32,6 @@ export const usePosts = (page: number, limit: number) => {
fetchData();
}, [page, limit]);

return { posts, loading, error };
return { posts, totalPosts, loading, error };
};

2 changes: 1 addition & 1 deletion admin-panel/src/pages/CategoriesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const CategoryPage: React.FC = () => {
<input
type="text"
placeholder="Search categories..."
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
className="w-1/2 px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
Expand Down
151 changes: 151 additions & 0 deletions admin-panel/src/pages/CreatePostPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { useState, useRef } from "react";
import { Link } from "react-router-dom";
import { MdCloudUpload } from "react-icons/md";
import JoditEditor from "jodit-react";
import { toast } from "sonner";
import { MainLayout } from "@/layout/MainLayout";
import { useAuthStore } from "@/store/authStore";
import { useCreatePost } from "@/hooks/useCreatePost";

const CreatePost: React.FC = () => {
const { store } = useAuthStore();
const editor = useRef<JoditEditor | null>(null);

// State variables
const [title, setTitle] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [image, setImage] = useState<File | null>(null);
const [imgPreview, setImgPreview] = useState<string>("");

const { handleCreatePost, isLoading } = useCreatePost();

const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { files } = e.target;
if (files && files.length > 0) {
const file = files[0];
if (file.type.startsWith("image/")) {
setImage(file);
setImgPreview(URL.createObjectURL(file));
} else {
toast.error("Please select a valid image file.");
}
}
};

const addPost = async (e: React.FormEvent) => {
e.preventDefault();
if (!image) {
toast.error("Please select an image.");
return;
}

const formData = new FormData();
formData.append("title", title);
formData.append("description", description);
formData.append("image", image);

try {
const response = await handleCreatePost(formData);
toast.success(response.message);
} catch (error) {
// Error handling is already managed in the hook, so no additional logic is needed here.
}
};

return (
<MainLayout>
<div className="bg-white rounded-md">
<div className="flex justify-between p-4">
<h2 className="text-xl font-medium">Add Post</h2>
<Link
className="px-3 py-[6px] bg-purple-500 rounded-sm text-white hover:bg-purple-600"
to="/dashboard/posts"
>
Posts
</Link>
</div>

<div className="p-4">
<form onSubmit={addPost}>
{/* Title Input */}
<div className="flex flex-col gap-y-2 mb-6">
<label
className="text-md font-medium text-gray-600"
htmlFor="title"
>
Title
</label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter title"
required
className="px-3 py-2 rounded-md border border-gray-300 focus:border-green-500"
/>
</div>

{/* Image Upload */}
<div className="mb-6">
<label
htmlFor="image"
className="w-full h-[240px] flex items-center justify-center border-2 border-dashed rounded cursor-pointer text-gray-600"
>
{imgPreview ? (
<img
src={imgPreview}

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix AI about 1 month ago

To fix the problem, we need to ensure that the imgPreview value is safe before using it as the src attribute of an img element. One way to do this is to validate the file type and ensure it is an image before creating the object URL. Additionally, we can use a library like DOMPurify to sanitize the URL if necessary.

  • Validate the file type to ensure it is an image.
  • Create the object URL only if the file is a valid image.
  • Use the validated and sanitized URL as the src attribute of the img element.
Suggested changeset 1
admin-panel/src/pages/CreatePostPage.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/admin-panel/src/pages/CreatePostPage.tsx b/admin-panel/src/pages/CreatePostPage.tsx
--- a/admin-panel/src/pages/CreatePostPage.tsx
+++ b/admin-panel/src/pages/CreatePostPage.tsx
@@ -24,4 +24,9 @@
     if (files && files.length > 0) {
-      setImage(files[0]);
-      setImgPreview(URL.createObjectURL(files[0]));
+      const file = files[0];
+      if (file.type.startsWith("image/")) {
+        setImage(file);
+        setImgPreview(URL.createObjectURL(file));
+      } else {
+        toast.error("Please select a valid image file.");
+      }
     }
EOF
@@ -24,4 +24,9 @@
if (files && files.length > 0) {
setImage(files[0]);
setImgPreview(URL.createObjectURL(files[0]));
const file = files[0];
if (file.type.startsWith("image/")) {
setImage(file);
setImgPreview(URL.createObjectURL(file));
} else {
toast.error("Please select a valid image file.");
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
@sthsuyash sthsuyash committed this autofix suggestion about 1 month ago.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
alt="Preview"
className="w-full h-full object-cover"
/>
) : (
<div className="flex flex-col items-center">
<MdCloudUpload className="text-2xl" />
<p>Select Image</p>
</div>
)}
</label>
<input
type="file"
id="image"
onChange={handleImageChange}
className="hidden"
required
/>
</div>

{/* Description */}
<div className="flex flex-col gap-y-2 mb-6">
<label
className="text-md font-medium text-gray-600"
htmlFor="description"
>
Description
</label>
<JoditEditor
ref={editor}
value={description}
onBlur={(value) => setDescription(value)}
onChange={() => {}}
/>
</div>

{/* Submit Button */}
<button
type="submit"
disabled={isLoading}
className={`px-3 py-[6px] rounded-sm text-white ${
isLoading ? "bg-gray-500" : "bg-purple-500 hover:bg-purple-600"
}`}
>
{isLoading ? "Loading..." : "Add Post"}
</button>
</form>
</div>
</div>
</MainLayout>
);
};

export default CreatePost;
Loading
Loading