diff --git a/backend/src/db/api/orders.js b/backend/src/db/api/orders.js
index 5bba851..ec5ce9d 100644
--- a/backend/src/db/api/orders.js
+++ b/backend/src/db/api/orders.js
@@ -291,11 +291,20 @@ module.exports = class OrdersDBApi {
const transaction = (options && options.transaction) || undefined;
let include = [
-
+ {
+ model: db.order_items,
+ as: 'order_items_order',
+ include: [
+ {
+ model: db.products,
+ as: 'product',
+ }
+ ]
+ },
{
model: db.customers,
as: 'customer',
-
+ required: false,
where: filter.customer ? {
[Op.or]: [
{ id: { [Op.in]: filter.customer.split('|').map(term => Utils.uuid(term)) } },
diff --git a/frontend/src/components/LoginModal.tsx b/frontend/src/components/LoginModal.tsx
new file mode 100644
index 0000000..e8e86d9
--- /dev/null
+++ b/frontend/src/components/LoginModal.tsx
@@ -0,0 +1,84 @@
+
+import React, { useState } from 'react';
+import { Field, Form, Formik } from 'formik';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
+import { loginUser } from '../stores/authSlice';
+import CardBoxModal from './CardBoxModal';
+import FormField from './FormField';
+import BaseButton from './BaseButton';
+import BaseButtons from './BaseButtons';
+import { mdiEye, mdiEyeOff } from '@mdi/js';
+import BaseIcon from './BaseIcon';
+
+type Props = {
+ isActive: boolean;
+ onClose: () => void;
+};
+
+const LoginModal = ({ isActive, onClose }: Props) => {
+ const dispatch = useAppDispatch();
+ const { isFetching } = useAppSelector((state) => state.auth);
+ const [showPassword, setShowPassword] = useState(false);
+
+ const handleSubmit = (values) => {
+ dispatch(loginUser(values))
+ .unwrap()
+ .then(() => {
+ onClose();
+ })
+ .catch(() => {
+ // error is handled by slice
+ });
+ };
+
+ const togglePasswordVisibility = () => {
+ setShowPassword(!showPassword);
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default LoginModal;
diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx
index 3c09ec4..05e928d 100644
--- a/frontend/src/components/NavBar.tsx
+++ b/frontend/src/components/NavBar.tsx
@@ -1,4 +1,4 @@
-import React, { ReactNode, useState, useEffect } from 'react'
+import React, { ReactNode, useState, useEffect, useContext } from 'react'
import { mdiClose, mdiDotsVertical } from '@mdi/js'
import { containerMaxW } from '../config'
import BaseIcon from './BaseIcon'
@@ -6,6 +6,7 @@ import NavBarItemPlain from './NavBarItemPlain'
import NavBarMenuList from './NavBarMenuList'
import { MenuNavBarItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks';
+import { ModalContext } from '../context/ModalContext';
type Props = {
menu: MenuNavBarItem[]
@@ -19,6 +20,7 @@ export default function NavBar({ menu, className = '', children }: Props) {
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const { orders } = useAppSelector((state) => state.orders)
const { currentUser } = useAppSelector((state) => state.auth)
+ const { openLoginModal } = useContext(ModalContext);
const pendingOrder = orders.find(order => order.status === 'pending' && order.customer?.id === currentUser?.id);
const cartItemsCount = pendingOrder?.order_items_order?.reduce((acc, item) => acc + item.quantity, 0) || 0;
@@ -38,6 +40,13 @@ export default function NavBar({ menu, className = '', children }: Props) {
setIsMenuNavBarActive(!isMenuNavBarActive)
}
+ const menuToShow = menu.filter((item) => {
+ if (currentUser) {
+ return !item.isLogin
+ }
+ return !item.isLogout && !item.isCurrentUser
+ })
+
return (
diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index e80061c..756625b 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -15,10 +15,11 @@ import ClickOutside from "./ClickOutside";
type Props = {
item: MenuNavBarItem,
- cartItemsCount?: number
+ cartItemsCount?: number,
+ openLoginModal?: () => void
}
-export default function NavBarItem({ item, cartItemsCount }: Props) {
+export default function NavBarItem({ item, cartItemsCount, openLoginModal }: Props) {
const router = useRouter();
const dispatch = useAppDispatch();
const excludedRef = useRef(null);
@@ -59,9 +60,12 @@ export default function NavBarItem({ item, cartItemsCount }: Props) {
dispatch(setDarkMode(null))
}
+ if (item.isLogin) {
+ openLoginModal();
+ }
+
if(item.isLogout) {
dispatch(logoutUser())
- router.push('/login')
}
}
@@ -93,8 +97,13 @@ export default function NavBarItem({ item, cartItemsCount }: Props) {
item.isDesktopNoLabel && item.icon ? 'lg:hidden' : ''
}`}
>
- {itemLabel} {item.label === 'Cart' && cartItemsCount > 0 && `(${cartItemsCount})`}
+ {itemLabel}
+ {item.label === 'Cart' && cartItemsCount > 0 && (
+
+ {cartItemsCount}
+
+ )}
{item.isCurrentUser && }
{item.menu && (
void
}
-export default function NavBarMenuList({ menu, cartItemsCount }: Props) {
+export default function NavBarMenuList({ menu, cartItemsCount, openLoginModal }: Props) {
return (
<>
{menu.map((item, index) => (
-
+
))}
>
diff --git a/frontend/src/context/ModalContext.tsx b/frontend/src/context/ModalContext.tsx
new file mode 100644
index 0000000..9337c8f
--- /dev/null
+++ b/frontend/src/context/ModalContext.tsx
@@ -0,0 +1,30 @@
+
+import React, { createContext, useState, ReactNode } from 'react';
+
+export const ModalContext = createContext({
+ isLoginModalActive: false,
+ openLoginModal: () => {},
+ closeLoginModal: () => {},
+});
+
+type Props = {
+ children: ReactNode;
+};
+
+export const ModalProvider = ({ children }: Props) => {
+ const [isLoginModalActive, setIsLoginModalActive] = useState(false);
+
+ const openLoginModal = () => {
+ setIsLoginModalActive(true);
+ };
+
+ const closeLoginModal = () => {
+ setIsLoginModalActive(false);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/frontend/src/layouts/LayoutShop.tsx b/frontend/src/layouts/LayoutShop.tsx
new file mode 100644
index 0000000..2e65d27
--- /dev/null
+++ b/frontend/src/layouts/LayoutShop.tsx
@@ -0,0 +1,27 @@
+
+import React, { ReactNode, useContext } from 'react';
+import { useAppSelector } from '../stores/hooks';
+import NavBar from '../components/NavBar';
+import menuNavBar from '../menuNavBar';
+import LoginModal from '../components/LoginModal';
+import { ModalContext } from '../context/ModalContext';
+
+type Props = {
+ children: ReactNode;
+};
+
+export default function LayoutShop({ children }: Props) {
+ const darkMode = useAppSelector((state) => state.style.darkMode);
+ const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
+ const { isLoginModalActive, closeLoginModal } = useContext(ModalContext);
+
+ return (
+
+ );
+}
diff --git a/frontend/src/menuNavBar.ts b/frontend/src/menuNavBar.ts
index be5b4b6..023b761 100644
--- a/frontend/src/menuNavBar.ts
+++ b/frontend/src/menuNavBar.ts
@@ -10,7 +10,8 @@ import {
mdiThemeLightDark,
mdiGithub,
mdiVuejs,
- mdiCart
+ mdiCart,
+ mdiLogin,
} from '@mdi/js'
import { MenuNavBarItem } from './interfaces'
@@ -44,6 +45,11 @@ const menuNavBar: MenuNavBarItem[] = [
isDesktopNoLabel: true,
isToggleLightDark: true,
},
+ {
+ icon: mdiLogin,
+ label: 'Login',
+ isLogin: true,
+ },
{
icon: mdiLogout,
label: 'Log out',
diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx
index 494a671..6b60c33 100644
--- a/frontend/src/pages/_app.tsx
+++ b/frontend/src/pages/_app.tsx
@@ -16,6 +16,7 @@ import { appWithTranslation } from 'next-i18next';
import '../i18n';
import IntroGuide from '../components/IntroGuide';
import { appSteps, loginSteps, usersSteps, rolesSteps } from '../stores/introSteps';
+import { ModalProvider } from '../context/ModalContext';
// Initialize axios
axios.defaults.baseURL = process.env.NEXT_PUBLIC_BACK_API
@@ -158,42 +159,44 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
return (
- {getLayout(
- <>
-
-
+
+ {getLayout(
+ <>
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
- {(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') && }
- >
- )}
+
+
+
+
+ {(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') && }
+ >
+ )}
+
)
}
diff --git a/frontend/src/pages/cart.tsx b/frontend/src/pages/cart.tsx
index 516af72..bd53d38 100644
--- a/frontend/src/pages/cart.tsx
+++ b/frontend/src/pages/cart.tsx
@@ -1,18 +1,16 @@
-
import React, { ReactElement, useEffect } from 'react';
import Head from 'next/head'
import {
useAppDispatch,
useAppSelector
-} from "../../stores/hooks";
+} from "../stores/hooks";
import { useRouter } from "next/router";
-import { fetch as fetchOrders } from '../../stores/orders/ordersSlice'
-import LayoutAuthenticated from "../../layouts/Authenticated";
-import { getPageTitle } from "../../config";
-import SectionTitleLineWithButton from "../../components/SectionTitleLineWithButton";
-import SectionMain from "../../components/SectionMain";
-import CardBox from "../../components/CardBox";
-import BaseButton from "../../components/BaseButton";
+import { fetch as fetchOrders } from '../stores/orders/ordersSlice'
+import LayoutShop from "../layouts/LayoutShop";
+import { getPageTitle } from "../config";
+import SectionTitleLineWithButton from "../components/SectionTitleLineWithButton";
+import SectionMain from "../components/SectionMain";
+import BaseButton from "../components/BaseButton";
import { mdiCart } from "@mdi/js";
const CartPage = () => {
@@ -21,11 +19,17 @@ const CartPage = () => {
const { orders } = useAppSelector((state) => state.orders)
const { currentUser } = useAppSelector((state) => state.auth)
- const pendingOrder = orders.find(order => order.status === 'pending' && order.customer?.id === currentUser?.id);
+ const pendingOrder =
+ orders &&
+ orders.find(
+ (order) => order.status === 'Pending' && order.customer?.id === currentUser?.user.id,
+ );
useEffect(() => {
- dispatch(fetchOrders({ query: '?status=pending' }));
- }, [dispatch]);
+ if (currentUser) {
+ dispatch(fetchOrders({ query: '?status=Pending' }));
+ }
+ }, [dispatch, currentUser]);
return (
<>
@@ -35,14 +39,12 @@ const CartPage = () => {
-
<>
Order Items
-
-
+
@@ -57,7 +59,7 @@ const CartPage = () => {
pendingOrder.order_items_order.map((item: any) => (
|
- {item.product_name}
+ {item.product.name}
|
{item.quantity}
@@ -74,14 +76,13 @@ const CartPage = () => {
|
{!pendingOrder?.order_items_order?.length &&
Your cart is empty
}
-
+
>
router.push('/checkout')}
/>
-
>
);
@@ -89,11 +90,11 @@ const CartPage = () => {
CartPage.getLayout = function getLayout(page: ReactElement) {
return (
-
{page}
-
+
)
}
-export default CartPage;
+export default CartPage;
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index cfeca57..c64e369 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,38 +1,41 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useContext } from 'react';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { fetch as fetchProducts } from '../stores/products/productsSlice';
-import LayoutGuest from '../layouts/Guest';
import CardBox from '../components/CardBox';
import { getPageTitle } from '../config';
import Head from 'next/head';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import { mdiStorefrontOutline } from '@mdi/js';
-import { create as createOrder } from '../stores/orders/ordersSlice';
+import { create as createOrder, fetch as fetchOrders } from '../stores/orders/ordersSlice';
import { create as createOrderItem } from '../stores/order_items/order_itemsSlice';
+import { ModalContext } from '../context/ModalContext';
+import LayoutShop from '../layouts/LayoutShop';
const IndexPage = () => {
const dispatch = useAppDispatch();
const { products, loading } = useAppSelector((state) => state.products);
const { orders } = useAppSelector((state) => state.orders);
const { currentUser } = useAppSelector((state) => state.auth);
+ const { openLoginModal } = useContext(ModalContext);
useEffect(() => {
dispatch(fetchProducts({}));
+ dispatch(fetchOrders({}));
}, [dispatch]);
const handleAddToCart = async (product) => {
if (!currentUser) {
- alert('Please login to add items to your cart.');
+ openLoginModal();
return;
}
- let pendingOrder = orders.find(order => order.status === 'pending' && order.customer?.id === currentUser.id);
+ let pendingOrder = orders.find(order => order.status === 'Pending' && order.customer?.id === currentUser.id);
if (!pendingOrder) {
const orderData = {
customer: currentUser.id,
- status: 'pending',
+ status: 'Pending',
};
const newOrderAction = await dispatch(createOrder(orderData));
if (newOrderAction.payload) {
@@ -52,17 +55,13 @@ const IndexPage = () => {
}
};
-
-
-
return (
-
+ <>
{getPageTitle('Shop')}
-
-
+
{loading && Loading...
}
@@ -89,8 +88,12 @@ const IndexPage = () => {
)}
-
+ >
);
};
+IndexPage.getLayout = function getLayout(page) {
+ return {page};
+};
+
export default IndexPage;
\ No newline at end of file
diff --git a/frontend/src/stores/authSlice.ts b/frontend/src/stores/authSlice.ts
index 2eaa1f2..6960459 100644
--- a/frontend/src/stores/authSlice.ts
+++ b/frontend/src/stores/authSlice.ts
@@ -86,8 +86,10 @@ export const authSlice = createSlice({
const token = action.payload;
const user = jwt.decode(token);
+ state.isFetching = false;
state.errorMessage = '';
state.token = token;
+ state.currentUser = user;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;