diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 71db1bc16..0ac20b15b 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -1,34 +1,27 @@ import React from "react" -import { Box, ChakraProvider } from "@chakra-ui/react" +import { ChakraProvider } from "@chakra-ui/react" import { Provider as ReduxProvider } from "react-redux" import { RouterProvider } from "react-router-dom" -import { useInitApp } from "./hooks" -import { store } from "./store" -import theme from "./theme" +import { AcreSdkProvider } from "./acre-react/contexts" +import GlobalStyles from "./components/GlobalStyles" import { DocsDrawerContextProvider, LedgerWalletAPIProvider, SidebarContextProvider, WalletContextProvider, } from "./contexts" -import { AcreSdkProvider } from "./acre-react/contexts" -import Header from "./components/Header" -import Sidebar from "./components/Sidebar" -import DocsDrawer from "./components/DocsDrawer" -import GlobalStyles from "./components/GlobalStyles" +import { useInitApp } from "./hooks" import { router } from "./router" +import { store } from "./store" +import theme from "./theme" function DApp() { useInitApp() return ( <> -
- - - - - + + ) } @@ -42,7 +35,6 @@ function DAppProviders() { - diff --git a/dapp/src/assets/images/card-value-decorator.svg b/dapp/src/assets/images/card-value-decorator.svg new file mode 100644 index 000000000..6c3c8aa18 --- /dev/null +++ b/dapp/src/assets/images/card-value-decorator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/content-card-bg.png b/dapp/src/assets/images/content-card-bg.png new file mode 100644 index 000000000..8a509bb96 Binary files /dev/null and b/dapp/src/assets/images/content-card-bg.png differ diff --git a/dapp/src/assets/images/mystery-box.svg b/dapp/src/assets/images/mystery-box.svg new file mode 100644 index 000000000..608b0b347 --- /dev/null +++ b/dapp/src/assets/images/mystery-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/partner-logos/base-logo.svg b/dapp/src/assets/images/partner-logos/base-logo.svg new file mode 100644 index 000000000..c23469b19 --- /dev/null +++ b/dapp/src/assets/images/partner-logos/base-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/partner-logos/index.ts b/dapp/src/assets/images/partner-logos/index.ts new file mode 100644 index 000000000..7f83f7bef --- /dev/null +++ b/dapp/src/assets/images/partner-logos/index.ts @@ -0,0 +1,4 @@ +export { default as baseLogo } from "#/assets/images/partner-logos/base-logo.svg" +export { default as thresholdLogo } from "#/assets/images/partner-logos/threshold-logo.svg" +export { default as ledgerLogo } from "#/assets/images/partner-logos/ledger-logo.svg" +export { default as wormholeLogo } from "#/assets/images/partner-logos/wormhole-logo.svg" diff --git a/dapp/src/assets/images/partner-logos/ledger-logo.svg b/dapp/src/assets/images/partner-logos/ledger-logo.svg new file mode 100644 index 000000000..de33c0ce2 --- /dev/null +++ b/dapp/src/assets/images/partner-logos/ledger-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/partner-logos/threshold-logo.svg b/dapp/src/assets/images/partner-logos/threshold-logo.svg new file mode 100644 index 000000000..a0ed69338 --- /dev/null +++ b/dapp/src/assets/images/partner-logos/threshold-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/partner-logos/wormhole-logo.svg b/dapp/src/assets/images/partner-logos/wormhole-logo.svg new file mode 100644 index 000000000..e195bd8ad --- /dev/null +++ b/dapp/src/assets/images/partner-logos/wormhole-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/rewards-boost.svg b/dapp/src/assets/images/rewards-boost.svg new file mode 100644 index 000000000..70d7f5144 --- /dev/null +++ b/dapp/src/assets/images/rewards-boost.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/assets/images/season-countdown-section-background.png b/dapp/src/assets/images/season-countdown-section-background.png new file mode 100644 index 000000000..a6f2dfbdc Binary files /dev/null and b/dapp/src/assets/images/season-countdown-section-background.png differ diff --git a/dapp/src/assets/images/season-countdown-section-foreground.png b/dapp/src/assets/images/season-countdown-section-foreground.png new file mode 100644 index 000000000..e8554ba51 Binary files /dev/null and b/dapp/src/assets/images/season-countdown-section-foreground.png differ diff --git a/dapp/src/assets/images/season-key.svg b/dapp/src/assets/images/season-key.svg new file mode 100644 index 000000000..e40917ff3 --- /dev/null +++ b/dapp/src/assets/images/season-key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/components/Header/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx index 0398874df..5abbb5d89 100644 --- a/dapp/src/components/Header/ConnectWallet.tsx +++ b/dapp/src/components/Header/ConnectWallet.tsx @@ -1,9 +1,9 @@ import React from "react" import { Button, HStack, Icon, Tooltip } from "@chakra-ui/react" -import { useWallet } from "#/hooks" +import { useIsHomeRouteActive, useWallet, useWalletContext } from "#/hooks" import { CurrencyBalance } from "#/components/shared/CurrencyBalance" import { TextMd } from "#/components/shared/Typography" -import { BitcoinIcon, EthereumIcon } from "#/assets/icons" +import { BitcoinIcon } from "#/assets/icons" import { Account } from "@ledgerhq/wallet-api-client" import { CURRENCY_ID_BITCOIN } from "#/constants" import { @@ -11,6 +11,12 @@ import { logPromiseFailure, truncateAddress, } from "#/utils" +import { AnimatePresence, motion, Variants } from "framer-motion" + +const containerVariants: Variants = { + hidden: { opacity: 0, y: -48 }, + visible: { opacity: 1, y: 0 }, +} const getCustomDataByAccount = ( account?: Account, @@ -28,53 +34,54 @@ const getCustomDataByAccount = ( export default function ConnectWallet() { const { bitcoin: { account: btcAccount, requestAccount: requestBitcoinAccount }, - ethereum: { account: ethAccount, requestAccount: requestEthereumAccount }, } = useWallet() + // TODO: Move `isConnected` to useWallet hook + const { isConnected } = useWalletContext() const customDataBtcAccount = getCustomDataByAccount(btcAccount) - const customDataEthAccount = getCustomDataByAccount(ethAccount) + + const isHomeRoute = useIsHomeRouteActive() const handleConnectBitcoinAccount = () => { logPromiseFailure(requestBitcoinAccount()) } - const handleConnectEthereumAccount = () => { - logPromiseFailure(requestEthereumAccount()) - } - return ( - - - Balance - - - - - - - + + Balance + + + + + + + )} + ) } diff --git a/dapp/src/components/Header/Navigation/Navigation.tsx b/dapp/src/components/Header/Navigation/Navigation.tsx new file mode 100644 index 000000000..bcfe4357f --- /dev/null +++ b/dapp/src/components/Header/Navigation/Navigation.tsx @@ -0,0 +1,24 @@ +import React from "react" +import { Box, BoxProps, HStack, List } from "@chakra-ui/react" +import { NavigationItemType } from "#/types/navigation" +import NavigationItem from "./NavigationItem" + +type NavigationProps = BoxProps & { + items: NavigationItemType[] +} + +function Navigation(props: NavigationProps) { + const { items, ...restProps } = props + + return ( + + + {items.map((item) => ( + + ))} + + + ) +} + +export default Navigation diff --git a/dapp/src/components/Header/Navigation/NavigationItem.tsx b/dapp/src/components/Header/Navigation/NavigationItem.tsx new file mode 100644 index 000000000..ad5c4817f --- /dev/null +++ b/dapp/src/components/Header/Navigation/NavigationItem.tsx @@ -0,0 +1,38 @@ +import React from "react" +import { + Box, + ListItem, + ListItemProps, + useMultiStyleConfig, +} from "@chakra-ui/react" +import { motion } from "framer-motion" +import { NavigationItemType } from "#/types/navigation" +import { NavLink } from "../../shared/NavLink" + +type NavigationItemProps = ListItemProps & NavigationItemType + +function NavigationItem(props: NavigationItemProps) { + const { label, href, ...restProps } = props + const styles = useMultiStyleConfig("Link", { variant: "navigation" }) + + return ( + + + {({ isActive }) => ( + <> + {label} + {isActive && ( + + )} + + )} + + + ) +} + +export default NavigationItem diff --git a/dapp/src/components/Header/Navigation/index.ts b/dapp/src/components/Header/Navigation/index.ts new file mode 100644 index 000000000..168487f66 --- /dev/null +++ b/dapp/src/components/Header/Navigation/index.ts @@ -0,0 +1 @@ +export { default as Navigation } from "./Navigation" diff --git a/dapp/src/components/Header/index.tsx b/dapp/src/components/Header/index.tsx index df0a2eb53..48c514332 100644 --- a/dapp/src/components/Header/index.tsx +++ b/dapp/src/components/Header/index.tsx @@ -1,12 +1,22 @@ import React from "react" -import { Flex, HStack, Icon } from "@chakra-ui/react" import { AcreLogo } from "#/assets/icons" +import { routerPath } from "#/router/path" +import { Flex, HStack, Icon } from "@chakra-ui/react" +import { NavigationItemType } from "#/types" import ConnectWallet from "./ConnectWallet" +import { Navigation } from "./Navigation" + +// TODO: To be adjusted after project pivot/cleanup +const NAVIGATION_ITEMS: NavigationItemType[] = [ + { label: "Season 1", href: routerPath.home }, + { label: "Dashboard", href: routerPath.dashboard }, +] export default function Header() { return ( - + + diff --git a/dapp/src/components/Layout.tsx b/dapp/src/components/Layout.tsx new file mode 100644 index 000000000..08c738b2b --- /dev/null +++ b/dapp/src/components/Layout.tsx @@ -0,0 +1,46 @@ +import React from "react" +import { AnimatePresence, motion, Variants } from "framer-motion" +import { useState } from "react" +import { useLocation, useOutlet } from "react-router-dom" +import DocsDrawer from "./DocsDrawer" +import Header from "./Header" +import Sidebar from "./Sidebar" + +const wrapperVariants: Variants = { + in: { opacity: 0, y: 48 }, + out: { opacity: 0, y: -48 }, + visible: { opacity: 1, y: 0 }, +} + +// This tricky component makes Outlet persistent so React and Framer Motion can +// distinguish wheather it should be rerendered between routes. +// Ref: https://github.com/remix-run/react-router/discussions/8008#discussioncomment-1280897 +function PersistentOutlet() { + const [outlet] = useState(useOutlet()) + return outlet +} + +function Layout() { + const location = useLocation() + return ( + <> +
+ + + + + + + + + ) +} + +export default Layout diff --git a/dapp/src/components/shared/CurrencyBalance/index.tsx b/dapp/src/components/shared/CurrencyBalance/index.tsx index e14fcc0d6..a5f6f1706 100644 --- a/dapp/src/components/shared/CurrencyBalance/index.tsx +++ b/dapp/src/components/shared/CurrencyBalance/index.tsx @@ -13,9 +13,10 @@ export type CurrencyBalanceProps = { shouldBeFormatted?: boolean desiredDecimals?: number size?: string - variant?: "greater-balance-xl" | "greater-balance-xxl" + variant?: "greater-balance-xl" | "greater-balance-xxl" | "unstyled" balanceFontWeight?: string symbolFontWeight?: string + symbolPosition?: "prefix" | "suffix" } & TextProps export function CurrencyBalance({ @@ -27,9 +28,14 @@ export function CurrencyBalance({ variant, balanceFontWeight = "bold", symbolFontWeight = "bold", + symbolPosition = "suffix", ...textProps }: CurrencyBalanceProps) { - const styles = useMultiStyleConfig("CurrencyBalance", { size, variant }) + const styles = useMultiStyleConfig("CurrencyBalance", { + size, + variant, + symbolPosition, + }) const { symbol, @@ -47,7 +53,7 @@ export function CurrencyBalance({ }, [amount, decimals, desiredDecimals, shouldBeFormatted]) return ( - + & + Pick + +export function Link(props: LinkProps) { + return +} diff --git a/dapp/src/components/shared/NavLink.tsx b/dapp/src/components/shared/NavLink.tsx new file mode 100644 index 000000000..1826af89a --- /dev/null +++ b/dapp/src/components/shared/NavLink.tsx @@ -0,0 +1,21 @@ +import React from "react" +import { + Link as ChakraLink, + LinkProps as ChakraLinkProps, +} from "@chakra-ui/react" +import { + NavLink as RouterNavLink, + NavLinkProps as RouterNavLinkProps, +} from "react-router-dom" + +type NavLinkProps = Omit & + Pick + +export function NavLink(props: NavLinkProps) { + const { children, ...restProps } = props + return ( + + {children as React.ReactNode} + + ) +} diff --git a/dapp/src/components/shared/SeasonCountdownSection/CountdownTimer.tsx b/dapp/src/components/shared/SeasonCountdownSection/CountdownTimer.tsx new file mode 100644 index 000000000..9f7370bb5 --- /dev/null +++ b/dapp/src/components/shared/SeasonCountdownSection/CountdownTimer.tsx @@ -0,0 +1,127 @@ +import React, { useMemo } from "react" +import { useCountdown } from "#/hooks" +import { + BoxProps, + HStack, + Grid, + Box, + Text, + TextProps, + StackProps, +} from "@chakra-ui/react" +import { AnimatePresence, motion } from "framer-motion" +import { Tuple } from "#/types" + +const MotionBox = motion(Box) + +type CountdownTimerDigitProps = Omit & { + children: number +} +function CountdownTimerDigit(props: CountdownTimerDigitProps) { + const { children, ...restProps } = props + return ( + + + + {children} + + + + ) +} + +function CountdownTimerSegmentLabel(props: TextProps) { + return ( + + ) +} + +type CountdownTimerSegmentProps = Omit & { + label: string + value: Tuple +} +function CountdownTimerSegment(props: CountdownTimerSegmentProps) { + const { label, value, ...restProps } = props + return ( + + + {value[0]} + {value[1]} + {label} + + + ) +} + +type CountdownTimerProps = Omit & { + timestamp: number +} +export function CountdownTimer(props: CountdownTimerProps) { + const { timestamp, ...restProps } = props + const countdown = useCountdown(timestamp) + + const parsedCountdown = useMemo( + () => + Object.entries(countdown).reduce<[string, Tuple][]>( + (accumulator, currentValue) => { + const [key, stringValue] = currentValue + + if (key === "seconds") return accumulator + + const value = +stringValue + const parsedValue = ( + value > 0 + ? Array.from(value.toString().padStart(2, "0")).map(Number) + : Array(2).fill(0) + ) as Tuple + + return [...accumulator, [key, parsedValue]] + }, + [], + ), + [countdown], + ) + return ( + + {parsedCountdown.map(([label, value]) => ( + + ))} + + ) +} diff --git a/dapp/src/components/shared/SeasonCountdownSection/LiveTag.tsx b/dapp/src/components/shared/SeasonCountdownSection/LiveTag.tsx new file mode 100644 index 000000000..6061cde22 --- /dev/null +++ b/dapp/src/components/shared/SeasonCountdownSection/LiveTag.tsx @@ -0,0 +1,45 @@ +import React from "react" +import { TagProps, Tag, TagLabel, Box } from "@chakra-ui/react" +import { motion } from "framer-motion" + +const MotionBox = motion(Box) + +export function LiveTag(props: TagProps) { + return ( + + + + + Live + + + ) +} diff --git a/dapp/src/components/shared/SeasonCountdownSection/SeasonCountdownSectionBackground.tsx b/dapp/src/components/shared/SeasonCountdownSection/SeasonCountdownSectionBackground.tsx new file mode 100644 index 000000000..57fbe4e00 --- /dev/null +++ b/dapp/src/components/shared/SeasonCountdownSection/SeasonCountdownSectionBackground.tsx @@ -0,0 +1,125 @@ +import React, { useRef } from "react" +import { Box, BoxProps } from "@chakra-ui/react" +import { useSize } from "@chakra-ui/react-use-size" +import seasonCountdownBackground from "#/assets/images/season-countdown-section-background.png" +import seasonCountdownForeground from "#/assets/images/season-countdown-section-foreground.png" +import { + MotionValue, + motion, + useScroll, + useSpring, + useTransform, + useTime, + wrap, +} from "framer-motion" + +export function SeasonCountdownSectionBackground(props: BoxProps) { + const containerRef = useRef(null) + const { scrollYProgress } = useScroll({ + target: containerRef, + offset: ["center start", "start end"], + }) + const smoothScrollYProgress = useSpring(scrollYProgress, { + damping: 10, + stiffness: 90, + mass: 0.75, + }) as MotionValue + const foregroundParallax = useTransform( + smoothScrollYProgress, + [0, 1], + ["25%", "65%"], + ) + const time = useTime() + // Seed value is wrapped to prevent infinite increment causing potential memory leaks + const seed = useTransform(time, (value) => wrap(0, 2137, Math.floor(value))) + + const size = useSize(containerRef) + + return ( + + + + + + + + + + + + + + + + + + ) +} diff --git a/dapp/src/components/shared/SeasonCountdownSection/index.ts b/dapp/src/components/shared/SeasonCountdownSection/index.ts new file mode 100644 index 000000000..aace35052 --- /dev/null +++ b/dapp/src/components/shared/SeasonCountdownSection/index.ts @@ -0,0 +1,3 @@ +export * from "./LiveTag" +export * from "./CountdownTimer" +export * from "./SeasonCountdownSectionBackground" diff --git a/dapp/src/constants/externalHref.ts b/dapp/src/constants/externalHref.ts index cdf77bfcf..cffca6baf 100644 --- a/dapp/src/constants/externalHref.ts +++ b/dapp/src/constants/externalHref.ts @@ -1,4 +1,6 @@ export const EXTERNAL_HREF = { // TODO: Add a correct link DISCORD: "https://discord.com/", + DOCS: "", + FAQ: "", } diff --git a/dapp/src/constants/index.ts b/dapp/src/constants/index.ts index 653ef4777..559b26e25 100644 --- a/dapp/src/constants/index.ts +++ b/dapp/src/constants/index.ts @@ -3,3 +3,4 @@ export * from "./staking" export * from "./chains" export * from "./time" export * from "./externalHref" +export * from "./partnerLogos" diff --git a/dapp/src/constants/partnerLogos.ts b/dapp/src/constants/partnerLogos.ts new file mode 100644 index 000000000..48449c07c --- /dev/null +++ b/dapp/src/constants/partnerLogos.ts @@ -0,0 +1,9 @@ +import { ImageProps } from "@chakra-ui/react" +import * as partnerLogos from "#/assets/images/partner-logos" + +export const PARTNER_LOGOS: Pick[] = [ + { src: partnerLogos.baseLogo, maxW: "5.625rem", alt: "Base logo" }, // 90px + { src: partnerLogos.thresholdLogo, maxW: "13.125rem", alt: "Threshold logo" }, // 210px + { src: partnerLogos.ledgerLogo, maxW: "7.4375rem", alt: "Ledger logo" }, // 119px + { src: partnerLogos.wormholeLogo, maxW: "11.375rem", alt: "Wormhole logo" }, // 182px +] diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 5b6543a7f..5524c6f4b 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -24,4 +24,5 @@ export * from "./useTimeout" export * from "./useCountdown" export * from "./useActivities" export * from "./useSize" +export * from "./router" export * from "./useTransactionFee" diff --git a/dapp/src/hooks/router/index.ts b/dapp/src/hooks/router/index.ts new file mode 100644 index 000000000..cb8162e91 --- /dev/null +++ b/dapp/src/hooks/router/index.ts @@ -0,0 +1 @@ +export * from "./useIsActiveRoute" diff --git a/dapp/src/hooks/router/useIsActiveRoute.ts b/dapp/src/hooks/router/useIsActiveRoute.ts new file mode 100644 index 000000000..715c3702a --- /dev/null +++ b/dapp/src/hooks/router/useIsActiveRoute.ts @@ -0,0 +1,10 @@ +import { routerPath } from "#/router/path" +import { useLocation } from "react-router-dom" + +export const useIsActiveRoute = (route: string) => { + const location = useLocation() + + return location.pathname === route +} + +export const useIsHomeRouteActive = () => useIsActiveRoute(routerPath.home) diff --git a/dapp/src/pages/ActivityPage/index.tsx b/dapp/src/pages/ActivityPage/index.tsx index 8d61143c2..c1aef174f 100644 --- a/dapp/src/pages/ActivityPage/index.tsx +++ b/dapp/src/pages/ActivityPage/index.tsx @@ -3,6 +3,7 @@ import { Flex, Link as ChakraLink, Icon } from "@chakra-ui/react" import { Link as ReactRouterLink } from "react-router-dom" import { useSidebar } from "#/hooks" +import { routerPath } from "#/router/path" import { ArrowLeft } from "#/assets/icons" import ActivityDetails from "./ActivityDetails" import { ActivityBar } from "./ActivityBar" @@ -17,7 +18,7 @@ export default function ActivityPage() { return ( - + diff --git a/dapp/src/pages/LandingPage/components/BenefitCard.tsx b/dapp/src/pages/LandingPage/components/BenefitCard.tsx new file mode 100644 index 000000000..bd45a9bf7 --- /dev/null +++ b/dapp/src/pages/LandingPage/components/BenefitCard.tsx @@ -0,0 +1,71 @@ +import React from "react" +import { + Box, + Card, + CardBody, + CardHeader, + CardProps, + Image, + ImageProps, +} from "@chakra-ui/react" + +type IconCardProps = CardProps & { + header: React.ReactNode + icon: ImageProps +} + +export default function BenefitCard(props: IconCardProps) { + const { header, children, icon, ...restProps } = props + + return ( + + + {header} + + + {children && ( + + {children} + + )} + + + + + + ) +} diff --git a/dapp/src/pages/LandingPage/components/CardButton.tsx b/dapp/src/pages/LandingPage/components/CardButton.tsx new file mode 100644 index 000000000..a88f2c270 --- /dev/null +++ b/dapp/src/pages/LandingPage/components/CardButton.tsx @@ -0,0 +1,15 @@ +import React, { ComponentProps } from "react" +import ButtonLink from "#/components/shared/ButtonLink" + +export default function CardButton(props: ComponentProps) { + return ( + + ) +} diff --git a/dapp/src/pages/LandingPage/components/ContentCard.tsx b/dapp/src/pages/LandingPage/components/ContentCard.tsx new file mode 100644 index 000000000..f051c48b1 --- /dev/null +++ b/dapp/src/pages/LandingPage/components/ContentCard.tsx @@ -0,0 +1,61 @@ +import React from "react" +import { Card, CardBody, CardHeader, CardProps } from "@chakra-ui/react" +import contentCardBackground from "#/assets/images/content-card-bg.png" + +export type ContentCardProps = CardProps & { + header: React.ReactNode + withBackground?: boolean +} + +export default function ContentCard(props: ContentCardProps) { + const { header, children, withBackground = false, ...restProps } = props + + return ( + + + {header} + + + {children} + + + ) +} diff --git a/dapp/src/pages/LandingPage/components/HeroSection.tsx b/dapp/src/pages/LandingPage/components/HeroSection.tsx new file mode 100644 index 000000000..14d8a7e4a --- /dev/null +++ b/dapp/src/pages/LandingPage/components/HeroSection.tsx @@ -0,0 +1,27 @@ +import React from "react" +import { Button, Heading, VStack, Text } from "@chakra-ui/react" + +export default function HeroSection() { + return ( + + + Bitcoin staking done right. + + + The open source, decentralized way to grow your bitcoin + + + + ) +} diff --git a/dapp/src/pages/LandingPage/components/HighlightedValueCard.tsx b/dapp/src/pages/LandingPage/components/HighlightedValueCard.tsx new file mode 100644 index 000000000..ef49e0c31 --- /dev/null +++ b/dapp/src/pages/LandingPage/components/HighlightedValueCard.tsx @@ -0,0 +1,107 @@ +import React from "react" +import { + Card, + CardBody, + CardFooter, + CardHeader, + CardProps, + Box, +} from "@chakra-ui/react" +import valueCardDecorator from "#/assets/images/card-value-decorator.svg" +import { + CurrencyBalance, + CurrencyBalanceProps, +} from "#/components/shared/CurrencyBalance" + +type HighlightedValueCardProps = CardProps & { + header: React.ReactNode + footer?: React.ReactNode[] +} + +function StyledCurrencyBalance(props: CurrencyBalanceProps) { + return ( + + ) +} + +function HighlightedValueCardBase(props: HighlightedValueCardProps) { + const { header, children, footer = [], ...restProps } = props + const isFooterValid = footer.every( + (footerItem) => React.isValidElement(footerItem) && footerItem.type === Box, + ) + + if (!isFooterValid) { + throw new Error( + "Footer items must be valid components.", + ) + } + + return ( + + + {header} + + + {children} + + {footer.length > 0 && ( + + {footer.map((footerItem, index) => ( + // This is never rerendered, index key is fine + // eslint-disable-next-line react/no-array-index-key + {footerItem} + ))} + + )} + + ) +} + +const HighlightedValueCard = Object.assign(HighlightedValueCardBase, { + FooterItem: Box, + CurrencyBalance: StyledCurrencyBalance, +}) +export default HighlightedValueCard diff --git a/dapp/src/pages/LandingPage/components/SeasonCountdownSection.tsx b/dapp/src/pages/LandingPage/components/SeasonCountdownSection.tsx new file mode 100644 index 000000000..e498e543b --- /dev/null +++ b/dapp/src/pages/LandingPage/components/SeasonCountdownSection.tsx @@ -0,0 +1,45 @@ +import React from "react" +import { Box, VStack, Heading, Text } from "@chakra-ui/react" + +import { + LiveTag, + SeasonCountdownSectionBackground, + CountdownTimer, +} from "#/components/shared/SeasonCountdownSection" + +const SEASON_DUE_TIMESTAMP = new Date(2024, 5, 9).getTime() / 1000 + +export default function SeasonCountdownSection() { + return ( + *": { gridArea: "-1 / -1" } }}> + + + + Season 1. Pre-launch staking + + + Season 1 users that stake bitcoin before Acre launches earn the
+ highest rewards and first access to upcoming Seasons. +
+ +
+ +
+ ) +} diff --git a/dapp/src/pages/LandingPage/components/TVLCard.tsx b/dapp/src/pages/LandingPage/components/TVLCard.tsx new file mode 100644 index 000000000..8907d3271 --- /dev/null +++ b/dapp/src/pages/LandingPage/components/TVLCard.tsx @@ -0,0 +1,36 @@ +import React from "react" +import { Text } from "@chakra-ui/react" +import { CurrencyBalance } from "#/components/shared/CurrencyBalance" +import HighlightedValueCard from "./HighlightedValueCard" + +export default function TVLCard() { + return ( + + + +2% + + +24h +
, + + + , + ]} + > + + + ) +} diff --git a/dapp/src/pages/LandingPage/components/index.ts b/dapp/src/pages/LandingPage/components/index.ts new file mode 100644 index 000000000..bfcaad1b7 --- /dev/null +++ b/dapp/src/pages/LandingPage/components/index.ts @@ -0,0 +1,7 @@ +export { default as HeroSection } from "./HeroSection" +export { default as SeasonCountdownSection } from "./SeasonCountdownSection" +export { default as TVLCard } from "./TVLCard" +export { default as CardButton } from "./CardButton" +export { default as BenefitCard } from "./BenefitCard" +export { default as ContentCard } from "./ContentCard" +export { default as HighlightedValueCard } from "./HighlightedValueCard" diff --git a/dapp/src/pages/LandingPage/index.tsx b/dapp/src/pages/LandingPage/index.tsx new file mode 100644 index 000000000..1d0d9cace --- /dev/null +++ b/dapp/src/pages/LandingPage/index.tsx @@ -0,0 +1,86 @@ +import React from "react" +import { Flex, VStack, Image } from "@chakra-ui/react" +import boostCardIcon from "#/assets/images/rewards-boost.svg" +import mysteryCardIcon from "#/assets/images/mystery-box.svg" +import keyCardIcon from "#/assets/images/season-key.svg" +import { EXTERNAL_HREF, PARTNER_LOGOS } from "#/constants" +import { TextMd } from "#/components/shared/Typography" +import { + SeasonCountdownSection, + HeroSection, + CardButton, + ContentCard, + BenefitCard, +} from "#/pages/LandingPage/components" + +export default function LandingPage() { + return ( + + + + + + + Boosts your APY when + Acre fully launches + + + Grants you a random + reward gift. + + + Grants access to all + upcoming seasons + + + {/* + TODO: Bring back when TVL, user count and/or how-it-works diagram are available + + 8,172 + + + + insert diagram here + + + */} + + {PARTNER_LOGOS.map((logoAttributes) => ( + + ))} + + + Docs + + + FAQ + + + + ) +} diff --git a/dapp/src/router/index.tsx b/dapp/src/router/index.tsx index 5d280ad22..a050462e2 100644 --- a/dapp/src/router/index.tsx +++ b/dapp/src/router/index.tsx @@ -1,16 +1,29 @@ import React from "react" -import { createBrowserRouter } from "react-router-dom" -import OverviewPage from "#/pages/OverviewPage" +import Layout from "#/components/Layout" import ActivityPage from "#/pages/ActivityPage" +import DashboardPage from "#/pages/DashboardPage" +import LandingPage from "#/pages/LandingPage" +import { createBrowserRouter } from "react-router-dom" import { routerPath } from "./path" export const router = createBrowserRouter([ { - path: routerPath.home, - element: , - }, - { - path: `${routerPath.activity}/:activityId`, - element: , + path: "/", + element: , + children: [ + { + index: true, + path: routerPath.home, + element: , + }, + { + path: routerPath.dashboard, + element: , + }, + { + path: `${routerPath.activity}/:activityId`, + element: , + }, + ], }, ]) diff --git a/dapp/src/router/path.ts b/dapp/src/router/path.ts index be0fbef7c..693a1aab7 100644 --- a/dapp/src/router/path.ts +++ b/dapp/src/router/path.ts @@ -1,4 +1,5 @@ export const routerPath = { home: "/", + dashboard: "/dashboard", activity: "/activity-details", } diff --git a/dapp/src/theme/Card.ts b/dapp/src/theme/Card.ts index f4487bd1d..c117b2550 100644 --- a/dapp/src/theme/Card.ts +++ b/dapp/src/theme/Card.ts @@ -1,9 +1,7 @@ -import { - ComponentSingleStyleConfig, - StyleFunctionProps, -} from "@chakra-ui/react" +import { ComponentMultiStyleConfig, StyleFunctionProps } from "@chakra-ui/react" +import { cardAnatomy as parts } from "@chakra-ui/anatomy" -export const cardTheme: ComponentSingleStyleConfig = { +export const cardTheme: ComponentMultiStyleConfig = { baseStyle: { container: { boxShadow: "none", @@ -11,6 +9,7 @@ export const cardTheme: ComponentSingleStyleConfig = { bg: "gold.200", }, }, + parts: parts.keys, variants: { elevated: ({ colorScheme }: StyleFunctionProps) => { if (!colorScheme) return {} diff --git a/dapp/src/theme/CurrencyBalance.ts b/dapp/src/theme/CurrencyBalance.ts index 27dd0ba06..e4985bbba 100644 --- a/dapp/src/theme/CurrencyBalance.ts +++ b/dapp/src/theme/CurrencyBalance.ts @@ -1,13 +1,18 @@ import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react" -const PARTS = ["balance", "symbol"] +const PARTS = ["container", "balance", "symbol"] -const baseStyleBalance = defineStyle({ +const baseStyleContainer = defineStyle(({ symbolPosition }) => ({ + display: "flex", + flexDir: symbolPosition === "prefix" ? "row-reverse" : "row", +})) + +const baseStyleBalance = defineStyle(({ symbolPosition }) => ({ fontWeight: "bold", fontSize: "md", lineHeight: "md", - mr: 1, -}) + [symbolPosition === "prefix" ? "ml" : "mr"]: "0.5ch", // dynamic value based on font size +})) const baseStyleSymbol = defineStyle({ fontSize: "md", @@ -16,10 +21,11 @@ const baseStyleSymbol = defineStyle({ const multiStyleConfig = createMultiStyleConfigHelpers(PARTS) -const baseStyle = multiStyleConfig.definePartsStyle({ - balance: baseStyleBalance, +const baseStyle = multiStyleConfig.definePartsStyle((props) => ({ + container: baseStyleContainer(props), + balance: baseStyleBalance(props), symbol: baseStyleSymbol, -}) +})) const variantGreaterBalanceXl = multiStyleConfig.definePartsStyle({ balance: { @@ -114,6 +120,17 @@ const size4Xl = multiStyleConfig.definePartsStyle({ }, }) +const size6Xl = multiStyleConfig.definePartsStyle({ + balance: { + fontSize: "6xl", + lineHeight: "6xl", + }, + symbol: { + fontSize: "6xl", + lineHeight: "6xl", + }, +}) + const sizes = { xs: sizeXs, sm: sizeSm, @@ -121,6 +138,7 @@ const sizes = { lg: sizeLg, xl: sizeXl, "4xl": size4Xl, + "6xl": size6Xl, } export const currencyBalanceTheme = multiStyleConfig.defineMultiStyleConfig({ diff --git a/dapp/src/theme/Link.ts b/dapp/src/theme/Link.ts index 3c9d7c7aa..7c283559e 100644 --- a/dapp/src/theme/Link.ts +++ b/dapp/src/theme/Link.ts @@ -1,6 +1,9 @@ -import { defineStyle, defineStyleConfig } from "@chakra-ui/react" +import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react" -const baseStyle = defineStyle({ +const PARTS = ["container", "indicator"] +const multiStyleConfig = createMultiStyleConfigHelpers(PARTS) + +const containerBaseStyle = defineStyle({ fontWeight: "semibold", fontSize: "sm", lineHeight: "sm", @@ -10,6 +13,33 @@ const baseStyle = defineStyle({ }, }) -export const linkTheme = defineStyleConfig({ - baseStyle, +const navigationContainerStyles = defineStyle({ + display: "block", + fontSize: "md", + lineHeight: "md", + fontWeight: "bold", + marginBottom: 2, + color: "grey.500", + _activeLink: { color: "grey.700" }, +}) + +const navigationIndicatorStyles = defineStyle({ + pos: "absolute", + bottom: 0.5, + left: 0, + w: "full", + h: 0.5, + bg: "brand.400", +}) + +export const linkTheme = multiStyleConfig.defineMultiStyleConfig({ + baseStyle: { + container: containerBaseStyle, + }, + variants: { + navigation: { + container: navigationContainerStyles, + indicator: navigationIndicatorStyles, + }, + }, }) diff --git a/dapp/src/theme/Tag.ts b/dapp/src/theme/Tag.ts index 839c8eb3a..1ff607434 100644 --- a/dapp/src/theme/Tag.ts +++ b/dapp/src/theme/Tag.ts @@ -1,20 +1,41 @@ import { tagAnatomy as parts } from "@chakra-ui/anatomy" import { defineStyle, createMultiStyleConfigHelpers } from "@chakra-ui/react" -const tagStyle = defineStyle({ +const multiStyleConfig = createMultiStyleConfigHelpers(parts.keys) + +const baseStyleContainer = defineStyle({ borderRadius: "full", w: "fit-content", bg: "gold.100", - borderColor: "white", - borderWidth: 1, paddingX: 4, paddingY: 2.5, + shadow: "none", }) -const multiStyleConfig = createMultiStyleConfigHelpers(parts.keys) +const baseStyle = multiStyleConfig.definePartsStyle({ + container: baseStyleContainer, +}) + +const variantSolid = multiStyleConfig.definePartsStyle({ + container: { + borderWidth: 0, + }, +}) + +const variantOutline = multiStyleConfig.definePartsStyle({ + container: { + borderColor: "white", + borderWidth: 1, + }, +}) -const baseStyle = multiStyleConfig.definePartsStyle({ container: tagStyle }) +const variants = { + solid: variantSolid, + outline: variantOutline, +} export const tagTheme = multiStyleConfig.defineMultiStyleConfig({ + defaultProps: { variant: "outline" }, baseStyle, + variants, }) diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index add79614a..fb32c4d92 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -45,6 +45,11 @@ const defaultTheme = { zIndices, semanticTokens, styles, + space: { + 13: "3.25rem", + 15: "3.75rem", + 30: "7.5rem", + }, components: { Alert: alertTheme, Button: buttonTheme, diff --git a/dapp/src/types/core.ts b/dapp/src/types/core.ts new file mode 100644 index 000000000..83860e243 --- /dev/null +++ b/dapp/src/types/core.ts @@ -0,0 +1 @@ +export type Tuple = [T, T] diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index 4e51f4533..dc645ffc8 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -13,5 +13,7 @@ export * from "./coingecko" export * from "./time" export * from "./size" export * from "./toast" +export * from "./core" export * from "./fee" +export * from "./navigation" export * from "./subgraphAPI" diff --git a/dapp/src/types/navigation.ts b/dapp/src/types/navigation.ts new file mode 100644 index 000000000..058b095d0 --- /dev/null +++ b/dapp/src/types/navigation.ts @@ -0,0 +1,4 @@ +export type NavigationItemType = { + label: string + href: string +}