From 78de3f4a8ce0da68c8e0e6347d5f37ab11454d9b Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 14:00:50 +0800 Subject: [PATCH 1/9] chore: change wording and description --- README.md | 4 +-- app/home/HomePage.tsx | 74 +++++++++++++++++++++++++++++++++++-------- app/page.tsx | 2 +- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b860294b..5458460e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Monarch

-
Easy access to Morpho Blue.
+
Customized lending on Morpho Blue.
LICENSE @@ -16,7 +16,7 @@ ## Overview -Monarch is an unofficial user interface designed to facilitate access to [Morpho Blue](https://github.com/morpho-org/morpho-blue) markets. It provides a streamlined way to supply to any markets created on the Morpho Blue protocol, without the need for MetaMorpho vaults. +Monarch is an unofficial user interface designed for composing custom lending strategies on [Morpho Blue](https://github.com/morpho-org/morpho-blue). It enables you to compose your own lending strategies by bundling multiple markets, each with your defined risk parameters. Access Morpho Blue directly without intermediaries, maintaining full control over your lending positions. ## Local Setup diff --git a/app/home/HomePage.tsx b/app/home/HomePage.tsx index a5a30364..e91dd52a 100644 --- a/app/home/HomePage.tsx +++ b/app/home/HomePage.tsx @@ -7,15 +7,27 @@ import Footer from '@/components/layout/footer/Footer'; import HomeHeader from './_components/HomeHeader'; export default function HomePage() { - const [isMorphoBlue, setIsMorphoBlue] = useState(false); + const [counter, setCounter] = useState(0); + + const firstPhrases = ['Customized lending', 'Manage your own yield', 'Control your risk']; + const secondPhrases = ['on Morpho Blue', 'with no intermediates']; useEffect(() => { const interval = setInterval(() => { - setIsMorphoBlue((prev) => !prev); + setCounter(prev => (prev + 1)); }, 3000); return () => clearInterval(interval); }, []); + // Get current phrases based on counter + const currentFirstIndex = Math.floor(counter / 2) % firstPhrases.length; + const nextFirstIndex = (currentFirstIndex + 1) % firstPhrases.length; + const currentSecondIndex = Math.floor((counter + 3) / 2) % secondPhrases.length; + const nextSecondIndex = (currentSecondIndex + 1) % secondPhrases.length; + + // Determine which section is changing + const isFirstChanging = counter % 2 === 0; + const { address } = useAccount(); return ( @@ -24,18 +36,52 @@ export default function HomePage() {
-
- {' '} - {/* Fixed height container */} -

- Direct access to{' '} - - {isMorphoBlue ? '{Morpho Blue}' : 'the most decentralized lending protocol.'} - +
+

+
+ + {firstPhrases[currentFirstIndex]} + + + {firstPhrases[nextFirstIndex]} + +
+
+ + {secondPhrases[currentSecondIndex].includes('Morpho Blue') ? ( + + on Morpho Blue + + ) : ( + secondPhrases[currentSecondIndex] + )} + + + {secondPhrases[nextSecondIndex].includes('Morpho Blue') ? ( + + on Morpho Blue + + ) : ( + secondPhrases[nextSecondIndex] + )} + +

