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

457 fe content panel edit blog fixes #470

Open
wants to merge 15 commits into
base: staging
Choose a base branch
from
5 changes: 5 additions & 0 deletions Types/index.ts
Original file line number Diff line number Diff line change
@@ -47,6 +47,11 @@ export interface AuthContextType {
loginStatus: MutationStatus;
}

export type ErrorResponse = {
message: string;
statusCode?: number;
};

export interface LoginResponse {
data: {
user: UserType;
1 change: 1 addition & 0 deletions apis/endpoints.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ const ENDPOINTS = {
BLOGS: {
GET_ALL: (limit: number, skip: number) => `${API_BASE_URL}/posts?limit=${limit}&skip=${skip}`,
CREATE: `${API_BASE_URL}/content/blog-posts`,
UPDATE: (id: string) => `${API_BASE_URL}/content/blog-posts/${id}`,
},
USERS: {
GET_ALL: `${API_BASE_URL}/users`,
6 changes: 1 addition & 5 deletions apis/mutations/blogs/useAddNewPost.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { toast } from 'react-toastify';
import { useAxios } from '../../AxiosProvider';
import ENDPOINTS from '../../endpoints';
import QUERY_KEYS from '../../queryKeys';
import { ErrorResponse } from '../../../Types';

export type NewPost = {
title: string;
@@ -14,11 +15,6 @@ export type NewPost = {
tags: string[];
};

type ErrorResponse = {
message: string;
statusCode?: number;
};

const useAddNewPost = () => {
const queryClient = useQueryClient();
const axios = useAxios();
57 changes: 57 additions & 0 deletions apis/mutations/blogs/useUpdatePost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { toast } from 'react-toastify';
import { useAxios } from '../../AxiosProvider';
import ENDPOINTS from '../../endpoints';
import QUERY_KEYS from '../../queryKeys';
import { ErrorResponse } from '../../../Types';
import { BlogDetailsData, UpdatePostParams } from '../../../components/reusable-components/_Types';

const useUpdatePost = () => {
const queryClient = useQueryClient();
const axios = useAxios();

const getAllPosts = useQuery<BlogDetailsData[], AxiosError>({
queryKey: QUERY_KEYS.BLOGS.ALL,
queryFn: async () => {
const url = `${process.env.NEXT_PUBLIC_API_BASE_URL}/blog-posts`;
const response = await axios.get(url);
return response.data;
},
});

if (getAllPosts.error) {
toast.error('Настана грешка при вчитување на блог постовите.');
}

const updatePost = useMutation<void, AxiosError<ErrorResponse>, UpdatePostParams>({
mutationFn: async ({ id, updatedPost }: UpdatePostParams) => {
const { title, excerpt, slug, content, tags, authorId } = updatedPost;
const formattedTags = Array.isArray(tags)
? tags
: (tags as string | undefined)?.split(',').map((tag: string) => tag.trim());
const postData = {
authorId,
title,
excerpt,
slug,
content,
tags: formattedTags,
};
await axios.put(ENDPOINTS.BLOGS.UPDATE(id), postData, {
headers: { 'Content-Type': 'application/json' },
});
},
onError: (error) => {
toast.error(error?.response?.data?.message || 'Настана грешка при ажурирање на статијата.');
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.BLOGS.ALL });
toast.success('Статијата беше успешно ажурирана!');
},
});

return { getAllPosts, updatePost };
};

export default useUpdatePost;
29 changes: 15 additions & 14 deletions app/content-panel/blogs/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -66,21 +66,22 @@ const BlogDetailsPage = ({ params }: { params: { id: string } }) => {
return (
<div className={styles.blogDetailsPageContainer}>
<BlogDetailsCard
title={blogDetailsData.title}
imageUrl={blogDetailsData.image}
content={blogDetailsData.content}
publishDate={blogDetailsData.publishDate}
tags={blogDetailsData.tags}
onChange={handleChange}
onImageChange={handleImageChange}
onValidationError={handleValidationError}
onDeleteClick={() => {
// Future delete logic will go here
postId={params.id}
blogContent={blogDetailsData}
actions={{
onChange: handleChange,
onImageChange: handleImageChange,
onDeleteClick: () => {},
onCancelClick: handleCancelClick,
}}
errors={{
onValidationError: handleValidationError,
imageError,
}}
states={{
isEditing,
setIsEditing,
}}
onCancelClick={handleCancelClick}
imageError={imageError}
isEditing={isEditing}
setIsEditing={setIsEditing}
/>
</div>
);
9 changes: 5 additions & 4 deletions app/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// pages/signup/index.tsx

'use client';

import React from 'react';
@@ -51,8 +49,11 @@ const SignupPage = () => {
await response.json();
toast.success('Successfully registered!');
} catch (err) {
console.error(err);
toast.error('Registration failed');
if (err instanceof Error) {
toast.error(err.message);
} else {
toast.error('Something went wrong! Please try again later!');
}
}
};

54 changes: 36 additions & 18 deletions components/reusable-components/_Types/index.ts
Original file line number Diff line number Diff line change
@@ -13,27 +13,10 @@ export interface IPasswordValidation {
minLength: boolean;
}

export interface BlogDetailsCardProps {
title: string;
imageUrl: string;
content: string;
publishDate: string;
tags: string[];
onImageChange: (files: File[]) => void;
onChange: (
event: React.ChangeEvent<HTMLInputElement> | { target: { name: string; value: string } }
) => void;
onDeleteClick: () => void;
onCancelClick: () => void;
imageError: string | null;
onValidationError: (error: string) => void;
isEditing: boolean;
setIsEditing: React.Dispatch<React.SetStateAction<boolean>>;
}

export interface Author {
firstName: string;
lastName: string;
authorId?: string;
}

export interface BlogDetailsData {
@@ -43,4 +26,39 @@ export interface BlogDetailsData {
author: Author;
publishDate: string;
tags: string[];
excerpt?: string;
slug?: string;
authorId?: string;
}

export interface IBlogCardState {
showModal: boolean;
modalType: 'back' | 'cancel';
hasUnsavedChanges: boolean;
}

export interface BlogDetailsCardProps {
postId: string;
blogContent: BlogDetailsData;
states: {
isEditing: boolean;
setIsEditing: React.Dispatch<React.SetStateAction<boolean>>;
};
actions: {
onImageChange: (files: File[]) => void;
onChange: (
event: React.ChangeEvent<HTMLInputElement> | { target: { name: string; value: string } }
) => void;
onDeleteClick: () => void;
onCancelClick: () => void;
};
errors: {
imageError: string | null;
onValidationError: (error: string) => void;
};
}

export interface UpdatePostParams {
id: string;
updatedPost: Partial<BlogDetailsData>;
}
Loading