From 7344d28f2847cf518cb1432c53bda2a75cf0e9f4 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Tue, 28 May 2024 19:20:23 +0400 Subject: [PATCH 1/6] Add rewards today tooltip --- frontend/components/Main/MainRewards.tsx | 50 +++++++++++++++++++++--- frontend/styles/globals.scss | 4 ++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/frontend/components/Main/MainRewards.tsx b/frontend/components/Main/MainRewards.tsx index da0617a72..59efab4da 100644 --- a/frontend/components/Main/MainRewards.tsx +++ b/frontend/components/Main/MainRewards.tsx @@ -1,4 +1,15 @@ -import { Button, Col, Flex, Modal, Row, Skeleton, Tag, Typography } from 'antd'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { + Button, + Col, + Flex, + Modal, + Row, + Skeleton, + Tag, + Tooltip, + Typography, +} from 'antd'; import Image from 'next/image'; import { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; @@ -12,7 +23,7 @@ import { useStore } from '@/hooks/useStore'; import { ConfettiAnimation } from '../common/ConfettiAnimation'; -const { Text, Title } = Typography; +const { Text, Title, Paragraph } = Typography; const RewardsRow = styled(Row)` margin: 0 -24px; @@ -24,6 +35,11 @@ const RewardsRow = styled(Row)` } `; +const TOOLTIP_OVERLAY_STYLE = { + maxWidth: 'calc(100% - 48px)', + left: '24px', +}; + const Loader = () => ( @@ -52,8 +68,32 @@ const DisplayRewards = () => { Staking rewards today {isBalanceLoaded ? ( <> - - {balanceFormat(availableRewardsForEpochEth, 2)} OLAS + + {balanceFormat(availableRewardsForEpochEth, 2)} OLAS  + + + + Rewards are specified for the ongoing epoch + + + + An epoch is a period when the agent can earn the + staking rewards. It lasts approximately one day, from + 12 am to 12 am UTC. Start your agent earlier in the + day to get all the rewards. Sometimes, epochs can last + longer, and you can earn more than the number + specified here. + + + } + > + + + {isEligibleForRewards ? ( Earned @@ -72,7 +112,7 @@ const DisplayRewards = () => { Staked amount {isBalanceLoaded ? ( <> - + {balanceFormat(totalOlasStakedBalance, 2)} OLAS {minimumStakedAmountRequired && !isStaked ? ( diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index a823f6f9d..708ec60d3 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -141,6 +141,10 @@ button, input, select, textarea, .ant-input-suffix { margin-right: auto !important; } +.text-xl { + font-size: 20px; +} + .text-base { font-size: 16px !important; } From 604d072396f1d10c39ca969bd6cf8c9cc7c6a1d9 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Fri, 31 May 2024 17:42:57 +0400 Subject: [PATCH 2/6] Remove staked amount block and add current balance breakdown --- frontend/components/Main/KeepAgentRunning.tsx | 4 +- frontend/components/Main/MainOlasBalance.tsx | 100 ++++++++++++++- frontend/components/Main/MainRewards.tsx | 117 +++++------------- frontend/hooks/useReward.ts | 2 + 4 files changed, 129 insertions(+), 94 deletions(-) diff --git a/frontend/components/Main/KeepAgentRunning.tsx b/frontend/components/Main/KeepAgentRunning.tsx index e0ad0f5ee..c44b4bb44 100644 --- a/frontend/components/Main/KeepAgentRunning.tsx +++ b/frontend/components/Main/KeepAgentRunning.tsx @@ -9,6 +9,8 @@ import { CardSection } from '../styled/CardSection'; const { Text } = Typography; +const COVER_BLOCK_BORDERS_STYLE = { marginBottom: '-1px' }; + export const KeepAgentRunning = () => { const { storeState } = useStore(); const { serviceStatus } = useServices(); @@ -17,7 +19,7 @@ export const KeepAgentRunning = () => { if (serviceStatus !== DeploymentStatus.DEPLOYED) return null; return ( - + span { + background: #fff; + z-index: 1; + &:first-child { + padding-right: 6px; + } + &:last-child { + padding-left: 6px; + } + } + + &:before { + content: ''; + position: absolute; + bottom: 6px; + width: 100%; + border-bottom: 2px dotted #c2cbd7; + } + + &:not(:last-child) { + margin-bottom: 8px; + } +`; +const OVERLAY_STYLE = { maxWidth: '300px', width: '300px' }; + +const CurrentBalance = () => { + const { totalOlasBalance, totalOlasStakedBalance } = useBalance(); + const { accruedServiceStakingRewards } = useReward(); + + const balances = useMemo(() => { + return [ + { + title: 'Staked amount', + value: balanceFormat(totalOlasStakedBalance ?? 0, 2), + }, + { + title: 'Unclaimed rewards', + value: balanceFormat(accruedServiceStakingRewards ?? 0, 2), + }, + { + title: 'Unused funds', + value: balanceFormat( + (totalOlasBalance ?? 0) - (totalOlasStakedBalance ?? 0), + 2, + ), + }, + ]; + }, [accruedServiceStakingRewards, totalOlasBalance, totalOlasStakedBalance]); + + return ( + + Current balance  + + {balances.map((item, index) => ( + + {item.title} + {item.value} OLAS + + ))} + + } + > + + + + ); +}; export const MainOlasBalance = () => { const { isBalanceLoaded, totalOlasBalance } = useBalance(); @@ -21,12 +108,15 @@ export const MainOlasBalance = () => { }, [totalOlasBalance]); return ( - + {isBalanceLoaded ? ( <> - {UNICODE_SYMBOLS.OLAS} - {balance} - OLAS + + + {UNICODE_SYMBOLS.OLAS} + {balance} + OLAS + ) : ( diff --git a/frontend/components/Main/MainRewards.tsx b/frontend/components/Main/MainRewards.tsx index 59efab4da..3696cc997 100644 --- a/frontend/components/Main/MainRewards.tsx +++ b/frontend/components/Main/MainRewards.tsx @@ -1,45 +1,19 @@ import { InfoCircleOutlined } from '@ant-design/icons'; -import { - Button, - Col, - Flex, - Modal, - Row, - Skeleton, - Tag, - Tooltip, - Typography, -} from 'antd'; +import { Button, Flex, Modal, Skeleton, Tag, Tooltip, Typography } from 'antd'; import Image from 'next/image'; import { useCallback, useEffect, useState } from 'react'; -import styled from 'styled-components'; import { balanceFormat } from '@/common-util'; -import { COLOR } from '@/constants'; import { useBalance } from '@/hooks'; import { useElectronApi } from '@/hooks/useElectronApi'; import { useReward } from '@/hooks/useReward'; import { useStore } from '@/hooks/useStore'; import { ConfettiAnimation } from '../common/ConfettiAnimation'; +import { CardSection } from '../styled/CardSection'; const { Text, Title, Paragraph } = Typography; -const RewardsRow = styled(Row)` - margin: 0 -24px; - > .ant-col { - padding: 24px; - &:not(:last-child) { - border-right: 1px solid ${COLOR.BORDER_GRAY}; - } - } -`; - -const TOOLTIP_OVERLAY_STYLE = { - maxWidth: 'calc(100% - 48px)', - left: '24px', -}; - const Loader = () => ( @@ -62,69 +36,36 @@ const DisplayRewards = () => { totalOlasStakedBalance >= minimumStakedAmountRequired; return ( - - - - Staking rewards today - {isBalanceLoaded ? ( - <> - - {balanceFormat(availableRewardsForEpochEth, 2)} OLAS  - - - - Rewards are specified for the ongoing epoch - - - - An epoch is a period when the agent can earn the - staking rewards. It lasts approximately one day, from - 12 am to 12 am UTC. Start your agent earlier in the - day to get all the rewards. Sometimes, epochs can last - longer, and you can earn more than the number - specified here. - - - } - > - - - - - {isEligibleForRewards ? ( - Earned - ) : ( - Not yet earned - )} - - ) : ( - - )} - - - - - - Staked amount - {isBalanceLoaded ? ( - <> - - {balanceFormat(totalOlasStakedBalance, 2)} OLAS - - {minimumStakedAmountRequired && !isStaked ? ( - Not yet staked - ) : null} - + + + Staking rewards this work period  + + The agent's working period lasts at least 24 hours, but its + start and end point may not be at the same time every day. + + } + > + + + + {isBalanceLoaded ? ( + + + {balanceFormat(availableRewardsForEpochEth, 2)} OLAS  + + {isEligibleForRewards ? ( + Earned ) : ( - + Not yet earned )} - - + ) : ( + + )} + ); }; diff --git a/frontend/hooks/useReward.ts b/frontend/hooks/useReward.ts index d0222ba9b..f72b03edc 100644 --- a/frontend/hooks/useReward.ts +++ b/frontend/hooks/useReward.ts @@ -8,6 +8,7 @@ export const useReward = () => { availableRewardsForEpochEth, isEligibleForRewards, minimumStakedAmountRequired, + accruedServiceStakingRewards, } = useContext(RewardContext); return { @@ -15,5 +16,6 @@ export const useReward = () => { availableRewardsForEpochEth, isEligibleForRewards, minimumStakedAmountRequired, + accruedServiceStakingRewards, }; }; From cd0b6b1267f0e6c91a577d3e2c538a7634489d26 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Mon, 3 Jun 2024 10:57:02 +0400 Subject: [PATCH 3/6] Show notification and modal on first staking --- electron/main.js | 2 +- frontend/components/Main/MainHeader.tsx | 80 +++++++++++++++++++++--- frontend/components/Main/MainRewards.tsx | 14 +---- frontend/context/ElectronApiProvider.tsx | 2 +- frontend/styles/globals.scss | 4 ++ 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/electron/main.js b/electron/main.js index c820346d6..faf838883 100644 --- a/electron/main.js +++ b/electron/main.js @@ -236,7 +236,7 @@ const createMainWindow = () => { mainWindow.setSize(width, height); }); - ipcMain.on('show-notification', (title, description) => { + ipcMain.on('show-notification', (_event, title, description) => { showNotification(title, description || undefined); }); diff --git a/frontend/components/Main/MainHeader.tsx b/frontend/components/Main/MainHeader.tsx index e222970b6..4d2ce3d8b 100644 --- a/frontend/components/Main/MainHeader.tsx +++ b/frontend/components/Main/MainHeader.tsx @@ -1,5 +1,5 @@ import { InfoCircleOutlined } from '@ant-design/icons'; -import { Badge, Button, Flex, Popover, Typography } from 'antd'; +import { Badge, Button, Flex, Modal, Popover, Typography } from 'antd'; import { formatUnits } from 'ethers/lib/utils'; import Image from 'next/image'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -8,23 +8,73 @@ import { Chain, DeploymentStatus } from '@/client'; import { COLOR, LOW_BALANCE, SERVICE_TEMPLATES } from '@/constants'; import { useBalance, useServiceTemplates } from '@/hooks'; import { useElectronApi } from '@/hooks/useElectronApi'; +import { useReward } from '@/hooks/useReward'; import { useServices } from '@/hooks/useServices'; import { useStore } from '@/hooks/useStore'; import { useWallet } from '@/hooks/useWallet'; import { ServicesService } from '@/service'; import { WalletService } from '@/service/Wallet'; -const { Text } = Typography; +const { Text, Title, Paragraph } = Typography; const LOADING_MESSAGE = "It may take a while to start your agent, so feel free to close the app. We'll notify you once your agent is running."; - enum ServiceButtonLoadingState { Starting, Pausing, NotLoading, } +const FirstRunModal = ({ + open, + onClose, +}: { + open: boolean; + onClose: () => void; +}) => { + const { minimumStakedAmountRequired } = useReward(); + + if (!open) return null; + return ( + + Got it + , + ]} + > + + OLAS logo + + + Your agent is running and you've staked{' '} + {minimumStakedAmountRequired} OLAS! + + Your agent is working towards earning rewards. + + Pearl is designed to make it easy for you to earn staking rewards every + day. Simply leave the app and agent running in the background for ~1hr a + day. + + + ); +}; + export const MainHeader = () => { const { storeState } = useStore(); const { services, serviceStatus, setServiceStatus } = useServices(); @@ -39,6 +89,11 @@ export const MainHeader = () => { setIsPaused: setIsBalancePollingPaused, } = useBalance(); + const [isModalOpen, setIsModalOpen] = useState(false); + const handleModalClose = useCallback(() => setIsModalOpen(false), []); + + const { minimumStakedAmountRequired } = useReward(); + const safeOlasBalanceWithStaked = useMemo(() => { if (safeBalance?.OLAS === undefined) return; if (totalOlasStakedBalance === undefined) return; @@ -122,7 +177,14 @@ export const MainHeader = () => { }) .then(() => { setServiceStatus(DeploymentStatus.DEPLOYED); - showNotification?.('Your agent is now running!'); + if (totalOlasStakedBalance === 0) { + showNotification?.( + `Your agent is running and you've staked ${minimumStakedAmountRequired} OLAS!`, + ); + setIsModalOpen(true); + } else { + showNotification?.('Your agent is now running!'); + } }) .finally(() => { setIsBalancePollingPaused(false); @@ -134,11 +196,13 @@ export const MainHeader = () => { } }, [ masterSafeAddress, + minimumStakedAmountRequired, serviceTemplate, setIsBalancePollingPaused, setServiceStatus, - wallets, showNotification, + totalOlasStakedBalance, + wallets, ]); const handlePause = useCallback(() => { @@ -253,14 +317,14 @@ export const MainHeader = () => { if (!isDeployable) { return ( ); } return ( ); }, [ @@ -273,12 +337,14 @@ export const MainHeader = () => { services, storeState?.isInitialFunded, totalEthBalance, + totalOlasStakedBalance, ]); return ( {agentHead} {serviceToggleButton} + ); }; diff --git a/frontend/components/Main/MainRewards.tsx b/frontend/components/Main/MainRewards.tsx index 3696cc997..a28bb8d05 100644 --- a/frontend/components/Main/MainRewards.tsx +++ b/frontend/components/Main/MainRewards.tsx @@ -22,18 +22,8 @@ const Loader = () => ( ); const DisplayRewards = () => { - const { - availableRewardsForEpochEth, - isEligibleForRewards, - minimumStakedAmountRequired, - } = useReward(); - const { isBalanceLoaded, totalOlasStakedBalance } = useBalance(); - - // check if the staked amount is greater than the minimum required - const isStaked = - minimumStakedAmountRequired && - totalOlasStakedBalance && - totalOlasStakedBalance >= minimumStakedAmountRequired; + const { availableRewardsForEpochEth, isEligibleForRewards } = useReward(); + const { isBalanceLoaded } = useBalance(); return ( diff --git a/frontend/context/ElectronApiProvider.tsx b/frontend/context/ElectronApiProvider.tsx index 08327df01..032551c99 100644 --- a/frontend/context/ElectronApiProvider.tsx +++ b/frontend/context/ElectronApiProvider.tsx @@ -28,7 +28,7 @@ type ElectronApiContextProps = { saveLogs?: (data: { store?: ElectronStore; debugData?: Record; - }) => Promise<{ success: true; dirPath: string } | { success: false }>; + }) => Promise<{ success: true; dirPath: string } | { success?: false }>; openPath?: (filePath: string) => void; }; diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index 708ec60d3..13afcf16c 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -153,6 +153,10 @@ button, input, select, textarea, .ant-input-suffix { font-size: 14px !important; } +.text-center { + text-align: center !important; +} + .font-weight-600 { font-weight: 600 !important; } From bb783b4cf11a06846037e03507c8a16ed6d170e3 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Mon, 3 Jun 2024 13:02:15 +0400 Subject: [PATCH 4/6] Move colors to constants --- frontend/components/Main/MainHeader.tsx | 3 +-- frontend/components/Main/MainOlasBalance.tsx | 7 ++++--- frontend/constants/color.ts | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/components/Main/MainHeader.tsx b/frontend/components/Main/MainHeader.tsx index 4d2ce3d8b..3b9f66c2b 100644 --- a/frontend/components/Main/MainHeader.tsx +++ b/frontend/components/Main/MainHeader.tsx @@ -62,8 +62,7 @@ const FirstRunModal = ({ /> - Your agent is running and you've staked{' '} - {minimumStakedAmountRequired} OLAS! + {`Your agent is running and you've staked ${minimumStakedAmountRequired} OLAS!`} Your agent is working towards earning rewards. diff --git a/frontend/components/Main/MainOlasBalance.tsx b/frontend/components/Main/MainOlasBalance.tsx index 8408b59d3..a3f356e71 100644 --- a/frontend/components/Main/MainOlasBalance.tsx +++ b/frontend/components/Main/MainOlasBalance.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import styled from 'styled-components'; import { balanceFormat } from '@/common-util/numberFormatters'; +import { COLOR } from '@/constants'; import { UNICODE_SYMBOLS } from '@/constants/unicode'; import { useBalance } from '@/hooks'; import { useReward } from '@/hooks/useReward'; @@ -24,10 +25,10 @@ const BalanceBreakdownLine = styled.div` align-items: center; justify-content: space-between; font-size: 14px; - color: #1f2229; + color: ${COLOR.TEXT}; > span { - background: #fff; + background: ${COLOR.WHITE}; z-index: 1; &:first-child { padding-right: 6px; @@ -42,7 +43,7 @@ const BalanceBreakdownLine = styled.div` position: absolute; bottom: 6px; width: 100%; - border-bottom: 2px dotted #c2cbd7; + border-bottom: 2px dotted ${COLOR.BORDER_GRAY}; } &:not(:last-child) { diff --git a/frontend/constants/color.ts b/frontend/constants/color.ts index 1c825de0f..7625d03ee 100644 --- a/frontend/constants/color.ts +++ b/frontend/constants/color.ts @@ -8,4 +8,5 @@ export const COLOR = { WHITE: '#ffffff', BORDER_GRAY: '#DFE5EE', BROWN: '#873800', + TEXT: '#1f2229', }; From 754dbafb22a0a459981de32566ef99fd0b088859 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Mon, 3 Jun 2024 13:40:51 +0400 Subject: [PATCH 5/6] Review fix --- frontend/components/Main/MainHeader.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/components/Main/MainHeader.tsx b/frontend/components/Main/MainHeader.tsx index 3b9f66c2b..abeb35c21 100644 --- a/frontend/components/Main/MainHeader.tsx +++ b/frontend/components/Main/MainHeader.tsx @@ -169,6 +169,8 @@ export const MainHeader = () => { // }); // } + const isFirstTimeStaking = totalOlasStakedBalance === 0; + // For now POST /api/services will take care of creating, starting and updating the service return ServicesService.createService({ serviceTemplate, @@ -176,7 +178,7 @@ export const MainHeader = () => { }) .then(() => { setServiceStatus(DeploymentStatus.DEPLOYED); - if (totalOlasStakedBalance === 0) { + if (isFirstTimeStaking) { showNotification?.( `Your agent is running and you've staked ${minimumStakedAmountRequired} OLAS!`, ); From 4b035fb623cd53c28e177bcc4f86a6788e410fd5 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Mon, 3 Jun 2024 17:38:22 +0400 Subject: [PATCH 6/6] Review fix --- frontend/components/Main/MainHeader.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/components/Main/MainHeader.tsx b/frontend/components/Main/MainHeader.tsx index abeb35c21..9b5f25d24 100644 --- a/frontend/components/Main/MainHeader.tsx +++ b/frontend/components/Main/MainHeader.tsx @@ -169,7 +169,7 @@ export const MainHeader = () => { // }); // } - const isFirstTimeStaking = totalOlasStakedBalance === 0; + const serviceExists = !!services?.[0]; // For now POST /api/services will take care of creating, starting and updating the service return ServicesService.createService({ @@ -178,13 +178,13 @@ export const MainHeader = () => { }) .then(() => { setServiceStatus(DeploymentStatus.DEPLOYED); - if (isFirstTimeStaking) { + if (serviceExists) { + showNotification?.('Your agent is now running!'); + } else { showNotification?.( `Your agent is running and you've staked ${minimumStakedAmountRequired} OLAS!`, ); setIsModalOpen(true); - } else { - showNotification?.('Your agent is now running!'); } }) .finally(() => { @@ -199,10 +199,10 @@ export const MainHeader = () => { masterSafeAddress, minimumStakedAmountRequired, serviceTemplate, + services, setIsBalancePollingPaused, setServiceStatus, showNotification, - totalOlasStakedBalance, wallets, ]); @@ -315,17 +315,19 @@ export const MainHeader = () => { ); })(); + const serviceExists = !!services?.[0]; + if (!isDeployable) { return ( ); } return ( ); }, [ @@ -338,7 +340,6 @@ export const MainHeader = () => { services, storeState?.isInitialFunded, totalEthBalance, - totalOlasStakedBalance, ]); return (