diff --git a/app/page.tsx b/app/page.tsx index 8a16c956..8c985418 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,7 +3,7 @@ import HomePage from './home/HomePage'; export const metadata = generateMetadata({ title: 'Monarch', - description: 'Permission-less access to morpho blue protocol', + description: 'Customized lending with Morpho Blue', images: 'themes.png', pathname: '', }); From a0f55d699a1490a99f53fc47f7cf1ccc7b326208 Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 15:20:30 +0800 Subject: [PATCH 2/9] chore: styling updates --- app/home/HomePage.tsx | 156 ++++++++++-------- app/home/_components/Home.module.css | 102 ------------ app/home/_components/HomeHeader.tsx | 10 -- app/home/_components/WhyUseIt.tsx | 3 - app/positions/components/FromAndToMarkets.tsx | 5 +- app/positions/components/PositionsContent.tsx | 2 +- .../components/PositionsSummaryTable.tsx | 2 +- app/settings/faq/page.tsx | 56 +++++++ src/components/common/PrimaryButton.tsx | 2 +- 9 files changed, 149 insertions(+), 189 deletions(-) delete mode 100644 app/home/_components/Home.module.css delete mode 100644 app/home/_components/HomeHeader.tsx delete mode 100644 app/home/_components/WhyUseIt.tsx create mode 100644 app/settings/faq/page.tsx diff --git a/app/home/HomePage.tsx b/app/home/HomePage.tsx index e91dd52a..52e6e7e9 100644 --- a/app/home/HomePage.tsx +++ b/app/home/HomePage.tsx @@ -3,99 +3,117 @@ import React, { useState, useEffect } from 'react'; import { useAccount } from 'wagmi'; import PrimaryButton from '@/components/common/PrimaryButton'; -import Footer from '@/components/layout/footer/Footer'; -import HomeHeader from './_components/HomeHeader'; +import Header from '@/components/layout/header/Header'; export default function HomePage() { - const [counter, setCounter] = useState(0); + const [showCustomized, setShowCustomized] = useState(true); + const [riskYieldIndex, setRiskYieldIndex] = useState(0); + const [secondCounter, setSecondCounter] = useState(0); - const firstPhrases = ['Customized lending', 'Manage your own yield', 'Control your risk']; + const riskYieldTerms = ['risk', 'yield']; const secondPhrases = ['on Morpho Blue', 'with no intermediates']; useEffect(() => { - const interval = setInterval(() => { - setCounter(prev => (prev + 1)); + // Toggle between customized and manage your own every 5 seconds + const customizedInterval = setInterval(() => { + setShowCustomized((prev) => !prev); + }, 5000); + + // Change risk/yield every 3 seconds when showing manage your own + const riskYieldInterval = setInterval(() => { + if (!showCustomized) { + setRiskYieldIndex((prev) => (prev + 1) % riskYieldTerms.length); + } }, 3000); - return () => clearInterval(interval); - }, []); - // Get current phrases based on counter - const currentFirstIndex = Math.floor(counter / 2) % firstPhrases.length; - const nextFirstIndex = (currentFirstIndex + 1) % firstPhrases.length; - const currentSecondIndex = Math.floor((counter + 3) / 2) % secondPhrases.length; - const nextSecondIndex = (currentSecondIndex + 1) % secondPhrases.length; + // Second segment changes every 4 seconds + const secondInterval = setInterval(() => { + setSecondCounter((prev) => (prev + 1) % secondPhrases.length); + }, 4000); + + return () => { + clearInterval(customizedInterval); + clearInterval(riskYieldInterval); + clearInterval(secondInterval); + }; + }, [showCustomized]); - // Determine which section is changing - const isFirstChanging = counter % 2 === 0; + const renderFirstPhrase = () => { + if (showCustomized) { + return ( + + Customized lending + + ); + } + return ( + + + Manage your own + + {riskYieldTerms.map((term, index) => ( + + {term} + + ))} + + + + ); + }; const { address } = useAccount(); return (
-
- -
-
-
-

-
- - {firstPhrases[currentFirstIndex]} - - - {firstPhrases[nextFirstIndex]} - -
-
- - {secondPhrases[currentSecondIndex].includes('Morpho Blue') ? ( - - on Morpho Blue - - ) : ( - secondPhrases[currentSecondIndex] - )} - +
+
+
+
+

+
{renderFirstPhrase()}
+
+ {secondPhrases.map((phrase, index) => ( - {secondPhrases[nextSecondIndex].includes('Morpho Blue') ? ( + {phrase.includes('Morpho Blue') ? ( on Morpho Blue ) : ( - secondPhrases[nextSecondIndex] + phrase )} -
-

-
-
-
-
- - Why Monarch - - - View Portfolio - -
-
-
+ ))} +

+ +
+
+ + Why Monarch + + + Get Started + +
+ +
); } diff --git a/app/home/_components/Home.module.css b/app/home/_components/Home.module.css deleted file mode 100644 index c010849c..00000000 --- a/app/home/_components/Home.module.css +++ /dev/null @@ -1,102 +0,0 @@ -.HomeHeader { - --home-header-height: 400px; - - height: var(--home-header-height); -} - -.HomeHeaderGradient { - position: absolute; - z-index: -1; - top: 0; - left: 0; - width: 100vw; - height: var(--home-header-height); - background: linear-gradient(180deg, #ff5800 0%, #cb59ab 100%, #ea36b8 100%); -} - -.HomeHeaderHeadline { - margin-top: 45px; - color: white; - font-size: 92px; - font-weight: 400; - line-height: 85px; - text-align: center; - word-wrap: break-word; - - @media (width <= 768px) { - font-size: 64px; - } -} - -.HomeHeaderParagraph { - margin: 35px 0; - color: white; - font-size: 20px; - font-weight: 400; - line-height: 24px; - text-align: center; - word-wrap: break-word; - - @media (width <= 768px) { - padding: 0 20px; - font-size: 18px; - } -} - -.HomeHeaderCta { - display: flex; - justify-content: center; - margin: 16px 0 52px; - - @media (width <= 768px) { - width: 80%; - } -} - -.HomeHeaderWaves { - --home-header-waves-height: 25px; - - position: relative; - margin-top: 4px; - margin-bottom: var(--home-header-waves-height); -} - -.HomeHeaderWaves svg { - width: 100vw; - height: var(--home-header-waves-height); -} - -.HomeHeaderWavesParallax > use { - animation: move-forever 30s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite; -} - -.HomeHeaderWavesParallax > use:nth-child(1), -.HomeHeaderWavesParallax > use:nth-child(2) { - animation-delay: -2s; - animation-duration: 6s; -} - -.HomeHeaderWavesParallax > use:nth-child(3) { - animation-delay: -3s; - animation-duration: 12s; -} - -.HomeHeaderWavesParallax > use:nth-child(4) { - animation-delay: -4s; - animation-duration: 15s; -} - -.HomeHeaderWavesParallax > use:nth-child(5) { - animation-delay: -5s; - animation-duration: 20s; -} - -@keyframes move-forever { - 0% { - transform: translate3d(-90px, 0, 0); - } - - 100% { - transform: translate3d(85px, 0, 0); - } -} diff --git a/app/home/_components/HomeHeader.tsx b/app/home/_components/HomeHeader.tsx deleted file mode 100644 index 42597294..00000000 --- a/app/home/_components/HomeHeader.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import Header from '@/components/layout/header/Header'; -import styles from './Home.module.css'; - -export default function HomeHeader() { - return ( -
-
-
- ); -} diff --git a/app/home/_components/WhyUseIt.tsx b/app/home/_components/WhyUseIt.tsx deleted file mode 100644 index 15dbfc6a..00000000 --- a/app/home/_components/WhyUseIt.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function HomeMain() { - return
; -} diff --git a/app/positions/components/FromAndToMarkets.tsx b/app/positions/components/FromAndToMarkets.tsx index 42982c15..a54a8f37 100644 --- a/app/positions/components/FromAndToMarkets.tsx +++ b/app/positions/components/FromAndToMarkets.tsx @@ -180,7 +180,8 @@ export function FromAndToMarkets({ variant="flat" className="h-5 min-w-0 px-2 text-xs" isDisabled={ - BigInt(marketPosition.supplyAssets) + BigInt(marketPosition.pendingDelta) <= + BigInt(marketPosition.supplyAssets) + + BigInt(marketPosition.pendingDelta) <= 0n } onClick={(e) => { @@ -192,7 +193,7 @@ export function FromAndToMarkets({ if (remainingAmount > 0n) { onSelectMax?.( marketPosition.market.uniqueKey, - Number(remainingAmount) + Number(remainingAmount), ); } }} diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index b89e3fce..2bf68e88 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -29,7 +29,7 @@ export default function Positions() {
-

Portfolio

+

Portfolio

From 269cd93da7a0a3582e34313705a0178a820ce3aa Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 15:49:01 +0800 Subject: [PATCH 3/9] chore: connect botton --- app/home/HomePage.tsx | 4 +- .../components/RebalanceActionInput.tsx | 2 +- .../layout/header/AccountConnect.tsx | 8 +- .../layout/header/AccountDropdown.tsx | 72 +++++++++++++--- .../layout/header/AccountInfoPanel.tsx | 62 -------------- src/components/layout/header/Navbar.tsx | 82 ++++--------------- 6 files changed, 81 insertions(+), 149 deletions(-) delete mode 100644 src/components/layout/header/AccountInfoPanel.tsx diff --git a/app/home/HomePage.tsx b/app/home/HomePage.tsx index 52e6e7e9..b5644165 100644 --- a/app/home/HomePage.tsx +++ b/app/home/HomePage.tsx @@ -48,7 +48,7 @@ export default function HomePage() { } return ( - + Manage your own {riskYieldTerms.map((term, index) => ( @@ -73,7 +73,7 @@ export default function HomePage() { return (
-
+
diff --git a/app/positions/components/RebalanceActionInput.tsx b/app/positions/components/RebalanceActionInput.tsx index 05f646ca..7436208d 100644 --- a/app/positions/components/RebalanceActionInput.tsx +++ b/app/positions/components/RebalanceActionInput.tsx @@ -28,7 +28,7 @@ export function RebalanceActionInput({ onAddAction, }: RebalanceActionInputProps) { return ( -
+
Rebalance -
- -
-
+
- ); })()}
diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx index 8d955693..e0960a8c 100644 --- a/src/components/layout/header/AccountDropdown.tsx +++ b/src/components/layout/header/AccountDropdown.tsx @@ -1,27 +1,42 @@ +import { useCallback, useState } from 'react'; +import { Name } from '@coinbase/onchainkit/identity'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { ExitIcon, ExternalLinkIcon } from '@radix-ui/react-icons'; import { clsx } from 'clsx'; -import { useAccount } from 'wagmi'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { FiSettings } from 'react-icons/fi'; +import { useAccount, useDisconnect } from 'wagmi'; import { Avatar } from '@/components/Avatar/Avatar'; -import { AccountInfoPanel } from './AccountInfoPanel'; +import { getSlicedAddress } from '@/utils/address'; +import { getExplorerURL } from '@/utils/external'; const DropdownMenuContentStyle = { marginTop: '-22px', }; export function AccountDropdown() { - const { address } = useAccount(); + const { address, chainId } = useAccount(); + const { disconnect } = useDisconnect(); + const pathname = usePathname(); + const [isOpen, setIsOpen] = useState(false); + + const handleDisconnectWallet = useCallback(() => { + disconnect(); + }, [disconnect]); + + if (!address) return null; return ( - + -
- {address && ( - - )} +
+
+ - +
+ +
+
+ +
+ + {getSlicedAddress(address)} + +
+ + + +
+ +
+ + Settings + + +
diff --git a/src/components/layout/header/AccountInfoPanel.tsx b/src/components/layout/header/AccountInfoPanel.tsx deleted file mode 100644 index f25f94d3..00000000 --- a/src/components/layout/header/AccountInfoPanel.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useCallback } from 'react'; -import { Name } from '@coinbase/onchainkit/identity'; -import { ExitIcon, ExternalLinkIcon } from '@radix-ui/react-icons'; -import { clsx } from 'clsx'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { FiSettings } from 'react-icons/fi'; -import { useAccount, useDisconnect } from 'wagmi'; -import { Avatar } from '@/components/Avatar/Avatar'; -import { getSlicedAddress } from '@/utils/address'; -import { getExplorerURL } from '@/utils/external'; - -export function AccountInfoPanel() { - const { address, chainId } = useAccount(); - const { disconnect } = useDisconnect(); - const pathname = usePathname(); - - const handleDisconnectWallet = useCallback(() => { - disconnect(); - }, [disconnect]); - - if (!address) return null; - - return ( - <> -
- -
-
- -
- - {getSlicedAddress(address)} - -
- - - -
-
- - Settings - - - - - ); -} diff --git a/src/components/layout/header/Navbar.tsx b/src/components/layout/header/Navbar.tsx index c8e8983e..0bf0c6da 100644 --- a/src/components/layout/header/Navbar.tsx +++ b/src/components/layout/header/Navbar.tsx @@ -1,13 +1,11 @@ 'use client'; -import { useState } from 'react'; import { clsx } from 'clsx'; import Image from 'next/image'; import NextLink from 'next/link'; import { usePathname } from 'next/navigation'; import { useTheme } from 'next-themes'; import { FaRegMoon, FaSun } from 'react-icons/fa'; -import { RiMenu3Line, RiCloseLine } from 'react-icons/ri'; import { useAccount } from 'wagmi'; import logo from '../../imgs/logo.png'; import AccountConnect from './AccountConnect'; @@ -32,9 +30,10 @@ export function NavbarLink({ + ); } - export default Navbar; From 5760611bffee8705ef6248a9a68e64ab778b93b9 Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 15:51:22 +0800 Subject: [PATCH 4/9] chore: styling --- app/positions/components/RebalanceActionInput.tsx | 4 ++-- app/positions/components/RebalanceModal.tsx | 2 +- src/components/layout/header/AccountConnect.tsx | 4 ++-- src/components/layout/header/AccountDropdown.tsx | 12 ++++++++---- src/components/layout/header/Navbar.tsx | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/positions/components/RebalanceActionInput.tsx b/app/positions/components/RebalanceActionInput.tsx index 7436208d..aeb189c1 100644 --- a/app/positions/components/RebalanceActionInput.tsx +++ b/app/positions/components/RebalanceActionInput.tsx @@ -28,7 +28,7 @@ export function RebalanceActionInput({ onAddAction, }: RebalanceActionInputProps) { return ( -
+
Rebalance diff --git a/app/positions/components/RebalanceModal.tsx b/app/positions/components/RebalanceModal.tsx index c9b37795..808e3ce1 100644 --- a/app/positions/components/RebalanceModal.tsx +++ b/app/positions/components/RebalanceModal.tsx @@ -357,7 +357,7 @@ export function RebalanceModal({ onPress={() => void handleExecuteRebalance()} isDisabled={isConfirming || rebalanceActions.length === 0} isLoading={isConfirming} - className="rounded-sm bg-orange-500 p-4 px-10 font-zen text-white opacity-80 transition-all duration-200 ease-in-out hover:scale-105 hover:opacity-100 disabled:opacity-50 dark:bg-orange-600" + className="rounded-sm bg-primary p-4 px-10 font-zen text-white opacity-80 transition-all duration-200 ease-in-out hover:scale-105 hover:opacity-100 disabled:opacity-50 dark:bg-primary" > {needSwitchChain ? 'Switch Network & Execute' : 'Execute Rebalance'} diff --git a/src/components/layout/header/AccountConnect.tsx b/src/components/layout/header/AccountConnect.tsx index f787ff52..16c558d2 100644 --- a/src/components/layout/header/AccountConnect.tsx +++ b/src/components/layout/header/AccountConnect.tsx @@ -46,8 +46,8 @@ function AccountConnect() { return (
- -
+ +
); })()}
diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx index e0960a8c..a03f952f 100644 --- a/src/components/layout/header/AccountDropdown.tsx +++ b/src/components/layout/header/AccountDropdown.tsx @@ -44,7 +44,7 @@ export function AccountDropdown() { className={clsx( 'h-42 inline-flex w-60 flex-col items-start justify-start', 'bg-surface rounded bg-opacity-90 px-6 pb-2 pt-6 shadow backdrop-blur-2xl', - 'animate-in fade-in slide-in-from-right duration-300' + 'animate-in fade-in slide-in-from-right duration-300', )} style={DropdownMenuContentStyle} > @@ -68,10 +68,12 @@ export function AccountDropdown() { href="/settings" className={clsx( 'my-4 inline-flex items-center justify-between self-stretch no-underline', - pathname === '/settings' && 'text-primary' + pathname === '/settings' && 'text-primary', )} > - Settings + + Settings + diff --git a/src/components/layout/header/Navbar.tsx b/src/components/layout/header/Navbar.tsx index 0bf0c6da..7fe56efa 100644 --- a/src/components/layout/header/Navbar.tsx +++ b/src/components/layout/header/Navbar.tsx @@ -33,7 +33,7 @@ export function NavbarLink({ 'px-2 py-1 text-center font-zen text-base font-normal text-primary no-underline', 'relative after:absolute after:bottom-[-4px] after:left-0 after:h-[2px] after:w-full after:bg-primary', 'transition-all duration-200 hover:-translate-y-[2px]', - isActive ? 'after:opacity-100' : 'after:opacity-0' + isActive ? 'after:opacity-100' : 'after:opacity-0', )} target={target} aria-label={ariaLabel} From f4af9b232a10acfb754c549be3de769d819b28a4 Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 16:12:33 +0800 Subject: [PATCH 5/9] chore: transition to AssetFilter --- app/markets/components/AssetFilter.tsx | 105 ++++++++++++++----------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/app/markets/components/AssetFilter.tsx b/app/markets/components/AssetFilter.tsx index 71a9c275..44da0a26 100644 --- a/app/markets/components/AssetFilter.tsx +++ b/app/markets/components/AssetFilter.tsx @@ -3,6 +3,7 @@ import { useState, useRef, useEffect, KeyboardEvent } from 'react'; import { ChevronDownIcon, TrashIcon } from '@radix-ui/react-icons'; import Image from 'next/image'; import { ERC20Token, infoToKey } from '@/utils/tokens'; +import { motion, AnimatePresence } from 'framer-motion'; type FilterProps = { label: string; @@ -115,57 +116,65 @@ export default function AssetFilter({
- {isOpen && !loading && ( -
- setQuery(e.target.value)} - placeholder="Search tokens..." - className="w-full border-none bg-transparent p-3 text-sm focus:outline-none" - /> -
-
    - {filteredItems.map((token) => ( -
  • + {isOpen && !loading && ( + + setQuery(e.target.value)} + placeholder="Search tokens..." + className="w-full border-none bg-transparent p-3 text-sm focus:outline-none" + /> +
    +
      + {filteredItems.map((token) => ( +
    • infoToKey(n.address, n.chain.id)).join('|'), + ) + ? 'bg-gray-300 dark:bg-gray-700' + : '' + }`} + onClick={() => selectOption(token)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + selectOption(token); + } + }} + role="option" + aria-selected={selectedAssets.includes( token.networks.map((n) => infoToKey(n.address, n.chain.id)).join('|'), - ) - ? 'bg-gray-300 dark:bg-gray-700' - : '' - }`} - onClick={() => selectOption(token)} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - selectOption(token); - } - }} - role="option" - aria-selected={selectedAssets.includes( - token.networks.map((n) => infoToKey(n.address, n.chain.id)).join('|'), - )} - tabIndex={0} + )} + tabIndex={0} + > + {token.symbol} + {token.img && {token.symbol}} +
    • + ))} +
    +
    +
  • - ))} -
-
- + Clear All + + +
-
-
- )} + + )} +
); } From eece018a77927d1a6f5c38ad2bd21b5533718098 Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 16:22:59 +0800 Subject: [PATCH 6/9] feat: stable scrollbars --- app/global.css | 10 +++++- app/markets/components/MarketTableBody.tsx | 38 +++++++++++++++++----- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/global.css b/app/global.css index 21ee7548..08d067ba 100644 --- a/app/global.css +++ b/app/global.css @@ -56,6 +56,8 @@ html { height: 100%; scroll-behavior: smooth; + /* Reserve space for scrollbar to prevent layout shifts */ + scrollbar-gutter: stable; } body { @@ -66,6 +68,8 @@ body { color: var(--color-text); font-family: Inter, sans-serif; overflow-x: hidden; + /* Add padding to account for scrollbar width in modern browsers */ + padding-right: calc(100vw - 100%); -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } @@ -120,6 +124,10 @@ h1 { text-align: center; } +.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr) { + border-left: 2px solid transparent; +} + .table-body tr:not(.no-hover-effect tr, .no-hover-effect tr):hover { background-color: var(--palette-bg-hovered); border-left: 2px solid var(--palette-orange); @@ -127,7 +135,7 @@ h1 { .table-body-focused { background-color: var(--palette-bg-hovered); - border-left: 2px solid var(--palette-orange); + border-left: 2px solid var(--palette-orange) !important; } svg { diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 47c83de1..21814280 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Tooltip } from '@nextui-org/tooltip'; import Image from 'next/image'; import { FaShieldAlt } from 'react-icons/fa'; @@ -11,6 +11,7 @@ import { Market } from '@/utils/types'; import { ExpandedMarketDetail } from './MarketRowDetail'; import { TDAsset, TDTotalSupplyOrBorrow } from './MarketTableUtils'; import { MarketAssetIndicator, MarketOracleIndicator, MarketDebtIndicator } from './RiskIndicator'; +import { motion, AnimatePresence } from 'framer-motion'; const MORPHO_LOGO = require('../../../src/imgs/tokens/morpho.svg') as string; @@ -37,6 +38,14 @@ export function MarketTableBody({ unstarMarket, onMarketClick, }: MarketTableBodyProps) { + const [visibleRowId, setVisibleRowId] = useState(null); + + useEffect(() => { + if (expandedRowId) { + setVisibleRowId(expandedRowId); + } + }, [expandedRowId]); + return ( {currentEntries.map((item, index) => { @@ -169,13 +178,26 @@ export function MarketTableBody({ - {expandedRowId === item.uniqueKey && ( - - - - - - )} + + {expandedRowId === item.uniqueKey && ( + + + +
+ +
+
+ + + )} +
); })} From 09afb3a53c3dfc0311e82630871f33c1735be4c1 Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 16:44:40 +0800 Subject: [PATCH 7/9] chore: animation positions --- app/positions/components/PositionsContent.tsx | 2 +- .../components/PositionsSummaryTable.tsx | 67 ++-- .../components/SuppliedMarketsDetail.tsx | 305 +++++++++--------- src/components/layout/header/Navbar.tsx | 2 +- src/components/layout/header/NavbarMobile.tsx | 8 +- 5 files changed, 203 insertions(+), 181 deletions(-) diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index 2bf68e88..9f8936b4 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -28,7 +28,7 @@ export default function Positions() {
-
+

Portfolio

diff --git a/app/positions/components/PositionsSummaryTable.tsx b/app/positions/components/PositionsSummaryTable.tsx index 33ad288e..5b14761d 100644 --- a/app/positions/components/PositionsSummaryTable.tsx +++ b/app/positions/components/PositionsSummaryTable.tsx @@ -15,6 +15,7 @@ import { } from 'app/markets/components/RiskIndicator'; import { RebalanceModal } from './RebalanceModal'; import { SuppliedMarketsDetail } from './SuppliedMarketsDetail'; +import { motion, AnimatePresence } from 'framer-motion'; type PositionsSummaryTableProps = { marketPositions: MarketPosition[]; @@ -195,10 +196,10 @@ export function PositionsSummaryTable({ - {processedPositions.map((position) => { - const rowKey = `${position.loanAssetAddress}-${position.chainId}`; + {processedPositions.map((groupedPosition) => { + const rowKey = `${groupedPosition.loanAssetAddress}-${groupedPosition.chainId}`; const isExpanded = expandedRows.has(rowKey); - const avgApy = position.totalWeightedApy / position.totalSupply; + const avgApy = groupedPosition.totalWeightedApy / groupedPosition.totalSupply; return ( @@ -209,8 +210,8 @@ export function PositionsSummaryTable({
{`Chain @@ -218,11 +219,11 @@ export function PositionsSummaryTable({
- {formatReadable(position.totalSupply)} - {position.loanAsset} + {formatReadable(groupedPosition.totalSupply)} + {groupedPosition.loanAsset} @@ -233,12 +234,12 @@ export function PositionsSummaryTable({
- {position.collaterals.length > 0 ? ( - position.collaterals.map((collateral, index) => ( + {groupedPosition.collaterals.length > 0 ? ( + groupedPosition.collaterals.map((collateral, index) => ( @@ -251,15 +252,15 @@ export function PositionsSummaryTable({
@@ -270,7 +271,7 @@ export function PositionsSummaryTable({ type="button" className="bg-hovered rounded-sm bg-opacity-50 p-2 text-xs duration-300 ease-in-out hover:bg-primary" onClick={() => { - setSelectedGroupedPosition(position); + setSelectedGroupedPosition(groupedPosition); setShowRebalanceModal(true); }} > @@ -279,18 +280,28 @@ export function PositionsSummaryTable({
- {isExpanded && ( - - - - - - )} + + {expandedRows.has(rowKey) && ( + + + + + + + + )} + ); })} diff --git a/app/positions/components/SuppliedMarketsDetail.tsx b/app/positions/components/SuppliedMarketsDetail.tsx index d9fc917c..ec9be57e 100644 --- a/app/positions/components/SuppliedMarketsDetail.tsx +++ b/app/positions/components/SuppliedMarketsDetail.tsx @@ -7,6 +7,7 @@ import { TokenIcon } from '@/components/TokenIcon'; import { formatReadable, formatBalance } from '@/utils/balance'; import { MarketPosition, GroupedPosition, WarningWithDetail, WarningCategory } from '@/utils/types'; import { getCollateralColor } from '../utils/colors'; +import { motion, AnimatePresence } from 'framer-motion'; type SuppliedMarketsDetailProps = { groupedPosition: GroupedPosition; @@ -57,164 +58,172 @@ export function SuppliedMarketsDetail({ }; return ( -
-
-
-

Collateral Exposure

-
- {groupedPosition.processedCollaterals.map((collateral, colIndex) => ( -
- ))} -
-
- {groupedPosition.processedCollaterals.map((collateral, colIndex) => ( - - +
+
+
+

Collateral Exposure

+
+ {groupedPosition.processedCollaterals.map((collateral, colIndex) => ( +
- ■ - {' '} - {collateral.symbol}: {formatReadable(collateral.percentage)}% - - ))} + title={`${collateral.symbol}: ${collateral.percentage.toFixed(2)}%`} + /> + ))} +
+
+ {groupedPosition.processedCollaterals.map((collateral, colIndex) => ( + + + ■ + {' '} + {collateral.symbol}: {formatReadable(collateral.percentage)}% + + ))} +
-
- - - - - - - - - - - - - - - {sortedMarkets.map((position) => { - const suppliedAmount = Number( - formatBalance(position.supplyAssets, position.market.loanAsset.decimals), - ); - const percentageOfPortfolio = - totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0; - const warningColor = getWarningColor(position.market.warningsWithDetail); +
MarketCollateralOracleLLTVAPYSupplied% of PortfolioActions
+ + + + + + + + + + + + + + {sortedMarkets.map((position) => { + const suppliedAmount = Number( + formatBalance(position.supplyAssets, position.market.loanAsset.decimals), + ); + const percentageOfPortfolio = + totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0; + const warningColor = getWarningColor(position.market.warningsWithDetail); - return ( - - + - + + - - - - - + + + + - - - ); - })} - -
MarketCollateralOracleLLTVAPYSupplied% of PortfolioActions
-
-
- {position.market.warningsWithDetail.length > 0 ? ( - } - placement="top" - > -
- -
-
- ) : ( -
- )} + return ( +
+
+
+ {position.market.warningsWithDetail.length > 0 ? ( + } + placement="top" + > +
+ +
+
+ ) : ( +
+ )} +
+ {/* */} + + {position.market.uniqueKey.slice(2, 8)} + + {/* */}
- {/* */} - - {position.market.uniqueKey.slice(2, 8)} - - {/* */} -
-
- {position.market.collateralAsset ? ( -
- - {position.market.collateralAsset.symbol} +
+ {position.market.collateralAsset ? ( +
+ + {position.market.collateralAsset.symbol} +
+ ) : ( + 'N/A' + )} +
+
+
- ) : ( - 'N/A' - )} -
-
- -
-
- {formatBalance(position.market.lltv, 16)}% - - {formatReadable(position.market.dailyApys.netSupplyApy * 100)}% - - {formatReadable(suppliedAmount)} {position.market.loanAsset.symbol} - -
-
-
+
+ {formatBalance(position.market.lltv, 16)}% + + {formatReadable(position.market.dailyApys.netSupplyApy * 100)}% + + {formatReadable(suppliedAmount)} {position.market.loanAsset.symbol} + +
+
+
+
+ + {formatReadable(percentageOfPortfolio)}% +
- - {formatReadable(percentageOfPortfolio)}% - -
-
- - -
-
+ + + + + + + ); + })} + + +
+ ); } diff --git a/src/components/layout/header/Navbar.tsx b/src/components/layout/header/Navbar.tsx index 7fe56efa..efb7c7c4 100644 --- a/src/components/layout/header/Navbar.tsx +++ b/src/components/layout/header/Navbar.tsx @@ -24,7 +24,7 @@ export function NavbarLink({ matchKey?: string; }) { const pathname = usePathname(); - const isActive = pathname.includes(matchKey ?? href); + const isActive = matchKey === '/' ? pathname === matchKey : pathname.includes(matchKey ?? href); return (
  • - +

    Home

  • - Markets + +

    Markets

    +
  • - +

    Portfolio

  • From e8df59247daeae0e158edd2b5c390929f0ca9ecb Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Mon, 25 Nov 2024 16:53:30 +0800 Subject: [PATCH 8/9] chore: review feedbacks --- app/markets/components/AssetFilter.tsx | 6 ++++-- app/markets/components/MarketTableBody.tsx | 10 +--------- app/positions/components/PositionsContent.tsx | 2 +- app/positions/components/PositionsSummaryTable.tsx | 6 ++++-- app/positions/components/SuppliedMarketsDetail.tsx | 6 ++++-- src/components/layout/header/AccountConnect.tsx | 2 +- src/components/layout/header/NavbarMobile.tsx | 10 +++++----- 7 files changed, 20 insertions(+), 22 deletions(-) diff --git a/app/markets/components/AssetFilter.tsx b/app/markets/components/AssetFilter.tsx index 44da0a26..290057e2 100644 --- a/app/markets/components/AssetFilter.tsx +++ b/app/markets/components/AssetFilter.tsx @@ -1,9 +1,9 @@ 'use client'; import { useState, useRef, useEffect, KeyboardEvent } from 'react'; import { ChevronDownIcon, TrashIcon } from '@radix-ui/react-icons'; +import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { ERC20Token, infoToKey } from '@/utils/tokens'; -import { motion, AnimatePresence } from 'framer-motion'; type FilterProps = { label: string; @@ -157,7 +157,9 @@ export default function AssetFilter({ tabIndex={0} > {token.symbol} - {token.img && {token.symbol}} + {token.img && ( + {token.symbol} + )} ))}
diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 21814280..a6d9f8c1 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Tooltip } from '@nextui-org/tooltip'; +import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { FaShieldAlt } from 'react-icons/fa'; import { GoStarFill, GoStar } from 'react-icons/go'; @@ -11,7 +12,6 @@ import { Market } from '@/utils/types'; import { ExpandedMarketDetail } from './MarketRowDetail'; import { TDAsset, TDTotalSupplyOrBorrow } from './MarketTableUtils'; import { MarketAssetIndicator, MarketOracleIndicator, MarketDebtIndicator } from './RiskIndicator'; -import { motion, AnimatePresence } from 'framer-motion'; const MORPHO_LOGO = require('../../../src/imgs/tokens/morpho.svg') as string; @@ -38,14 +38,6 @@ export function MarketTableBody({ unstarMarket, onMarketClick, }: MarketTableBodyProps) { - const [visibleRowId, setVisibleRowId] = useState(null); - - useEffect(() => { - if (expandedRowId) { - setVisibleRowId(expandedRowId); - } - }, [expandedRowId]); - return ( {currentEntries.map((item, index) => { diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index 9f8936b4..d587003b 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -28,7 +28,7 @@ export default function Positions() {
-
+

Portfolio

diff --git a/app/positions/components/PositionsSummaryTable.tsx b/app/positions/components/PositionsSummaryTable.tsx index 5b14761d..029910a9 100644 --- a/app/positions/components/PositionsSummaryTable.tsx +++ b/app/positions/components/PositionsSummaryTable.tsx @@ -1,6 +1,7 @@ import React, { useMemo, useState, useEffect } from 'react'; import { Spinner } from '@nextui-org/react'; import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; +import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { GrRefresh } from 'react-icons/gr'; import { toast } from 'react-toastify'; @@ -15,7 +16,6 @@ import { } from 'app/markets/components/RiskIndicator'; import { RebalanceModal } from './RebalanceModal'; import { SuppliedMarketsDetail } from './SuppliedMarketsDetail'; -import { motion, AnimatePresence } from 'framer-motion'; type PositionsSummaryTableProps = { marketPositions: MarketPosition[]; @@ -219,7 +219,9 @@ export function PositionsSummaryTable({
- {formatReadable(groupedPosition.totalSupply)} + + {formatReadable(groupedPosition.totalSupply)} + {groupedPosition.loanAsset} {position.market.warningsWithDetail.length > 0 ? ( } + content={ + + } placement="top" >
diff --git a/src/components/layout/header/AccountConnect.tsx b/src/components/layout/header/AccountConnect.tsx index 16c558d2..d6ac5d93 100644 --- a/src/components/layout/header/AccountConnect.tsx +++ b/src/components/layout/header/AccountConnect.tsx @@ -45,7 +45,7 @@ function AccountConnect() { } return ( -
+
); diff --git a/src/components/layout/header/NavbarMobile.tsx b/src/components/layout/header/NavbarMobile.tsx index d73b64ba..615be7bd 100644 --- a/src/components/layout/header/NavbarMobile.tsx +++ b/src/components/layout/header/NavbarMobile.tsx @@ -14,13 +14,13 @@ export default function NavbarMobile() { const navbarClass = [ 'flex flex-1 flex-grow items-center justify-between', - 'rounded-sm bg-main p-4 backdrop-blur-2xl', + 'rounded bg-surface p-4 backdrop-blur-2xl', 'mx-4', ].join(' '); if (isMobileMenuOpen) { return ( -