From 516019a47048f259bb48d7c391d853e3d745aae5 Mon Sep 17 00:00:00 2001 From: marshall <99344331+marshall2112@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:54:11 -0500 Subject: [PATCH] wip --- .../SpiceBazaar/Earn/Auctions/BidUSDS.tsx | 93 +++++-- .../SpiceBazaar/Earn/DisclaimerModal.tsx | 69 +++++ .../SpiceBazaar/Earn/StakeTemple/Stake.tsx | 81 ++++-- .../DappPages/SpiceBazaar/Earn/TopNav.tsx | 11 +- .../Core/DappPages/SpiceBazaar/Earn/index.tsx | 253 ++++++++++-------- .../SpiceBazaar/MyActivity/TopNav.tsx | 2 +- .../Core/DappPages/SpiceBazaar/TopNav.tsx | 13 + .../SpiceBazaar/components/CheckBox.tsx | 7 +- .../components/LargeRoundCheckBox.tsx | 67 +++++ 9 files changed, 427 insertions(+), 169 deletions(-) create mode 100644 apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/DisclaimerModal.tsx create mode 100644 apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/LargeRoundCheckBox.tsx diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/Auctions/BidUSDS.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/Auctions/BidUSDS.tsx index 4101dfa18..4255fc344 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/Auctions/BidUSDS.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/Auctions/BidUSDS.tsx @@ -14,8 +14,9 @@ import { useSpiceBazaar } from 'providers/SpiceBazaarProvider'; import { formatBigNumber } from 'components/Vault/utils'; import { ZERO } from 'utils/bigNumber'; import { getTokenInfo } from 'components/Vault/utils'; +import LargeRoundCheckBox from 'components/Pages/Core/DappPages/SpiceBazaar/components/LargeRoundCheckBox'; -const PRICE_UPDATE_INTERVAL = 5000; +const PRICE_UPDATE_INTERVAL = 10000; const FADE_EFFECT_DURATION = 500; interface BidUSDSProps { @@ -41,6 +42,7 @@ export const BidUSDS = ({ const [lastTgldPrice, setLastTgldPrice] = useState('0'); const [lastPriceUpdate, setLastPriceUpdate] = useState(Date.now()); const [fadeEffect, setFadeEffect] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const { daiGoldAuctions: { bid }, @@ -58,6 +60,12 @@ export const BidUSDS = ({ return daiGoldAuctionInfo?.data?.priceRatio || '0'; }, [daiGoldAuctionInfo]); + const resetState = useCallback(() => { + setIsCheckboxChecked1(false); + setIsCheckboxChecked2(false); + setInputValue(''); + }, []); + const handleBidClick = async (value: string) => { const now = Date.now(); @@ -73,15 +81,19 @@ export const BidUSDS = ({ } } - if (mode === BidUSDSMode.IncreaseBid) { - // Possible we only want to increase bid - // by the difference between the current bid and the new bid - await bid(value); - } else { - await bid(value); + setIsSubmitting(true); + try { + if (mode === BidUSDSMode.IncreaseBid) { + await bid(value); + } else { + await bid(value); + } + resetState(); + onBidSubmitted?.(); + } catch (error) { + console.error('Bid submission failed:', error); + setIsSubmitting(false); } - setInputValue(''); - onBidSubmitted?.(); }; const handleHintClick = () => { @@ -137,7 +149,8 @@ export const BidUSDS = ({ useEffect(() => { // Update price every n seconds - if (!inputValue) return; + if (!inputValue || isSubmitting) return; + const interval = setInterval(async () => { const newPrice = await fetchTGLDPrice().toString(); setFadeEffect(true); @@ -147,7 +160,17 @@ export const BidUSDS = ({ }, PRICE_UPDATE_INTERVAL); return () => clearInterval(interval); - }, [fetchTGLDPrice, lastTgldPrice, inputValue]); + }, [fetchTGLDPrice, lastTgldPrice, inputValue, isSubmitting]); + + const [isCheckboxChecked1, setIsCheckboxChecked1] = useState(false); + const handleCheckboxToggle1 = (checked: boolean) => { + setIsCheckboxChecked1(checked); + }; + + const [isCheckboxChecked2, setIsCheckboxChecked2] = useState(false); + const handleCheckboxToggle2 = (checked: boolean) => { + setIsCheckboxChecked2(checked); + }; return ( @@ -233,31 +256,44 @@ export const BidUSDS = ({ {exceededAmount && ( - -

i

-
+ +

i

+
Amount exceeds TGLD auction limit.
)} - -

i

-
+ Current TGLD price may rise before the end of the auction. - - Once submitted, the bid cannot be withdrawn or cancelled. - + + + + Once submitted, bids cannot be withdrawn or canceled.
handleBidClick(inputValue)} - disabled={!inputValue || exceededAmount || fadeEffect} + onClick={async () => { + await handleBidClick(inputValue); + }} + disabled={ + !inputValue || + exceededAmount || + fadeEffect || + !isCheckboxChecked1 || + !isCheckboxChecked2 + } > SUBMIT BID @@ -440,13 +476,14 @@ const InfoCircle = styled.div` const MessageText = styled.div` display: flex; - flex-direction: column; - gap: 8px; + flex-direction: row; + gap: 10px; + align-items: center; `; const Text = styled.p` margin: 0px; - color: ${({ theme }) => theme.palette.brand}; + color: ${({ theme }) => theme.palette.brandLight}; font-size: 12px; line-height: 18px; font-weight: 700; @@ -454,8 +491,8 @@ const Text = styled.p` const WarningMessage = styled.div` display: flex; - flex-direction: row; - align-items: center; + flex-direction: column; + align-items: flex-start; width: 100%; border: 2px solid transparent; border-image-source: linear-gradient( @@ -466,6 +503,6 @@ const WarningMessage = styled.div` border-image-slice: 1; border-radius: 6px; padding: 10px 0px 10px 10px; - gap: 8px; + gap: 10px; background: #24272c; `; diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/DisclaimerModal.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/DisclaimerModal.tsx new file mode 100644 index 000000000..9011b67f4 --- /dev/null +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/DisclaimerModal.tsx @@ -0,0 +1,69 @@ +import styled from 'styled-components'; +import { Button } from 'components/Button/Button'; +import { Popover } from 'components/Pages/Core/DappPages/SpiceBazaar/components/Popover'; + +interface IProps { + isOpen: boolean; + onClose: () => void; +} + +export const DisclaimerModal: React.FC = ({ isOpen, onClose }) => { + return ( + <> + + + TGLD Warning + + TGLD cannot be cross chained to another address. Multisig wallets + might have a different address on different chains. + + I Understand + + + + ); +}; + +const ModalContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: ${({ theme }) => theme.palette.brand}; + width: 400px; + padding: 0px 20px 0px 20px; + margin-top: 22px; + gap: 20px; +`; + +const Title = styled.div` + font-size: 24px; + color: ${({ theme }) => theme.palette.brandLight}; +`; + +const Text = styled.div` + color: ${({ theme }) => theme.palette.brand}; + font-size: 16px; + line-height: 20px; + text-align: center; +`; + +const ConsentButton = styled(Button)` + padding: 12px 20px 12px 20px; + margin: 0px 0px 0px 0px; + width: max-content; + height: min-content; + background: ${({ theme }) => theme.palette.gradients.dark}; + border: 1px solid ${({ theme }) => theme.palette.brandDark}; + box-shadow: 0px 0px 20px rgba(222, 92, 6, 0.4); + border-radius: 10px; + font-size: 16px; + line-height: 19px; + text-transform: uppercase; + color: ${({ theme }) => theme.palette.brandLight}; +`; diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/StakeTemple/Stake.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/StakeTemple/Stake.tsx index c29b53807..f2537e327 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/StakeTemple/Stake.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/StakeTemple/Stake.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'; import styled from 'styled-components'; import stakeTemple from 'assets/icons/stake-temple.svg?react'; import templeGold from 'assets/icons/temple-gold.svg?react'; -import priorityHigh from 'assets/icons/priority-high.svg?react'; import { Input } from 'components/Pages/Core/DappPages/SpiceBazaar/components/Input'; import { Button } from 'components/Button/Button'; import { useWallet } from 'providers/WalletProvider'; @@ -14,6 +13,7 @@ import { useSpiceBazaar } from 'providers/SpiceBazaarProvider'; import * as breakpoints from 'styles/breakpoints'; import { useMediaQuery } from 'react-responsive'; import { queryPhone } from 'styles/breakpoints'; +import LargeRoundCheckBox from 'components/Pages/Core/DappPages/SpiceBazaar/components/LargeRoundCheckBox'; export const Stake = () => { const isPhoneOrAbove = useMediaQuery({ @@ -48,6 +48,16 @@ export const Stake = () => { setInputValue(value); }; + const [isCheckboxChecked1, setIsCheckboxChecked1] = useState(false); + const handleCheckboxToggle1 = (checked: boolean) => { + setIsCheckboxChecked1(checked); + }; + + const [isCheckboxChecked2, setIsCheckboxChecked2] = useState(false); + const handleCheckboxToggle2 = (checked: boolean) => { + setIsCheckboxChecked2(checked); + }; + return ( @@ -89,21 +99,43 @@ export const Stake = () => { */} - - TGLD is non-transferrable.
- Once submitted, bids cannot be withdrawn {!isPhoneOrAbove &&
} or - canceled, {isPhoneOrAbove &&
} - and your stake will be locked {!isPhoneOrAbove &&
} for X days - before it can be {isPhoneOrAbove &&
} - unstaked. + + + + TGLD is non-transferrable but can be cross- + {!isPhoneOrAbove &&
} + chain {isPhoneOrAbove &&
} + transferred to the same address. For{!isPhoneOrAbove &&
} + multisigs,{' '} + + read more. + +
+
+ + + Once submitted, stakes cannot be {!isPhoneOrAbove &&
} + withdrawn or canceled {isPhoneOrAbove &&
} + and your staked {!isPhoneOrAbove &&
} + TEMPLE tokens will be locked for X days
+ before they can be unstaked. +
{ await stakeTemple(inputValue); setInputValue(''); + setIsCheckboxChecked1(false); + setIsCheckboxChecked2(false); }} - disabled={!inputValue} + disabled={!inputValue || !isCheckboxChecked1 || !isCheckboxChecked2} > SUBMIT @@ -199,11 +231,30 @@ const ReceiveAmount = styled.h3` margin: 0px; `; -const WarningMessage = styled.div` +const Message = styled.div` display: flex; flex-direction: row; - align-items: center; + gap: 10px; + + a { + color: ${({ theme }) => theme.palette.brandLight}; + font-weight: 700; + text-decoration: underline; + font-size: 13px; + line-height: 20px; + } +`; +const TextMessage = styled.div` + display: inline; + line-height: 20px; + white-space: pre-wrap; +`; + +const WarningMessage = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; border: 2px solid transparent; border-image-source: linear-gradient( 180deg, @@ -212,8 +263,7 @@ const WarningMessage = styled.div` ); border-image-slice: 1; border-radius: 6px; - - padding: 10px 10px 10px 10px; + padding: 10px 0px 10px 10px; gap: 10px; background: #24272c; color: ${({ theme }) => theme.palette.brandLight}; @@ -226,11 +276,6 @@ const WarningMessage = styled.div` `)} `; -const ExclamationIcon = styled(priorityHigh)` - min-width: 26px; - min-height: 26px; -`; - export const TradeButton = styled(Button)` padding: 12px 20px 12px 20px; width: ${(props) => props.width || 'min-content'}; diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/TopNav.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/TopNav.tsx index 35a08eb4f..f816f630b 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/TopNav.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/Earn/TopNav.tsx @@ -6,11 +6,16 @@ import { useMediaQuery } from 'react-responsive'; import { queryPhone } from 'styles/breakpoints'; enum EarnLocPaths { + Overview = '/dapp/spice/earn', Stake = '/dapp/spice/earn/staketemple/stake', Auctions = '/dapp/spice/earn/auctions', } const EarnConfig = [ + { + label: 'Overview', + linkTo: EarnLocPaths.Overview, + }, { label: 'Stake TEMPLE', linkTo: EarnLocPaths.Stake, @@ -27,7 +32,9 @@ export const EarnTopNav = () => { }); const loc = useLocation(); const [menuNavItems, setMenuNavItems] = useState( - EarnConfig.map((item) => ({ + EarnConfig.filter( + (item) => !(item.label === 'Overview' && !isPhoneOrAbove) + ).map((item) => ({ label: item.label, path: item.linkTo, selected: item.linkTo === loc.pathname, @@ -62,7 +69,7 @@ export const EarnTopNav = () => { {isPhoneOrAbove ? ( { const navigate = useNavigate(); @@ -30,129 +31,145 @@ export const Earn = () => { return ( -
- - Earn Temple Gold - - - - Temple Gold (TGLD) is the native currency of the{' '} - {!isPhoneOrAbove &&
} - Spice Bazaar. TGLD can be used to enter bids on{' '} - {!isPhoneOrAbove &&
} - certain tokens from the Treasury offered {!isPhoneOrAbove &&
} - through periodic Auctions called Spice Auctions.{' '} -
- - Temple Gold is accessible to everyone. If you do{' '} - {!isPhoneOrAbove &&
} - not hold TEMPLE, You may directly enter USDS{' '} - {!isPhoneOrAbove &&
} - bids for TGLD through bi-weekly Gold Auctions.{' '} - {!isPhoneOrAbove &&
} - If you are a TEMPLE holder, simply stake TEMPLE{' '} - {!isPhoneOrAbove &&
} - for TGLD rewards every Epoch and use them in{' '} - {!isPhoneOrAbove &&
} - Spice Auctions.{' '} -
-
- - - - Stake TEMPLE for TGLD - - - - - Temple Gold (TGLD) rewards will be distributed every Epoch - to staked TEMPLE tokens. - - - The TGLD reward rate for each Epoch depends on the number of - staked TEMPLE and the amount of TGLD in circulation. - - - Earned TGLD rewards can be used to bid in Spice Auctions. - - - Once staked, there will be a Cooldown before you can unstake - TEMPLE. Learn More - - - - navigate('staketemple/stake')} - style={{ whiteSpace: 'nowrap', margin: 0 }} - > - Stake Temple for TGLD - - - - - - - {isPhoneOrAbove && ( - - )} - - {daiGoldAuctionInfo?.currentEpochAuctionLive && ( - - - AN AUCTION IS LIVE - - )} - - - - Bid USDS for TGLD {!isPhoneOrAbove &&
} - in Gold Auctions -
- - - Bid USDS in a Gold Auction to earn TGLD for use in Spice - Auctions. - - - You do not need to hold TEMPLE to participate in a Gold - Auction. - - - There is no minimum reserve USDS price and you cannot be - outbid. - - - TGLD will be awarded pro rata to all USDS Bidders at the end - of the Gold Auction. - - - Once submitted, USDS bids cannot be withdrawn.{' '} - Learn More - - -
- navigate('auctions')} - > - Bid USDS For TGLD - -
- {!isPhoneOrAbove && ( + + +
+ + Earn Temple Gold + + + + Temple Gold (TGLD) is the native currency of the{' '} + {!isPhoneOrAbove &&
} + Spice Bazaar. TGLD can be used to enter bids on{' '} + {!isPhoneOrAbove &&
} + certain tokens from the Treasury offered {!isPhoneOrAbove &&
} + through periodic Auctions called Spice Auctions.{' '} +
+ + Temple Gold is accessible to everyone. If you do{' '} + {!isPhoneOrAbove &&
} + not hold TEMPLE, You may directly enter USDS{' '} + {!isPhoneOrAbove &&
} + bids for TGLD through bi-weekly Gold Auctions.{' '} + {!isPhoneOrAbove &&
} + If you are a TEMPLE holder, simply stake TEMPLE{' '} + {!isPhoneOrAbove &&
} + for TGLD rewards every Epoch and use them in{' '} + {!isPhoneOrAbove &&
} + Spice Auctions.{' '} +
+
+ + + + Stake TEMPLE for TGLD + + + + + Temple Gold (TGLD) rewards will be distributed every Epoch + to staked TEMPLE tokens. + + + The TGLD reward rate for each Epoch depends on the number + of staked TEMPLE and the amount of TGLD in circulation. + + + Earned TGLD rewards can be used to bid in Spice Auctions. + + + Once staked, there will be a Cooldown before you can + unstake TEMPLE.{' '} + + Learn more + + + + + navigate('staketemple/stake')} + style={{ whiteSpace: 'nowrap', margin: 0 }} + > + Stake Temple for TGLD + + + + + + + {isPhoneOrAbove && ( )} -
-
-
+ + {daiGoldAuctionInfo?.currentEpochAuctionLive && ( + + + AN AUCTION IS LIVE + + )} + + + + Bid USDS for TGLD {!isPhoneOrAbove &&
} + in Gold Auctions +
+ + + Bid USDS in a Gold Auction to earn TGLD for use in Spice + Auctions. + + + You do not need to hold TEMPLE to participate in a Gold + Auction. + + + There is no minimum reserve USDS price and you cannot be + outbid. + + + TGLD will be awarded pro rata to all USDS Bidders at the + end of the Gold Auction. + + + Once submitted, USDS bids cannot be withdrawn.{' '} + + Learn more + + + +
+ navigate('auctions')} + > + Bid USDS For TGLD + +
+ {!isPhoneOrAbove && ( + + )} +
+ +
+ ); }; const PageContainer = styled.div` - margin-top: -40px; + margin-top: -20px; display: flex; flex-direction: column; - gap: 60px; + gap: 40px; max-width: 1000px; ${breakpoints.phoneAndAbove(` @@ -160,6 +177,12 @@ const PageContainer = styled.div` `)} `; +const BodyContainer = styled.div` + display: flex; + flex-direction: column; + gap: 60px; +`; + const Header = styled.div` display: flex; flex-direction: column; @@ -274,7 +297,7 @@ const ListItem = styled.li` &:last-child { margin-bottom: 0; } - .learn-more { + a { font-weight: 700; text-decoration: underline; font-size: 13px; diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/MyActivity/TopNav.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/MyActivity/TopNav.tsx index 6cf48214c..adf2433e2 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/MyActivity/TopNav.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/MyActivity/TopNav.tsx @@ -59,7 +59,7 @@ export const MyActivityTopNav = () => { {isPhoneOrAbove ? ( { query: queryPhone, }); const loc = useLocation(); + const [disclaimerModalOpen, setDisclaimerModalOpen] = useState(false); const [menuNavItems, setMenuNavItems] = useState( SpiceBazaarConfig.map((item) => ({ label: item.label, @@ -46,6 +48,13 @@ export const SpiceBazaarTopNav = () => { })) ); + // Disabled for now + // useEffect(() => { + // if (loc.pathname === SpiceLocPaths.Earn) { + // setDisclaimerModalOpen(true); + // } + // }, [loc.pathname]); + useEffect(() => { setMenuNavItems((prevMenuNavItems) => prevMenuNavItems.map((menuItem) => ({ @@ -89,6 +98,10 @@ export const SpiceBazaarTopNav = () => { onSelectMenuNavItems={onSelectMenuNavItems} /> )} + setDisclaimerModalOpen(false)} + /> diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/CheckBox.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/CheckBox.tsx index 22461db4a..116d6c7ac 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/CheckBox.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/CheckBox.tsx @@ -1,17 +1,14 @@ -import { useState } from 'react'; import styled from 'styled-components'; import box from 'assets/icons/box.svg?react'; import checkmark from 'assets/icons/checkmark-in-box.svg?react'; type CustomCheckboxProps = { + checked: boolean; onToggle?: (checked: boolean) => void; }; -const Checkbox: React.FC = ({ onToggle }) => { - const [checked, setChecked] = useState(false); - +const Checkbox: React.FC = ({ checked, onToggle }) => { const handleClick = () => { - setChecked(!checked); if (onToggle) { onToggle(!checked); } diff --git a/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/LargeRoundCheckBox.tsx b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/LargeRoundCheckBox.tsx new file mode 100644 index 000000000..a62474eb8 --- /dev/null +++ b/apps/dapp/src/components/Pages/Core/DappPages/SpiceBazaar/components/LargeRoundCheckBox.tsx @@ -0,0 +1,67 @@ +import styled from 'styled-components'; +import checkmark from 'assets/icons/check.svg?react'; + +type CustomCheckboxProps = { + checked: boolean; + onToggle?: (checked: boolean) => void; +}; + +const LargeRoundCheckBox: React.FC = ({ + checked, + onToggle, +}) => { + const handleClick = () => { + if (onToggle) { + onToggle(!checked); + } + }; + + return ( + + + + + + + ); +}; + +export default LargeRoundCheckBox; + +const CheckboxContainer = styled.div` + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +`; + +const CheckboxSvgWrapper = styled.div` + position: relative; + display: flex; + justify-content: center; + align-items: center; +`; + +const StyledCircle = styled.div` + display: inline-block; + width: 26px; + height: 26px; + border-radius: 50%; + background-color: #202020; + border: 2px solid ${({ theme }) => theme.palette.brand}; +`; + +const StyledCheckmark = styled(checkmark)<{ visible: boolean }>` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 20px; + height: 20px; + opacity: ${({ visible }) => (visible ? 1 : 0)}; + transition: opacity 0.2s ease; +`;