From faefc4e7650c04e5e5685f2e556ea5a42c1e2182 Mon Sep 17 00:00:00 2001 From: Juan Miguel Sanchez Mola Date: Tue, 16 Jul 2024 10:42:54 -0400 Subject: [PATCH] Revert "Merge branch 'main' into dev" This reverts commit c4682883d2f22e4a6d50514beb6f32afb1f568a8, reversing changes made to f3f2e4fa9929db8feafc94d48db349ad57f97036. --- .env.sample | 2 +- README.md | 4 +- rair-front/Dockerfile.prod | 4 +- rair-front/nginx/nginx.conf.ssl | 2 +- rair-front/src/App.tsx | 17 + .../src/components/Header/MainHeader.tsx | 36 +- .../MockUpPage/FilteringBlock/modal/index.tsx | 4 +- .../actions/actionFavorites.ts | 12 +- .../PaginationBox/PaginationBox.tsx | 17 +- .../src/components/MockUpPage/SearchPanel.tsx | 2 +- .../components/MockUpPage/mockupPage.types.ts | 3 +- rair-front/src/components/Navigation/Menu.tsx | 75 ++++- .../MenuComponents/MobileChoiseNav.tsx | 29 +- .../MenuComponents/MobileListMenu.tsx | 2 +- .../MenuComponents/MobileNavigationList.tsx | 317 ++++++++++++------ .../src/components/SplashPage/SplashPage.css | 15 +- .../NotificationPage/NotificationPage.css | 27 ++ .../NotificationPage/NotificationPage.tsx | 38 +-- .../NotificationBox/NotificationBox.css | 18 + .../NotificationBox/NotificationBox.tsx | 101 ++++++ .../PopUpNotification/PopUpNotification.tsx | 194 ++++++----- .../UploadProfilePicture/Edit/Edit.module.css | 12 +- .../Profile/Profile.module.css | 12 +- .../UserProfileSettings.css | 62 +++- .../components/adminViews/ServerSettings.tsx | 126 +++++++ .../adminViews/useServerSettings.tsx | 4 + rair-front/src/ducks/auth/actions.ts | 14 +- rair-front/src/ducks/metadata/actions.ts | 8 +- rair-front/src/ducks/pages/actions.ts | 8 +- rair-front/src/ducks/resales/actions.ts | 12 +- rair-front/src/ducks/search/actions.ts | 8 +- rair-front/src/ducks/seo/actions.ts | 4 +- rair-front/src/ducks/uploadDemo/action.ts | 8 +- rair-front/src/ducks/videos/actions.ts | 12 +- rair-front/src/index.css | 38 ++- .../SocialLinkIcons/SocialLinkIcons.tsx | 17 + .../src/styled-components/nft/Token.styles.ts | 2 +- rair-node/README.md | 6 +- rair-node/bin/api/auth/auth.Service.js | 10 +- .../api/categories/categories.Controller.js | 26 ++ .../bin/api/categories/categories.Service.js | 63 ++++ rair-node/bin/api/nft/nft.Controller.js | 28 +- rair-node/bin/api/nft/nft.Service.js | 70 ++-- .../notifications/notifications.Controller.js | 8 +- .../notifications/notifications.Service.js | 46 ++- rair-node/bin/api/tokens/tokens.Controller.js | 9 - rair-node/bin/api/tokens/tokens.Service.js | 28 +- .../integrations/ethers/importContractData.js | 11 +- rair-node/bin/models/notification.js | 2 +- rair-node/bin/models/serverSettings.js | 1 + rair-node/bin/routes/index.js | 2 + rair-node/bin/schemas/commonApiSchemas.js | 3 + rair-node/bin/schemas/databaseSchemas.js | 7 + rair-node/bin/schemas/index.js | 2 + rair-node/bin/schemas/v2TokenSchemas.js | 3 +- rair-node/bin/seeds/index.js | 19 +- .../current/nft/nft_manual_attributes.md | 10 +- .../readme/current/nft/nft_manual_files.md | 10 +- .../current/nft/nft_manual_files_token.md | 10 +- .../readme/current/nft/nft_manual_locks.md | 10 +- .../readme/current/nft/nft_manual_numbers.md | 49 +++ .../readme/current/nft/nft_manual_offers.md | 10 +- .../readme/current/nft/nft_manual_product.md | 10 +- .../readme/current/notifications/delete.md | 15 +- .../readme/current/notifications/get_list.md | 4 +- .../readme/current/notifications/mark_read.md | 15 +- .../readme/current/tokens/tokens_numbers.md | 34 -- 67 files changed, 1281 insertions(+), 506 deletions(-) create mode 100644 rair-front/src/components/UserProfileSettings/PopUpNotification/NotificationBox/NotificationBox.css create mode 100644 rair-front/src/components/UserProfileSettings/PopUpNotification/NotificationBox/NotificationBox.tsx create mode 100644 rair-node/bin/api/categories/categories.Controller.js create mode 100644 rair-node/bin/api/categories/categories.Service.js create mode 100644 rair-node/readme/current/nft/nft_manual_numbers.md delete mode 100644 rair-node/readme/current/tokens/tokens_numbers.md diff --git a/.env.sample b/.env.sample index 5f751562f..2d19d1707 100644 --- a/.env.sample +++ b/.env.sample @@ -42,7 +42,7 @@ base_mainnet_rpc=https://base-mainnet.g.alchemy.com/v2/LPVexfumI81FHrSqJyhwpZ9yb #default image for NFTs that have no metadata default_product_cover=https://rair.myfilebase.com/ipfs/QmcV94NurwfWVGpXTST1we8uDbYiVQamKe87WEHK6DRzqa #ipfs configuration - pinata or ipfs -ipfs_service=pinata +ipfs_service=filebase ipfs_gateway=http://rairipfs:8080/ipfs ipfs_api=http://rairipfs:5001 #gcp storage configuration diff --git a/README.md b/README.md index f360f5c41..ebf0cdf61 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ # Getting Started _Building RAIR is a snap! Follow these simple steps and you'll be up and running in no time._ -![click walkthrough no narration](https://github.com/rairprotocol/RAIRsite/blob/main/src/assets/images/rair-install.webm) + + +![download video from git](https://github.com/rairprotocol/RAIRsite/blob/main/src/assets/images/rair-install.webm) ## Clone the RAIR repository diff --git a/rair-front/Dockerfile.prod b/rair-front/Dockerfile.prod index 2b0d8affa..06bf63f38 100644 --- a/rair-front/Dockerfile.prod +++ b/rair-front/Dockerfile.prod @@ -1,6 +1,6 @@ # build environment -FROM node:21.2.0 as build +FROM node:22.4.0 as build WORKDIR /usr/src/minting @@ -22,4 +22,4 @@ COPY --from=build /usr/src/minting/nginx/nginx.conf /etc/nginx/conf.d/default.co EXPOSE 80 # The default parameters to ENTRYPOINT (unless overruled on the command line) -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +CMD ["nginx", "-g", "daemon off;"] diff --git a/rair-front/nginx/nginx.conf.ssl b/rair-front/nginx/nginx.conf.ssl index 3b9ef8868..b2020dc2d 100644 --- a/rair-front/nginx/nginx.conf.ssl +++ b/rair-front/nginx/nginx.conf.ssl @@ -58,7 +58,7 @@ server { } location /socket.io { - proxy_pass http://rair-stream:5002; + proxy_pass http://rair-node:5000; client_max_body_size 200000M; } } diff --git a/rair-front/src/App.tsx b/rair-front/src/App.tsx index 911c04ce6..4ed5da3ae 100644 --- a/rair-front/src/App.tsx +++ b/rair-front/src/App.tsx @@ -97,6 +97,7 @@ import ErrorFallback from './views/ErrorFallback/ErrorFallback'; import 'bootstrap/dist/css/bootstrap.min.css'; import './App.css'; +import { rFetch } from './utils/rFetch'; /* Track a page view */ // const analytics = getInformationGoogleAnalytics(); // analytics.page(); @@ -133,6 +134,7 @@ function App() { const [tabIndexItems, setTabIndexItems] = useState(0); const [tokenNumber, setTokenNumber] = useState(undefined); const navigate = useNavigate(); + const [notificationCount, setNotificationCount] = useState(0); // Redux const { primaryColor, textColor, backgroundImage, backgroundImageEffect } = @@ -194,6 +196,19 @@ function App() { } }, [dispatch, logoutUser]); + const getNotificationsCount = useCallback( async () => { + if (currentUserAddress) { + const result = await rFetch(`/api/notifications?onlyUnread=true`); + if (result.success && result.totalCount > 0) { + setNotificationCount(result.totalCount); + } + } + }, [currentUserAddress]); + + useEffect(() => { + getNotificationsCount(); + }, [getNotificationsCount]) + // gtag useEffect(() => { @@ -367,6 +382,8 @@ function App() { selectedChain={correctBlockchain(realChain)} setTabIndexItems={setTabIndexItems} isAboutPage={isAboutPage} + notificationCount={notificationCount} + getNotificationsCount={getNotificationsCount} /> ) )} diff --git a/rair-front/src/components/Header/MainHeader.tsx b/rair-front/src/components/Header/MainHeader.tsx index a37f5b8c8..13c0395aa 100644 --- a/rair-front/src/components/Header/MainHeader.tsx +++ b/rair-front/src/components/Header/MainHeader.tsx @@ -37,6 +37,7 @@ import TalkSalesComponent from './HeaderItems/TalkToSalesComponent/TalkSalesComp //styles import './Header.css'; +import { rFetch } from '../../utils/rFetch'; const MainHeader: React.FC = ({ goHome, @@ -70,6 +71,8 @@ const MainHeader: React.FC = ({ ); const hotdropsVar = import.meta.env.VITE_TESTNET; + const [realDataNotification, setRealDataNotification] = useState([]); + const [notificationCount, setNotificationCount] = useState(0); const [textSearch, setTextSearch] = useState(''); const [adminPanel, setAdminPanel] = useState(false); @@ -122,6 +125,35 @@ const MainHeader: React.FC = ({ setTextSearch(''); }; + const getNotifications = useCallback(async (pageNum?: number) => { + if(currentUserAddress) { + // const result = await rFetch(`/api/notifications${itemsPerPage && pageNum ? `?itemsPerPage=${itemsPerPage}&pageNum=${pageNum}` : ''}`); + const result = await rFetch(`/api/notifications${`?pageNum=${Number(pageNum)}`}`); + + if (result.success) { + setRealDataNotification(result.notifications); + } + } + }, [currentUserAddress]); + + const getNotificationsCount = useCallback( async () => { + if(currentUserAddress) { + const result = await rFetch(`/api/notifications?onlyUnread=true`); + if (result.success && result.totalCount > 0) { + setNotificationCount(result.totalCount); + } + } + }, [currentUserAddress]) + + useEffect(() => { + getNotificationsCount(); + }, [getNotificationsCount]) + + + useEffect(() => { + getNotifications(0); + }, [currentUserAddress]) + const Highlight = (props) => { const { filter, str } = props; if (!filter) return str; @@ -206,7 +238,7 @@ const MainHeader: React.FC = ({ backgroundColor: primaryColor }} type="text" - placeholder="Search the rairverse..." + placeholder="Search..." onChange={handleChangeText} value={textSearch} onClick={() => setIsComponentVisible(true)} @@ -414,7 +446,7 @@ const MainHeader: React.FC = ({ isSplashPage={isSplashPage} />
- {currentUserAddress && } + {currentUserAddress && } ({ type: types.ADD_ITEM_FAVORITES_START - } as const); + }) as const; const addItemFavoriteEnd = () => ({ type: types.ADD_ITEM_FAVORITES_END - } as const); + }) as const; const removeItemFavoriteEnd = () => ({ type: types.REMOVE_ITEM_FAVORITES_END - } as const); + }) as const; const getCurrentItemSuccess = (item: TTokenData | null) => ({ type: types.GET_CURRENT_ITEM_SUCCESS, item - } as const); + }) as const; const getCurrentItemFalse = () => ({ type: types.GET_CURRENT_ITEM_FALSE - } as const); + }) as const; const errorFavorites = () => ({ type: types.ERROR_FAVORITES - } as const); + }) as const; export { addItemFavoriteEnd, diff --git a/rair-front/src/components/MockUpPage/PaginationBox/PaginationBox.tsx b/rair-front/src/components/MockUpPage/PaginationBox/PaginationBox.tsx index a759cb8e2..081e3bd7b 100644 --- a/rair-front/src/components/MockUpPage/PaginationBox/PaginationBox.tsx +++ b/rair-front/src/components/MockUpPage/PaginationBox/PaginationBox.tsx @@ -11,12 +11,16 @@ const PaginationBox: React.FC = ({ changePage, currentPage, totalPageForPagination, - whatPage + whatPage, + itemsPerPageNotifications }) => { const itemsPerPage = useSelector( (store) => store.nftDataStore.itemsPerPage ); + console.info(totalPageForPagination, 'totalPageForPagination'); + console.info(itemsPerPageNotifications, 'itemsPerPageNotifications') + const { primaryColor, primaryButtonColor } = useSelector< RootState, ColorStoreType @@ -26,6 +30,8 @@ const PaginationBox: React.FC = ({ const [totalPage, setTotalPages] = useState(); const [totalPageVideo, setTotalPagesVideo] = useState(); + console.info(totalPage, 'totalPage') + // const hotdropsVar = import.meta.env.VITE_TESTNET; const pagesArray: number[] = []; @@ -37,6 +43,10 @@ const PaginationBox: React.FC = ({ for (let i = 0; i < totalPageVideo; i++) { pagesArray.push(i + 1); } + } else if(whatPage && whatPage === 'notifications' && totalPage) { + for (let i = 0; i < totalPage; i++) { + pagesArray.push(i + 1); + } } const getPagesCount = (totalCount: number, itemsPerPage: number) => { @@ -56,7 +66,10 @@ const PaginationBox: React.FC = ({ } else if (totalPageForPagination && whatPage === 'video') { setTotalPagesVideo(getPagesCount(totalPageForPagination, itemsPerPage)); } - }, [setTotalPages, totalPageForPagination, itemsPerPage, whatPage]); + else if(totalPageForPagination && whatPage === 'notifications' && itemsPerPageNotifications){ + setTotalPages(getPagesCount(totalPageForPagination, itemsPerPageNotifications)); + } + }, [setTotalPages, totalPageForPagination, itemsPerPage, whatPage, itemsPerPageNotifications]); if (totalPageForPagination === 0) { return null; diff --git a/rair-front/src/components/MockUpPage/SearchPanel.tsx b/rair-front/src/components/MockUpPage/SearchPanel.tsx index c5eb174b8..cb23aae3f 100644 --- a/rair-front/src/components/MockUpPage/SearchPanel.tsx +++ b/rair-front/src/components/MockUpPage/SearchPanel.tsx @@ -226,7 +226,7 @@ const SearchPanel: React.FC = ({ tabIndex, setTabIndex }) => { primaryColor === '#dedede' ? 'default' : 'dark' }`} className="category-button-nft category-button"> - {hotdropsVar === 'true' ? 'Collectible' : 'NFT'} + MARKET { diff --git a/rair-front/src/components/MockUpPage/mockupPage.types.ts b/rair-front/src/components/MockUpPage/mockupPage.types.ts index 133def035..70980433f 100644 --- a/rair-front/src/components/MockUpPage/mockupPage.types.ts +++ b/rair-front/src/components/MockUpPage/mockupPage.types.ts @@ -60,13 +60,14 @@ export interface ISvgLock { color: string; } -export type TWhatPage = 'nft' | 'video'; +export type TWhatPage = 'nft' | 'video' | 'notifications'; export interface IPaginationBox { changePage: (currentPage: number) => void; currentPage: number; totalPageForPagination: number | undefined; whatPage: TWhatPage; + itemsPerPageNotifications?: number; } export interface ILikeButton { diff --git a/rair-front/src/components/Navigation/Menu.tsx b/rair-front/src/components/Navigation/Menu.tsx index 1ebc62205..44ef45750 100644 --- a/rair-front/src/components/Navigation/Menu.tsx +++ b/rair-front/src/components/Navigation/Menu.tsx @@ -25,7 +25,6 @@ import { UserIconMobile } from '../../styled-components/SocialLinkIcons/SocialLinkIcons'; import chainData from '../../utils/blockchainData'; -import LoadingComponent from '../common/LoadingComponent'; import { SvgUserIcon } from '../UserProfileSettings/SettingsIcons/SettingsIcons'; import MobileChoiseNav from './MenuComponents/MobileChoiseNav'; @@ -38,6 +37,7 @@ import { } from './NavigationItems/NavigationItems'; import './Menu.css'; +import { rFetch } from '../../utils/rFetch'; interface IMenuNavigation { connectUserData: () => void; @@ -53,6 +53,8 @@ interface IMenuNavigation { isSplashPage: boolean; isAboutPage: boolean; realChainId: string | undefined; + notificationCount?: number; + getNotificationsCount?: any; } const MenuNavigation: React.FC = ({ @@ -61,7 +63,9 @@ const MenuNavigation: React.FC = ({ setTabIndexItems, isSplashPage, isAboutPage, - realChainId + realChainId, + notificationCount, + getNotificationsCount }) => { const [click, setClick] = useState(false); const [userData, setUserData] = useState(null); @@ -78,6 +82,7 @@ const MenuNavigation: React.FC = ({ const { loggedIn, loginProcess } = useSelector( (store) => store.userStore ); + const [realDataNotification, setRealDataNotification] = useState([]); const { erc777Instance, currentUserAddress, currentChain } = useSelector< RootState, ContractsInitialType @@ -97,6 +102,24 @@ const MenuNavigation: React.FC = ({ setActiveSearch((prev) => !prev); }; + const getNotifications = useCallback(async () => { + if(currentUserAddress) { + const result = await rFetch(`/api/notifications`); + if (result.success) { + setRealDataNotification(result.notifications); + } + } +}, [currentUserAddress]); + +useEffect(() => { + getNotificationsCount(); +}, [click]) + + +useEffect(() => { + getNotifications(); +}, []) + const toggleMenu = (otherPage?: string | undefined) => { if (otherPage === 'nav') { setClick(true); @@ -179,6 +202,7 @@ const MenuNavigation: React.FC = ({ getBalance(); }, [getBalance]); + return ( = ({ {/* this is where the aikon widget should go: */} - {currentUserAddress && userBalance.length < 7 && ( + {/* {currentUserAddress && userBalance.length < 7 && ( <> { @@ -267,10 +291,39 @@ const MenuNavigation: React.FC = ({ - )} + )} */} )} +
{ + handleMessageAlert('notification'); + toggleMenu('nav'); + }} + className="social-media-profile"> + {currentUserAddress && ( + + + {notificationCount && notificationCount > 0 ? ( +
{notificationCount > 9 ? "9+" : notificationCount}
+ ) : ''} +
+ )} +
{ handleMessageAlert('profileEdit'); @@ -287,9 +340,17 @@ const MenuNavigation: React.FC = ({ className={`profile-user-balance ${ primaryColor === 'rhyno' ? 'rhyno' : '' }`}> - logo + logo {currentChain && chainData[currentChain] && ( = ({ const { userRd } = useSelector( (state) => state.userStore ); + const [notificationCount, setNotificationCount] = useState(0); + + const getNotificationsCount = useCallback( async () => { + if (currentUserAddress) { + const result = await rFetch(`/api/notifications?onlyUnread=true`); + if (result.success && result.totalCount > 0) { + setNotificationCount(result.totalCount); + } + } + }, [currentUserAddress, messageAlert]) + + useEffect(() => { + getNotificationsCount(); + }, [getNotificationsCount]) return (
@@ -70,11 +85,21 @@ const MobileChoiseNav: React.FC = ({
{currentUserAddress && ( + {notificationCount > 0 && ( +
{notificationCount > 9 ? "9+" : notificationCount}
+ )}
)}
Notifications
diff --git a/rair-front/src/components/Navigation/MenuComponents/MobileListMenu.tsx b/rair-front/src/components/Navigation/MenuComponents/MobileListMenu.tsx index 25c075554..24cffafb7 100644 --- a/rair-front/src/components/Navigation/MenuComponents/MobileListMenu.tsx +++ b/rair-front/src/components/Navigation/MenuComponents/MobileListMenu.tsx @@ -174,7 +174,7 @@ const MobileListMenu: React.FC = ({ type="text" onChange={handleChangeText} value={textSearch} - placeholder="Search the rairverse..." + placeholder="Search..." /> )} diff --git a/rair-front/src/components/Navigation/MenuComponents/MobileNavigationList.tsx b/rair-front/src/components/Navigation/MenuComponents/MobileNavigationList.tsx index 8da159ca0..806e6d115 100644 --- a/rair-front/src/components/Navigation/MenuComponents/MobileNavigationList.tsx +++ b/rair-front/src/components/Navigation/MenuComponents/MobileNavigationList.tsx @@ -1,24 +1,29 @@ import React, { useCallback, useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; import { NavLink } from 'react-router-dom'; +import { BigNumber, utils } from 'ethers'; +import { formatEther } from 'ethers/lib/utils'; -import useConnectUser from '../../../hooks/useConnectUser'; -import { NavFooter, NavFooterBox } from '../../Footer/FooterItems/FooterItems'; -import { BackBtnMobileNav } from '../NavigationItems/NavigationItems'; -import chainData from '../../../utils/blockchainData'; -import { RairFavicon, RairTokenLogo } from '../../../images'; -import { TooltipBox } from '../../common/Tooltip/TooltipBox'; -import { useSelector } from 'react-redux'; import { RootState } from '../../../ducks'; import { ContractsInitialType } from '../../../ducks/contracts/contracts.types'; -import { BigNumber, utils } from 'ethers'; -import useWeb3Tx from '../../../hooks/useWeb3Tx'; -import { formatEther } from 'ethers/lib/utils'; import { TUsersInitialState } from '../../../ducks/users/users.types'; +import useConnectUser from '../../../hooks/useConnectUser'; +import useWeb3Tx from '../../../hooks/useWeb3Tx'; +import { RairFavicon, RairTokenLogo } from '../../../images'; +import chainData from '../../../utils/blockchainData'; +import { rFetch } from '../../../utils/rFetch'; +import LoadingComponent from '../../common/LoadingComponent'; +import { TooltipBox } from '../../common/Tooltip/TooltipBox'; +import { NavFooter, NavFooterBox } from '../../Footer/FooterItems/FooterItems'; +import NotificationBox from '../../UserProfileSettings/PopUpNotification/NotificationBox/NotificationBox'; +import { BackBtnMobileNav } from '../NavigationItems/NavigationItems'; +import PaginationBox from '../../MockUpPage/PaginationBox/PaginationBox'; +import { ColorStoreType } from '../../../ducks/colors/colorStore.types'; interface IMobileNavigationList { messageAlert: string | null; setMessageAlert: (arg: string | null) => void; - primaryColor: string; + primaryColor?: string; currentUserAddress: string | undefined; toggleMenu: (otherPage?: string) => void; setTabIndexItems: (arg: number) => void; @@ -29,7 +34,6 @@ interface IMobileNavigationList { const MobileNavigationList: React.FC = ({ messageAlert, setMessageAlert, - primaryColor, toggleMenu, currentUserAddress, click @@ -40,29 +44,36 @@ const MobileNavigationList: React.FC = ({ const [userRairBalance, setUserRairBalance] = useState( BigNumber.from(0) ); - const { userData } = useSelector((store) => store.userStore); + const { userData } = useSelector( + (store) => store.userStore + ); + + const { primaryColor } = useSelector< + RootState, + ColorStoreType +>((store) => store.colorStore); const { web3TxHandler } = useWeb3Tx(); const { erc777Instance, currentChain } = useSelector< - RootState, - ContractsInitialType ->((store) => store.contractStore); + RootState, + ContractsInitialType + >((store) => store.contractStore); -const getBalance = useCallback(async () => { - if (currentUserAddress && erc777Instance?.provider) { - const balance = - await erc777Instance.provider.getBalance(currentUserAddress); + const getBalance = useCallback(async () => { + if (currentUserAddress && erc777Instance?.provider) { + const balance = + await erc777Instance.provider.getBalance(currentUserAddress); - if (balance) { - const result = utils.formatEther(balance); - const final = Number(result.toString())?.toFixed(2)?.toString(); + if (balance) { + const result = utils.formatEther(balance); + const final = Number(result.toString())?.toFixed(2)?.toString(); - setUserBalance(final); + setUserBalance(final); + } } - } - // eslint-disable-next-line react-hooks/exhaustive-deps -}, [currentUserAddress, erc777Instance, userData]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentUserAddress, erc777Instance, userData]); const getUserRairBalance = useCallback(async () => { if (!erc777Instance || userRairBalance?.gt(0)) { @@ -77,12 +88,52 @@ const getBalance = useCallback(async () => { }, [erc777Instance, currentUserAddress, userRairBalance, web3TxHandler]); const [copyEth, setCopyEth] = useState(false); + const [notificationArray, setNotificationArray] = useState(); + const [notificationCount, setNotificationCount] = useState(0); + const [flagLoading, setFlagLoading] = useState(false); + const [currentPageNotification, setCurrentPageNotification] = useState(1); const { logoutUser } = useConnectUser(); + const getNotifications = useCallback(async (pageNum: number) => { + if (messageAlert && currentUserAddress) { + setFlagLoading(true); + const result = await rFetch(`/api/notifications${`?pageNum=${Number(pageNum)}`}`); + if (result.success) { + setNotificationArray(result.notifications); + setFlagLoading(false); + } + } + }, [messageAlert, currentUserAddress]); + + const getNotificationsCount = useCallback( async () => { + if (currentUserAddress) { + setFlagLoading(true); + const result = await rFetch(`/api/notifications`); + if (result.success && result.totalCount > 0) { + setNotificationCount(result.totalCount); + setFlagLoading(true); + } + } + }, [currentUserAddress]) + + const changePageForVideo = (currentPage: number) => { + setCurrentPageNotification(currentPage); + const currentPageNumber = currentPage === 0 ? currentPage : currentPage - 1; + getNotifications(Number(currentPageNumber)); + }; + + useEffect(() => { + getNotificationsCount(); + }, [getNotificationsCount]) + + useEffect(() => { + getNotifications(0); + }, [getNotifications]); + useEffect(() => { getBalance(); - }, [getBalance]) + }, [getBalance]); useEffect(() => { getUserRairBalance(); @@ -105,7 +156,46 @@ const getBalance = useCallback(async () => { setMessageAlert(null)}> -
  • You don’t have notifications yet
  • +
    + {flagLoading ? ( + + ) : notificationArray && notificationArray.length > 0 ? ( + notificationArray.map((el) => { + return ( + + ) + }) + ) : ( +
    + You don't have any notifications now +
    + )} +
    + {notificationCount && } ) : messageAlert === 'profile' ? ( { primaryColor={primaryColor} messageAlert={messageAlert}>
    -
    -
    -
    -
    - {userBalance ? userBalance : 0.00} - {/* {isLoadingBalance ? : userBalance} */} -
    -
    - {currentChain && chainData[currentChain] && ( - logo - )} -
    -
    -
    -
    - {userRairBalance ? formatEther(userRairBalance) : 0.00} - {/* {isLoadingBalance ? : userBalance} */} +
    +
    +
    + {userBalance ? userBalance : 0.0} + {/* {isLoadingBalance ? : userBalance} */} +
    +
    + {currentChain && chainData[currentChain] && ( + logo + )} +
    -
    - logo +
    +
    + {userRairBalance ? formatEther(userRairBalance) : 0.0} + {/* {isLoadingBalance ? : userBalance} */} +
    +
    + logo +
    -
    -
    -
    -
    Exchange rate
    -
    50K RAIR/bETH
    +
    +
    +
    + Exchange rate +
    +
    + 50K RAIR/bETH +
    - - + +
    diff --git a/rair-front/src/components/SplashPage/SplashPage.css b/rair-front/src/components/SplashPage/SplashPage.css index 2832c3cd9..ba97c782a 100644 --- a/rair-front/src/components/SplashPage/SplashPage.css +++ b/rair-front/src/components/SplashPage/SplashPage.css @@ -78,8 +78,16 @@ url('./images/splashPageImages/or.webp'), url('./images/splashPageImages/humble.webp'); */ background-repeat: no-repeat, no-repeat, no-repeat, no-repeat; - background-size: 891px 353px, 1203px 432px, 992px 420px, 1202px 415px; - background-position: 600px 730px, 200px 2750px, 0 1400px, 0px 3800px; + background-size: + 891px 353px, + 1203px 432px, + 992px 420px, + 1202px 415px; + background-position: + 600px 730px, + 200px 2750px, + 0 1400px, + 0px 3800px; display: flex; justify-content: center; align-items: center; @@ -1501,7 +1509,8 @@ -webkit-appearance: none; background-color: transparent; /* border: 2px solid #0079bf; */ - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), + box-shadow: + 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05); padding: 10px; display: inline-block; diff --git a/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.css b/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.css index 79117fcaf..daae18034 100644 --- a/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.css +++ b/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.css @@ -15,6 +15,15 @@ transition: all 0.3s ease; } +.wrapper-notification.rhyno .box-notification { + background: #fff; + color: #000; +} + +.wrapper-notification.rhyno .box-notification:hover { + background-color: #a9a9a9; +} + .wrapper-notification .box-notification:hover { background-color: #4e4e4e; } @@ -93,3 +102,21 @@ .wrapper-notification .text-notification .text-notif { font-size: 14px; } + +@media screen and (max-width: 500px) { + .wrapper-notification { + height: 50vh; + } + + .notification-from-rair { + padding: 0; + } + + .wrapper-notification .notification-left { + flex-direction: column; + } + + .title-notif { + margin-top: 10px; + } +} diff --git a/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.tsx b/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.tsx index aba57bc2a..6b8bab440 100644 --- a/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.tsx +++ b/rair-front/src/components/UserProfileSettings/NotificationPage/NotificationPage.tsx @@ -1,23 +1,26 @@ //@ts-nocheck +import { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '../../../ducks'; import { ColorStoreType } from '../../../ducks/colors/colorStore.types'; -import IconRemove from './images/icon-remove.png'; +// import IconRemove from './images/icon-remove.png'; // import { useSelector } from 'react-redux'; import './NotificationPage.css'; -const NotificationPage = () => { - const currentName = - import.meta.env.VITE_TESTNET === 'true' ? 'HotDrops' : 'Rair.tech'; - const { headerLogo, primaryColor } = useSelector( +const NotificationPage = ({ el, readNotification, removeItem }) => { + const { headerLogoMobile, primaryColor } = useSelector( (store) => store.colorStore ); + useEffect(() => { + readNotification(); + }, []) + return ( -
    +
    { style={{ color: `${primaryColor === 'rhyno' && '#000'}` }}> - New + Viewed
    { backgroundColor: `${primaryColor === 'rhyno' && '#c0c0c0'}` }}>
    -
    - Rair Tech + Rair Tech
    - Notification from {currentName} -
    -
    - Don’t click away! You can navigate away from the page once - your video is done uploading + {el.message}
    +
    {el.title}
    @@ -55,20 +54,13 @@ const NotificationPage = () => { }}> 3 hours ago
    */} -
    + {/*
    {removeItem}} className="icon-remove"> Close notification item -
    +
    */}
    -
    - Viewed -
    {/*
    { + const { headerLogoMobile } = useSelector< + RootState, + ColorStoreType + >((store) => store.colorStore); + + const reactSwal = useSwal(); + const store = useStore(); + + const removeItem = useCallback(async () => { + if(currentUserAddress) { + const result = await rFetch(`/api/notifications/${el._id}`, { + method: 'DELETE' + }); + + if (result.success) { + getNotifications(); + getNotificationsCount(); + } + } + }, [currentUserAddress]) + + const readNotification = useCallback( + async () => { + if(currentUserAddress) { + const result = await rFetch(`/api/notifications/${el._id}`, { + method: 'PUT', + body: JSON.stringify({ + ...el, + read: true + }) + }); + + if (result.success) { + getNotifications(); + getNotificationsCount(); + } + } + }, [currentUserAddress]) + + const showMoreDetails = () => { + reactSwal.fire({ + html: + + , + width: '90vw', + customClass: { + popup: `bg-${primaryColor}` + }, + showConfirmButton: false, + showCloseButton: true + // cancelButtonText: + // '', + // cancelButtonAriaLabel: 'Thumbs down' + }); + }; + + return ( +
    +
    +
    + {!el.read &&
    } +
    + Exclusive NFT token by RAIR +
    +
    +
    +
    { + // readNotification(); + showMoreDetails(); + readNotification(); + }} + className="title-notif"> + {title && title.length > 35 ? title.substr(0, 35) + "..." : title} +
    +
    +
    + + + +
    +
    +
    + ); +}; + +export default NotificationBox; diff --git a/rair-front/src/components/UserProfileSettings/PopUpNotification/PopUpNotification.tsx b/rair-front/src/components/UserProfileSettings/PopUpNotification/PopUpNotification.tsx index 487b73de4..8b2e0afb8 100644 --- a/rair-front/src/components/UserProfileSettings/PopUpNotification/PopUpNotification.tsx +++ b/rair-front/src/components/UserProfileSettings/PopUpNotification/PopUpNotification.tsx @@ -1,26 +1,33 @@ //@ts-nocheck import { useCallback, useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useStore } from 'react-redux'; import { Popup } from 'reactjs-popup'; -import Swal from 'sweetalert2'; import { RootState } from '../../../ducks'; import { ColorStoreType } from '../../../ducks/colors/colorStore.types'; +import { ContractsInitialType } from '../../../ducks/contracts/contracts.types'; import { TUsersInitialState } from '../../../ducks/users/users.types'; -import useSwal from '../../../hooks/useSwal'; import { BellIcon } from '../../../images'; import { SocialBox } from '../../../styled-components/SocialLinkIcons/SocialLinkIcons'; -import NotificationPage from '../NotificationPage/NotificationPage'; +import { rFetch } from '../../../utils/rFetch'; +import PaginationBox from '../../MockUpPage/PaginationBox/PaginationBox'; -import NftImg from './images/image.png'; +import NotificationBox from './NotificationBox/NotificationBox'; -const PopUpNotification = () => +const PopUpNotification = ({getNotifications, realDataNotification, notificationCount, getNotificationsCount, setRealDataNotification}) => // props was - isNotification { const currentName = import.meta.env.VITE_TESTNET === 'true' ? 'HotDrops' : 'Rair.tech'; const [openModal, setOpenModal] = useState(false); - const { headerLogo, primaryColor, headerLogoMobile } = useSelector< + const store = useStore(); + const { currentUserAddress } = useSelector< + RootState, + ContractsInitialType + >((state) => state.contractStore); + const [totalPageForPagination, setTotalPageForPagination] = useState(0); + const [currentPageForNotification, setCurrentPageNotification] = useState(1); + const { primaryColor, primaryButtonColor, textColor } = useSelector< RootState, ColorStoreType >((store) => store.colorStore); @@ -30,7 +37,32 @@ const PopUpNotification = () => const { userRd } = useSelector( (store) => store.userStore ); - const reactSwal = useSwal(); + + const changePageForVideo = (currentPage: number) => { + setCurrentPageNotification(currentPage); + const currentPageNumber = currentPage === 0 ? currentPage : currentPage - 1; + getNotifications(Number(currentPageNumber)); + }; + + const getNotificationsCountPagitation = useCallback( async () => { + if(currentUserAddress) { + const result = await rFetch(`/api/notifications`); + if (result.success && result.totalCount > 0) { + setTotalPageForPagination(result.totalCount); + } + } + }, [currentUserAddress]) + + useEffect(() => { + if(openModal) { + getNotifications(0); + getNotificationsCount(); + } + }, [openModal]); + + useEffect(() => { + getNotificationsCountPagitation(); + }, [getNotificationsCountPagitation]) const onCloseNext = useCallback(() => { if (!openModal) { @@ -50,102 +82,106 @@ const PopUpNotification = () => } }, [uploadVideo]); + return ( <> setOpenModal((prev) => !prev)} - className="social-bell-icon" + className="social-bell-icon notifications" marginRight={'17px'} notification={true}> {uploadVideo && userRd?.email && } + {notificationCount > 0 && ( +
    {notificationCount > 9 ? "9+" : notificationCount}
    + )}
    { setOpenModal(false); }}> - {openModal && userRd?.email && ( + {openModal && (
    { - setOpenModal(false); - reactSwal.fire({ - html: ( - - ), - width: '90vw', - customClass: { - popup: `bg-${primaryColor}` - }, - onBeforeOpen: () => { - Swal.showLoading(); - }, - showConfirmButton: false, - showCloseButton: true - // cancelButtonText: - // '', - // cancelButtonAriaLabel: 'Thumbs down' - }); + color: `${primaryColor === 'rhyno' && '#000'}`, + maxHeight: '500px', }}> -
    -
    -
    -
    - Rair Tech -
    -
    -
    - Notification from {currentName} -
    -
    - Don’t click away! You can navigate away from the page once - your video is done uploading -
    -
    - {/*
    - 3 hours ago -
    */} -
    -
    - {/*
    -
    -
    -
    - Exclusive NFT token by RAIR -
    -
    -
    Factory updates
    -
    - Your nft “Pegayo” has been listed -
    +
    +
    Notifications
    +
    +
    + {realDataNotification && realDataNotification.length > 0 ? ( + realDataNotification.map((el) => { + return ( + + ) + }) + ) : (
    - 5 hours ago + You don't have any notifications now
    + )} +
    + { + +totalPageForPagination && notificationCount > 0 && + } +
    -
    */}
    )} diff --git a/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Edit/Edit.module.css b/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Edit/Edit.module.css index b956b9405..a60ab8672 100644 --- a/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Edit/Edit.module.css +++ b/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Edit/Edit.module.css @@ -11,7 +11,9 @@ background: #383637; /* background-image: url('../../../../images/binance-diamond.svg'), url('../../../../images/ethereum-logo.svg'); */ background-repeat: no-repeat, no-repeat; - background-position: 120% -5%, 200% -40%; + background-position: + 120% -5%, + 200% -40%; background-size: 40%, 80%; animation: open 0.5s; position: absolute; @@ -22,10 +24,14 @@ @keyframes open { 0% { - background-position: 166% -25%, 220% -69%; + background-position: + 166% -25%, + 220% -69%; } 100% { - background-position: 120% -5%, 200% -40%; + background-position: + 120% -5%, + 200% -40%; } } diff --git a/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Profile/Profile.module.css b/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Profile/Profile.module.css index cb538f085..924aee3a7 100644 --- a/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Profile/Profile.module.css +++ b/rair-front/src/components/UserProfileSettings/UploadProfilePicture/Profile/Profile.module.css @@ -14,7 +14,9 @@ /* --- */ /* background-image: url('../assets/leaf2.png'), url('../assets/leaf2.png'); background-repeat: no-repeat, no-repeat; */ - background-position: 120% -5%, 200% -40%; + background-position: + 120% -5%, + 200% -40%; /* ---- */ /* background-image: url('../../../../images/binance-diamond.svg'), url('../../../../images/ethereum-logo.svg'); */ background-repeat: no-repeat, no-repeat; @@ -29,10 +31,14 @@ @keyframes open { 0% { - background-position: 166% -25%, 220% -69%; + background-position: + 166% -25%, + 220% -69%; } 100% { - background-position: 120% -5%, 200% -40%; + background-position: + 120% -5%, + 200% -40%; } } diff --git a/rair-front/src/components/UserProfileSettings/UserProfileSettings.css b/rair-front/src/components/UserProfileSettings/UserProfileSettings.css index c1d42d77f..446dc4a56 100644 --- a/rair-front/src/components/UserProfileSettings/UserProfileSettings.css +++ b/rair-front/src/components/UserProfileSettings/UserProfileSettings.css @@ -45,6 +45,29 @@ top: 85px; } +.btn-clear-nofitications button { + border-radius: 12px; + border: 1px solid #fff; + background: none; + color: #fff; + padding: 2px 10px; +} + +.notification-title { + font-size: 20px; +} + +.btn-clear-nofitications button:hover { + background: #000; +} + +.btn-clear-nofitications { + display: flex; + justify-content: space-between; + padding: 20px 25px 20px 25px; + border-bottom: 1px solid #fff; +} + .user-block { position: relative; } @@ -71,18 +94,26 @@ overflow: hidden; } +.pop-up-notification.rhyno { + color: #000; +} + .notification-from-rair, .notification-from-factory { cursor: pointer; } .notification-from-rair { - padding: 25px 16px; + padding: 25px; border-bottom: 1px solid #2d2d2d; } .notification-from-factory { - padding: 25px 16px; + padding: 20px 25px; +} + +.pop-up-notification .notification-from-factory:last-child { + padding-bottom: 30px; } .pop-up-notification .notification-from-rair .box-notification img { @@ -91,25 +122,24 @@ } .notification-img { - border: 1px solid #fff; border-radius: 16px; - width: 48px; - height: 48px; + width: 40px; + height: 40px; display: flex; justify-content: center; align-items: center; - margin: 0 16px; } .notification-img img { - width: 48px; - height: 48px; + width: 40px; + height: 40px; object-fit: contain; } .pop-up-notification .box-notification { display: flex; align-items: center; + justify-content: space-between; } .pop-up-notification .time-notification { @@ -142,6 +172,8 @@ height: 8px; background-color: #19a7f6; border-radius: 50%; + position: absolute; + left: -15px; } .list-popup { @@ -532,3 +564,17 @@ span.profile-input-edit:hover { color: #fff; border: solid 1px #fff; } + +@media screen and (max-width: 500px) { + .notification-from-factory { + padding: 10px; + } + + .box-notification .dot-notification { + left: 0px; + } + + .notification-img { + padding-left: 25px; + } +} diff --git a/rair-front/src/components/adminViews/ServerSettings.tsx b/rair-front/src/components/adminViews/ServerSettings.tsx index e021832a9..4527355ca 100644 --- a/rair-front/src/components/adminViews/ServerSettings.tsx +++ b/rair-front/src/components/adminViews/ServerSettings.tsx @@ -13,9 +13,16 @@ import { OptionsType } from '../common/commonTypes/InputSelectTypes.types'; import InputField from '../common/InputField'; import InputSelect from '../common/InputSelect'; +type Category = { + name: string; + _id?: string; + files?: number; +}; + const ServerSettings = ({ fullContractData }) => { const serverSettings = useServerSettings(); const [productOptions, setProductOptions] = useState(); + const [categoryList, setCategoryList] = useState([]); const [customLightModeLogo, setCustomLightModeLogo] = useState({ name: '' }); const [customDarkModeLogo, setCustomDarkModeLogo] = useState({ name: '' }); @@ -60,6 +67,17 @@ const ServerSettings = ({ fullContractData }) => { serverSettings.setFooterLinks(aux); }; + const getCategories = useCallback(async () => { + const { success, result } = await rFetch('/api/categories'); + if (success) { + setCategoryList(result); + } + }, []); + + useEffect(() => { + getCategories(); + }, [getCategories]); + const setServerSetting = useCallback( async (setting) => { const { success } = await rFetch(`/api/settings/`, { @@ -144,6 +162,27 @@ const ServerSettings = ({ fullContractData }) => { serverSettings.getServerSettings(); }, [serverSettings.getServerSettings]); + const deleteCategory = useCallback( + (index) => { + const aux = [...categoryList]; + aux.splice(index, 1); + setCategoryList(aux); + }, + [categoryList] + ); + + const updateCategory = useCallback( + (index) => (value) => { + const aux = [...categoryList]; + aux[index] = { + ...aux[index], + name: value + }; + setCategoryList(aux); + }, + [categoryList] + ); + useEffect(() => { serverSettings.setFeaturedProduct('null'); if (serverSettings.featuredContract === 'null') { @@ -578,6 +617,73 @@ const ServerSettings = ({ fullContractData }) => { ); })}
    +
    +

    Categories

    + {categoryList.map((categoryData, index) => { + return ( +
    +
    + +
    + +
    + ); + })} + + +

    Footer items

    {serverSettings.footerLinks && @@ -657,6 +763,26 @@ const ServerSettings = ({ fullContractData }) => { Set
    +
    +

    Default Signup Message

    + + +
    ); diff --git a/rair-front/src/components/adminViews/useServerSettings.tsx b/rair-front/src/components/adminViews/useServerSettings.tsx index d08dd71a0..8dda8d375 100644 --- a/rair-front/src/components/adminViews/useServerSettings.tsx +++ b/rair-front/src/components/adminViews/useServerSettings.tsx @@ -20,6 +20,7 @@ const useServerSettings = () => { import.meta.env.VITE_NODE_ADDRESS ); const [legal, setLegal] = useState(''); + const [signupMessage, setSignupMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [featuredContract, setFeaturedContract] = useState('null'); const [featuredProduct, setFeaturedProduct] = useState('null'); @@ -47,6 +48,7 @@ const useServerSettings = () => { settings?.nodeAddress || import.meta.env.VITE_NODE_ADDRESS ); setLegal(settings?.legal || ''); + setSignupMessage(settings?.signupMessage || ''); if (settings.featuredCollection) { setFeaturedContract(settings?.featuredCollection?.contract?._id); setFeaturedProduct(settings?.featuredCollection?._id); @@ -171,6 +173,8 @@ const useServerSettings = () => { setFooterLinks, legal, setLegal, + signupMessage, + setSignupMessage, blockchainSettings, isLoading }; diff --git a/rair-front/src/ducks/auth/actions.ts b/rair-front/src/ducks/auth/actions.ts index c5ab1e812..415584d2e 100644 --- a/rair-front/src/ducks/auth/actions.ts +++ b/rair-front/src/ducks/auth/actions.ts @@ -3,25 +3,25 @@ import { ethers } from 'ethers'; import * as types from './types'; //unused-snippet -const getProviderStart = () => ({ type: types.GET_PROVIDER_START } as const); +const getProviderStart = () => ({ type: types.GET_PROVIDER_START }) as const; const getProviderComplete = (provider: ethers.providers.Web3Provider | null) => - ({ type: types.GET_PROVIDER_COMPLETE, provider } as const); + ({ type: types.GET_PROVIDER_COMPLETE, provider }) as const; const getProviderError = (error: string | null) => - ({ type: types.GET_PROVIDER_ERROR, error } as const); + ({ type: types.GET_PROVIDER_ERROR, error }) as const; -const getTokenStart = () => ({ type: types.GET_TOKEN_START } as const); +const getTokenStart = () => ({ type: types.GET_TOKEN_START }) as const; const getTokenComplete = (token: string | null) => - ({ type: types.GET_TOKEN_COMPLETE, token } as const); + ({ type: types.GET_TOKEN_COMPLETE, token }) as const; const getTokenError = (error: boolean | null) => - ({ type: types.GET_TOKEN_ERROR, error } as const); + ({ type: types.GET_TOKEN_ERROR, error }) as const; //unused-snippet const getPublicAddressComplete = (publicAddress: string | null) => - ({ type: types.GET_PUBLIC_ADDRESS_COMPLETE, publicAddress } as const); + ({ type: types.GET_PUBLIC_ADDRESS_COMPLETE, publicAddress }) as const; export { getProviderComplete, diff --git a/rair-front/src/ducks/metadata/actions.ts b/rair-front/src/ducks/metadata/actions.ts index 969f927d2..7b990c0ef 100644 --- a/rair-front/src/ducks/metadata/actions.ts +++ b/rair-front/src/ducks/metadata/actions.ts @@ -5,11 +5,11 @@ const updateTokenMetadataAC = (url: string, formData: any) => type: types.UPDATE_TOKEN_METADATA, url, formData - } as const); -const setShowSidebarFalse = () => ({ type: types.SHOW_SIDEBAR_FALSE } as const); -const setShowSidebarTrue = () => ({ type: types.SHOW_SIDEBAR_TRUE } as const); + }) as const; +const setShowSidebarFalse = () => ({ type: types.SHOW_SIDEBAR_FALSE }) as const; +const setShowSidebarTrue = () => ({ type: types.SHOW_SIDEBAR_TRUE }) as const; const updateTokenMetadataError = (errorMessage: string | null) => - ({ type: types.UPDATE_TOKEN_METADATA_ERROR, errorMessage } as const); + ({ type: types.UPDATE_TOKEN_METADATA_ERROR, errorMessage }) as const; export { setShowSidebarFalse, diff --git a/rair-front/src/ducks/pages/actions.ts b/rair-front/src/ducks/pages/actions.ts index 3f2ecd211..4a3347348 100644 --- a/rair-front/src/ducks/pages/actions.ts +++ b/rair-front/src/ducks/pages/actions.ts @@ -4,20 +4,20 @@ const getCurrentPage = (currentPage: number) => ({ type: types.GET_CURRENT_PAGE_START, currentPage - } as const); + }) as const; const getCurrentPageEnd = () => ({ type: types.GET_CURRENT_PAGE_END - } as const); + }) as const; const getCurrentPageNull = () => ({ type: types.GET_CURRENT_NULL - } as const); + }) as const; const getCurrentPageComplete = (currentPage: number) => - ({ type: types.GET_CURRENT_PAGE_COMPLETE, currentPage } as const); + ({ type: types.GET_CURRENT_PAGE_COMPLETE, currentPage }) as const; export { getCurrentPage, diff --git a/rair-front/src/ducks/resales/actions.ts b/rair-front/src/ducks/resales/actions.ts index 99b97d66b..b945ac93c 100644 --- a/rair-front/src/ducks/resales/actions.ts +++ b/rair-front/src/ducks/resales/actions.ts @@ -3,27 +3,27 @@ import * as types from './types'; const getListResalesStart = () => ({ type: types.GET_RESALES_LIST_START - } as const); + }) as const; const getResalesListComplete = (resaleList: any | null) => - ({ type: types.GET_RESALES_LIST_COMPLETE, resaleList } as const); + ({ type: types.GET_RESALES_LIST_COMPLETE, resaleList }) as const; const getResalesListTotalClear = () => ({ type: types.GET_RESALES_LIST_TOTAL_CLEAR - } as const); + }) as const; const getResalesListTotal = (totalNumberVideo: number) => - ({ type: types.GET_RESALES_LIST_TOTAL, totalNumberVideo } as const); + ({ type: types.GET_RESALES_LIST_TOTAL, totalNumberVideo }) as const; const refreshAction = (refresh: boolean) => ({ type: types.REFRESH_RESALES_LIST, refresh - } as const); + }) as const; const getListResalesError = (error: string | null) => - ({ type: types.GET_RESALES_LIST_ERROR, error } as const); + ({ type: types.GET_RESALES_LIST_ERROR, error }) as const; export { getListResalesError, diff --git a/rair-front/src/ducks/search/actions.ts b/rair-front/src/ducks/search/actions.ts index 18a02c0fb..857d43a30 100644 --- a/rair-front/src/ducks/search/actions.ts +++ b/rair-front/src/ducks/search/actions.ts @@ -5,21 +5,21 @@ const getDataAllStart = (titleSearchDemo: string) => ({ type: types.GET_DATA_ALL_START, titleSearchDemo - } as const); + }) as const; const getDataAllComplete = (data: TSearchDataObject) => ({ type: types.GET_DATA_ALL_COMPLETE, data - } as const); + }) as const; const getDataAllEmpty = (message: string) => ({ type: types.GET_DATA_ALL_EMPTY, message - } as const); + }) as const; const getDataAllClear = () => ({ type: types.GET_DATA_ALL_CLEAR - } as const); + }) as const; export { getDataAllClear, diff --git a/rair-front/src/ducks/seo/actions.ts b/rair-front/src/ducks/seo/actions.ts index ed7cce869..3480799b1 100644 --- a/rair-front/src/ducks/seo/actions.ts +++ b/rair-front/src/ducks/seo/actions.ts @@ -5,9 +5,9 @@ export const setInfoSEO = (info: TInfoSeo) => ({ type: types.SET_INFO_HELMET, info - } as const); + }) as const; export const resetInfoSeo = () => ({ type: types.RESET_INFO_HELMET - } as const); + }) as const; diff --git a/rair-front/src/ducks/uploadDemo/action.ts b/rair-front/src/ducks/uploadDemo/action.ts index 5233dfc01..00404a079 100644 --- a/rair-front/src/ducks/uploadDemo/action.ts +++ b/rair-front/src/ducks/uploadDemo/action.ts @@ -1,11 +1,11 @@ import * as types from './types'; -const uploadVideoStart = () => ({ type: types.UPLOAD_VIDEO_START } as const); -const uploadVideoEnd = () => ({ type: types.UPLOAD_VIDEO_END } as const); +const uploadVideoStart = () => ({ type: types.UPLOAD_VIDEO_START }) as const; +const uploadVideoEnd = () => ({ type: types.UPLOAD_VIDEO_END }) as const; const uploadVideoSuccess = () => - ({ type: types.UPLOAD_VIDEO_SUCCESS } as const); + ({ type: types.UPLOAD_VIDEO_SUCCESS }) as const; const getProviderError = (error: string | null) => - ({ type: types.UPLOAD_VIDEO_ERROR, error } as const); + ({ type: types.UPLOAD_VIDEO_ERROR, error }) as const; export { getProviderError, diff --git a/rair-front/src/ducks/videos/actions.ts b/rair-front/src/ducks/videos/actions.ts index 5b9496b47..0fced2bf6 100644 --- a/rair-front/src/ducks/videos/actions.ts +++ b/rair-front/src/ducks/videos/actions.ts @@ -7,24 +7,24 @@ const getListVideosStart = (params: TUpdataVideoParams) => ({ type: types.GET_LIST_VIDEOS_START, params - } as const); + }) as const; const setLoading = (loading: boolean) => - ({ type: types.SET_LOADING, loading } as const); + ({ type: types.SET_LOADING, loading }) as const; const getVideoListComplete = (videoList: MediaListResponseType | null) => - ({ type: types.GET_LIST_VIDEOS_COMPLETE, videoList } as const); + ({ type: types.GET_LIST_VIDEOS_COMPLETE, videoList }) as const; const getVideoListTotalClear = () => ({ type: types.GET_LIST_VIDEOS_TOTAL_CLEAR - } as const); + }) as const; const getVideoListTotal = (totalNumberVideo: number) => - ({ type: types.GET_LIST_VIDEOS_TOTAL, totalNumberVideo } as const); + ({ type: types.GET_LIST_VIDEOS_TOTAL, totalNumberVideo }) as const; const getListVideosError = (error: string | null) => - ({ type: types.GET_LIST_VIDEOS_ERROR, error } as const); + ({ type: types.GET_LIST_VIDEOS_ERROR, error }) as const; export { getListVideosError, diff --git a/rair-front/src/index.css b/rair-front/src/index.css index ac79b7b11..78e47ced8 100644 --- a/rair-front/src/index.css +++ b/rair-front/src/index.css @@ -1,18 +1,22 @@ @font-face { font-family: 'Plus Jakarta Sans'; - src: local('Plus Jakarta Sans'), + src: + local('Plus Jakarta Sans'), url(./fonts/webfonts/PlusJakartaSans-Regular.woff) format('woff'); } @font-face { font-family: 'Plus Jakarta Sans Bold'; - src: local('Plus Jakarta Sans Bold'), + src: + local('Plus Jakarta Sans Bold'), url(./fonts/webfonts/PlusJakartaSans-Bold.woff) format('woff'); } @font-face { font-family: 'Bazar'; - src: local('Bazar'), url(./fonts/Bazar.ttf) format('truetype'); + src: + local('Bazar'), + url(./fonts/Bazar.ttf) format('truetype'); } @font-face { @@ -29,58 +33,68 @@ @font-face { font-family: 'Acme'; - src: local('Acme'), url(./fonts/Acme/Acme-Regular.ttf) format('truetype'); + src: + local('Acme'), + url(./fonts/Acme/Acme-Regular.ttf) format('truetype'); } @font-face { font-family: 'Nunito'; - src: local('Nunito'), + src: + local('Nunito'), url(./fonts/Nunito/Nunito-VariableFont_wght.ttf) format('truetype'); } @font-face { font-family: 'Inter'; - src: local('Inter'), + src: + local('Inter'), url(./fonts/Inter/Inter-VariableFontwght.ttf) format('truetype'); } @font-face { font-family: 'Charriot Deluxe'; - src: local('Charriot Deluxe'), + src: + local('Charriot Deluxe'), url(./fonts/charriot_deluxe/CharriotDeluxe.ttf) format('truetype'); } @font-face { font-family: 'Cooper Std Black'; - src: local('Cooper Std Black'), + src: + local('Cooper Std Black'), url(./fonts/Cooper\ Std\ Black/Cooper\ Std\ Black.ttf) format('truetype'); } @font-face { font-family: 'Copperplate'; font-weight: normal; - src: local('Copperplate'), + src: + local('Copperplate'), url(./fonts/copperplate/OPTICopperplate.otf) format('opentype'); } @font-face { font-family: 'Copperplate'; font-weight: heavy; - src: local('Copperplate'), + src: + local('Copperplate'), url(./fonts/copperplate/OPTICopperplate-Heavy.otf) format('opentype'); } @font-face { font-family: 'Copperplate'; font-weight: normal; - src: local('Copperplate'), + src: + local('Copperplate'), url(./fonts/copperplate/OPTICopperplate.otf) format('opentype'); } @font-face { font-family: 'Copperplate'; font-weight: light; - src: local('Copperplate'), + src: + local('Copperplate'), url(./fonts/copperplate/OPTICopperplate-Light.otf) format('opentype'); } diff --git a/rair-front/src/styled-components/SocialLinkIcons/SocialLinkIcons.tsx b/rair-front/src/styled-components/SocialLinkIcons/SocialLinkIcons.tsx index 0f7926444..64afe63e0 100644 --- a/rair-front/src/styled-components/SocialLinkIcons/SocialLinkIcons.tsx +++ b/rair-front/src/styled-components/SocialLinkIcons/SocialLinkIcons.tsx @@ -48,6 +48,19 @@ export const SocialBox = styled.div` props.primaryColor === '#dedede' ? '1px solid #E882D5' : 'none'}; } + &.social-bell-icon.notifications { + .red-circle-notifications { + width: 20px; + height: 20px; + border-radius: 50%; + background: red; + position: absolute; + top: -7px; + right: -7px; + display: block; + } + } + &.social-bell-icon { background: none; border: ${(props) => @@ -55,6 +68,10 @@ export const SocialBox = styled.div` ? '1px solid #fff' : '1px solid #D0D0D0'}; + .red-circle-notifications { + display: none; + } + svg path { fill: ${(props) => props.notification diff --git a/rair-front/src/styled-components/nft/Token.styles.ts b/rair-front/src/styled-components/nft/Token.styles.ts index 02c59bdf6..e4e6685fb 100644 --- a/rair-front/src/styled-components/nft/Token.styles.ts +++ b/rair-front/src/styled-components/nft/Token.styles.ts @@ -6,5 +6,5 @@ export const Col = styled.div` display: flex; flex-direction: ${(props) => (props.direction ? props.direction : 'row')}; justify-content: center; - align-items: ${(props) => (props.align ? props.align : '')}; ; + align-items: ${(props) => (props.align ? props.align : '')}; `; diff --git a/rair-node/README.md b/rair-node/README.md index adb6fec5e..eab2460bc 100644 --- a/rair-node/README.md +++ b/rair-node/README.md @@ -145,6 +145,7 @@ The benefits of using the Dockerfile are that it uses the latest OFAC list. * [x] /network/:networkId/:contract/:product * [x] / - GET - Get the tokens in product ([details](readme/current/nft/nft_manual_product.md)) * [x] /attributes - GET - Get all attributes from the NFTs in a product ([details](readme/current/nft/nft_manual_attributes.md)) + * [x] /numbers - GET - Get all tokens numbers in the product ([details](readme/current/nft/nft_manual_numbers.md)) * [x] /files - GET - Get all files associated with the product ([details](readme/current/nft/nft_manual_files.md)) * [x] /files/:token - GET - Get all files associated with a token in the product ([details](readme/current/nft/nft_manual_files_token.md)) * [x] /offers - GET - Get the product data and the offers associated ([details](readme/current/nft/nft_manual_offers.md)) @@ -180,7 +181,6 @@ The benefits of using the Dockerfile are that it uses the latest OFAC list. * [x] /tokens * [x] / - GET - Search tokens ([details](readme/current/tokens/tokens_list.md)) * [x] /id/:id - GET - Get data for a specific token ([details](readme/current/tokens/tokens_single.md)) - * [x] /tokenNumbers - GET - Get all tokens on a specific contract and offer ([details](readme/current/tokens/tokens_numbers.md)) * [x] /:token - GET - Get information for a single token ([details](readme/current/tokens/tokens_single_number.md)) * [x] /transaction/:network/:hash - POST - Process a blockchain transaction ([details](readme/current/transactions/transaction_hash.md)) * [x] /users @@ -196,8 +196,8 @@ The benefits of using the Dockerfile are that it uses the latest OFAC list. * [x] /notifications * [x] / - GET - List of notifications ([details](readme/current/notifications/get_list.md)) * [x] /:id - GET - Get a single notification ([details](readme/current/notifications/get_single.md)) - * [x] /:id - PUT - Mark a notification as read ([details](readme/current/notifications/mark_read.md)) - * [x] /:id - DELETE - Delete a notification ([details](readme/current/notifications/delete.md)) + * [x] / - PUT - Mark a notification as read ([details](readme/current/notifications/mark_read.md)) + * [x] / - DELETE - Delete a notification ([details](readme/current/notifications/delete.md)) # Contributors Valerii Kovalov \ diff --git a/rair-node/bin/api/auth/auth.Service.js b/rair-node/bin/api/auth/auth.Service.js index fea75051b..f8e40b992 100644 --- a/rair-node/bin/api/auth/auth.Service.js +++ b/rair-node/bin/api/auth/auth.Service.js @@ -4,6 +4,7 @@ const { File, User, MediaViewLog, Unlock, ServerSetting } = require('../../model const AppError = require('../../utils/errors/AppError'); const { checkBalanceAny, checkBalanceProduct, checkAdminTokenOwns } = require('../../integrations/ethers/tokenValidation'); const { superAdminInstance } = require('../../utils/vaultSuperAdmin'); +const { emitEvent } = require('../../integrations/socket.io'); module.exports = { generateChallengeMessage: async (req, res, next) => { @@ -87,7 +88,14 @@ module.exports = { } userData.adminRights = await checkAdminTokenOwns(userData.publicAddress); - const { superAdmins, superAdminsOnVault } = await ServerSetting.findOne({}); + const { superAdmins, superAdminsOnVault, signupMessage } = await ServerSetting.findOne({}); + const socket = req.app.get('socket'); + emitEvent(socket)( + userData.publicAddress, + 'message', + signupMessage, + [], + ); userData.superAdmin = superAdminsOnVault ? await superAdminInstance.hasSuperAdminRights(userData.publicAddress) : superAdmins.includes(userData.publicAddress); diff --git a/rair-node/bin/api/categories/categories.Controller.js b/rair-node/bin/api/categories/categories.Controller.js new file mode 100644 index 000000000..d427ad06e --- /dev/null +++ b/rair-node/bin/api/categories/categories.Controller.js @@ -0,0 +1,26 @@ +const { Router } = require('express'); +const { + updateCategories, + getCategories, +} = require('./categories.Service'); +const { requireUserSession } = require('../../middleware/verifyUserSession'); +const validation = require('../../middleware/validation'); +const { verifySuperAdmin } = require('../../middleware'); + +const router = Router(); + +router.get( + '/', + getCategories, +); + +router.post( + '/', + validation(['dbCategory'], 'body'), + requireUserSession, + verifySuperAdmin, + updateCategories, + getCategories, +); + +module.exports = router; diff --git a/rair-node/bin/api/categories/categories.Service.js b/rair-node/bin/api/categories/categories.Service.js new file mode 100644 index 000000000..7d3ae62cb --- /dev/null +++ b/rair-node/bin/api/categories/categories.Service.js @@ -0,0 +1,63 @@ +const { Category, File } = require('../../models'); +const AppError = require('../../utils/errors/AppError'); + +module.exports = { + getCategories: async (req, res, next) => { + try { + const list = await Category.aggregate([{ + $lookup: { + from: 'File', + localField: '_id', + foreignField: 'category', + as: 'files', + }, + }, { + $project: { + _id: 1, + name: 1, + files: { + $size: '$files', + }, + }, + }]); + return res.json({ result: list, success: true }); + } catch (error) { + return next(new AppError(error)); + } + }, + updateCategories: async (req, res, next) => { + try { + const currentList = await Category.find().lean(); + const { list } = req.body; + // eslint-disable-next-line no-restricted-syntax + for await (const category of currentList) { + const update = list.find((item) => item?._id === category._id.toString()); + if (update) { + await Category.findByIdAndUpdate(update._id, { $set: { name: update.name } }); + } else { + const usingCategory = await File.findOne({ + category: category._id, + }); + if (!usingCategory) { + await Category.findByIdAndDelete(category._id); + } + } + } + // eslint-disable-next-line no-restricted-syntax + for await (const category of list) { + if (category._id) { + // eslint-disable-next-line no-continue + continue; + } else { + const newCat = new Category({ + name: category.name, + }); + await newCat.save(); + } + } + return next(); + } catch (error) { + return next(new AppError(error)); + } + }, +}; diff --git a/rair-node/bin/api/nft/nft.Controller.js b/rair-node/bin/api/nft/nft.Controller.js index ba799b0f9..97a3e6ed1 100644 --- a/rair-node/bin/api/nft/nft.Controller.js +++ b/rair-node/bin/api/nft/nft.Controller.js @@ -7,10 +7,10 @@ const { getUserTokensProfile, metadataCSVSample, pinMetadataToIPFS, - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, getProductAttributes, getTokensForProduct, + getTokenNumbers, filesForTokenInProduct, getFilesForProduct, findOffersForProductMiddleware, @@ -69,52 +69,45 @@ router.get( router.get( '/network/:networkId/:contract/:product/attributes', validation(['nftContract', 'nftProduct'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, getProductAttributes, ); router.get( '/network/:networkId/:contract/:product/files/', validation(['nftContract', 'nftProduct'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, loadUserSession, getFilesForProduct, ); router.get( '/network/:networkId/:contract/:product/files/:token', validation(['nftContract', 'nftProduct', 'tokenNumber'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, filesForTokenInProduct, ); router.get( '/network/:networkId/:contract/:product/offers', validation(['nftContract', 'nftProduct'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, getOffersForProduct, ); router.get( '/network/:networkId/:contract/:product/locks', validation(['nftContract', 'nftProduct'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, getLockedOffersForProduct, ); router.get( '/network/:networkId/:contract/:product/token/:token', validation(['nftContract', 'nftProduct', 'tokenNumber'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, findOffersForProductMiddleware, getSingleToken, ); router.post( '/network/:networkId/:contract/:product/token/:token', validation(['nftContract', 'nftProduct', 'tokenNumber'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, findOffersForProductMiddleware, requireUserSession, upload.array('files', 2), @@ -125,8 +118,7 @@ router.post( router.post( '/network/:networkId/:contract/:product/token/:token/pinning', validation(['nftContract', 'nftProduct', 'tokenNumber'], 'params'), - findContractMiddleware, - findProductMiddleware, + findContractAndProductMiddleware, findOffersForProductMiddleware, requireUserSession, pinSingleTokenMetadata, diff --git a/rair-node/bin/api/nft/nft.Service.js b/rair-node/bin/api/nft/nft.Service.js index ffba53a49..b68aaaf87 100644 --- a/rair-node/bin/api/nft/nft.Service.js +++ b/rair-node/bin/api/nft/nft.Service.js @@ -4,6 +4,7 @@ const path = require('path'); const _ = require('lodash'); const fsPromises = fs.promises; +const { ZeroAddress } = require('ethers'); const { addPin, addFolder, addMetadata, removePin, addFile } = require('../../integrations/ipfsService')(); const config = require('../../config'); const log = require('../../utils/logger')(module); @@ -66,21 +67,21 @@ module.exports = { if (onResale.toString() === 'true') { pipeline.push({ $lookup: { - from: 'ResaleTokenOffer', - localField: 'uniqueIndexInContract', - foreignField: 'tokenIndex', - as: 'resaleData', - let: { - tokenContract: '$tokenContract', - }, - pipeline: [{ - $match: { - $expr: { - $eq: ['$$tokenContract', '$contract'], - }, - buyer: { $exists: false }, - }, - }], + from: 'ResaleTokenOffer', + localField: 'uniqueIndexInContract', + foreignField: 'tokenIndex', + as: 'resaleData', + let: { + tokenContract: '$tokenContract', + }, + pipeline: [{ + $match: { + $expr: { + $eq: ['$$tokenContract', '$contract'], + }, + buyer: { $exists: false }, + }, + }], }, }, { $addFields: { @@ -313,18 +314,43 @@ module.exports = { return next(err); } }, - findContractMiddleware: async (req, res, next) => { + findContractAndProductMiddleware: async (req, res, next) => { try { - const contract = await Contract.findOne({ - contractAddress: req.params.contract.toLowerCase(), - blockchain: req.params.networkId, - }); + const { contract, networkId, product } = req.params; + const data = await Contract.aggregate([ + { + $match: { + blockchain: networkId, + contractAddress: contract.toLowerCase(), + }, + }, + { + $lookup: { + from: 'Product', + as: 'productData', + let: { contractId: '$_id' }, + pipeline: [ + { + $match: { + $expr: { + $eq: ['$$contractId', '$contract'], + }, + collectionIndexInContract: product, + }, + }, + ], + }, + }, + ]); if (!contract) { - return next(new AppError('Contract not found.', 404)); + return next(new AppError('Data not found.', 404)); } - req.contract = contract; + const { productData, ...contractData } = data[0]; + + req.contract = contractData; + [req.product] = productData; return next(); } catch (e) { diff --git a/rair-node/bin/api/notifications/notifications.Controller.js b/rair-node/bin/api/notifications/notifications.Controller.js index 0186af7b8..85a387c08 100644 --- a/rair-node/bin/api/notifications/notifications.Controller.js +++ b/rair-node/bin/api/notifications/notifications.Controller.js @@ -22,15 +22,15 @@ router.get( getSingleNotification, ); router.put( - '/:id/', + '/', requireUserSession, - validation(['dbId'], 'params'), + validation(['dbIdArray'], 'params'), markNotificationAsRead, ); router.delete( - '/:id', + '/', requireUserSession, - validation(['dbId'], 'params'), + validation(['dbIdArray'], 'params'), deleteNotification, ); diff --git a/rair-node/bin/api/notifications/notifications.Service.js b/rair-node/bin/api/notifications/notifications.Service.js index 1daea790d..f3e2e8042 100644 --- a/rair-node/bin/api/notifications/notifications.Service.js +++ b/rair-node/bin/api/notifications/notifications.Service.js @@ -28,13 +28,47 @@ module.exports = { if (user && adminRights) { filter.user = user.toLowerCase(); } - const list = await Notification.find(filter) - .sort({ createdAt: 'descending' }) - .skip(itemsPerPage * pageNum) - .limit(itemsPerPage); + const list = await Notification.aggregate([ + { + $match: filter, + }, + { + $lookup: { + from: 'MintedToken', + let: { + tokenId: '$data', + }, + pipeline: [ + { + $match: { + $expr: { + $in: [ + { + $toString: '$_id', + }, + '$$tokenId', + ], + }, + }, + }, + ], + as: 'tokenData', + }, + }, + { + $addFields: { + tokenData: '$tokenData.metadata.image', + }, + }, + { $sort: { createdAt: 1 } }, + { $skip: itemsPerPage * pageNum }, + { $limit: itemsPerPage }, + ]); + const count = await Notification.count(filter); return res.json({ success: true, notifications: list, + totalCount: count, }); } catch (err) { logger.error(err); @@ -73,7 +107,7 @@ module.exports = { ); return res.json({ success: true, - notification, + updated: result.modifiedCount, }); } catch (err) { logger.error(err); @@ -93,7 +127,7 @@ module.exports = { const result = await Notification.deleteMany(filter); return res.json({ success: true, - notification, + deleted: result.deletedCount, }); } catch (err) { logger.error(err); diff --git a/rair-node/bin/api/tokens/tokens.Controller.js b/rair-node/bin/api/tokens/tokens.Controller.js index efd928a98..ba442f097 100644 --- a/rair-node/bin/api/tokens/tokens.Controller.js +++ b/rair-node/bin/api/tokens/tokens.Controller.js @@ -2,13 +2,11 @@ const { Router } = require('express'); const { getSingleToken, getAllTokens, - getTokenNumbers, getFullTokenInfo, } = require('./tokens.Service'); const { getSpecificContracts } = require('../contracts/contracts.Service'); const { validation, - requireUserSession, } = require('../../middleware'); const router = Router(); @@ -20,13 +18,6 @@ router.get( getAllTokens, ); -router.get( - '/tokenNumbers', - requireUserSession, - validation(['getTokenNumbers'], 'query'), - getTokenNumbers, -); - router.get( '/:token', validation(['tokenNumber'], 'params'), diff --git a/rair-node/bin/api/tokens/tokens.Service.js b/rair-node/bin/api/tokens/tokens.Service.js index fa9e5c2a1..9d5e9bb52 100644 --- a/rair-node/bin/api/tokens/tokens.Service.js +++ b/rair-node/bin/api/tokens/tokens.Service.js @@ -97,32 +97,6 @@ exports.getOfferPoolByContractAndProduct = async (req, res, next) => { } }; -exports.getTokenNumbers = async (req, res, next) => { - try { - const { contract, offerPool, offers } = req.query; - const options = { - contract, - }; - if (offerPool) { - options.offerPool = offerPool.marketplaceCatalogIndex; - } - if (offers) { - options.offer = { $in: offers }; - } - const tokens = await MintedToken.find(options) - .sort([['token', 1]]) - .collation({ locale: 'en_US', numericOrdering: true }) - .distinct('token'); - // handle respond \|/ - if (!tokens || tokens.length === 0) { - return next(new AppError('No Tokens found', 404)); - } - return res.json({ success: true, tokens }); - } catch (err) { - return next(err); - } -}; - exports.getAllTokens = async (req, res, next) => { try { const { skip, limit, query } = processPaginationQuery(req.query); @@ -134,7 +108,7 @@ exports.getAllTokens = async (req, res, next) => { } catch (err) { return next(new AppError(err)); } -} +}; exports.updateTokenCommonMetadata = async (req, res, next) => { try { diff --git a/rair-node/bin/integrations/ethers/importContractData.js b/rair-node/bin/integrations/ethers/importContractData.js index c5ef97398..d440cff50 100644 --- a/rair-node/bin/integrations/ethers/importContractData.js +++ b/rair-node/bin/integrations/ethers/importContractData.js @@ -227,8 +227,15 @@ module.exports = { limit, ], ); - const ownerResponse = await alchemySDK.nft.getOwnersForNft(nft.contract.address, nft.tokenId); - [nft.owner] = ownerResponse.owners; + try { + const ownerResponse = await alchemySDK.nft.getOwnersForNft( + nft.contract.address, + nft.tokenId, + ); + [nft.owner] = ownerResponse.owners; + } catch (err) { + log.error(`Could not query owner of NFT #${nft.tokenId}`); + } if (insertToken(nft, contract._id)) { numberOfTokensAdded += 1; } diff --git a/rair-node/bin/models/notification.js b/rair-node/bin/models/notification.js index 547663299..bf3bc95fb 100644 --- a/rair-node/bin/models/notification.js +++ b/rair-node/bin/models/notification.js @@ -6,6 +6,6 @@ const Notification = new Schema({ message: { type: String, required: false, default: true }, data: [{ type: String, required: false }], read: { type: Boolean, required: true, default: false }, -}, { versionKey: false }); +}, { versionKey: false, timestamps: true }); module.exports = Notification; diff --git a/rair-node/bin/models/serverSettings.js b/rair-node/bin/models/serverSettings.js index b0dcd83b1..710cd0cbf 100644 --- a/rair-node/bin/models/serverSettings.js +++ b/rair-node/bin/models/serverSettings.js @@ -41,6 +41,7 @@ const ServerSetting = new Schema({ legal: { type: String, required: false }, // Favicon favicon: { type: String, required: false }, + signupMessage: { type: String, required: false, default: 'Welcome' }, }, { versionKey: false, timestamps: false }); module.exports = ServerSetting; diff --git a/rair-node/bin/routes/index.js b/rair-node/bin/routes/index.js index 7bb9563b9..f9d8f43a1 100644 --- a/rair-node/bin/routes/index.js +++ b/rair-node/bin/routes/index.js @@ -17,6 +17,7 @@ const nftController = require('../api/nft/nft.Controller'); const uploadController = require('../api/upload/upload.Controller'); const favoritesController = require('../api/favorites/favorites.Controller'); const notificationsController = require('../api/notifications/notifications.Controller'); +const categoriesController = require('../api/categories/categories.Controller'); const router = Router(); router.use('/analytics', analyticsController); @@ -36,6 +37,7 @@ router.use('/settings', settingsRouter); router.use('/tokens', tokensController); router.use('/upload', uploadController); router.use('/notifications', notificationsController); +router.use('/categories', categoriesController); // Custom temporary endpoint for the monaco2021 router.use('/', require('./monaco2021')); diff --git a/rair-node/bin/schemas/commonApiSchemas.js b/rair-node/bin/schemas/commonApiSchemas.js index 17fc2aba6..eef2b6ab1 100644 --- a/rair-node/bin/schemas/commonApiSchemas.js +++ b/rair-node/bin/schemas/commonApiSchemas.js @@ -9,6 +9,9 @@ module.exports = { dbId: () => ({ id: mongoId, }), + dbIdArray: () => ({ + ids: Joi.array().items(mongoId), + }), fileId: () => ({ id: Joi.string().required(), }), diff --git a/rair-node/bin/schemas/databaseSchemas.js b/rair-node/bin/schemas/databaseSchemas.js index f14cda3db..0c042a2ef 100644 --- a/rair-node/bin/schemas/databaseSchemas.js +++ b/rair-node/bin/schemas/databaseSchemas.js @@ -21,6 +21,7 @@ module.exports = { url: Joi.string(), })), legal: Joi.string(), + signupMessage: Joi.string(), }), dbContracts: () => ({ title: Joi.string(), @@ -123,4 +124,10 @@ module.exports = { type: Joi.string(), read: Joi.boolean(), }), + dbCategory: () => ({ + list: Joi.array().items(Joi.object({ + name: Joi.string().required(), + _id: mongoId, + })), + }), }; diff --git a/rair-node/bin/schemas/index.js b/rair-node/bin/schemas/index.js index 606dd71a0..25dc4a8d7 100644 --- a/rair-node/bin/schemas/index.js +++ b/rair-node/bin/schemas/index.js @@ -51,6 +51,7 @@ const { const { pagination, dbId, + dbIdArray, fileId, userAddress, resaleFlag, @@ -134,6 +135,7 @@ module.exports = { // Common schemas pagination, dbId, + dbIdArray, fileId, userAddress, resaleFlag, diff --git a/rair-node/bin/schemas/v2TokenSchemas.js b/rair-node/bin/schemas/v2TokenSchemas.js index a6535f518..feb8ca83e 100644 --- a/rair-node/bin/schemas/v2TokenSchemas.js +++ b/rair-node/bin/schemas/v2TokenSchemas.js @@ -8,8 +8,7 @@ module.exports = { forceOverwrite: Joi.boolean(), }), getTokenNumbers: () => ({ - contract: mongoId, - product: Joi.string(), + contract: mongoId.required(), offerPool: Joi.string(), offers: Joi.string(), }), diff --git a/rair-node/bin/seeds/index.js b/rair-node/bin/seeds/index.js index 53537cec7..2dfd3d48a 100644 --- a/rair-node/bin/seeds/index.js +++ b/rair-node/bin/seeds/index.js @@ -6,18 +6,21 @@ const { Blockchain, Category, ServerSetting, Contract } = require('../models'); module.exports = async () => { try { - // eslint-disable-next-line no-restricted-syntax - for await (const category of categories) { - await Category.findOneAndUpdate( - category, - category, - { upsert: true, new: true }, - ); + const count = await Category.estimatedDocumentCount(); + if (count === 0) { + // eslint-disable-next-line no-restricted-syntax + for await (const category of categories) { + await Category.findOneAndUpdate( + category, + category, + { upsert: true, new: true }, + ); + } + log.info('Categories empty, populating with default values.'); } } catch (e) { log.error(`Error seeding categories: ${e}`); } - log.info('Categories seeded.'); try { // eslint-disable-next-line no-restricted-syntax diff --git a/rair-node/readme/current/nft/nft_manual_attributes.md b/rair-node/readme/current/nft/nft_manual_attributes.md index 2e085b85a..061153634 100644 --- a/rair-node/readme/current/nft/nft_manual_attributes.md +++ b/rair-node/readme/current/nft/nft_manual_attributes.md @@ -57,15 +57,9 @@ Search using network and address } ``` -**Condition** : Contract not found +**Condition** : Contract or Product not found **Code** : `404 NOT FOUND` **Content** : ```json -{ "success": false, "error": true, "message": "Contract not found" } -``` -**Condition** : Product not found -**Code** : `404 NOT FOUND` -**Content** : -```json -{ "success": false, "error": true, "message": "Product not found" } +{ "success": false, "error": true, "message": "Data not found" } ``` \ No newline at end of file diff --git a/rair-node/readme/current/nft/nft_manual_files.md b/rair-node/readme/current/nft/nft_manual_files.md index dc90d0596..671273462 100644 --- a/rair-node/readme/current/nft/nft_manual_files.md +++ b/rair-node/readme/current/nft/nft_manual_files.md @@ -46,15 +46,9 @@ Search using network and address } ``` -**Condition** : Contract not found +**Condition** : Contract or Product not found **Code** : `404 NOT FOUND` **Content** : ```json -{ "success": false, "error": true, "message": "Contract not found" } -``` -**Condition** : Product not found -**Code** : `404 NOT FOUND` -**Content** : -```json -{ "success": false, "error": true, "message": "Product not found" } +{ "success": false, "error": true, "message": "Data not found" } ``` \ No newline at end of file diff --git a/rair-node/readme/current/nft/nft_manual_files_token.md b/rair-node/readme/current/nft/nft_manual_files_token.md index a21ddc653..98584d79b 100644 --- a/rair-node/readme/current/nft/nft_manual_files_token.md +++ b/rair-node/readme/current/nft/nft_manual_files_token.md @@ -47,15 +47,9 @@ Search using network and address } ``` -**Condition** : Contract not found +**Condition** : Contract or Product not found **Code** : `404 NOT FOUND` **Content** : ```json -{ "success": false, "error": true, "message": "Contract not found" } -``` -**Condition** : Product not found -**Code** : `404 NOT FOUND` -**Content** : -```json -{ "success": false, "error": true, "message": "Product not found" } +{ "success": false, "error": true, "message": "Data not found" } ``` \ No newline at end of file diff --git a/rair-node/readme/current/nft/nft_manual_locks.md b/rair-node/readme/current/nft/nft_manual_locks.md index 233a5d87b..6d01a8b6c 100644 --- a/rair-node/readme/current/nft/nft_manual_locks.md +++ b/rair-node/readme/current/nft/nft_manual_locks.md @@ -91,17 +91,11 @@ Search using network and address } ``` -**Condition** : Contract not found +**Condition** : Contract or Product not found **Code** : `404 NOT FOUND` **Content** : ```json -{ "success": false, "error": true, "message": "Contract not found" } -``` -**Condition** : Product not found -**Code** : `404 NOT FOUND` -**Content** : -```json -{ "success": false, "error": true, "message": "Product not found" } +{ "success": false, "error": true, "message": "Data not found" } ``` **Condition** : There are no offers with locked tokens **Code** : `404 NOT FOUND` diff --git a/rair-node/readme/current/nft/nft_manual_numbers.md b/rair-node/readme/current/nft/nft_manual_numbers.md new file mode 100644 index 000000000..f495f5fa7 --- /dev/null +++ b/rair-node/readme/current/nft/nft_manual_numbers.md @@ -0,0 +1,49 @@ +# Get the files associated to a product +Search using network and address + +**URL** : `/api/nft/network/:network/:contractAddress/:product/files` +**Method** : `GET` + +**Parameters** +```json +{ + "network": { "required": true, "content": { "type": "string" } }, + "contractAddress": { "required": true, "content": { "type": "string" } }, + "product": { "required": true, "content": { "type": "string" } }, +} +``` + +## Success Response +**Code** : `200 OK` +**Content-Type**: `application/json` +**Content example** +```json +{ + "success": true, + "tokens": [ + { + "token": "0", + "sold": true + }, + { + "token": "1", + "sold": true + }, + { + "token": "2", + "sold": true + }, + { + "token": "3", + "sold": true + } + ] +} +``` + +**Condition** : Contract or Product not found +**Code** : `404 NOT FOUND` +**Content** : +```json +{ "success": false, "error": true, "message": "Data not found" } +``` \ No newline at end of file diff --git a/rair-node/readme/current/nft/nft_manual_offers.md b/rair-node/readme/current/nft/nft_manual_offers.md index 6df13aa5b..c6a44439f 100644 --- a/rair-node/readme/current/nft/nft_manual_offers.md +++ b/rair-node/readme/current/nft/nft_manual_offers.md @@ -65,15 +65,9 @@ Search using network and address } ``` -**Condition** : Contract not found +**Condition** : Contract or Product not found **Code** : `404 NOT FOUND` **Content** : ```json -{ "success": false, "error": true, "message": "Contract not found" } -``` -**Condition** : Product not found -**Code** : `404 NOT FOUND` -**Content** : -```json -{ "success": false, "error": true, "message": "Product not found" } +{ "success": false, "error": true, "message": "Data not found" } ``` \ No newline at end of file diff --git a/rair-node/readme/current/nft/nft_manual_product.md b/rair-node/readme/current/nft/nft_manual_product.md index be28e1501..91d46ea15 100644 --- a/rair-node/readme/current/nft/nft_manual_product.md +++ b/rair-node/readme/current/nft/nft_manual_product.md @@ -126,15 +126,9 @@ Search using network and address } ``` -**Condition** : Contract not found +**Condition** : Contract or Product not found **Code** : `404 NOT FOUND` **Content** : ```json -{ "success": false, "error": true, "message": "Contract not found" } -``` -**Condition** : Product not found -**Code** : `404 NOT FOUND` -**Content** : -```json -{ "success": false, "error": true, "message": "Product not found" } +{ "success": false, "error": true, "message": "Data not found" } ``` \ No newline at end of file diff --git a/rair-node/readme/current/notifications/delete.md b/rair-node/readme/current/notifications/delete.md index 21b3be43d..2eb8c27ae 100644 --- a/rair-node/readme/current/notifications/delete.md +++ b/rair-node/readme/current/notifications/delete.md @@ -1,12 +1,12 @@ # Delete single notification -**URL** : `/api/notifications/:id` +**URL** : `/api/notifications/` **Method** : `DELETE` -**Parameters** +**Body Parameters** ```json { - "id": { "required": false, "content": { "type": "string" } }, + "ids": { "required": true, "content": { "type": "string[]" } }, } ``` @@ -17,13 +17,6 @@ ```json { "success": true, - "notification": { - "_id": "6657edb4d1f06c7b91e39089", - "user": "0xec30759d0a3f3ce0a730111dc29d74e441f492c3", - "type": "message", - "message": "Logged in as 0xec30759d0a3f3ce0a730111dc29d74e441f492c3", - "data": [], - "read": true - }, + "deleted": 1 } ``` \ No newline at end of file diff --git a/rair-node/readme/current/notifications/get_list.md b/rair-node/readme/current/notifications/get_list.md index ac716bd81..9687f466a 100644 --- a/rair-node/readme/current/notifications/get_list.md +++ b/rair-node/readme/current/notifications/get_list.md @@ -11,7 +11,8 @@ Uses pagination and filtering, user search is only available to admins "itemsPerPage": { "required": false, "content": { "type": "number" } }, "user": { "required": false, "content": { "type": "string" } }, "type": { "required": false, "content": { "type": "string" } }, - "read": { "required": false, "content": { "type": "boolean" } }, + "onlyRead": { "required": false, "content": { "type": "boolean" } }, + "onlyUnread": { "required": false, "content": { "type": "boolean" } }, } ``` @@ -22,6 +23,7 @@ Uses pagination and filtering, user search is only available to admins ```json { "success": true, + "totalCount": 90, "notifications": [ { "_id": "6657edb4d1f06c7b91e39089", diff --git a/rair-node/readme/current/notifications/mark_read.md b/rair-node/readme/current/notifications/mark_read.md index ae599d2f5..c6eadd7cc 100644 --- a/rair-node/readme/current/notifications/mark_read.md +++ b/rair-node/readme/current/notifications/mark_read.md @@ -1,12 +1,12 @@ # Mark single notification as read -**URL** : `/api/notifications/:id` +**URL** : `/api/notifications/` **Method** : `PUT` -**Parameters** +**Body Parameters** ```json { - "id": { "required": false, "content": { "type": "string" } }, + "ids": { "required": true, "content": { "type": "string[]" } }, } ``` @@ -17,13 +17,6 @@ ```json { "success": true, - "notification": { - "_id": "6657edb4d1f06c7b91e39089", - "user": "0xec30759d0a3f3ce0a730111dc29d74e441f492c3", - "type": "message", - "message": "Logged in as 0xec30759d0a3f3ce0a730111dc29d74e441f492c3", - "data": [], - "read": false - }, + "updated": 3 } ``` \ No newline at end of file diff --git a/rair-node/readme/current/tokens/tokens_numbers.md b/rair-node/readme/current/tokens/tokens_numbers.md deleted file mode 100644 index 1dc68dbd4..000000000 --- a/rair-node/readme/current/tokens/tokens_numbers.md +++ /dev/null @@ -1,34 +0,0 @@ -# Get all token numbers available in a contract -Ideally add used with the offers param - -**URL** : `/api/tokens/tokenNumbers` -**Method** : `GET` - -**Query Parameters** -```json -{ - "contract": { "required": true, "content": { "type": "string" } }, - "offerPool": { "required": true, "content": { "type": "string" } }, - "offers": { "required": false, "content": { "type": "string[]" } }, -} -``` - -## Success Response -**Code** : `200 OK` -**Content-Type**: `application/json` -**Content example** -```json -{ - "success": true, - "tokens": [ - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7" - ] -} -``` \ No newline at end of file