From 6939ec684f9148bcf072ba8559a3719aa4c85a72 Mon Sep 17 00:00:00 2001 From: vamsidhar-914 Date: Mon, 3 Jun 2024 23:20:33 +0530 Subject: [PATCH 1/3] fix-#339: feature,implemented like functionality to the posts --- backend/app.js | 16 +++-- backend/controllers/posts-controller.js | 34 ++++++++++- backend/data/sample_posts.json | 44 +++++++++++++ backend/models/post.js | 8 +++ backend/routes/posts.js | 10 +++ frontend/src/components/HandleLikeButton.tsx | 0 frontend/src/components/blog-feed.tsx | 53 ++++++++++++++-- .../src/components/featured-post-card.tsx | 43 ++++++++++--- frontend/src/components/require-auth.tsx | 23 +++---- frontend/src/components/unprotected-route.tsx | 6 +- frontend/src/context/authContext.tsx | 61 +++++++++++++++++++ frontend/src/layouts/header-layout.tsx | 20 +++--- frontend/src/main.tsx | 5 +- frontend/src/pages/add-blog.tsx | 8 +-- frontend/src/pages/home-page.tsx | 1 + frontend/src/pages/signin-page.tsx | 13 +++- frontend/src/types/post-type.tsx | 2 + 17 files changed, 296 insertions(+), 51 deletions(-) create mode 100644 frontend/src/components/HandleLikeButton.tsx create mode 100644 frontend/src/context/authContext.tsx diff --git a/backend/app.js b/backend/app.js index 58e82b780..d24e1424c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -31,13 +31,17 @@ app.get('/', (req, res) => { res.send('Yay!! Backend of wanderlust app is now accessible'); }); -app.all('*', (req, res) => { - res.status(404).json({ - status: 404, - success: false, - message: '!Oops page not found', - }); +app.get('/likePost/:postId', (req, res) => { + res.send('yayyy'); }); +// app.all('*', (req, res) => { +// res.status(404).json({ +// status: 404, +// success: false, +// message: '!Oops page not found', +// }); +// }); + app.use(errorMiddleware); export default app; diff --git a/backend/controllers/posts-controller.js b/backend/controllers/posts-controller.js index 6de2f83c6..eae79b61a 100644 --- a/backend/controllers/posts-controller.js +++ b/backend/controllers/posts-controller.js @@ -44,7 +44,7 @@ export const createPostHandler = async (req, res) => { description, categories, isFeaturedPost, - authorId: req.user._id, + authorId: req.user._id || req.body.authorId, }); const [savedPost] = await Promise.all([ @@ -112,12 +112,14 @@ export const getLatestPostsHandler = async (req, res) => { export const getPostByIdHandler = async (req, res) => { try { - const post = await Post.findById(req.params.id); + const post = await Post.findById({ _id: req.params.id }); // Validation - check if post exists if (!post) { return res.status(HTTP_STATUS.NOT_FOUND).json({ message: RESPONSE_MESSAGES.POSTS.NOT_FOUND }); } + const authorId = post.authorId; + console.log(authorId); res.status(HTTP_STATUS.OK).json(post); } catch (err) { @@ -157,3 +159,31 @@ export const deletePostByIdHandler = async (req, res) => { res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message }); } }; + +export const likePostByUser = async (req, res) => { + try { + const post = await Post.findById({ _id: req.params.postId }); + if (!post) { + return res.status(HTTP_STATUS.NOT_FOUND).json({ message: RESPONSE_MESSAGES.POSTS.NOT_FOUND }); + } + + const userIndex = post.likes.indexOf(req.user._id); + if (userIndex === -1) { + post.numberOfLikes += 1; + post.likes.push(req.user._id); + } else { + post.numberOfLikes -= 1; + post.likes.splice(userIndex, 1); + } + const [savedPost] = await Promise.all([ + post.save(), // Save the post + deleteDataFromCache(REDIS_KEYS.ALL_POSTS), // Invalidate cache for all posts + deleteDataFromCache(REDIS_KEYS.FEATURED_POSTS), // Invalidate cache for featured posts + deleteDataFromCache(REDIS_KEYS.LATEST_POSTS), // Invalidate cache for latest posts + ]); + + res.status(HTTP_STATUS.OK).json(savedPost); + } catch (err) { + res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message }); + } +}; diff --git a/backend/data/sample_posts.json b/backend/data/sample_posts.json index d04115e73..e2bb59731 100644 --- a/backend/data/sample_posts.json +++ b/backend/data/sample_posts.json @@ -9,6 +9,9 @@ "categories": ["Travel", "Beaches"], "description": "The Maldives is a tropical paradise known for its stunning beaches, crystal-clear waters, and vibrant marine life. In this journey, we explore the breathtaking beauty of the Maldives' beaches. From the white sands to the vibrant coral reefs, there's something for every traveler.The Maldives is a tropical paradise known for its stunning beaches, crystal-clear waters, and vibrant marine life. In this journey, we explore the breathtaking beauty of the Maldives' beaches. From the white sands to the vibrant coral reefs, there's something for every traveler.", "isFeaturedPost": true, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-11-24T14:33:21.700Z" } @@ -23,6 +26,9 @@ "categories": ["Travel", "City", "Night"], "description": "Discover the magic of cityscapes at night in this captivating journey. The city lights, towering skyscrapers, and bustling streets create a unique atmosphere that is truly mesmerizing. Join us as we explore cities after dark and witness their vibrant energy. As the sun sets and darkness descends, the city transforms into a different world. It's a world of illuminated skyscrapers, neon signs, and the hustle and bustle of the nightlife. In this blog post, we'll take you on a journey through the cityscape at night, and you'll see why it's a mesmerizing experience. The city comes alive in a way that is entirely different from the daytime. The streets are aglow with a sea of lights, and the architecture of the city takes on a whole new dimension. It's a visual spectacle that leaves a lasting impression. We'll explore the beauty of city lights, the towering skyscrapers that touch the sky, and the vibrancy of the nightlife. You'll get a sense of the energy that flows through the city when the moon is high, and the stars twinkle overhead. The cityscape at night is a canvas of colors and shapes. It's a photographer's dream, and if you have a passion for photography, this is the time to capture the city's true essence. Whether you're in a bustling metropolis or a quaint town, the magic of the cityscape at night is universal. The city's landmarks, from iconic bridges to historic buildings, take on a different aura. The reflections in the water, the long-exposure shots of traffic, and the silhouettes of people on the streets all contribute to the enchanting experience. We'll also delve into the sounds of the night, from the chatter of people enjoying their evenings to the distant hum of traffic. It's a symphony of urban life that's unique to the nighttime hours. If you're a traveler seeking a new perspective on familiar places or an adventurer exploring a new city, this blog post will inspire you to go out and experience the cityscape at night. The night holds surprises and discoveries that the daytime can't offer. We'll share tips on how to make the most of your night photography and how to capture the essence of the city in your images. Whether you're a professional photographer or an amateur with a smartphone, you'll find something valuable in this journey through the city's nightscape. So, join us as we step into the night and immerse ourselves in the captivating world of the cityscape at night. It's a mesmerizing experience that will leave you with memories and photographs to cherish for a lifetime.", "isFeaturedPost": true, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-11-04T14:33:21.700Z" } @@ -37,6 +43,9 @@ "categories": ["Travel", "Adventure", "Nature"], "description": "Get ready to experience the thrill of outdoor adventures in some of the world's most beautiful natural settings. From hiking and camping to white-water rafting, this journey takes you to the heart of nature and offers an adrenaline rush like no other.", "isFeaturedPost": true, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-10-04T14:33:21.700Z" } @@ -51,6 +60,9 @@ "categories": ["Travel", "City", "Landmarks"], "description": "Marvel at the iconic London skyline, featuring historic landmarks like Big Ben, the London Eye, and the Shard. Join us on a journey to explore the rich history and modernity of the British capital through its stunning skyline.", "isFeaturedPost": true, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-09-11T14:33:21.700Z" } @@ -65,6 +77,9 @@ "categories": ["Nature", "Scenic", "Tranquil"], "description": "Find solace and serenity in the heart of nature's most scenic landscapes. This journey takes you to tranquil forests, serene lakes, and picturesque countryside. Immerse yourself in the beauty of the natural world and experience true peace.", "isFeaturedPost": true, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-08-22T14:33:21.700Z" } @@ -79,6 +94,9 @@ "categories": ["Travel", "Beaches", "Sunset"], "description": "Witness the breathtaking beauty of sunsets by the beach. As the sun dips below the horizon, it paints the sky with hues of orange, pink, and gold. This journey captures the magical moments of evening by the sea.", "isFeaturedPost": false, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-03-25T14:33:21.700Z" } @@ -93,6 +111,9 @@ "categories": ["Travel", "City", "Night"], "description": "Experience the vibrant energy of cities after dark. The city lights, bustling streets, and lively nightlife create a spectacle that must be seen to be believed. Join us for a journey through the electrifying nightscapes of urban hubs.", "isFeaturedPost": false, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-05-26T14:33:21.700Z" } @@ -107,6 +128,9 @@ "categories": ["Travel", "Adventure", "Mountains"], "description": "Embark on thrilling mountain adventures in some of the world's most spectacular mountain ranges. From trekking through alpine meadows to conquering challenging peaks, this journey takes you to new heights in the world of adventure.", "isFeaturedPost": false, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-11-27T14:33:21.700Z" } @@ -121,6 +145,9 @@ "categories": ["Travel", "History", "Landmarks"], "description": "Embark on a journey to explore historical sites and ancient landmarks that tell the stories of civilizations past. Uncover the secrets of the past as you visit historic sites and delve into the mysteries of history.", "isFeaturedPost": false, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-11-11T14:33:21.700Z" } @@ -135,8 +162,25 @@ "categories": ["Nature", "Forests", "Retreat"], "description": "Find solace in the tranquility of the forest. This retreat into nature's embrace allows you to disconnect from the bustling world and discover the peaceful beauty of dense forests, serene lakes, and gentle streams.", "isFeaturedPost": false, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", "timeOfPost": { "$date": "2023-07-22T14:33:21.700Z" } + }, + { + "authorName": "vamsiii", + "title": "vamsiPosts", + "imageLink": "https://i.ibb.co/3z72vmc/clean-lake-mountains-range-trees-nature-4k.webp", + "categories": ["Travel", "Beaches"], + "description": "The Maldives is a tropical paradise known for its stunning beaches, crystal-clear waters, and vibrant marine life. In this journey, we explore the breathtaking beauty of the Maldives' beaches. From the white sands to the vibrant coral reefs, there's something for every traveler.The Maldives is a tropical paradise known for its stunning beaches, crystal-clear waters, and vibrant marine life. In this journey, we explore the breathtaking beauty of the Maldives' beaches. From the white sands to the vibrant coral reefs, there's something for every traveler.", + "isFeaturedPost": false, + "likes": [], + "numberOfLikes": 0, + "authorId": "665bc7c00c0d962738398b5c", + "_id": "665dd4f2b23f177dd4170450", + "timeOfPost": "2024-06-03T14:36:34.186Z", + "__v": 0 } ] diff --git a/backend/models/post.js b/backend/models/post.js index 1a3ab4db1..00a813690 100644 --- a/backend/models/post.js +++ b/backend/models/post.js @@ -8,6 +8,14 @@ const postSchema = new Schema({ description: String, isFeaturedPost: Boolean, timeOfPost: { type: Date, default: Date.now }, + likes: { + type: Array, + default: [], + }, + numberOfLikes: { + type: Number, + default: 0, + }, authorId: { type: Schema.Types.ObjectId, ref: 'User', diff --git a/backend/routes/posts.js b/backend/routes/posts.js index 5e732678a..42d3fc3cb 100644 --- a/backend/routes/posts.js +++ b/backend/routes/posts.js @@ -7,6 +7,8 @@ import { getLatestPostsHandler, getPostByCategoryHandler, getPostByIdHandler, + getRandomData, + likePostByUser, updatePostHandler, } from '../controllers/posts-controller.js'; import { REDIS_KEYS } from '../utils/constants.js'; @@ -29,12 +31,20 @@ router.get('/categories/:category', getPostByCategoryHandler); // Route for fetching the latest posts router.get('/latest', cacheHandler(REDIS_KEYS.LATEST_POSTS), getLatestPostsHandler); + +router.get('/random', getRandomData); + // Get a specific post by ID router.get('/:id', getPostByIdHandler); +//random test +router.put('/likePost/:postId', authMiddleware, likePostByUser); + // Update a post by ID router.patch('/:id', authMiddleware, isAuthorMiddleware, updatePostHandler); +// add like to post + // Delete a post by ID router.delete('/:id', authMiddleware, isAuthorMiddleware, deletePostByIdHandler); diff --git a/frontend/src/components/HandleLikeButton.tsx b/frontend/src/components/HandleLikeButton.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/blog-feed.tsx b/frontend/src/components/blog-feed.tsx index 0056d5711..82657b8da 100644 --- a/frontend/src/components/blog-feed.tsx +++ b/frontend/src/components/blog-feed.tsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; import { useEffect, useState } from 'react'; import FeaturedPostCard from '@/components/featured-post-card'; import LatestPostCard from '@/components/latest-post-card'; @@ -6,15 +6,29 @@ import { FeaturedPostCardSkeleton } from '@/components/skeletons/featured-post-c import { LatestPostCardSkeleton } from '@/components/skeletons/latest-post-card-skeleton'; import CategoryPill from '@/components/category-pill'; import { categories } from '@/utils/category-colors'; +import { toast } from 'react-toastify'; +import axiosInstance from '@/helpers/axios-instance'; + +type PostData = { + _id: string; + authorName: string; + title: string; + imageLink: string; + timeOfPost: string; + description: string; + categories: string[]; + numberOfLikes: number; + likes: string[]; +}; export default function BlogFeed() { const [selectedCategory, setSelectedCategory] = useState('featured'); - const [posts, setPosts] = useState([]); + const [posts, setPosts] = useState([]); const [latestPosts, setLatestPosts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { - let categoryEndpoint = + const categoryEndpoint = selectedCategory === 'featured' ? '/api/posts/featured' : `/api/posts/categories/${selectedCategory}`; @@ -42,6 +56,30 @@ export default function BlogFeed() { }); }, []); + // handle like for feature posts + const handleLike = async (postId: string) => { + try { + const { data } = await axiosInstance.put(`/api/posts/likePost/${postId}`); + setPosts( + posts.map((post) => + post._id === postId + ? { + ...post, + likes: data.likes, + numberOfLikes: data.numberOfLikes, + } + : post + ) + ); + } catch (err) { + if (err instanceof AxiosError) { + if (err.response?.status === 400) { + toast.error('please login'); + } + } + } + }; + return (
@@ -61,7 +99,14 @@ export default function BlogFeed() { .map((_, index) => ) : posts .slice(0, 5) - .map((post, index) => )} + .map((post) => ( + + ))}
diff --git a/frontend/src/components/featured-post-card.tsx b/frontend/src/components/featured-post-card.tsx index 374b804d7..608dbf7c8 100644 --- a/frontend/src/components/featured-post-card.tsx +++ b/frontend/src/components/featured-post-card.tsx @@ -3,22 +3,31 @@ import Post from '@/types/post-type'; import formatPostTime from '@/utils/format-post-time'; import CategoryPill from '@/components/category-pill'; import { createSlug } from '@/utils/slug-generator'; -import { TestProps } from '@/types/test-props'; -export default function FeaturedPostCard({ - post, - testId = 'featuredPostCard', -}: { post: Post } & TestProps) { +import { ThumbsUp } from 'lucide-react'; + +import { useAuthContext } from '@/context/authContext'; + +type PostCardProps = { + post: Post; + testId: string; + onLike: (postId: string) => void; +}; + +export default function FeaturedPostCard({ post, onLike, testId }: PostCardProps) { const navigate = useNavigate(); + + const { user } = useAuthContext(); const slug = createSlug(post.title); + return (
navigate(`/details-page/${slug}/${post._id}`, { state: { post } })} + className={` flex h-auto cursor-pointer flex-col gap-2 rounded-lg border bg-slate-50 dark:border-none dark:bg-dark-card sm:h-48 sm:flex-row`} data-testid={testId} >
navigate(`/details-page/${slug}/${post._id}`, { state: { post } })} src={post.imageLink} alt={post.title} className={`sm:group-hover:scale-hover h-48 w-full rounded-lg object-cover shadow-lg transition-transform ease-in-out sm:h-full`} @@ -38,8 +47,24 @@ export default function FeaturedPostCard({ {post.description}

-
- {post.authorName} • {formatPostTime(post.timeOfPost)} +
+
+ {post.authorName} • {formatPostTime(post.timeOfPost)} +
+
+
+

{post.numberOfLikes + ' ' + (post.numberOfLikes === 1 ? 'like' : 'likes')}

+
+
+ +
+
diff --git a/frontend/src/components/require-auth.tsx b/frontend/src/components/require-auth.tsx index 3dae12efc..d51ee7348 100644 --- a/frontend/src/components/require-auth.tsx +++ b/frontend/src/components/require-auth.tsx @@ -1,19 +1,20 @@ import { Navigate, Outlet } from 'react-router-dom'; -import Loader from './skeletons/loader'; -import useAuthData from '@/hooks/useAuthData'; + +import { useAuthContext } from '@/context/authContext'; function RequireAuth({ allowedRole }: { allowedRole: string[] }) { - const { role, token, loading } = useAuthData(); + // const { role, token, loading } = useAuthData(); + const { user } = useAuthContext(); - if (loading) { - return ( - <> - - - ); // Render a loading indicator - } + // if (loading) { + // return ( + // <> + // + // + // ); // Render a loading indicator + // } - return token && allowedRole.find((myRole) => myRole === role) ? ( + return user.token && allowedRole.find((myRole) => myRole === user.role) ? ( ) : ( diff --git a/frontend/src/components/unprotected-route.tsx b/frontend/src/components/unprotected-route.tsx index e620100cb..b85e94ec2 100644 --- a/frontend/src/components/unprotected-route.tsx +++ b/frontend/src/components/unprotected-route.tsx @@ -1,10 +1,10 @@ import { Navigate, Outlet } from 'react-router-dom'; -import useAuthData from '@/hooks/useAuthData'; +import { useAuthContext } from '@/context/authContext'; function UnprotectedRoute() { - const { token } = useAuthData(); + const { user } = useAuthContext(); - return token ? : ; + return user.token ? : ; } export default UnprotectedRoute; diff --git a/frontend/src/context/authContext.tsx b/frontend/src/context/authContext.tsx new file mode 100644 index 000000000..da54fce04 --- /dev/null +++ b/frontend/src/context/authContext.tsx @@ -0,0 +1,61 @@ +import { ReactNode, createContext, useContext, useEffect, useState } from 'react'; + +type Userstate = { + id: string; + token: string; + email: string; + role: string; +}; + +type AuthState = { + user: Userstate; + addAuth: (state: Userstate) => void; +}; + +export const Context = createContext(null); + +type ProviderProps = { + children: ReactNode; +}; + +export function Authprovider({ children }: ProviderProps) { + const [user, setUser] = useLocalStorage('AUTH1', { + email: '', + id: '', + token: '', + role: '', + }); + + function addAuth(state: Userstate) { + setUser({ + email: state.email, + id: state.id, + token: state.token, + role: state.role, + }); + } + return {children}; +} + +function useLocalStorage(key: string, initialValue: Userstate) { + const [value, setValue] = useState(() => { + const jsonValue = localStorage.getItem(key); + if (jsonValue == null) return initialValue; + + return JSON.parse(jsonValue) as Userstate; + }); + + useEffect(() => { + localStorage.setItem(key, JSON.stringify(value)); + }, [value, key]); + + return [value, setValue] as const; +} + +export function useAuthContext() { + const value = useContext(Context); + if (value == null) { + throw new Error('useAuth must be used with in and authprovider'); + } + return value; +} diff --git a/frontend/src/layouts/header-layout.tsx b/frontend/src/layouts/header-layout.tsx index 19f0a05fa..705bfb5ba 100644 --- a/frontend/src/layouts/header-layout.tsx +++ b/frontend/src/layouts/header-layout.tsx @@ -7,13 +7,14 @@ import Hero from '@/components/hero'; import { AxiosError, isAxiosError } from 'axios'; import { toast } from 'react-toastify'; import axiosInstance from '@/helpers/axios-instance'; -import Loader from '@/components/skeletons/loader'; -import useAuthData from '@/hooks/useAuthData'; -import userState from '@/utils/user-state'; + +import { useAuthContext } from '@/context/authContext'; function header() { const navigate = useNavigate(); - const { token, loading } = useAuthData(); + const { user, addAuth } = useAuthContext(); + + console.log(user); const handleLogout = async () => { try { @@ -22,7 +23,12 @@ function header() { pending: 'Wait ...', success: { render({ data }) { - userState.removeUser(); + addAuth({ + email: '', + id: '', + role: '', + token: '', + }); navigate('/'); return data?.data?.message; }, @@ -62,9 +68,7 @@ function header() {
- {loading ? ( - - ) : token ? ( + {user.token ? (
+ + + + +
+ ) : ( +
+ {' '} + + +
+ )} +
+ +
+
+
+
Profile
+
+ + setupdateName(e.target.value)} + className="rounded-full border border-black p-3" + /> +
+
+ + setupdateEmail(e.target.value)} + className="rounded-full border border-black p-3" + /> +
+
+ + setupdatePassword(e.target.value)} + className="rounded-full border border-black p-3" + /> +
+
+ + +
+
+
+
+ + ); +} diff --git a/frontend/src/components/YourPosts.tsx b/frontend/src/components/YourPosts.tsx new file mode 100644 index 000000000..54d67d095 --- /dev/null +++ b/frontend/src/components/YourPosts.tsx @@ -0,0 +1,3 @@ +export function YourPosts() { + return

your own posts

; +} diff --git a/frontend/src/components/featured-post-card.tsx b/frontend/src/components/featured-post-card.tsx index 608dbf7c8..54a5c47de 100644 --- a/frontend/src/components/featured-post-card.tsx +++ b/frontend/src/components/featured-post-card.tsx @@ -6,7 +6,7 @@ import { createSlug } from '@/utils/slug-generator'; import { ThumbsUp } from 'lucide-react'; -import { useAuthContext } from '@/context/authContext'; +import useAuthData from '@/hooks/useAuthData'; type PostCardProps = { post: Post; @@ -17,7 +17,7 @@ type PostCardProps = { export default function FeaturedPostCard({ post, onLike, testId }: PostCardProps) { const navigate = useNavigate(); - const { user } = useAuthContext(); + const { _id } = useAuthData(); const slug = createSlug(post.title); return ( @@ -59,7 +59,7 @@ export default function FeaturedPostCard({ post, onLike, testId }: PostCardProps diff --git a/frontend/src/components/require-auth.tsx b/frontend/src/components/require-auth.tsx index d51ee7348..7effaad4e 100644 --- a/frontend/src/components/require-auth.tsx +++ b/frontend/src/components/require-auth.tsx @@ -1,20 +1,19 @@ import { Navigate, Outlet } from 'react-router-dom'; - -import { useAuthContext } from '@/context/authContext'; +import useAuthData from '@/hooks/useAuthData'; +import { Loader } from 'lucide-react'; function RequireAuth({ allowedRole }: { allowedRole: string[] }) { - // const { role, token, loading } = useAuthData(); - const { user } = useAuthContext(); + const { role, token, loading } = useAuthData(); - // if (loading) { - // return ( - // <> - // - // - // ); // Render a loading indicator - // } + if (loading) { + return ( + <> + + + ); // Render a loading indicator + } - return user.token && allowedRole.find((myRole) => myRole === user.role) ? ( + return token && allowedRole.find((myRole) => myRole === role) ? ( ) : ( diff --git a/frontend/src/components/unprotected-route.tsx b/frontend/src/components/unprotected-route.tsx index b85e94ec2..9ae564d10 100644 --- a/frontend/src/components/unprotected-route.tsx +++ b/frontend/src/components/unprotected-route.tsx @@ -1,10 +1,11 @@ import { Navigate, Outlet } from 'react-router-dom'; -import { useAuthContext } from '@/context/authContext'; + +import useAuthData from '@/hooks/useAuthData'; function UnprotectedRoute() { - const { user } = useAuthContext(); + const { token } = useAuthData(); - return user.token ? : ; + return token ? : ; } export default UnprotectedRoute; diff --git a/frontend/src/context/authContext.tsx b/frontend/src/context/authContext.tsx index da54fce04..eeff84026 100644 --- a/frontend/src/context/authContext.tsx +++ b/frontend/src/context/authContext.tsx @@ -54,6 +54,7 @@ function useLocalStorage(key: string, initialValue: Userstate) { export function useAuthContext() { const value = useContext(Context); + if (value == null) { throw new Error('useAuth must be used with in and authprovider'); } diff --git a/frontend/src/hooks/useAuthData.ts b/frontend/src/hooks/useAuthData.ts index ded74bf4a..e1edeaae9 100644 --- a/frontend/src/hooks/useAuthData.ts +++ b/frontend/src/hooks/useAuthData.ts @@ -1,16 +1,16 @@ +import { useAuthContext } from '@/context/authContext'; import axiosInstance from '@/helpers/axios-instance'; import { AuthData } from '@/lib/types'; -import userState from '@/utils/user-state'; import { useState, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; const useAuthData = (): AuthData => { const location = useLocation; - const user = userState.getUser(); + const { user } = useAuthContext(); const [data, setData] = useState({ - _id: user?._id || '', - role: user?.role || '', + _id: user.id || '', + role: user.role || '', token: '', loading: true, }); @@ -20,7 +20,7 @@ const useAuthData = (): AuthData => { ...data, token: '', }); - }, [user?._id]); + }, [user.id]); useEffect(() => { async function fetchToken() { diff --git a/frontend/src/layouts/header-layout.tsx b/frontend/src/layouts/header-layout.tsx index 705bfb5ba..5a05742df 100644 --- a/frontend/src/layouts/header-layout.tsx +++ b/frontend/src/layouts/header-layout.tsx @@ -2,19 +2,27 @@ import ThemeToggle from '@/components/theme-toggle-button'; import AddIcon from '@/assets/svg/add-icon-white.svg'; import LogOutIcon from '@/assets/svg/logout-icon.svg'; import LogInIcon from '@/assets/svg/login-icon.svg'; -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import Hero from '@/components/hero'; import { AxiosError, isAxiosError } from 'axios'; import { toast } from 'react-toastify'; import axiosInstance from '@/helpers/axios-instance'; import { useAuthContext } from '@/context/authContext'; +import { Loader, User } from 'lucide-react'; +import { useState } from 'react'; +import useAuthData from '@/hooks/useAuthData'; function header() { const navigate = useNavigate(); - const { user, addAuth } = useAuthContext(); - - console.log(user); + const { addAuth } = useAuthContext(); + const [isopen, setisOpen] = useState(false); + const { token, role, _id, loading } = useAuthData(); + console.log({ + token, + _id, + role, + }); const handleLogout = async () => { try { @@ -55,6 +63,8 @@ function header() { } }; + console.log(isopen); + return (
@@ -68,8 +78,26 @@ function header() {
- {user.token ? ( -
+ {loading ? ( + + ) : token ? ( +
+ +
setisOpen(true)} + onMouseLeave={() => setisOpen(false)} + className="relative rounded-full border border-gray-300 p-3 hover:bg-gray-400" + > + +
+ {isopen && ( +
+
+
profile
+
+
+ )} +