Skip to content

Commit

Permalink
Merge pull request #25 from atlp-rwanda/187788643-category-crud-opera…
Browse files Browse the repository at this point in the history
…tion

[#finishes] Feature for Implementing category CRUD operation
  • Loading branch information
niyontwali authored Jul 5, 2024
2 parents b333734 + b45c3ba commit 6150a95
Show file tree
Hide file tree
Showing 31 changed files with 901 additions and 350 deletions.
28 changes: 25 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import Login from './pages/Login';
import LandingPage from './pages/LandingPage';
import GoogleAuthSuccess from './components/authentication/GoogleAuthSucces';
import { ToastContainer } from 'react-toastify';
import AdminPage from './pages/Admin';
import Category from './pages/Admin/Category';
import Searchpage from './containers/searchResults/SearchPage';
import { useDispatch } from 'react-redux';
import { ProductResponse, Product } from './types/Types';
import { useEffect, useRef } from 'react';
import { useGetProductsQuery } from './services/productApi';
import { setError, setIsLoading, setProductFetched, setProductsDataList } from './redux/slices/productsSlice';
import RestrictedRoute from './components/dashboard/RestrictedRoute';
import AdminPage from './pages/admin';
import Category from './pages/admin/Category';
import Sellers from './pages/admin/Sellers';
import Buyers from './pages/admin/Buyers';
import Messages from './pages/admin/Messages';
import Settings from './pages/admin/Settings';

const App = () => {
const { data, error, isLoading } = useGetProductsQuery();
const dispatch = useDispatch();
Expand Down Expand Up @@ -74,7 +80,7 @@ const App = () => {
},
{
path: 'admin',
element: <AdminPage />,
element: <RestrictedRoute role='admin' />,
children: [
{
index: true,
Expand All @@ -84,6 +90,22 @@ const App = () => {
path: 'categories',
element: <Category />,
},
{
path: 'sellers',
element: <Sellers />,
},
{
path: 'buyers',
element: <Buyers />,
},
{
path: 'messages',
element: <Messages />,
},
{
path: 'settings',
element: <Settings />,
},
],
},
{
Expand Down
Binary file added src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 0 additions & 85 deletions src/components/Sidebar.tsx

This file was deleted.

46 changes: 30 additions & 16 deletions src/components/authentication/LoginComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,44 @@ import Input from '../common/Input';
import { loginSchema, LoginData } from '../../utils/schemas';
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { setToken, setUser } from '../../redux/slices/userSlice';
import { setToken, setUser, setRole } from '../../redux/slices/userSlice';
import { useLoginUserMutation } from '../../services/authAPI';
import handleGoogleAuthentication from '../../utils/handleGoogleAuthentication';
import { Bounce, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { CustomJwtPayload } from '../../types/Types';
import { RootState } from '../../redux/store';
import { useGetUserByIdQuery } from '../../services/userApi';

const LoginComponent = () => {
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const [searchParams, setSearchParams] = useSearchParams();
const isAuthenticated = useSelector((state: any) => state.user.token);

const isAuthenticated = useSelector((state: RootState) => state.user.token);
const userId = useSelector((state: RootState) => state.user.userId);
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm<LoginData>({ resolver: zodResolver(loginSchema) });

const [loginUser, { data, error, isLoading, isSuccess, isError }] = useLoginUserMutation();
const [loginUser, { data: loginData, error, isLoading, isSuccess: isLoginSuccess, isError }] = useLoginUserMutation();

const { data: userData, isSuccess: isUserSuccess } = useGetUserByIdQuery(userId);

useEffect(() => {
if (isAuthenticated) {
navigate('/');
}
if (isError && error) {
setError('root', {
message: 'Invalid email or password',
});
}

if (isSuccess && data) {
const token = data.token;
if (isLoginSuccess && loginData.ok) {
const token: string = loginData.token;
dispatch(setToken(token));
const decodedToken = jwtDecode<CustomJwtPayload>(token);
dispatch(setUser(decodedToken.id));

const from = location.state?.from?.pathname || '/';
navigate(from);
}
const message = searchParams.get('message');
if (message) {
Expand All @@ -66,17 +64,33 @@ const LoginComponent = () => {
}, [
isError,
isAuthenticated,
isSuccess,
isLoginSuccess,
error,
data,
loginData,
searchParams,
setSearchParams,
navigate,
location.state,
dispatch,
setError,
userId,
]);

useEffect(() => {
if (isUserSuccess && userData) {
const role = userData.message.Role.name;
dispatch(setRole(role));
if (role === 'buyer' && isAuthenticated) {
const from = location.state?.from?.pathname || '/';
navigate(from);
} else if (role === 'admin') {
navigate('/admin');
} else {
navigate('seller');
}
}
}, [isUserSuccess, userData]);

const onSubmit: SubmitHandler<LoginData> = async userCredentials => {
await loginUser(userCredentials);
};
Expand Down Expand Up @@ -113,7 +127,7 @@ const LoginComponent = () => {
>
{isSubmitting || isLoading ? 'Loading...' : 'Sign In'}
</button>
{isSuccess && <p className='text-green-600 text-xs font-normal'>Login successful!</p>}
{isLoginSuccess && <p className='text-green-600 text-xs font-normal'>Login successful!</p>}
<span className='self-center font-bold text-grayColor'>or</span>
<button
onClick={handleGoogleAuthentication}
Expand Down
4 changes: 3 additions & 1 deletion src/components/common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cn } from '../../utils';

interface InputProps {
type: string;
label: string;
label?: string;
id: string;
placeholder: string;
error?: string;
Expand Down Expand Up @@ -31,4 +31,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
}
);

Input.displayName = 'Input';

export default Input;
106 changes: 106 additions & 0 deletions src/components/dashboard/AddCategoryModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { FaTimes } from 'react-icons/fa';
import { useCreateCategoryMutation } from '../../services/categoryApi';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useDispatch } from 'react-redux';
import { AppDispatch } from '../../redux/store';
import { useEffect } from 'react';
import { setIsCreated } from '../../redux/slices/categorySlice';
import CustomInput from './CustomInput';

export interface ICategory {
id: string;
name: string;
description: string;
image: string;
}

export default function AddCategoryModal({
openAddModal,
handleOpenModal,
}: {
openAddModal: boolean;
handleOpenModal: () => void;
}) {
const [createCategory, { isSuccess, isLoading }] = useCreateCategoryMutation();
const dispatch = useDispatch<AppDispatch>();

useEffect(() => {
if (isSuccess) {
dispatch(setIsCreated(true));
handleOpenModal();
}
}, [isSuccess]);

const {
register,
handleSubmit,
formState: { isSubmitting },
} = useForm<ICategory>();

const onSubmit: SubmitHandler<ICategory> = async data => {
const formData = new FormData();
formData.append('name', data.name);
formData.append('description', data.description);
formData.append('image', data.image[0]);

await createCategory(formData);
};

return (
<>
<div
className={`${openAddModal ? 'block' : 'hidden'} fixed z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full`}
>
<div className='relative p-5 w-10/12 mx-auto mt-28 max-w-md max-h-full'>
<div className='relative bg-whiteColor rounded-lg shadow'>
<div className='flex items-center justify-between p-4 md:p-5 border-b border-b-grayColor rounded-t'>
<h3 className='text-xl font-semibold text-[#111827] text-center'>Add new category</h3>
<button
type='button'
className='end-2.5 text-[#9CA3AF] bg-transparent hover:bg-[#E5E7EB] hover:text-[#111827] rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center'
onClick={handleOpenModal}
>
<FaTimes size={20} className='text-[#A08D8D]' />
<span className='sr-only'>Close modal</span>
</button>
</div>
<div className='p-4 md:p-5'>
<form className='space-y-4' onSubmit={handleSubmit(onSubmit)}>
<div>
<CustomInput
type='text'
id='name'
className='bg-[#f4f5f7] border-[#D1D5DB] text-[#111827] text-sm rounded-lg focus:ring-[#3B82F6] focus:border-[#3B82F6] block w-full p-2.5'
placeholder='Name of category'
{...register('name')}
/>
</div>
<div>
<textarea
className='bg-[#f4f5f7] border-[#D1D5DB] text-[#111827] text-sm rounded-lg focus:ring-[#3B82F6] focus:border-[#3B82F6] block w-full p-2.5 h-44 resize-none'
placeholder='Description of category'
{...register('description')}
></textarea>
</div>
<div>
<CustomInput
type='file'
id='image'
className='bg-[#f4f5f7] border-[#D1D5DB] text-[#111827] text-sm rounded-lg focus:ring-[#3B82F6] focus:border-[#3B82F6] block w-full p-2.5'
{...register('image')}
/>
</div>
<button
type='submit'
className='w-full text-[#FFFFFF] hover:bg-[#1877F2] bg-[#377eda] focus:ring-4 focus:outline-none focus:ring-[#93C5FD] font-medium rounded-lg text-sm px-5 py-2.5 text-center'
>
{isSubmitting || isLoading ? 'Creating...' : 'Create'}
</button>
</form>
</div>
</div>
</div>
</div>
</>
);
}
21 changes: 21 additions & 0 deletions src/components/dashboard/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState } from 'react';
import Navbar from './Navbar';
import { Outlet } from 'react-router-dom';
import Sidebar from './Sidebar';

export default function AdminLayout() {
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
setIsOpen(!isOpen);
};

return (
<>
<div className='bg-[#D3E4DE] min-h-screen'>
<Sidebar isOpen={isOpen} toggleSidebar={toggleSidebar} />
<Navbar toggleSidebar={toggleSidebar} />
<Outlet />
</div>
</>
);
}
Loading

0 comments on commit 6150a95

Please sign in to comment.