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

fix-#392: added related blogs section in detailed blog page #398

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions backend/controllers/posts-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,20 @@ export const deletePostByIdHandler = async (req, res) => {
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message });
}
};

export const getRelatedPostsByCategories = async (req, res) => {
const { categories } = req.query;
krishnaacharyaa marked this conversation as resolved.
Show resolved Hide resolved
if (!categories) {
return res
.status(HTTP_STATUS.NOT_FOUND)
.json({ message: RESPONSE_MESSAGES.POSTS.CATEGORIES_NOTFOUND });
}
try {
const filteredCategoryPosts = await Post.find({
categories: { $in: categories },
});
res.status(HTTP_STATUS.OK).json(filteredCategoryPosts);
} catch (err) {
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message });
}
};
4 changes: 4 additions & 0 deletions backend/routes/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getLatestPostsHandler,
getPostByCategoryHandler,
getPostByIdHandler,
getRelatedPostsByCategories,
updatePostHandler,
} from '../controllers/posts-controller.js';
import { REDIS_KEYS } from '../utils/constants.js';
Expand All @@ -24,6 +25,9 @@ router.get('/', cacheHandler(REDIS_KEYS.ALL_POSTS), getAllPostsHandler);
// Route to get featured posts
router.get('/featured', cacheHandler(REDIS_KEYS.FEATURED_POSTS), getFeaturedPostsHandler);

// Route to get related category posts
router.get('/related-posts-by-category', getRelatedPostsByCategories);

krishnaacharyaa marked this conversation as resolved.
Show resolved Hide resolved
// Route to get posts by category
router.get('/categories/:category', getPostByCategoryHandler);

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/assets/svg/arrow-right-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/assets/svg/arrow-right-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions frontend/src/components/PostMobileViewCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Skeleton } from './ui/skeleton';

export function PostMobileViewCardSkeleton() {
return (
<div
className="flex h-fit rounded-lg border bg-slate-50 dark:border-none dark:bg-dark-card"
data-testid={'postMobileViewCardSkeleton'}
>
<div className="flex h-fit w-full gap-2 p-3">
<Skeleton className="w-1/3 overflow-hidden rounded-lg bg-slate-200 dark:bg-slate-700" />
<div className="flex h-full w-2/3 flex-col gap-2 ">
<Skeleton className="mb-2 h-3 w-full bg-slate-200 pr-2 dark:bg-slate-700" />
<div className="mt-1 flex flex-wrap gap-1 sm:mt-2 sm:gap-1.5">
<Skeleton className={`h-6 w-16 rounded-full bg-slate-200 dark:bg-slate-700`} />
<Skeleton className={`h-6 w-16 rounded-full bg-slate-200 dark:bg-slate-700`} />
</div>
<Skeleton className="mb-2 h-6 w-full bg-slate-200 pr-2 dark:bg-slate-700 sm:mb-4" />
<Skeleton className="mb-2 h-3 w-full bg-slate-200 pr-2 dark:bg-slate-700" />
</div>
</div>
</div>
);
}
52 changes: 52 additions & 0 deletions frontend/src/components/PostMobileViewComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Post from '@/types/post-type';
import CategoryPill from './category-pill';
import formatPostTime from '@/utils/format-post-time';
import { useNavigate } from 'react-router-dom';
import { createSlug } from '@/utils/slug-generator';

export default function PostMobileViewComponent({
post,
testId = 'postMobileViewCard',
}: {
post: Post;
testId?: string;
}) {
const navigate = useNavigate();
const slug = createSlug(post.title);
return (
<div
className="active:scale-click group flex h-fit cursor-pointer rounded-lg border bg-slate-50 dark:border-none dark:bg-dark-card"
onClick={() => {
navigate(`/details-page/${slug}/${post._id}`, { state: { post } });
}}
data-testid={testId}
>
<div className="flex h-fit w-full gap-2 p-3">
<div className="w-1/3 overflow-hidden">
<img
src={post.imageLink}
className="group-hover:scale-hover h-32 w-32 rounded-lg object-cover shadow-lg transition-transform ease-in-out"
/>
</div>
<div className="flex h-full w-2/3 flex-col gap-2 ">
<div className="line-clamp-1 text-base font-semibold text-light-title dark:text-dark-title">
{post.title}
</div>
<div className="flex flex-wrap gap-2">
{post.categories.map((category, index) => (
<CategoryPill key={`${category}-${index}`} category={category} />
))}
</div>
<div className="line-clamp-2 ">
<p className="overflow-ellipsis text-light-description dark:text-dark-description">
{post.description}
</p>
</div>
<div className="text-xs text-light-info dark:text-dark-info">
{post.authorName} • {formatPostTime(post.timeOfPost)}
</div>
</div>
</div>
</div>
);
}
23 changes: 23 additions & 0 deletions frontend/src/components/skeletons/related-post-card-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Skeleton } from '../ui/skeleton';

