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

Publish blog posts 464 #473

Open
wants to merge 10 commits into
base: staging
Choose a base branch
from
Open
5 changes: 2 additions & 3 deletions Types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
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',
}

export type UserType = {
id: number | string;
is_verified: boolean;
email: string;
role: Role;
role: UserRole;
};

export interface LoginParams {
Expand Down
4 changes: 3 additions & 1 deletion apis/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
41 changes: 41 additions & 0 deletions apis/mutations/blogs/updatePostStatus.ts
Original file line number Diff line number Diff line change
@@ -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<ErrorResponse>) => {
toast.error(
error?.response?.data?.message || 'Настана грешка при ажурирање на статусот на статијата.'
);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.BLOGS.ALL });
toast.success('Статусот на статијата е успешно ажуриран!');
},
});
};

export default useUpdatePostStatus;
1 change: 1 addition & 0 deletions apis/mutations/blogs/useAddNewPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type NewPost = {
excerpt: string;
content: string;
tags: string[];
status: string;
};

type ErrorResponse = {
Expand Down
12 changes: 11 additions & 1 deletion app/content-panel/blogs/create/page.tsx
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove console.log

const userRole = session?.user.role as UserRole;

return (
<div className={styles.container}>
<h2>Објави статија</h2>

<div className={styles.controlsContainer}>
<PublishArticleForm />
<PublishArticleForm userRole={userRole} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main purpose of the create blog page should be to create/add new blog. On create we should call this endpoint: https://api.learnhub.mk/docs/#content-POSTcontent-blog-posts
We can change the status of already created blogs. That is why you need to send the id of the blog.

</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
7 changes: 1 addition & 6 deletions components/module-components/SearchAndFilter/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
Expand Down
33 changes: 29 additions & 4 deletions components/module-components/blog/PublishArticleForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<PublishArticleFormProps> = ({ userRole, postId }) => {
const addNewPostMutation = useAddNewPost();
const updatePostStatusMutation = UpdatePostStatus();
const [selectedTags, setSelectedTags] = useState<TagObject[]>([]);

const validationSchema = Yup.object({
Expand All @@ -28,8 +36,12 @@ const PublishArticleForm = () => {
.min(1, 'Мора да селектираш барем еден таг.'),
});

const handleAddPost = (values: NewPost) => {
addNewPostMutation.mutate(values);
Comment on lines -31 to -32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is handleAddPost deleted?

const handleSubmit = (values: NewPost) => {
if (postId) {
updatePostStatusMutation.mutate({ id: postId, status: values.status });
} else {
addNewPostMutation.mutate(values);
}
};

return (
Expand All @@ -40,8 +52,9 @@ const PublishArticleForm = () => {
excerpt: '',
content: '',
tags: [],
status: 'draft',
}}
onSubmit={handleAddPost}
onSubmit={handleSubmit}
>
{({ values, setFieldValue, touched, errors }) => (
<Form className={styles.form}>
Expand Down Expand Up @@ -104,6 +117,18 @@ const PublishArticleForm = () => {
{touched.tags && errors.tags && <div className={styles.error}>{errors.tags}</div>}
</div>

<div className={styles.fields}>
<label htmlFor="status" className={styles.inputLabel}>
Статус
</label>
<Field as="select" name="status" classname={styles.input}>
<option value="draft">Draft</option>
<option value="in_review">In Review</option>
{userRole === UserRole.admin && <option value="published">Published</option>}
</Field>
{touched.status && errors.status && <div className={styles.error}>{errors.status}</div>}
</div>

{addNewPostMutation.isPending ? (
<Button
disabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const ActionDropdown = ({ dropdownItems }: ActionDropdownProps) => {
{isOpen && (
<ul className={style.dropdownMenu}>
{dropdownItems.map((item) => (
<li key={item.id} className={style.dropdownItem}>
<li key={item.id} className={`${style.dropdownItem} ${style[item.id]}`}>
<button type="button" onClick={item.onClick}>
{item.label}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -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 });
Expand All @@ -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));
}
}
Expand All @@ -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));
}
}
Expand Down
7 changes: 4 additions & 3 deletions utils/actions/session.ts
Original file line number Diff line number Diff line change
@@ -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 };
Expand Down Expand Up @@ -34,7 +34,7 @@ export async function getNewToken({
role,
existingToken,
}: {
role: Role;
role: UserRole;
existingToken: string;
}): Promise<string | null> {
try {
Expand All @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions utils/getAuthUrl.ts
Original file line number Diff line number Diff line change
@@ -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;
}