Skip to content

Commit

Permalink
Merge pull request #35 from atlp-rwanda/feature-vendor-adding-product
Browse files Browse the repository at this point in the history
Feature addition of new product by the vendor
  • Loading branch information
faid-terence authored Jun 25, 2024
2 parents 8d54242 + 3328209 commit 911c279
Show file tree
Hide file tree
Showing 39 changed files with 1,567 additions and 32 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"axios": "^1.7.2",
"axios-mock-adapter": "^1.22.0",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.395.0",
"prettier": "^3.3.1",
"react": "^18.2.0",
Expand Down
5 changes: 5 additions & 0 deletions public/1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions public/Component 3.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 public/Dashboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/notification.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 public/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/user-square.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { Toaster } from 'react-hot-toast';
function App() {
return (
<Provider store={store}>
<Layout />
<Toaster position="top-center" reverseOrder={false} />
<div data-testid="app-component">
<Layout />
<Toaster position="top-center" reverseOrder={false} />
</div>
</Provider>
);
}
Expand Down
49 changes: 31 additions & 18 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import OtpPage from '../pages/Authentication/OtpPage';
import SuspendedAccount from '../components/SuspendedAccount/SuspendedAccount';
import { ForgotPassword } from '../pages/Authentication/ForgotPassword';
import { ResetPassword } from '../pages/Authentication/ResetPassword';
import DashboardLayout from '../layout/DashboardLayout';
import DashboarInnerLayout from '../layout/DashboarInnerLayout';
import DashboardProducts from '../components/Products/DashboardProducts/DashboardProducts';
import DashboardSingleProduct from '../components/Products/DashboardSingleProduct/DashboardSingleProduct';
import DashboardNewProducts from '../components/Products/DashboardNewProducts/DashboardNewProducts';
import MainLayout from '../layout/MainLayout';

const Router = () => {
const { userToken } = useSelector((state: RootState) => state.auth);
Expand All @@ -30,105 +36,112 @@ const Router = () => {
<Route
path="/register"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Register" />
<Register />
</>
</MainLayout>
}
/>

<Route
path="/register-vendor"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Register Vendor" />
<RegisterVendor />
</>
</MainLayout>
}
/>

<Route
path="/verify-email/:token"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Verify Email" />
<VerifyEmail />
</>
</MainLayout>
}
/>

<Route
path="/forgot-password"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Forgot Password" />
<ForgotPassword />
{userToken && isAdmin && <Navigate to="/admin/dashboard" />}
{userToken && isVendor && <Navigate to="/vendor/dashboard" />}
{userToken && isBuyer && <Navigate to="/" />}
</>
</MainLayout>
}
/>

<Route
path="/reset-password"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Reset Password" />
<ResetPassword />
{userToken && isAdmin && <Navigate to="/admin/dashboard" />}
{userToken && isVendor && <Navigate to="/vendor/dashboard" />}
{userToken && isBuyer && <Navigate to="/" />}
</>
</MainLayout>
}
/>

<Route
path="/login"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Login" />
{userToken && isAdmin && <Navigate to="/admin/dashboard" />}
{userToken && isVendor && <Navigate to="/vendor/dashboard" />}
{userToken && isBuyer && <Navigate to="/" />}
{!userToken && <Login />}
</>
</MainLayout>
}
/>
<Route
path="/login/google-auth"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Login" />
{userToken && isAdmin && <Navigate to="/admin/dashboard" />}
{userToken && isVendor && <Navigate to="/vendor/dashboard" />}
{userToken && isBuyer && <Navigate to="/" />}
{!userToken && <GoogleLoginSuccess />}
</>
</MainLayout>
}
/>
<Route
path="/suspended-account"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Suspended Account" />
{userToken && <Navigate to="/" />}
<SuspendedAccount />
</>
</MainLayout>
}
/>
<Route
path="/otp-verficaton"
element={
<>
<MainLayout>
<PageTitle title="Knights Store | Verify OTP" />
<OtpPage />
{userToken && isAdmin && <Navigate to="/admin/dashboard" />}
{userToken && isVendor && <Navigate to="/vendor/dashboard" />}
{userToken && isBuyer && <Navigate to="/" />}
</>
</MainLayout>
}
/>
<Route path="/vendor/dashboard" element={<DashboardLayout />}>
<Route path="products" element={<DashboarInnerLayout />}>
<Route path="" element={<DashboardProducts />} />
<Route path=":id" element={<DashboardSingleProduct />} />
<Route path="new" element={<DashboardNewProducts />} />
</Route>
</Route>
</Routes>
);
};
Expand Down
107 changes: 107 additions & 0 deletions src/components/Dashboard/DashboardNavbar/DashboardNavbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { jwtDecode } from 'jwt-decode';
import notificationIcon from '/notification.svg';
import searchIcon from '/search.svg';
import { Link } from 'react-router-dom';
import { AlignJustify } from 'lucide-react';

interface DashboardNavBarProps {
setOpenNav: (open: boolean) => void;
}

interface TokenPayload {
email: string;
name: string;
userType: string;
exp: number;
firstName: string;
}

const DashboardNavbar: React.FC<DashboardNavBarProps> = ({ setOpenNav }) => {
const [searchTerm, setSearchTerm] = useState('');
const [currentDateTime, setCurrentDateTime] = useState('');
const [userName, setUserName] = useState('');
const navigate = useNavigate();

const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};

