Skip to content

Commit

Permalink
minor changes and feat: create Post
Browse files Browse the repository at this point in the history
  • Loading branch information
AmishB7 committed Jan 14, 2025
1 parent ac7cc83 commit feb44b2
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 130 deletions.
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
146 changes: 146 additions & 0 deletions admin-panel/src/pages/CreatePostPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
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) {
setImage(files[0]);
setImgPreview(URL.createObjectURL(files[0]));
}
};

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.
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

0 comments on commit feb44b2

Please sign in to comment.