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
-
-
-
- }
- onClick={handleConnectBitcoinAccount}
+
+ {(isConnected || !isHomeRoute) && (
+
- {customDataBtcAccount.text}
-
-
- }
- onClick={handleConnectEthereumAccount}
- >
- {customDataEthAccount.text}
-
-
+
+ Balance
+
+
+
+ }
+ onClick={handleConnectBitcoinAccount}
+ >
+ {customDataBtcAccount.text}
+
+
+
+ )}
+
)
}
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
+}