diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 00180807..4ff0c26a 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -16,6 +16,7 @@ import RequireAuthBlog from './components/require-auth-blog';
import useThemeClass from './utils/theme-changer';
import AdminContainer from './components/admin-container';
import { Role } from './types/role-type.tsx';
+import EditBlog from './pages/edit-blog.tsx';
function App() {
useLayoutEffect(() => {
@@ -35,6 +36,7 @@ function App() {
}>
} />
+ } />
}>
}>
diff --git a/frontend/src/components/form-blog.tsx b/frontend/src/components/form-blog.tsx
new file mode 100644
index 00000000..53fc5ca0
--- /dev/null
+++ b/frontend/src/components/form-blog.tsx
@@ -0,0 +1,300 @@
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { toast } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import navigateBackBlackIcon from '@/assets/svg/navigate-back-black.svg';
+import navigateBackWhiteIcon from '@/assets/svg/navigate-back-white.svg';
+import ModalComponent from '@/components/modal';
+import CategoryPill from '@/components/category-pill';
+import { categories } from '@/utils/category-colors';
+import userState from '@/utils/user-state';
+import axiosInstance from '@/helpers/axios-instance';
+import { AxiosError, isAxiosError } from 'axios';
+import { useForm } from 'react-hook-form';
+import { TFormBlogSchema, formBlogSchema } from '@/lib/types';
+import { zodResolver } from '@hookform/resolvers/zod';
+import Post from '@/types/post-type';
+import useAuthData from '@/hooks/useAuthData';
+
+interface FormBlogPropType {
+ type: 'new' | 'edit';
+ postId?: string;
+ post?: Post;
+}
+
+function FormBlog({ type, postId, post }: FormBlogPropType) {
+ const [selectedImage, setSelectedImage] = useState('');
+ const {
+ register,
+ handleSubmit,
+ reset,
+ setValue,
+ trigger,
+ formState: { errors },
+ watch,
+ } = useForm({
+ resolver: zodResolver(formBlogSchema),
+ defaultValues: {
+ title: post?.title || '',
+ authorName: post?.authorName || '',
+ imageLink: post?.imageLink || '',
+ categories: post?.categories || [],
+ description: post?.description || '',
+ isFeaturedPost: false,
+ },
+ });
+ const formData = watch();
+ const handleImageSelect = (imageUrl: string) => {
+ setSelectedImage(imageUrl);
+ };
+
+ const [modal, setmodal] = useState(false);
+ const userData = useAuthData();
+
+ //checks the length of the categories array and if the category is already selected
+ const isValidCategory = (category: string): boolean => {
+ return formData.categories.length >= 3 && !formData.categories.includes(category);
+ };
+
+ const handleCategoryClick = (category: string) => {
+ if (isValidCategory(category)) return;
+
+ if (formData.categories.includes(category)) {
+ setValue(
+ 'categories',
+ formData.categories.filter((cat) => cat !== category)
+ );
+ } else {
+ setValue('categories', [...formData.categories, category]);
+ }
+ trigger('categories');
+ };
+
+ const handleselector = () => {
+ setValue('imageLink', selectedImage);
+ setmodal(false);
+ };
+ const handleCheckboxChange = () => {
+ setValue('isFeaturedPost', !formData.isFeaturedPost);
+ };
+ const onSumbit = async () => {
+ try {
+ let postPromise;
+ if (type === 'new') {
+ postPromise = axiosInstance.post('/api/posts/', formData);
+ }
+
+ if (type === 'edit' && postId) {
+ if (userData?.role === 'ADMIN') {
+ postPromise = axiosInstance.patch(`/api/posts/admin/${postId}`, formData);
+ } else {
+ postPromise = axiosInstance.patch(`/api/posts/${postId}`, formData);
+ }
+ }
+ if (postPromise)
+ toast.promise(postPromise, {
+ pending: 'Creating blog post...',
+ success: {
+ render() {
+ reset();
+ navigate('/');
+ return 'Blog created successfully';
+ },
+ },
+ error: {
+ render({ data }) {
+ if (data instanceof AxiosError) {
+ if (data?.response?.data?.message) {
+ return data?.response?.data?.message;
+ }
+ }
+ return 'Blog creation failed';
+ },
+ },
+ });
+ if (postPromise) return (await postPromise).data;
+ } catch (error: unknown) {
+ if (isAxiosError(error)) {
+ navigate('/');
+ userState.removeUser();
+ console.error(error.response?.data?.message);
+ } else {
+ console.log(error);
+ }
+ }
+ };
+ const navigate = useNavigate();
+ const [isDarkMode, setIsDarkMode] = useState(null);
+ useEffect(() => {
+ const storedTheme = localStorage.getItem('theme');
+ setIsDarkMode(storedTheme === 'dark');
+ }, []);
+
+ function Asterisk() {
+ return *;
+ }
+
+ return (
+
+
+
+
+
navigate(-1)}
+ className="active:scale-click h-5 w-10"
+ />
+
+
+ Create Blog
+
+
+
+
+
+ );
+}
+
+export default FormBlog;
diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts
index 0f44c877..0472efbd 100644
--- a/frontend/src/lib/types.ts
+++ b/frontend/src/lib/types.ts
@@ -32,7 +32,7 @@ const isValidImageLink = (value: string) => {
const imageLinkRegex = /\.(jpg|jpeg|png|webp)$/i;
return imageLinkRegex.test(value);
};
-export const addBlogSchema = z.object({
+export const formBlogSchema = z.object({
title: z.string().refine((value) => value.trim().split(/\s+/).length >= 3, {
message: 'Oops! Title needs more spice. Give it at least 3 words.',
}),
@@ -70,4 +70,4 @@ export interface AuthData {
export type TSignInSchema = z.infer;
export type TSignUpSchema = z.infer;
-export type TAddBlogScheme = z.infer;
+export type TFormBlogSchema = z.infer;
diff --git a/frontend/src/pages/add-blog.tsx b/frontend/src/pages/add-blog.tsx
index 3f00c463..a13627fb 100644
--- a/frontend/src/pages/add-blog.tsx
+++ b/frontend/src/pages/add-blog.tsx
@@ -1,279 +1,7 @@
-import { useState, useEffect } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { toast } from 'react-toastify';
-import 'react-toastify/dist/ReactToastify.css';
-import navigateBackBlackIcon from '@/assets/svg/navigate-back-black.svg';
-import navigateBackWhiteIcon from '@/assets/svg/navigate-back-white.svg';
-import ModalComponent from '@/components/modal';
-import CategoryPill from '@/components/category-pill';
-import { categories } from '@/utils/category-colors';
-import userState from '@/utils/user-state';
-import axiosInstance from '@/helpers/axios-instance';
-import { AxiosError, isAxiosError } from 'axios';
-import { useForm } from 'react-hook-form';
-import { TAddBlogScheme, addBlogSchema } from '@/lib/types';
-import { zodResolver } from '@hookform/resolvers/zod';
+import FormBlog from '@/components/form-blog';
-function AddBlog() {
- const [selectedImage, setSelectedImage] = useState('');
- const {
- register,
- handleSubmit,
- reset,
- setValue,
- trigger,
- formState: { errors },
- watch,
- } = useForm({
- resolver: zodResolver(addBlogSchema),
- defaultValues: {
- title: '',
- authorName: '',
- imageLink: '',
- categories: [],
- description: '',
- isFeaturedPost: false,
- },
- });
- const formData = watch();
- const handleImageSelect = (imageUrl: string) => {
- setSelectedImage(imageUrl);
- };
-
- const [modal, setmodal] = useState(false);
-
- //checks the length of the categories array and if the category is already selected
- const isValidCategory = (category: string): boolean => {
- return formData.categories.length >= 3 && !formData.categories.includes(category);
- };
-
- const handleCategoryClick = (category: string) => {
- if (isValidCategory(category)) return;
-
- if (formData.categories.includes(category)) {
- setValue(
- 'categories',
- formData.categories.filter((cat) => cat !== category)
- );
- } else {
- setValue('categories', [...formData.categories, category]);
- }
- trigger('categories');
- };
-
- const handleselector = () => {
- setValue('imageLink', selectedImage);
- setmodal(false);
- };
- const handleCheckboxChange = () => {
- setValue('isFeaturedPost', !formData.isFeaturedPost);
- };
- const onSumbit = async () => {
- try {
- const postPromise = axiosInstance.post('/api/posts/', formData);
- toast.promise(postPromise, {
- pending: 'Creating blog post...',
- success: {
- render() {
- reset();
- navigate('/');
- return 'Blog created successfully';
- },
- },
- error: {
- render({ data }) {
- if (data instanceof AxiosError) {
- if (data?.response?.data?.message) {
- return data?.response?.data?.message;
- }
- }
- return 'Blog creation failed';
- },
- },
- });
- return (await postPromise).data;
- } catch (error: unknown) {
- if (isAxiosError(error)) {
- navigate('/');
- userState.removeUser();
- console.error(error.response?.data?.message);
- } else {
- console.log(error);
- }
- }
- };
- const navigate = useNavigate();
- const [isDarkMode, setIsDarkMode] = useState(null);
- useEffect(() => {
- const storedTheme = localStorage.getItem('theme');
- setIsDarkMode(storedTheme === 'dark');
- }, []);
-
- function Asterisk() {
- return *;
- }
-
- return (
-
-
-
-
-
navigate(-1)}
- className="active:scale-click h-5 w-10"
- />
-
-
- Create Blog
-
-
-
-
-
-
-
-
- );
-}
+const AddBlog = () => {
+ return ;
+};
export default AddBlog;
diff --git a/frontend/src/pages/admin-blogs.tsx b/frontend/src/pages/admin-blogs.tsx
index 1506570d..4023ef55 100644
--- a/frontend/src/pages/admin-blogs.tsx
+++ b/frontend/src/pages/admin-blogs.tsx
@@ -6,10 +6,13 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import Post from '@/types/post-type';
+import { useNavigate } from 'react-router-dom';
const AdminBlogs = () => {
const [posts, setPosts] = useState([]);
+ const navigate = useNavigate();
+
const fetchData = async () => {
try {
const response = await axiosInstance.get('/api/posts');
@@ -24,6 +27,8 @@ const AdminBlogs = () => {
if (response.status === 200) {
fetchData();
toast.success('Post successfully deleted !');
+ } else {
+ toast?.error('Something went wrong!');
}
};
@@ -61,7 +66,10 @@ const AdminBlogs = () => {
-