From 11a970a1cedcc6a1bd1f892be62c87d0f60bc7a7 Mon Sep 17 00:00:00 2001 From: kunalmkv Date: Sat, 9 Dec 2023 23:32:00 +0530 Subject: [PATCH 1/4] dev | stable | token | logo --- backend/api/controllers/token.controller.js | 23 ++++++++++++++------- backend/model/token.logo.model.js | 18 ++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 backend/model/token.logo.model.js diff --git a/backend/api/controllers/token.controller.js b/backend/api/controllers/token.controller.js index bc11071..66a9647 100644 --- a/backend/api/controllers/token.controller.js +++ b/backend/api/controllers/token.controller.js @@ -6,7 +6,9 @@ const priceLib = require("../../lib/price.lib") const responseLib = require("../../lib/response.lib") const validatorsUtil = require("../../util/validators.util") const resStatusEnum = require("../../enum/res.status.enum") - +const tokenLogoModel = require("../../model/token.logo.model"); +const { mongo } = require("mongoose"); +const mongoLib = require("../../lib/mongo.lib"); async function getTokenInfo(req, res) { try { let web3 = await web3Lib.getWebSocketWeb3Instance(process.env.ETH_NODE_WS_URL); @@ -16,23 +18,28 @@ async function getTokenInfo(req, res) { return responseLib.sendResponse(res, null, "Missing token Address", resStatusEnum.VALIDATION_ERROR) } tokenAddress = tokenAddress.toLowerCase() - const url = `https://api.1inch.dev/token/v1.2/1/custom/${tokenAddress}`; + const tokenPrice = await priceLib.getTokenPriceFromChainlinkPriceOracle("eth", tokenAddress, web3) + const logoExist = await mongoLib.findOneByQuery(tokenLogoModel, {address : tokenAddress} ) + if(validatorsUtil.isNotEmpty(logoExist)){ + return responseLib.sendResponse(res,{ logo: logoExist.logo, price: tokenPrice.usdPrice } , null ,resStatusEnum.SUCCESS) + } + + const url = `https://api.1inch.dev/token/v1.2/1/custom/${tokenAddress}`; const config = { headers: { "Authorization": `Bearer ${apiKeyConfig["1inch"]}` }, params: {} }; - const response = await axios.get(url, config); + if (validatorsUtil.isEmpty(response) || validatorsUtil.isEmpty(response.data)) { return responseLib.sendResponse(res, null, "token not available", resStatusEnum.INTERNAL_SERVER_ERROR) } - - const tokenPrice = await priceLib.getTokenPriceFromChainlinkPriceOracle("eth", tokenAddress, web3) - response.data.price = tokenPrice.usdPrice - return responseLib.sendResponse(res, response.data, null, resStatusEnum.SUCCESS) - + + await mongoLib.findOneAndUpdate(tokenLogoModel , { address : response.data.address.toLowerCase() } , {logo :response.data.logoURI } ,{upsert : true}) + return responseLib.sendResponse(res, { logo : response.data.logoURI , price : tokenPrice.usdPrice }, null, resStatusEnum.SUCCESS) + } catch (error) { return responseLib.sendResponse(res, null, error, resStatusEnum.INTERNAL_SERVER_ERROR) } diff --git a/backend/model/token.logo.model.js b/backend/model/token.logo.model.js new file mode 100644 index 0000000..bdbd062 --- /dev/null +++ b/backend/model/token.logo.model.js @@ -0,0 +1,18 @@ +const mongoose = require("mongoose") +const dbEnum = require("../enum/db.enum") + +const tokenLogo = new mongoose.Schema( + { + address: { + type: String, + }, + logo: { + type: String, + } + }, + {timestamps: true}, +) + +tokenLogo.index({address: 1}) + +module.exports = mongoose.connection.useDb(dbEnum.FINSAFE).model("logo", tokenLogo) From 5ac9ec81caf5759c8ff5b38439fb1245cf937401 Mon Sep 17 00:00:00 2001 From: ciphernova Date: Sat, 9 Dec 2023 23:46:06 +0530 Subject: [PATCH 2/4] stable|backend|added alerts on subscribing --- backend/api/controllers/alert.controller.js | 9 +++++++++ backend/lib/alert.lib.js | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/api/controllers/alert.controller.js b/backend/api/controllers/alert.controller.js index 7452e1a..eec36fd 100644 --- a/backend/api/controllers/alert.controller.js +++ b/backend/api/controllers/alert.controller.js @@ -1,4 +1,7 @@ const mongoLib = require("../../lib/mongo.lib"); +const emailLib = require("../../lib/email.lib"); +const slackLib = require("../../lib/slack.lib"); +const discordLib = require("../../lib/discord.lib"); const responseLib = require("../../lib/response.lib"); const validatorUtil = require("../../util/validators.util"); @@ -20,6 +23,8 @@ async function subscribeToEmail(req, res) { $addToSet: {alertPreferences: alertTypeEnum.EMAIL}, email: email }); + await emailLib.sendEmail(email, "Welcome To finSafe", "You have successfully subscribed to finSafe for alerts.") + return responseLib.sendResponse(res, "Subscribed to email successfully!", null, resStatusEnum.SUCCESS); } catch (error) { return responseLib.sendResponse(res, null, error, resStatusEnum.INTERNAL_SERVER_ERROR) @@ -40,6 +45,8 @@ async function subscribeToSlack(req, res) { $addToSet: {alertPreferences: alertTypeEnum.SLACK}, slackWebhook: webhook }); + await slackLib.sendAlert(webhook, "Welcome To finSafe", "You have successfully subscribed to finSafe for alerts.") + return responseLib.sendResponse(res, "Subscribed to slack successfully!", null, resStatusEnum.SUCCESS); } catch (error) { return responseLib.sendResponse(res, null, error, resStatusEnum.INTERNAL_SERVER_ERROR) @@ -60,6 +67,8 @@ async function subscribeToDiscord(req, res) { $addToSet: {alertPreferences: alertTypeEnum.DISCORD}, discordWebhook: webhook }); + await discordLib.sendAlert(webhook, "Welcome To finSafe", "You have successfully subscribed to finSafe for alerts.") + return responseLib.sendResponse(res, "Subscribed to discord successfully!", null, resStatusEnum.SUCCESS); } catch (error) { return responseLib.sendResponse(res, null, error, resStatusEnum.INTERNAL_SERVER_ERROR) diff --git a/backend/lib/alert.lib.js b/backend/lib/alert.lib.js index 94cfc99..52b09ff 100644 --- a/backend/lib/alert.lib.js +++ b/backend/lib/alert.lib.js @@ -88,7 +88,7 @@ async function generateAlert(address, timestamp, title, body) { function isValidPushAlert(alert) { try { - if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.address) || validatorUtil.isEmpty(alert.title) || validatorUtil.isEmpty(alert.body) || validatorUtil.isEmpty(alert.timestamp)) { + if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.address) || validatorUtil.isEmpty(alert.title) || validatorUtil.isEmpty(alert.body) || validatorUtil.isNil(alert.timestamp)) { return false; } return true; @@ -99,7 +99,7 @@ function isValidPushAlert(alert) { function isValidEmailAlert(alert) { try { - if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.email) || validatorUtil.isEmpty(alert.subject) || validatorUtil.isEmpty(alert.body) || validatorUtil.isEmpty(alert.timestamp)) { + if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.email) || validatorUtil.isEmpty(alert.subject) || validatorUtil.isEmpty(alert.body) || validatorUtil.isNil(alert.timestamp)) { return false; } return true; @@ -110,7 +110,7 @@ function isValidEmailAlert(alert) { function isValidSlackAlert(alert) { try { - if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.title) || validatorUtil.isEmpty(alert.body) || validatorUtil.isEmpty(alert.webhook) || validatorUtil.isEmpty(alert.timestamp)) { + if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.title) || validatorUtil.isEmpty(alert.body) || validatorUtil.isEmpty(alert.webhook) || validatorUtil.isNil(alert.timestamp)) { return false; } return true; @@ -121,7 +121,7 @@ function isValidSlackAlert(alert) { function isValidDiscordAlert(alert) { try { - if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.body) || validatorUtil.isEmpty(alert.webhook) || validatorUtil.isEmpty(alert.timestamp)) { + if (validatorUtil.isEmpty(alert) || validatorUtil.isEmpty(alert.body) || validatorUtil.isEmpty(alert.webhook) || validatorUtil.isNil(alert.timestamp)) { return false; } return true; From bb3f557f8f92093bfe99f35b8fdf5e9252515726 Mon Sep 17 00:00:00 2001 From: kunalmkv Date: Sat, 9 Dec 2023 23:49:48 +0530 Subject: [PATCH 3/4] frontend|stable|feed UI donw --- frontend/package-lock.json | 6 +++++ frontend/package.json | 1 + frontend/src/api/axios.instance.js | 6 ++++- frontend/src/api/profile.api.js | 13 ++++++++++- frontend/src/components/HomePage.jsx | 15 ++++++------ frontend/src/components/TransactionsFeed.jsx | 6 +++-- frontend/src/components/transactionFeed.css | 19 ++++++++++------ frontend/src/pages/FeedPage.jsx | 24 ++++++++++++++++---- frontend/src/pages/ProfilePage.jsx | 4 +++- frontend/src/store/userStore.js | 2 ++ frontend/src/utils/index.js | 18 +++++++++++++++ 11 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 frontend/src/utils/index.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1a3fcf8..676c092 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.6.2", "body-parser": "^1.20.2", + "dayjs": "^1.11.10", "express": "^4.18.2", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -7311,6 +7312,11 @@ "node": ">=10" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7e78c55..6ee7314 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.6.2", "body-parser": "^1.20.2", + "dayjs": "^1.11.10", "express": "^4.18.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/frontend/src/api/axios.instance.js b/frontend/src/api/axios.instance.js index 6970f8e..2cd9ee7 100644 --- a/frontend/src/api/axios.instance.js +++ b/frontend/src/api/axios.instance.js @@ -11,4 +11,8 @@ let tokenAxios = axios.create({ baseURL: "https://finsafe-backend.insidefi.io/token/" }); -export { userAxios, tokenAxios }; \ No newline at end of file +let feedAxios = axios.create({ + baseURL:"https://finsafe-backend.insidefi.io/feed/" +}); + +export { userAxios, tokenAxios ,feedAxios}; \ No newline at end of file diff --git a/frontend/src/api/profile.api.js b/frontend/src/api/profile.api.js index 359e48b..c462e00 100644 --- a/frontend/src/api/profile.api.js +++ b/frontend/src/api/profile.api.js @@ -1,4 +1,4 @@ -import { userAxios, tokenAxios } from "./axios.instance"; +import {userAxios, tokenAxios, feedAxios} from "./axios.instance"; export const getPortfolioDetails = async (address) => { try { @@ -22,3 +22,14 @@ export const getTokenDetails = async (token) => { console.log(`Error occured while fetching token details`, err); } }; +export const getFeedDetails = async (address) => { + try { + const response = await feedAxios.get(`${address}?limit=20`); + const { result } = response.data; + console.log(result); + const alert=result.data.alerts; + return alert; + } catch (err) { + console.log(`Error occured while fetching feed details`, err); + } +} diff --git a/frontend/src/components/HomePage.jsx b/frontend/src/components/HomePage.jsx index 3fae627..33c5325 100644 --- a/frontend/src/components/HomePage.jsx +++ b/frontend/src/components/HomePage.jsx @@ -2,9 +2,11 @@ import React, {useState} from "react"; import Web3 from "web3"; import ConnectWalletButton from "./ConnectWalltet/Walltet"; import axios from "axios"; +import {useUserStore} from "../store/userStore"; const HomePage = () => { const [loading, setLoading] = useState(false); - const [address, setAddress] = useState(""); + const userAddress = useUserStore((state) => state.userAddress); + const setUserAddress = useUserStore((state) => state.setUserAddress); const onPressConnect = async () => { setLoading(true); @@ -17,7 +19,7 @@ const HomePage = () => { }); let account = Web3.utils.toChecksumAddress(accounts[0]); - setAddress(account); + setUserAddress(account); account = account.toLowerCase(); // get nonce const nonce = await axios.get( @@ -34,8 +36,6 @@ const HomePage = () => { params: [message, account], }); - console.log(signature); - // send signature to backend const response = await axios.post( "http://localhost:3001/user/validate/signature", @@ -45,7 +45,7 @@ const HomePage = () => { } ); - console.log(response); + } } catch (error) { console.log(error); @@ -53,8 +53,9 @@ const HomePage = () => { setLoading(false); }; + console.log(userAddress) - const onPressLogout = () => setAddress(""); + const onPressLogout = () => setUserAddress(""); return (
{ onPressConnect={onPressConnect} onPressLogout={onPressLogout} loading={loading} - address={address} + address={userAddress} /> {/* Add more text or components as needed */}
diff --git a/frontend/src/components/TransactionsFeed.jsx b/frontend/src/components/TransactionsFeed.jsx index b74b79a..26a75dd 100644 --- a/frontend/src/components/TransactionsFeed.jsx +++ b/frontend/src/components/TransactionsFeed.jsx @@ -1,12 +1,14 @@ import './transactionFeed.css'; import {Typography} from "@mui/material"; +import dayjs, { Dayjs } from "dayjs"; +import { calculateRelativeTime } from '../utils'; export const TransactionsFeed=({date,hash,description}) => { return ( <>
- {date} - {hash.slice(0,6) + '...' + hash.slice(-4)} + {calculateRelativeTime(date)} + {hash}
{description} diff --git a/frontend/src/components/transactionFeed.css b/frontend/src/components/transactionFeed.css index d28484e..3f25419 100644 --- a/frontend/src/components/transactionFeed.css +++ b/frontend/src/components/transactionFeed.css @@ -4,14 +4,16 @@ #main-container { display: flex; flex-direction: row; - width: 100vw; + width: 90vw; background-color: #ffffff; justify-content: flex-start; + align-items: center; color: #000000; padding: 10px; border-radius: 5px; border-bottom: 1px solid #333333; - margin :5px 0px; + margin: 5px 0px; + transition: background-color 0.3s ease, color 0.3s ease; /* Added transition for hover effect */ } /* Main section styling with date and address in columns */ @@ -19,7 +21,6 @@ display: flex; flex-direction: column; flex: 0.3; - } /* Styling for date and address */ @@ -31,12 +32,16 @@ /* Description section styling */ #description { flex: 0.7; - padding: 8px; } -/* Optional: Add hover effect on the main container */ +/* Hover effect on the main container */ +#main-container:hover { + background-color: #282525; + color: #ffffff; /* Change text color on hover */ +} + +/* Optional: Add a subtle box shadow on hover */ #main-container:hover { - background-color: #9d9696; - transition: background-color 0.3s ease; + box-shadow: 0 0 10px rgba(145, 140, 140, 0.2); } diff --git a/frontend/src/pages/FeedPage.jsx b/frontend/src/pages/FeedPage.jsx index 3c50d3d..6c965d3 100644 --- a/frontend/src/pages/FeedPage.jsx +++ b/frontend/src/pages/FeedPage.jsx @@ -1,14 +1,30 @@ import {Typography} from "@mui/material"; import {TransactionsFeed} from "../components/TransactionsFeed"; import {feedData} from "../components/data"; +import {useUserStore} from "../store/userStore"; +import {getFeedDetails} from "../api/profile.api"; +import {useEffect} from "react"; + +export const FeedPage=({isActive}) => { + const data=useUserStore((state) => state.feedData); + const setFeedData=useUserStore((state) => state.setFeedData); + const userAddress='0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c' + const getUserFeed=async () => { + const data=await getFeedDetails(userAddress); + setFeedData(data); + } + + console.log(data) + useEffect(() => { + getUserFeed(); + }, [isActive]); -export const FeedPage=() => { return( <> {/*Feed Content Goes Here*/} -
- {feedData.map((item) => ( - +
+ {data.map((item) => ( + ))}
diff --git a/frontend/src/pages/ProfilePage.jsx b/frontend/src/pages/ProfilePage.jsx index 64ad468..70eefa2 100644 --- a/frontend/src/pages/ProfilePage.jsx +++ b/frontend/src/pages/ProfilePage.jsx @@ -14,6 +14,8 @@ import PortfolioDetails from "../components/PortfolioDetails"; const ProfilePage = () => { const [currentTab, setCurrentTab] = React.useState(0); + const [isFeedReference,setisFeedReference]=React.useState(false); + // currentTab===1?setisFeedReference(true):setisFeedReference(false); const handleTabChange = (event, newValue) => { setCurrentTab(newValue); @@ -84,7 +86,7 @@ const ProfilePage = () => { )} {currentTab === 1 && (
- +
)}
diff --git a/frontend/src/store/userStore.js b/frontend/src/store/userStore.js index d74b630..d102699 100644 --- a/frontend/src/store/userStore.js +++ b/frontend/src/store/userStore.js @@ -3,6 +3,8 @@ import { devtools, persist } from "zustand/middleware"; let userStore = (set) => ({ userAddress:"", setUserAddress: (value) => set((state) => ({...state, userAddress: value})), + feedData:[], + setFeedData:(value)=>set((state)=>({...state,feedData:value})), }); userStore = devtools(userStore); diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js new file mode 100644 index 0000000..8c60c8a --- /dev/null +++ b/frontend/src/utils/index.js @@ -0,0 +1,18 @@ +export const calculateRelativeTime=(eventTimestamp) =>{ + const currentTimestamp = Math.floor(Date.now() / 1000); // Convert milliseconds to seconds + const timeDifference = parseInt(currentTimestamp - (eventTimestamp)/1000); + + if (timeDifference < 60) { + return `${timeDifference} second${timeDifference !== 1 ? 's' : ''} ago`; + } else if (timeDifference < 3600) { + const minutes = Math.floor(timeDifference / 60); + + return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`; + } else if (timeDifference < 86400) { + const hours = Math.floor(timeDifference / 3600); + return `${hours} hour${hours !== 1 ? 's' : ''} ago`; + } else { + const days = Math.floor(timeDifference / 86400); + return `${days} day${days !== 1 ? 's' : ''} ago`; + } + } \ No newline at end of file From 0ace1cc64bb9e5ba197a46c9a88cfd17d72a9eb1 Mon Sep 17 00:00:00 2001 From: kunalmkv <138765481+cd-nikhil@users.noreply.github.com> Date: Sun, 10 Dec 2023 01:30:27 +0530 Subject: [PATCH 4/4] frontend|stable|fixing portfolio feed --- frontend/src/components/PortfolioDetails.jsx | 102 +++++++++---------- frontend/src/pages/ProfilePage.jsx | 2 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/PortfolioDetails.jsx b/frontend/src/components/PortfolioDetails.jsx index a6bb217..bbb8a0c 100644 --- a/frontend/src/components/PortfolioDetails.jsx +++ b/frontend/src/components/PortfolioDetails.jsx @@ -1,9 +1,5 @@ import React, { useState, useEffect } from "react"; import { - Accordion, - AccordionSummary, - AccordionDetails, - Typography, Table, TableBody, TableCell, @@ -12,28 +8,37 @@ import { TableRow, Paper, } from "@mui/material"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { useUserStore } from "../store/userStore"; -import { getPortfolioDetails } from "../api/profile.api"; +import { getPortfolioDetails, getTokenDetails } from "../api/profile.api"; const PortfolioDetails = () => { - const [expanded, setExpanded] = useState(false); const [suppliedDetails, setSuppliedDetails] = useState([]); const [borrowedDetails, setBorrowedDetails] = useState([]); - const handleAccordionChange = () => { - setExpanded((prevExpanded) => !prevExpanded); - }; + let userAddress = useUserStore((state) => state.userAddress); + userAddress = "0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c"; + const convertSuppliedToDesiredFormat = (array) => { let result = []; - // array.map((value) => { - // let obj = {}; - // obj.supplied = - // }) + array.map(async (value) => { + const { token, balance, symbol } = value; + // const moreDetails = await getTokenDetails(token); + const moreDetails = { + logoURI: + "https://tokens-data.1inch.io/images/0xdac17f958d2ee523a2206206994597c13d831ec7.png", + price: 1, + }; + let obj = {}; + obj.logo = moreDetails?.logoURI; + obj.symbol = symbol; + obj.balance = balance.toFixed(2); + obj.price = (moreDetails?.price * balance).toFixed(2); + result.push(obj); + }); + return result; }; const callApis = async () => { - const details = await getPortfolioDetails( - "0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c" - ); + const details = await getPortfolioDetails(userAddress); const { metadata } = details[0]; const { supplied, borrowed } = metadata; const derivedSupplied = convertSuppliedToDesiredFormat(supplied); @@ -45,42 +50,37 @@ const PortfolioDetails = () => { }, []); console.log(suppliedDetails); return ( - - } - aria-controls="panel-content" - id="panel-header" - > - Supplied - - - - - - - Supplied - Balance - USD Value - - - - {/* Add your table rows here */} - - Item 1 - 10 - $100 - + +
+ + + Supplied + Balance + USD Value + + + + {/* Add your table rows here */} + {suppliedDetails.map((value) => { + const { logo, balance, price, symbol } = value; + return ( - Item 2 - 20 - $200 + +
+ + {symbol} +
+
+ + {balance} {symbol} + + ${price}
- {/* Add more rows as needed */} -
-
-
-
-
+ ); + })} + + + ); }; diff --git a/frontend/src/pages/ProfilePage.jsx b/frontend/src/pages/ProfilePage.jsx index 70eefa2..a0619b2 100644 --- a/frontend/src/pages/ProfilePage.jsx +++ b/frontend/src/pages/ProfilePage.jsx @@ -54,7 +54,7 @@ const ProfilePage = () => {
{/* Bottom Section - Tabs (Portfolio and Feed) */} -
+