diff --git a/Types/index.ts b/Types/index.ts index 9d7d1fae..38b5706d 100644 --- a/Types/index.ts +++ b/Types/index.ts @@ -3,10 +3,9 @@ import { MutationStatus, QueryStatus } from '@tanstack/react-query'; import { HTMLProps } from 'react'; -export enum Role { +export enum UserRole { admin = 'admin', content_manager = 'content_manager', - content = 'content', member = 'member', } @@ -14,7 +13,7 @@ export type UserType = { id: number | string; is_verified: boolean; email: string; - role: Role; + role: UserRole; }; export interface LoginParams { diff --git a/apis/endpoints.ts b/apis/endpoints.ts index 59015f12..0295cdd4 100644 --- a/apis/endpoints.ts +++ b/apis/endpoints.ts @@ -6,8 +6,10 @@ if (!API_BASE_URL) { const ENDPOINTS = { BLOGS: { - GET_ALL: (limit: number, skip: number) => `${API_BASE_URL}/posts?limit=${limit}&skip=${skip}`, + GET_ALL: (limit: number, skip: number) => + `${API_BASE_URL}/blog-posts?limit=${limit}&skip=${skip}`, CREATE: `${API_BASE_URL}/content/blog-posts`, + UPDATE_STATUS: (id: string) => `${API_BASE_URL}/content/blog-posts/${id}/statuses`, }, USERS: { GET_ALL: `${API_BASE_URL}/users`, diff --git a/apis/mutations/blogs/updatePostStatus.ts b/apis/mutations/blogs/updatePostStatus.ts new file mode 100644 index 00000000..3c817d4c --- /dev/null +++ b/apis/mutations/blogs/updatePostStatus.ts @@ -0,0 +1,41 @@ +'use client'; + +import { useMutation, useQueryClient } 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'; + +type UpdatePostStatusPayload = { + id: string; + status: string; +}; + +type ErrorResponse = { + message: string; + statusCode?: number; +}; + +const useUpdatePostStatus = () => { + const queryClient = useQueryClient(); + const axios = useAxios(); + + return useMutation({ + mutationFn: async ({ id, status }: UpdatePostStatusPayload) => { + const response = await axios.patch(ENDPOINTS.BLOGS.UPDATE_STATUS(id), { status }); + return response.data; + }, + onError: (error: AxiosError) => { + toast.error( + error?.response?.data?.message || 'Настана грешка при ажурирање на статусот на статијата.' + ); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: QUERY_KEYS.BLOGS.ALL }); + toast.success('Статусот на статијата е успешно ажуриран!'); + }, + }); +}; + +export default useUpdatePostStatus; diff --git a/apis/mutations/blogs/useAddNewPost.ts b/apis/mutations/blogs/useAddNewPost.ts index 9a38666a..22a3443b 100644 --- a/apis/mutations/blogs/useAddNewPost.ts +++ b/apis/mutations/blogs/useAddNewPost.ts @@ -12,6 +12,7 @@ export type NewPost = { excerpt: string; content: string; tags: string[]; + status: string; }; type ErrorResponse = { diff --git a/app/content-panel/blogs/create/page.tsx b/app/content-panel/blogs/create/page.tsx index f0085e6d..edfc6b25 100644 --- a/app/content-panel/blogs/create/page.tsx +++ b/app/content-panel/blogs/create/page.tsx @@ -1,15 +1,25 @@ +'use client'; + /* eslint-disable jsx-a11y/label-has-associated-control */ +import React from 'react'; +import { useSession } from 'next-auth/react'; import styles from './createArticlePage.module.scss'; import PublishArticleForm from '../../../../components/module-components/blog/PublishArticleForm'; +import { UserRole } from '../../../../Types'; const PostArticle = () => { + const { data: session } = useSession(); + + // console.log('Session data:', session); + const userRole = session?.user.role as UserRole; + return (

Објави статија

- +
); diff --git a/components/module-components/SearchAndFilter/DisplayNames.tsx b/components/module-components/SearchAndFilter/DisplayNames.tsx index 797cf160..abd7ae94 100644 --- a/components/module-components/SearchAndFilter/DisplayNames.tsx +++ b/components/module-components/SearchAndFilter/DisplayNames.tsx @@ -1,8 +1,8 @@ import React, { useState, useEffect } from 'react'; import fetchSearchResultsFromApi from './SubmitSearchForm'; import style from './displayNames.module.scss'; -import { UserRole } from './Filter'; import Loading from '../../../app/loading'; +import { UserRole } from '../../../Types'; interface Author { id: string; diff --git a/components/module-components/SearchAndFilter/Filter.tsx b/components/module-components/SearchAndFilter/Filter.tsx index 916a040c..86544a95 100644 --- a/components/module-components/SearchAndFilter/Filter.tsx +++ b/components/module-components/SearchAndFilter/Filter.tsx @@ -2,12 +2,7 @@ import React, { useState, useEffect, useMemo } from 'react'; import style from './filter.module.scss'; - -export enum UserRole { - Admin = 'admin', - Member = 'member', - ContentManager = 'content-manager', -} +import { UserRole } from '../../../Types'; interface FilterProps { handleRoleChange: (roles: UserRole[]) => void; diff --git a/components/module-components/SearchAndFilter/SearchAndFilter.tsx b/components/module-components/SearchAndFilter/SearchAndFilter.tsx index 5ad491f4..f7904fe6 100644 --- a/components/module-components/SearchAndFilter/SearchAndFilter.tsx +++ b/components/module-components/SearchAndFilter/SearchAndFilter.tsx @@ -4,9 +4,10 @@ import React, { useState, useEffect, useRef } from 'react'; import style from './searchAndFilter.module.scss'; -import Filter, { UserRole } from './Filter'; +import Filter from './Filter'; import Search from './Search'; import DisplayNames from './DisplayNames'; +import { UserRole } from '../../../Types'; const SearchAndFilter = () => { const [searchValue, setSearchValue] = useState(''); diff --git a/components/module-components/blog/PublishArticleForm.tsx b/components/module-components/blog/PublishArticleForm.tsx index cbde366a..0ceec301 100644 --- a/components/module-components/blog/PublishArticleForm.tsx +++ b/components/module-components/blog/PublishArticleForm.tsx @@ -9,9 +9,17 @@ import styles from './PublishArticleForm.module.scss'; import TiptapEditor from '../../editor/TiptapEditor'; import TagManager from './TagManager'; import Button from '../../reusable-components/button/Button'; +import { UserRole } from '../../../Types'; +import UpdatePostStatus from '../../../apis/mutations/blogs/updatePostStatus'; -const PublishArticleForm = () => { +interface PublishArticleFormProps { + userRole: UserRole; + postId?: string; +} + +const PublishArticleForm: React.FC = ({ userRole, postId }) => { const addNewPostMutation = useAddNewPost(); + const updatePostStatusMutation = UpdatePostStatus(); const [selectedTags, setSelectedTags] = useState([]); const validationSchema = Yup.object({ @@ -28,8 +36,12 @@ const PublishArticleForm = () => { .min(1, 'Мора да селектираш барем еден таг.'), }); - const handleAddPost = (values: NewPost) => { - addNewPostMutation.mutate(values); + const handleSubmit = (values: NewPost) => { + if (postId) { + updatePostStatusMutation.mutate({ id: postId, status: values.status }); + } else { + addNewPostMutation.mutate(values); + } }; return ( @@ -40,8 +52,9 @@ const PublishArticleForm = () => { excerpt: '', content: '', tags: [], + status: 'draft', }} - onSubmit={handleAddPost} + onSubmit={handleSubmit} > {({ values, setFieldValue, touched, errors }) => (
@@ -104,6 +117,18 @@ const PublishArticleForm = () => { {touched.tags && errors.tags &&
{errors.tags}
} +
+ + + + + {userRole === UserRole.admin && } + + {touched.status && errors.status &&
{errors.status}
} +
+ {addNewPostMutation.isPending ? ( diff --git a/components/reusable-components/reusable-table/reusableTable.module.scss b/components/reusable-components/reusable-table/reusableTable.module.scss index 100807d0..d63189be 100644 --- a/components/reusable-components/reusable-table/reusableTable.module.scss +++ b/components/reusable-components/reusable-table/reusableTable.module.scss @@ -25,14 +25,14 @@ $table-height: 400px; padding: 15px; white-space: nowrap; text-align: left; - overflow: hidden; + overflow: visible; } td { height: 60px; padding: 15px; - overflow: hidden; - white-space: nowrap; + overflow: visible; + white-space: wrap; } th:first-child, diff --git a/middleware.ts b/middleware.ts index 4ef81a39..535e47d5 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { getToken } from 'next-auth/jwt'; +import { UserRole } from './Types'; export async function middleware(req: NextRequest) { const token = await getToken({ req }); @@ -24,14 +25,14 @@ export async function middleware(req: NextRequest) { // Content Panel access if (isContentPanelRoute && !isLoginRoute) { - if (userRole !== 'admin' && userRole !== 'content_manager') { + if (userRole !== UserRole.admin && userRole !== UserRole.content_manager) { return NextResponse.redirect(new URL('/unauthorized', req.url)); } } // Admin Panel access if (isAdminPanelRoute && !isLoginRoute) { - if (userRole !== 'admin') { + if (userRole !== UserRole.admin) { return NextResponse.redirect(new URL('/unauthorized', req.url)); } } @@ -40,11 +41,11 @@ export async function middleware(req: NextRequest) { if (isLoginRoute) { if ( path.startsWith('/content-panel/login') && - (userRole === 'admin' || userRole === 'content-manager') + (userRole === UserRole.admin || userRole === UserRole.content_manager) ) { return NextResponse.redirect(new URL('/content-panel', req.url)); } - if (path.startsWith('/admin-panel/login') && userRole === 'admin') { + if (path.startsWith('/admin-panel/login') && userRole === UserRole.admin) { return NextResponse.redirect(new URL('/admin-panel', req.url)); } } diff --git a/utils/actions/session.ts b/utils/actions/session.ts index 581d91d6..1d0b766b 100644 --- a/utils/actions/session.ts +++ b/utils/actions/session.ts @@ -1,12 +1,12 @@ 'use server'; import { cookies } from 'next/headers'; -import { Role } from '../../Types'; +import { UserRole } from '../../Types'; import getAuthUrl from '../getAuthUrl'; type Session = { token: string; - role: Role; + role: UserRole; }; type RefreshTokenResponse = { message: string; new_token: string }; @@ -34,7 +34,7 @@ export async function getNewToken({ role, existingToken, }: { - role: Role; + role: UserRole; existingToken: string; }): Promise { try { @@ -50,6 +50,7 @@ export async function getNewToken({ if (!response.ok) throw new Error(response.statusText); const data: RefreshTokenResponse = await response.json(); return data.new_token; + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error: any) { // console.error({ msg: 'Error from getNewToken', error }); return null; diff --git a/utils/getAuthUrl.ts b/utils/getAuthUrl.ts index 8b9fd35c..463d37d2 100644 --- a/utils/getAuthUrl.ts +++ b/utils/getAuthUrl.ts @@ -1,9 +1,9 @@ -import { Role } from '../Types'; +import { UserRole } from '../Types'; -export default function getAuthUrl(baseUrl: string, role: Role): string { +export default function getAuthUrl(baseUrl: string, role: UserRole): string { let url = baseUrl; - if (role === 'member') url = `${baseUrl}`; - else if (role === 'content_manager' || role === 'content') url = `${baseUrl}/content`; - else if (role === 'admin') url = `${baseUrl}/admin`; + if (role === UserRole.member) url = `${baseUrl}`; + else if (role === UserRole.content_manager) url = `${baseUrl}/content`; + else if (role === UserRole.admin) url = `${baseUrl}/admin`; return url; }