export function PostMobileViewCardSkeleton() {
return (
<div
className="flex h-fit rounded-lg border bg-slate-50 dark:border-none dark:bg-dark-card"
data-testid={'postMobileViewCardSkeleton'}
>
<div className="flex h-fit w-full gap-2 p-3">
<Skeleton className="w-1/3 overflow-hidden rounded-lg bg-slate-200 dark:bg-slate-700" />
<div className="flex h-full w-2/3 flex-col gap-2 ">
<Skeleton className="mb-2 h-3 w-full bg-slate-200 pr-2 dark:bg-slate-700" />
<div className="mt-1 flex flex-wrap gap-1 sm:mt-2 sm:gap-1.5">
<Skeleton className={`h-6 w-16 rounded-full bg-slate-200 dark:bg-slate-700`} />
<Skeleton className={`h-6 w-16 rounded-full bg-slate-200 dark:bg-slate-700`} />
</div>
<Skeleton className="mb-2 h-6 w-full bg-slate-200 pr-2 dark:bg-slate-700 sm:mb-4" />
<Skeleton className="mb-2 h-3 w-full bg-slate-200 pr-2 dark:bg-slate-700" />
</div>
</div>
</div>
);
}
75 changes: 71 additions & 4 deletions frontend/src/pages/details-page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import navigateBackWhiteIcon from '@/assets/svg/navigate-back-white.svg';
import arrowRightWhiteIcon from '@/assets/svg/arrow-right-white.svg';
import arrowRightBlackIcon from '@/assets/svg/arrow-right-black.svg';
import formatPostTime from '@/utils/format-post-time';
import CategoryPill from '@/components/category-pill';
import { useEffect, useState } from 'react';
import axios from 'axios';
import Post from '@/types/post-type';
import axiosInstance from '@/helpers/axios-instance';
import { PostCardSkeleton } from '@/components/skeletons/post-card-skeleton';
krishnaacharyaa marked this conversation as resolved.
Show resolved Hide resolved
import PostCard from '@/components/post-card';
import PostMobileViewComponent from '@/components/PostMobileViewComponent';
import { PostMobileViewCardSkeleton } from '@/components/PostMobileViewCardSkeleton';

export default function DetailsPage() {
const { state } = useLocation();
const [post, setPost] = useState(state?.post);
const [post, setPost] = useState<Post>(state?.post);
const initialVal = post === undefined;
const [loading, setIsLoading] = useState(initialVal);
const { postId } = useParams();
const navigate = useNavigate();
const [relatedCategoryPosts, setRelatedCategoryPosts] = useState<Post[]>([]);
const [relatedPostsLoading, setRelatedPostsLoading] = useState<boolean>(false);
const [isDarkMode, setIsDarkMode] = useState<boolean | null>(null);

useEffect(() => {
const theme = localStorage.getItem('theme');
setIsDarkMode(theme === 'dark');
}, []);

krishnaacharyaa marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
const getPostById = async () => {
Expand All @@ -25,10 +41,28 @@ export default function DetailsPage() {
console.log(error);
}
};
if (post === undefined) {
if (post === undefined || post !== state.post) {
getPostById();
}
}, [post]);
}, [state.post]);

useEffect(() => {
Copy link
Owner

Choose a reason for hiding this comment

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

It should use all the categories,
Basically if there are three categories a b and c , i need to get all the posts which has any of a b or c. and then get only top 3 posts in the mobile view, vertically and may be 4 or 5 in the desktop view

const fetchRelatedCategoryPosts = async () => {
try {
setRelatedPostsLoading(true);
const res = await axiosInstance.get('/api/posts/related-posts-by-category', {
params: {
categories: post.categories,
},
krishnaacharyaa marked this conversation as resolved.
Show resolved Hide resolved
});
setRelatedCategoryPosts(res.data);
setRelatedPostsLoading(false);
} catch (err) {
setRelatedPostsLoading(false);
}
};
fetchRelatedCategoryPosts();
}, [post.categories]);

if (!loading)
return (
Expand Down Expand Up @@ -66,6 +100,39 @@ export default function DetailsPage() {
</p>
</div>
</div>
<div className="container mx-auto flex flex-col space-y-2 px-4 py-6 dark:text-white">
<div className="flex justify-between text-2xl font-semibold ">
<div>Related Blogs</div>
<div className="flex cursor-pointer items-center text-sm text-gray-400 hover:underline sm:mr-10">
<Link to="/">
<div>see more blogs</div>
</Link>
<img
alt="arrow-right"
src={isDarkMode ? arrowRightWhiteIcon : arrowRightBlackIcon}
className="active:scale-click h-8 w-8"
/>
</div>
</div>
<div className="block space-y-4 sm:hidden">
{relatedPostsLoading
? Array(3)
.fill(0)
.map((_, index) => <PostMobileViewCardSkeleton key={index} />)
: relatedCategoryPosts
.slice(0, 3)
.map((post) => <PostMobileViewComponent key={post._id} post={post} />)}
</div>
<div className="hidden sm:flex sm:flex-wrap sm:p-3">
{relatedPostsLoading
? Array(4)
.fill(0)
.map((_, index) => <PostCardSkeleton key={index} />)
: relatedCategoryPosts
.slice(0, 4)
.map((post) => <PostCard key={post._id} post={post} />)}
</div>
</div>
</div>
);
else return <h1>Loading...</h1>;
Expand Down