useEffect(() => {
const updateDateTime = () => {
const now = new Date();
const options: Intl.DateTimeFormatOptions = {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
timeZone: 'Etc/GMT-2',
timeZoneName: 'short'
};
const formattedDateTime = new Intl.DateTimeFormat('en-GB', options).format(now);
setCurrentDateTime(formattedDateTime);
};

updateDateTime();
const intervalId = setInterval(updateDateTime, 60000);

return () => clearInterval(intervalId);
}, []);

useEffect(() => {
const tokenString = localStorage.getItem('userToken');
if (!tokenString) {
navigate('/login');
return;
}

try {
const decodedToken = jwtDecode<TokenPayload>(tokenString);
//send name in token
setUserName(decodedToken.firstName);
} catch (error) {
console.error('Failed to decode token:', error);
navigate('/login');
}
}, [navigate]);

return (
<div
className="flex justify-end gap-4 lg:gap-0 flex-col-reverse items-end lg:flex-row lg:justify-between lg:items-center p-8 border-b-[1px] border-[#7c7c7c] text-black relative"
data-testid="navbar"
>
<Link to={'/vendor/dashboard'} className="lg:hidden text-4xl font-bold text-[#070f2b] absolute left-8 top-8">
Knight
</Link>
<div className="flex flex-col items-end lg:items-start">
<p className="font-bold text-xl">Welcome, {userName}</p>
<p className="text-[#7c7c7c] text-sm">{currentDateTime}</p>
</div>
<div className="flex flex-col lg:flex-row gap-4">
<div className="flex gap-4 items-center">
<button className="px-4 lg:border-r-2 border-[#7c7c7c]">
<img src={notificationIcon} alt="Notification" />
</button>
<button onClick={() => setOpenNav(true)} className="lg:hidden">
<AlignJustify />
</button>
</div>
<div className="hidden lg:flex px-4 py-2 rounded-lg border border-[#d1d1d1] gap-2">
<img src={searchIcon} alt="Search" />
<input
type="text"
className="bg-white w-[200px] outline-none"
placeholder="Search..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
</div>
</div>
);
};

export default DashboardNavbar;
76 changes: 76 additions & 0 deletions src/components/Dashboard/DashboardSideBar/DashboardSideBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import one from '/1.svg';
import two from '/3.svg';
import three from '/Component 3.svg';
import f from '/user-square.svg';
import dashboardIcon from '/Dashboard.svg';
import { CircleX } from 'lucide-react';

interface DashboardSideBarProps {
openNav: boolean;
setOpenNav: (open: boolean) => void;
}

const DashboardSideBar: React.FC<DashboardSideBarProps> = ({ openNav, setOpenNav }) => {
return (
<div
data-testid="sidebar"
className={`fixed inset-y-0 left-0 z-20 bg-white transition-transform transform ${openNav ? 'translate-x-0' : '-translate-x-full'} lg:translate-x-0 lg:relative lg:z-10 lg:flex flex-col gap-8 w-[260px] p-4 min-h-screen border-r-[1px] border-[#7c7c7c] text-black ease-in-out duration-300`}
>
<NavLink to="/vendor/dashboard" className="text-4xl font-bold text-[#070f2b]">
Knight
</NavLink>
<button onClick={() => setOpenNav(false)} className="lg:hidden absolute right-4 top-6">
<CircleX />
</button>
<div className="flex flex-col gap-4 text-[#7c7c7c] items-start w-full pt-8 lg:pt-0">
<NavLink
to="dashboard"
className={({ isActive }) =>
`flex gap-4 p-2 w-full rounded transition-all duration-300 ease-in-out ${isActive ? 'bg-[#070f2b] text-white' : ''}`
}
>
<img src={dashboardIcon} alt="Dashboard" />
Dashboard
</NavLink>
<NavLink
to="orders"
className={({ isActive }) =>
`flex gap-4 p-2 w-full rounded transition-all duration-300 ease-in-out ${isActive ? 'bg-[#070f2b] text-white' : ''}`
}
>
<img src={three} alt="Orders" /> Orders
</NavLink>
<NavLink
to="products"
className={({ isActive }) =>
`flex gap-4 p-2 w-full rounded transition-all duration-300 ease-in-out ${isActive ? 'bg-[#070f2b] text-white' : ''}`
}
>
<img src={one} alt="Products" /> Products
</NavLink>
</div>
<div className="mt-auto lg:pt-8 lg:border-t-2 text-[#7c7c7c] flex flex-col gap-4 border-[#7c7c7c] w-full pt-4 ">
<NavLink
to="account"
className={({ isActive }) =>
`flex gap-4 p-2 w-full rounded transition-all duration-300 ease-in-out ${isActive ? 'bg-[#070f2b] text-white' : ''}`
}
>
<img src={f} alt="Account" /> Account
</NavLink>
<NavLink
to="logout"
className={({ isActive }) =>
`flex gap-4 p-2 w-full rounded transition-all duration-300 ease-in-out ${isActive ? 'bg-[#070f2b] text-white' : ''}`
}
>
<img src={two} alt="Logout" /> Logout
</NavLink>
</div>
</div>
);
};

export default DashboardSideBar;
Loading

0 comments on commit 911c279

Please sign in to comment.