Skip to content

Commit

Permalink
feat-#178: added admin UI to frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
devsharmag99 committed May 28, 2024
1 parent ddddc49 commit ffc2cc7
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 59 deletions.
11 changes: 8 additions & 3 deletions backend/controllers/user-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import User from '../models/user.js';

export const getAllUserHandler = async (req, res) => {
try {
const users = await User.find().select('_id name email');
const users = await User.find().select('_id fullName role email');
return res.status(HTTP_STATUS.OK).json({ users });
} catch (error) {
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message });
console.log(error);
res
.status(HTTP_STATUS.INTERNAL_SERVER_ERROR)
.json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR });
}
};

export const changeUserRoleHandler = async (req, res) => {
try {
const userId = req.params.userId;
const { role } = req.body;
if (role === 'user' || role === 'admin') {
if (role === 'USER' || role === 'ADMIN') {
const user = await User.findById(userId);
if (!user)
return res
Expand All @@ -29,6 +32,7 @@ export const changeUserRoleHandler = async (req, res) => {
}
return res.status(HTTP_STATUS.OK).json({ message: RESPONSE_MESSAGES.USERS.UPDATE });
} catch (error) {
console.log(error);
res
.status(HTTP_STATUS.INTERNAL_SERVER_ERROR)
.json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR });
Expand All @@ -45,6 +49,7 @@ export const deleteUserHandler = async (req, res) => {
.json({ message: RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS });
res.status(HTTP_STATUS.NO_CONTENT).json({ message: RESPONSE_MESSAGES.USERS.DELETED });
} catch (error) {
console.log(error);
res
.status(HTTP_STATUS.INTERNAL_SERVER_ERROR)
.json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR });
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from '@/pages/home-page';
import AddBlog from '@/pages/add-blog';
Expand All @@ -13,6 +14,7 @@ import UnprotectedRoute from './components/unprotected-route';
import { useLayoutEffect } from 'react';
import RequireAuth from './components/require-auth';
import useThemeClass from './utils/theme-changer';
import AdminContainer from './components/admin-container';

function App() {
useLayoutEffect(() => {
Expand All @@ -30,12 +32,14 @@ function App() {
<Route path="signin" element={<SignIn />} />
<Route path="signup" element={<SignUp />} />
</Route>
<Route element={<RequireAuth allowedRole={["ADMIN", "USER"]} />}>
<Route element={<RequireAuth allowedRole={['ADMIN', 'USER']} />}>
<Route path="add-blog" element={<AddBlog />} />
</Route>
<Route path='admin' element={<RequireAuth allowedRole={["ADMIN"]} />}>
<Route path="users" element={<AdminUsers />} />
<Route path="blogs" element={<AdminBlogs />} />
<Route path="admin" element={<RequireAuth allowedRole={['ADMIN']} />}>
<Route element={<AdminContainer />}>
<Route path="users" element={<AdminUsers />} />
<Route path="blogs" element={<AdminBlogs />} />
</Route>
</Route>
</Route>
<Route path="*" element={<NotFound />} />
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/admin-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NavLink } from 'react-router-dom';
import React from 'react';
import { NavLink, useNavigate } from 'react-router-dom';
import UserIcon from '@/assets/svg/user-icon';
import BlogIcon from '@/assets/svg/blog-icon';
import BarIcons from '@/assets/svg/bars-icon';
Expand All @@ -8,6 +9,8 @@ import CloseIcon from '@/assets/svg/close-icon';
const AdminSidebar = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);

const navigate = useNavigate();

return (
<>
<button
Expand All @@ -32,7 +35,12 @@ const AdminSidebar = () => {
<CloseIcon />
</button>
<div className="border-b border-[#D9D9D9] bg-light px-6 py-3 dark:border-gray-700 dark:bg-dark sm:p-6 ">
<h1 className="text-xl font-medium text-light-title dark:text-dark-title">WanderLust</h1>
<h1
onClick={() => navigate('/')}
className="cursor-pointer text-xl font-medium text-light-title dark:text-dark-title"
>
WanderLust
</h1>
</div>
<div className="flex flex-col gap-2 p-6">
<NavLink
Expand Down
34 changes: 23 additions & 11 deletions frontend/src/layouts/header-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import ThemeToggle from '@/components/theme-toggle-button';
import AddIcon from '@/assets/svg/add-icon-white.svg';
import LogOutIcon from '@/assets/svg/logout-icon.svg';
Expand All @@ -14,17 +15,18 @@ import userState from '@/utils/user-state';
function header() {
const navigate = useNavigate();
const { token, loading } = useAuthData();
const user = userState.getUser();

const handleLogout = async () => {
try {
const response = axiosInstance.post('/api/auth/signout')
const response = axiosInstance.post('/api/auth/signout');
toast.promise(response, {
pending: 'Wait ...',
success: {
render({ data }) {
userState.removeUser()
userState.removeUser();
navigate('/');
return data?.data?.message
return data?.data?.message;
},
},
error: {
Expand All @@ -34,13 +36,12 @@ function header() {
return data?.response?.data?.message;
}
}
return "Signout failed"
return 'Signout failed';
},
},
}
)
});

return (await response).data
return (await response).data;
} catch (error) {
if (isAxiosError(error)) {
console.error(error.response?.data?.message || 'An error occurred');
Expand All @@ -58,7 +59,7 @@ function header() {
<div className="flex cursor-text items-center justify-between text-2xl font-semibold">
WanderLust
</div>
<div className="flex justify-between items-center">
<div className="flex items-center justify-between">
<div className="flex items-center justify-end px-4 sm:px-20">
<ThemeToggle />
</div>
Expand All @@ -67,16 +68,27 @@ function header() {
<Loader />
) : token ? (
<div className="flex gap-2">
{user?.role === 'ADMIN' && (
<button
className="active:scale-click hidden rounded border border-slate-50 px-4 py-2 hover:bg-slate-500/25 md:inline-block"
onClick={() => {
navigate('/admin/blogs');
}}
>
Dashboard
</button>
)}

<button
className="active:scale-click rounded border border-slate-50 px-4 py-2 hidden hover:bg-slate-500/25 md:inline-block"
className="active:scale-click hidden rounded border border-slate-50 px-4 py-2 hover:bg-slate-500/25 md:inline-block"
onClick={() => {
navigate('/add-blog');
}}
>
Create post
</button>
<button
className="active:scale-click rounded border border-slate-50 px-4 py-2 hidden hover:bg-slate-500/25 md:inline-block"
className="active:scale-click hidden rounded border border-slate-50 px-4 py-2 hover:bg-slate-500/25 md:inline-block"
onClick={() => {
handleLogout();
}}
Expand Down Expand Up @@ -104,7 +116,7 @@ function header() {
<div className="flex">
{' '}
<button
className="active:scale-click rounded border border-slate-50 px-4 py-2 hidden hover:bg-slate-500/25 md:inline-block"
className="active:scale-click hidden rounded border border-slate-50 px-4 py-2 hover:bg-slate-500/25 md:inline-block"
onClick={() => {
navigate('/signin');
}}
Expand Down
94 changes: 66 additions & 28 deletions frontend/src/pages/admin-blogs.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,80 @@
import React from 'react';
import PenIcon from '@/assets/svg/pen-icon';
import TrasnIcon from '@/assets/svg/trash-icon';
import { imageUrls } from '@/constants/images';
import axiosInstance from '@/helpers/axios-instance';
import formatPostTime from '@/utils/format-post-time';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import Post from '@/types/post-type';

const AdminBlogs = () => {
const [posts, setPosts] = useState<Post[]>([]);

const fetchData = async () => {
try {
const response = await axiosInstance.get('/api/posts');
setPosts(response?.data);
} catch (error) {
toast.error('Something went wrong! Please try again.');
}
};

const handleDelete = async (postId: string) => {
const response = await axiosInstance.delete('/api/posts/admin/' + postId);
if (response.status === 200) {
fetchData();
toast.success('Post successfully deleted !');
}
};

useEffect(() => {
fetchData();
}, []);

return (
<>
<div className="w-full p-3 px-5 sm:p-12">
<h1 className="absolute left-16 top-3 text-2xl font-bold text-light-title dark:text-dark-title sm:static">
Blogs
</h1>
<div className="mt-2 flex flex-col sm:mt-12">
<div className="flex flex-row items-center justify-between gap-2 rounded-lg bg-light px-3 py-3 shadow-md dark:bg-dark-card sm:gap-5">
<img
src={imageUrls[1]}
className=" h-16 w-16 rounded-xl object-cover shadow-lg sm:h-24 sm:w-24"
alt=""
/>
<div className="flex w-12 flex-1 grow flex-col justify-between gap-2">
<h4 className="w-full truncate text-base font-semibold text-light-title dark:text-dark-title sm:text-xl">
A Serene Escape to Bali's Hidden Beaches
</h4>
<p className="hidden w-full truncate text-sm text-light-description dark:text-dark-description sm:inline">
Explore Bali's tranquil shores and discover the best-hidden beaches the island has
to offer. Dive into the crystal-clear water
</p>
<p className="text-sm font-semibold text-[#6941C6] dark:text-dark-secondary">
Drew Cano • 1 Jan 2023
</p>
</div>
<div className="mt-2 flex flex-col gap-2 sm:mt-0 sm:flex-row ">
<button className="h-fit rounded-xl border-0 text-base font-semibold text-light-title dark:text-dark-title sm:text-xl">
<PenIcon />
</button>
<button className="h-fit rounded-xl border-0 text-base font-semibold text-light-title dark:text-dark-title sm:text-xl ">
<TrasnIcon />
</button>
</div>
</div>
{posts?.map((post: Post) => {
return (
<div
key={post?._id}
className="mb-3 flex flex-row items-center justify-between gap-2 rounded-lg bg-light px-3 py-3 shadow-md dark:bg-dark-card sm:gap-5"
>
<img
src={post?.imageLink}
className=" h-16 w-16 rounded-xl object-cover shadow-lg sm:h-24 sm:w-24"
alt=""
/>
<div className="flex w-12 flex-1 grow flex-col justify-between gap-2">
<h4 className="w-full truncate text-base font-semibold text-light-title dark:text-dark-title sm:text-xl">
{post?.title}
</h4>
<p className="hidden w-full truncate text-sm text-light-description dark:text-dark-description sm:inline">
{post?.description}
</p>
<p className="text-sm font-semibold text-[#6941C6] dark:text-dark-secondary">
{post?.authorName}{formatPostTime(post?.timeOfPost)}
</p>
</div>
<div className="mt-2 flex flex-col gap-2 sm:mt-0 sm:flex-row ">
<button className="h-fit rounded-xl border-0 text-base font-semibold text-light-title dark:text-dark-title sm:text-xl">
<PenIcon />
</button>
<button
onClick={() => handleDelete(post?._id)}
className="h-fit rounded-xl border-0 text-base font-semibold text-light-title dark:text-dark-title sm:text-xl "
>
<TrasnIcon />
</button>
</div>
</div>
);
})}
</div>
</div>
</>
Expand Down
Loading

0 comments on commit ffc2cc7

Please sign in to comment.