From 87f1a1e3e8408d26eab8cffa6195d65b6b577b24 Mon Sep 17 00:00:00 2001 From: Ebube Date: Thu, 20 Jul 2023 10:22:10 +0100 Subject: [PATCH] Notification for header --- .../{messages.svg => message.svg} | 0 src/components/Helpers/TimeDifference.jsx | 36 +++ src/components/MyWallet/Balance.jsx | 236 ++++++++++-------- src/components/Partials/Header.jsx | 229 ++++------------- .../Settings/Tabs/RecipientAccountTab.jsx | 3 +- src/lib/fomattedDate.js | 17 ++ src/middleware/AuthRoute.jsx | 132 ++++++++-- src/store/notifications.js | 22 ++ src/store/store.js | 12 +- src/views/NotificationPage.jsx | 1 - 10 files changed, 362 insertions(+), 326 deletions(-) rename src/assets/images/notifications/{messages.svg => message.svg} (100%) create mode 100644 src/components/Helpers/TimeDifference.jsx create mode 100644 src/lib/fomattedDate.js create mode 100644 src/store/notifications.js diff --git a/src/assets/images/notifications/messages.svg b/src/assets/images/notifications/message.svg similarity index 100% rename from src/assets/images/notifications/messages.svg rename to src/assets/images/notifications/message.svg diff --git a/src/components/Helpers/TimeDifference.jsx b/src/components/Helpers/TimeDifference.jsx new file mode 100644 index 0000000..7988225 --- /dev/null +++ b/src/components/Helpers/TimeDifference.jsx @@ -0,0 +1,36 @@ +const TimeDifference = ({ time }) => { + const currentTime = new Date(); + const providedTime = new Date(time); + + const timeDifference = currentTime - providedTime; // Difference in milliseconds + + const minutes = Math.floor(timeDifference / (1000 * 60)); + const hours = Math.floor(timeDifference / (1000 * 60 * 60)); + const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); + const months = Math.floor(timeDifference / (1000 * 60 * 60 * 24 * 30)); + + if (minutes < 1) { + return "Just now"; + } else if (minutes < 60) { + return `${minutes} ${minutes === 1 ? "minute" : "minutes"} ago`; + } else if (hours < 12) { + return `${hours} ${hours === 1 ? "hour" : "hours"} ago`; + } else if (hours < 24) { + return "Today"; + } else if (days < 2) { + return "Tomorrow"; + } else if (days < 7) { + return `${days} ${days === 1 ? "day" : "days"} ago`; + } else if (months < 1) { + const weeks = Math.floor(days / 7); + return `${weeks} ${weeks === 1 ? "week" : "weeks"} ago`; + } else if (months < 6) { + return `${months} ${months === 1 ? "month" : "months"} ago`; + }else if (months < 8) { + return `"More than 6 months ago"`; + } else { + return time?.split(" ")[0]; + } +}; + +export default TimeDifference; diff --git a/src/components/MyWallet/Balance.jsx b/src/components/MyWallet/Balance.jsx index 391e037..5214f38 100644 --- a/src/components/MyWallet/Balance.jsx +++ b/src/components/MyWallet/Balance.jsx @@ -1,115 +1,129 @@ -import React, {useState} from 'react' -import { Link } from 'react-router-dom' -import RecentActivityTable from './WalletComponent/RecentActivityTable' -import PurchasesTable from './WalletComponent/PurchasesTable' -import CouponTable from './WalletComponent/CouponTable' -import LoadingSpinner from '../Spinners/LoadingSpinner' -import { PriceFormatter } from '../Helpers/PriceFormatter' +import React from "react"; +import { Link } from "react-router-dom"; +import { PriceFormatter } from "../Helpers/PriceFormatter"; +import LoadingSpinner from "../Spinners/LoadingSpinner"; -function Balance({wallet, coupon}) { - return ( -
- {/* heading */} -
-
-

- Wallet -

-
-
- -
-
-
- {/* WALLET SECTION */} -
-
-
- {/*

Wallet

*/} - {/*

Add New Wallet

*/} -
- {/* wallet balance */} - {wallet.loading ? - - : - wallet.data.length ? - wallet.data.map((item, index)=> ( -
-
-
-

- {item.description} -

{item.symbol}

-
-
-

Balance

- {PriceFormatter(item.amount * 0.01, item.code)} -
-
-

Escrow

- {PriceFormatter(item.escrow * 0.01, item.code)} -
-
- -
- { - item.action_type != 'AC_AD_FD_ONLY' ? - Transfer:'' - } - - - - - - Add Credit - -
-
- )) - : - wallet.error ? - ( -
-

Opps! An Error occurred, please try again

-
- ) - : - ( -
-

No Wallets Found!

-
- ) - } - {/* end of wallet balance */} -
-
- {/* END OF WALLET SECTION */} - - - -
- - {/*
*/} - - {/* /!* COUPON SECTION *!/*/} - {/*
*/} - {/*
*/} - {/*

Coupons

*/} - {/* {coupon.loading ?*/} - {/* */} - {/* :*/} - {/* */} - {/* }*/} - {/*
*/} - {/*
*/} - {/* /!* END OF COUPON SECTION *!/*/} - - {/*
*/} +function Balance({ wallet, coupon }) { + return ( +
+ {/* heading */} +
+
+

+ Wallet +

- ) +
+
+
+ {/* WALLET SECTION */} +
+
+
+ {/*

Wallet

*/} + {/*

Add New Wallet

*/} +
+ {/* wallet balance */} + {wallet.loading ? ( + + ) : wallet.data.length ? ( + wallet.data.map((item, index) => ( +
+
+
+

+ + {item.description} + +

{item.symbol}

+
+
+

Balance

+ + {PriceFormatter(item.amount * 0.01, item.code)} + +
+
+

Escrow

+ + {PriceFormatter(item.escrow * 0.01, item.code)} + +
+
+ +
+ {item.action_type != "AC_AD_FD_ONLY" ? ( + + Transfer + + ) : ( + "" + )} + + + + + + + Add Credit + +
+
+ )) + ) : wallet.error ? ( +
+

+ Opps! An Error occurred, please try again +

+
+ ) : ( +
+

No Wallets Found!

+
+ )} + {/* end of wallet balance */} +
+
+ {/* END OF WALLET SECTION */} +
+ + {/*
*/} + + {/* /!* COUPON SECTION *!/*/} + {/*
*/} + {/*
*/} + {/*

Coupons

*/} + {/* {coupon.loading ?*/} + {/* */} + {/* :*/} + {/* */} + {/* }*/} + {/*
*/} + {/*
*/} + {/* /!* END OF COUPON SECTION *!/*/} + + {/*
*/} +
+ ); } -export default Balance \ No newline at end of file +export default Balance; diff --git a/src/components/Partials/Header.jsx b/src/components/Partials/Header.jsx index 73e8dae..cfe906d 100644 --- a/src/components/Partials/Header.jsx +++ b/src/components/Partials/Header.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useMemo, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import bank1 from "../../assets/images/bank-1.png"; import bank2 from "../../assets/images/bank-2.png"; @@ -15,18 +15,18 @@ import WalletHeader from "../MyWallet/WalletHeader"; import { useSelector } from "react-redux"; import Flag from "../../assets/images/united-states.svg"; import siteLogo from "../../assets/images/wrenchboard.png"; +import TimeDifference from "../Helpers/TimeDifference"; export default function Header({ logoutModalHandler, sidebarHandler }) { const [balanceDropdown, setbalanceValue] = useToggle(false); const [notificationDropdown, setNotificationValue] = useToggle(false); const [userProfileDropdown, setProfileDropdown] = useToggle(false); const [moneyPopup, setPopup] = useToggle(false); - const [toggleNotification, setToggleNotification] = useToggle(false); const darkMode = useContext(DarkModeContext); const { userDetails } = useSelector((state) => state?.userDetails); - + const { notifications } = useSelector((state) => state?.notifications); const [myWalletList, setMyWalletList] = useState([]); - const api = new usersService(); + const api = useMemo(() => new usersService(), []); const getMyWalletList = async () => { try { @@ -84,10 +84,6 @@ export default function Header({ logoutModalHandler, sidebarHandler }) { setbalanceValue.set(false); }; - const setNotification = () => { - setToggleNotification.toggle(); - }; - // getting the location of head let { pathname } = useLocation(); @@ -101,6 +97,8 @@ export default function Header({ logoutModalHandler, sidebarHandler }) { let { firstname, lastname, email, profile_pic } = userDetails; let userEmail = email?.split("@")[0]; + // console.log("Notify: ", notifications?.data?.raw); + return ( <>
@@ -251,7 +249,9 @@ export default function Header({ logoutModalHandler, sidebarHandler }) { > - 10 + {notifications?.loading + ? "●" + : notifications?.data?.raw?.length}
+
    -
  • -
    -
    - - - ( +
  • +
    +
    + icon - -
    -
    -

    - Your Account has been created - +

    +
    +

    + {item?.title} + {/* successfully done - -

    -

    - 23 house ago -

    + */} +

    +

    + +

    +
    -
- -
  • -
    -
    - - - - - - -
    -
    -

    - You upload your frast product - - successfully done - -

    -

    - 23 house ago -

    -
    -
    -
  • -
  • -
    -
    - - - - - - - - -
    -
    -

    - Thank you ! - you made your frast sell - 232.98 ETH -

    -

    - 23 house ago -

    -
    -
    -
  • -
  • -
    -
    - - - - - - -
    -
    -

    - Your Account has been created - - successfully done - -

    -

    - 23 house ago -

    -
    -
    -
  • -
  • -
    -
    - - - - -
    -
    -

    - Your Account has been created - - successfully done - -

    -

    - 23 house ago -

    -
    -
    -
  • + + ))} +
    { - setRecipientAccount({ state: true, data: item }); + // setRecipientAccount({ state: true, data: item }); + console.log("Testing...") }} className="w-[95px] sm:h-[46px] h-[40px] rounded-full btn-gradient text-white sm:text-18 text-md tracking-wide" > diff --git a/src/lib/fomattedDate.js b/src/lib/fomattedDate.js new file mode 100644 index 0000000..305d6ad --- /dev/null +++ b/src/lib/fomattedDate.js @@ -0,0 +1,17 @@ +export default function formattedDate(dateString) { + const parts = dateString.split(" "); + const datePart = parts[0]; + const timePart = parts[1]; + + const [month, day, year] = datePart.split("-").map(Number); + let [hour, minute] = timePart.slice(0, -2).split(":").map(Number); + + // Convert 12-hour time to 24-hour time if necessary + if (timePart.endsWith("PM") && hour !== 12) { + hour += 12; + } else if (timePart.endsWith("AM") && hour === 12) { + hour = 0; + } + + return new Date(year, month - 1, day, hour, minute); +} diff --git a/src/middleware/AuthRoute.jsx b/src/middleware/AuthRoute.jsx index a3be098..b2d6c15 100644 --- a/src/middleware/AuthRoute.jsx +++ b/src/middleware/AuthRoute.jsx @@ -1,12 +1,14 @@ -import { useEffect, useState, useCallback, useMemo } from "react"; -import { Navigate, Outlet, useLocation, useNavigate } from "react-router-dom"; -import usersService from "../services/UsersService"; -import LoadingSpinner from "../components/Spinners/LoadingSpinner"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { Navigate, Outlet, useNavigate } from "react-router-dom"; +import LoadingSpinner from "../components/Spinners/LoadingSpinner"; +import formattedDate from "../lib/fomattedDate"; +import usersService from "../services/UsersService"; +import { commonHeadBanner } from "../store/CommonHeadBanner"; import { updateUserDetails } from "../store/UserDetails"; import { updateJobs } from "../store/jobLists"; +import { updateNotifications } from "../store/notifications"; import { updateUserJobList } from "../store/userJobList"; -import { commonHeadBanner } from "../store/CommonHeadBanner"; const AuthRoute = ({ redirectPath = "/login", children }) => { const apiCall = useMemo(() => new usersService(), []); @@ -18,9 +20,11 @@ const AuthRoute = ({ redirectPath = "/login", children }) => { const { jobListTable } = useSelector((state) => state.tableReload); - const { userDetails:{username, uid} } = useSelector((state) => state?.userDetails); // CHECKS IF USER Details are avaliable, to determine if user is active + const { + userDetails: { username, uid }, + } = useSelector((state) => state?.userDetails); // CHECKS IF USER Details are avaliable, to determine if user is active - let loggedIn = username && uid ? true : false // variable to determine if user is logged in + let loggedIn = username && uid ? true : false; // variable to determine if user is logged in useEffect(() => { //Removing Data stored at localStorage after session expires @@ -84,7 +88,7 @@ const AuthRoute = ({ redirectPath = "/login", children }) => { return; } setLoadProfileDetails(res.data); - dispatch(updateUserDetails({...res.data, loggedIn:true})); + dispatch(updateUserDetails({ ...res.data, loggedIn: true })); setIsLogin({ loading: false, status: true }); }) .catch((error) => { @@ -95,6 +99,91 @@ const AuthRoute = ({ redirectPath = "/login", children }) => { } }, []); + // const filterNotifications = (notifications, filterType) => { + // const currentDate = new Date(); + // if (filterType === "today") { + // return notifications?.filter((notification) => { + // const notificationDate = new Date(notification?.date); + // console.log(notificationDate) + // // return ( + // // notificationDate.getDate() === currentDate.getDate() && + // // notificationDate.getMonth() === currentDate.getMonth() && + // // notificationDate.getFullYear() === currentDate.getFullYear() + // // ); + // }); + // } else if (filterType === "days") { + // const sevenDaysAgo = new Date(currentDate); + // sevenDaysAgo.setDate(currentDate.getDate() - 7); + // return notifications?.filter((notification) => { + // const notificationDate = new Date(notification?.date); + // return notificationDate >= sevenDaysAgo; + // }); + // } else { + // return notifications; + // } + // }; + + useEffect(() => { + if (!loggedIn) { + const getNotifications = () => { + // function to load user notification + dispatch(updateNotifications({ loading: true })); + apiCall + .getMyNotifications() + .then((res) => { + if (res.data.internal_return < 0) { + dispatch(updateNotifications({ loading: false })); + return; + } + setLoadProfileDetails(res.data); + + const _raw = res.data?.result_list; + + //Sort the notifications in ascending order based on the API time + const _sorted = _raw?.sort((a, b) => { + const timeA = new Date(a?.date)?.getTime(); + const timeB = new Date(b?.date)?.getTime(); + return timeA - timeB; + }); + + // header component + const _header = _sorted?.slice(0, 5); + // Notification Layout + const _today = _sorted?.slice(0, 7); + + const _days = () => { + const sevenDaysAgo = new Date(); + sevenDaysAgo.setDate(new Date() - 7); + return _sorted?.filter((notification) => { + const notificationDate = new Date( + formattedDate(notification?.date) + ); + return notificationDate >= sevenDaysAgo; + }); + }; + + // Dispatch all notifications, sorted, and recent based on their filter type + dispatch( + updateNotifications({ + loading: false, + data: { + raw: _raw, + today: _today, + // days: _days(), + sort: _sorted, + header: _header, + }, + }) + ); + }) + .catch((error) => { + dispatch(updateNotifications({ loading: false })); + }); + }; + getNotifications(); + } + }, []); + useEffect(() => { const getMyJobList = async () => { dispatch(updateUserJobList({ loading: true, data: [] })); @@ -126,22 +215,23 @@ const AuthRoute = ({ redirectPath = "/login", children }) => { }, [apiCall, dispatch]); //FUNCTION TO GET COMMON HEAD DATA - useEffect(()=>{ - apiCall.getHeroJBanners().then((res) => { - if (res.data.internal_return < 0) { - return; - } - dispatch(commonHeadBanner(res.data)); - }) - .catch((error) => { - console.log('ERROR ', error) - }); - },[]) + useEffect(() => { + apiCall + .getHeroJBanners() + .then((res) => { + if (res.data.internal_return < 0) { + return; + } + dispatch(commonHeadBanner(res.data)); + }) + .catch((error) => { + console.log("ERROR ", error); + }); + }, []); return isLogin.loading && !loggedIn ? ( - ) : - !isLogin.status && !loggedIn ? ( + ) : !isLogin.status && !loggedIn ? ( ) : ( children || diff --git a/src/store/notifications.js b/src/store/notifications.js new file mode 100644 index 0000000..f703af0 --- /dev/null +++ b/src/store/notifications.js @@ -0,0 +1,22 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + notifications: { + loading: false, + data: null, + }, +}; + +export const notificationsSlice = createSlice({ + name: "notifications", + initialState, + reducers: { + updateNotifications: (state, action) => { + state.notifications = { ...action.payload }; + }, + }, +}); + +export const { updateNotifications } = notificationsSlice.actions; + +export default notificationsSlice.reducer; diff --git a/src/store/store.js b/src/store/store.js index a042c79..3f563b6 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -1,11 +1,12 @@ import { configureStore } from "@reduxjs/toolkit"; import drawerReducer from "./drawer"; +import commonHeadBannerReducer from "./CommonHeadBanner"; +import tableReloadReducer from "./TableReloads"; import userDetailReducer from "./UserDetails"; import jobReducer from "./jobLists"; -import tableReloadReducer from "./TableReloads"; -import userJobListReducer from './userJobList' -import commonHeadBannerReducer from './CommonHeadBanner' +import notificationsReducer from "./notifications"; +import userJobListReducer from "./userJobList"; export default configureStore({ reducer: { @@ -14,6 +15,7 @@ export default configureStore({ jobLists: jobReducer, tableReload: tableReloadReducer, userJobList: userJobListReducer, - commonHeadBanner: commonHeadBannerReducer + commonHeadBanner: commonHeadBannerReducer, + notifications: notificationsReducer, }, -}); \ No newline at end of file +}); diff --git a/src/views/NotificationPage.jsx b/src/views/NotificationPage.jsx index 20169d2..220a3a2 100644 --- a/src/views/NotificationPage.jsx +++ b/src/views/NotificationPage.jsx @@ -1,4 +1,3 @@ -import React from "react"; import Notification from "../components/Notification"; export default function notification() {