Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dApp: Acre points UI #704

Merged
merged 66 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
c95dd29
Implement `AcrePointsCard` component
kpyszkowski Aug 13, 2024
134911c
Implement `AcreRankCard` component
kpyszkowski Aug 13, 2024
dfa369b
Adjust layout
kpyszkowski Aug 13, 2024
8ffc54f
Fix layout row height
kpyszkowski Aug 13, 2024
6b65ea8
Fix Button styles
kpyszkowski Aug 14, 2024
610fcfd
Implement `AcrePointsRewardEstimation` component
kpyszkowski Aug 14, 2024
2e0bbed
Minor style adjustments
kpyszkowski Aug 14, 2024
b45f6b0
Implement `AcrePointsClaimModal` component
kpyszkowski Aug 20, 2024
e6f3f2a
Implement `AnimatedNumber` component
kpyszkowski Aug 21, 2024
ab5fe1e
Hide Acre Points behind feature flag
kpyszkowski Sep 11, 2024
4366e15
Merge branch 'main' into acre-points-ui
kpyszkowski Sep 11, 2024
c7a0626
Fix modal animation
kpyszkowski Sep 11, 2024
1259eb7
Add autoclose
kpyszkowski Sep 11, 2024
2df6cb9
Implement confetti
kpyszkowski Sep 11, 2024
4a82f71
Remove daily points displayed
kpyszkowski Sep 11, 2024
b42cd15
Integrate dApp with Acre Points API
kpyszkowski Sep 12, 2024
7df303f
Define API endpoint as env variable
kpyszkowski Sep 12, 2024
25aea8f
Hide points estimations behind feature flag
kpyszkowski Sep 12, 2024
f347184
Fix console error
kpyszkowski Sep 12, 2024
42ecf45
Resolve format CI error
kpyszkowski Sep 12, 2024
72cd217
Fix modal values calculations
kpyszkowski Sep 12, 2024
92e7fbb
Update fetching utils to latest changes
kpyszkowski Sep 13, 2024
86047b9
Update column width
kpyszkowski Sep 13, 2024
11e86a1
Update the value of `VITE_ACRE_API_ENDPOINT`
kkosiorowska Sep 16, 2024
74a8e78
Remove the unneeded function
kkosiorowska Sep 16, 2024
cc12763
Ordering query keys
kkosiorowska Sep 16, 2024
e33d083
Make the condition more readable
kkosiorowska Sep 16, 2024
0431d03
Add a space
kkosiorowska Sep 16, 2024
edbdd10
Update endpoint - use `GET /points`
kkosiorowska Sep 17, 2024
268f30f
Move the `mt` prop to level up for `AcrePointsRewardEstimation`.
kkosiorowska Sep 17, 2024
fccd65f
Rename from `dropTimeQuery` to `pointsData`
kkosiorowska Sep 17, 2024
b34af63
Refactor for acre API functions
kkosiorowska Sep 17, 2024
11236b4
Remove `bigIntStringToNumber` function
kkosiorowska Sep 17, 2024
2184a13
Add `AcrePointTemplate` component
kkosiorowska Sep 17, 2024
c810a83
Update query key names for the user
kkosiorowska Sep 17, 2024
b945b43
Remove the empty line
kkosiorowska Sep 17, 2024
4306454
Refactor for `AcrePointsRewardEstimation` component
kkosiorowska Sep 17, 2024
fff6c02
Update a default `desiredDecimals` for `numberToLocaleString`
kkosiorowska Sep 17, 2024
0bec155
Remove unneeded component - `AcreRankCard`
kkosiorowska Sep 17, 2024
fd55e21
Add missing `key`
kkosiorowska Sep 17, 2024
3c5eb76
Fix an error of nested `<div>` in `<p>`
kkosiorowska Sep 17, 2024
ccf775b
Fix eslint issue
kkosiorowska Sep 17, 2024
62b8f76
Use eth Address to fetch acre points data
kkosiorowska Sep 17, 2024
68a5277
Add `TODO` comment
kkosiorowska Sep 17, 2024
b1bac45
Fix incorrect path for acre API
kkosiorowska Sep 18, 2024
6b4dca9
Use eth address to fetch acre points data
kkosiorowska Sep 18, 2024
eb56b59
Update acre points endpoints
kkosiorowska Sep 18, 2024
2499826
Merge branch 'main' of github.com:thesis/acre into acre-points-ui
kkosiorowska Sep 18, 2024
5caed35
Fix post-merge errors
kkosiorowska Sep 18, 2024
4e1a0d0
Fixes for claim action
kkosiorowska Sep 18, 2024
f624f62
Move the `AcrePointsClaimModal` level up
kkosiorowska Sep 18, 2024
2254b7e
Fix import
kkosiorowska Sep 18, 2024
b4bcd39
Fix copy and text color
kpyszkowski Sep 30, 2024
61e3bdb
Fix timer alignment
kpyszkowski Sep 30, 2024
414a753
Resolve modal button issue
kpyszkowski Oct 3, 2024
29a4a72
Revert unnecessary changes
kpyszkowski Oct 8, 2024
9273d0f
Implement intermediate state of points gathering
kpyszkowski Oct 15, 2024
292d36e
Revert "Resolve modal button issue"
kpyszkowski Oct 15, 2024
13ef302
Use existing skeleton component
kpyszkowski Oct 15, 2024
f8ea2b1
Reduce content layout shift
kpyszkowski Oct 15, 2024
1ad292f
Update api handler and render conditions
kpyszkowski Oct 15, 2024
74fe818
Update points estimation due to latest changes
kpyszkowski Oct 15, 2024
3fe2b12
dApp: Acre points UI - fixes (#746)
kkosiorowska Oct 16, 2024
dad0260
Merge branch 'main' into acre-points-ui
kpyszkowski Oct 17, 2024
1df501b
Add TODO comments
kpyszkowski Oct 18, 2024
2be0e39
Merge branch 'main' into acre-points-ui
kkosiorowska Oct 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dapp/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ VITE_SENTRY_DSN=""
VITE_ETH_HOSTNAME_HTTP="https://sepolia.infura.io/v3/c80e8ccdcc4c4a809bce4fc165310617"
VITE_REFERRAL=0

# TODO: Set this env variable in CI.
# ENDPOINTS
VITE_TBTC_API_ENDPOINT=""
VITE_ACRE_API_ENDPOINT="http://localhost:8788/api/v1/"

Expand All @@ -26,5 +26,6 @@ VITE_FEATURE_FLAG_WITHDRAWALS_ENABLED="false"
VITE_FEATURE_FLAG_OKX_WALLET_ENABLED="false"
VITE_FEATURE_FLAG_XVERSE_WALLET_ENABLED="false"
VITE_FEATURE_FLAG_BEEHIVE_COMPONENT_ENABLED="false"
VITE_FEATURE_FLAG_ACRE_POINTS_ENABLED="true"
VITE_FEATURE_FLAG_TVL_ENABLED="false"

1 change: 1 addition & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"formik": "^2.4.5",
"framer-motion": "^10.16.5",
"react": "^18.2.0",
"react-confetti-explosion": "^2.1.2",
"react-dom": "^18.2.0",
"react-number-format": "^5.3.1",
"react-redux": "^9.1.0",
Expand Down
214 changes: 214 additions & 0 deletions dapp/src/components/AcrePointsClaimModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React, { ReactNode, useEffect, useMemo, useState } from "react"
import { useTimeout } from "#/hooks"
import { Box, Button, ModalBody, VStack } from "@chakra-ui/react"
import {
AnimationSequence,
motion,
Transition,
useAnimate,
} from "framer-motion"
import { logPromiseFailure, numberToLocaleString } from "#/utils"
import { ONE_SEC_IN_MILLISECONDS } from "#/constants"
import ConfettiExplosion from "react-confetti-explosion"
import { BaseModalProps } from "#/types"
import { AnimatedNumber } from "./shared/AnimatedNumber"
import { TextXl } from "./shared/Typography"
import withBaseModal from "./ModalRoot/withBaseModal"

const MotionBox = motion(Box)

const INITIAL_CONTAINER_HEIGHT = 214
const CONTAINER_HEIGHT = 288
const VALUE_SCALE = 0.375
const STEP_HEIGHT = CONTAINER_HEIGHT * (1 - VALUE_SCALE)
const STEP_SPACING = 32
const TRANSITION: Transition = {
type: "spring",
damping: 14,
stiffness: 86,
delay: 4, // step duration
}
const AUTOCLOSE_DELAY = 12 * ONE_SEC_IN_MILLISECONDS
const CONFETTI_DURATION = 4 * ONE_SEC_IN_MILLISECONDS

const getStepOffsets = (
stepCount: number,
stepHeight: number,
spacing: number,
) =>
Array(stepCount - 1)
.fill(0)
.map((_, index) =>
index === 0
? -stepHeight
: (index + 1) * -stepHeight - spacing * 2 ** index,
)

type AcrePointsClaimModalBaseProps = {
claimedAmount: number
totalAmount: number
} & BaseModalProps

function AcrePointsClaimModalBase({
claimedAmount,
totalAmount,
closeModal,
}: AcrePointsClaimModalBaseProps) {
const formattedClaimedAmount = numberToLocaleString(claimedAmount)
const formattedTotalAmount = numberToLocaleString(totalAmount)

const steps = useMemo<[string, ReactNode][]>(
() => [
[
"You earned",
<AnimatedNumber
value={formattedClaimedAmount}
prefix="+"
suffix=" PTS"
animateMode="whileInView"
color="brand.400"
/>,
],
[
"Updating points balance...",
<AnimatedNumber
value={formattedTotalAmount}
suffix=" PTS"
animateMode="whileInView"
indicationColor="brand.400"
/>,
],
// TODO: Uncomment when the leaderboard feature is ready
// [
// "Calculating rank...",
// <AnimatedNumber
// value={rankPositionDifference}
// prefix={rankPositionDifference > 0 ? "+" : "-"}
// animateMode="whileInView"
// color={rankPositionDifference > 0 ? "green.500" : "red.500"}
// />,
// ],
// [
// "Updating rank...",
// <AnimatedNumber
// value={estimatedRankPosition}
// prefix="#"
// animateMode="whileInView"
// indicationColor="brand.400"
// />,
// ],
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved
],
[formattedClaimedAmount, formattedTotalAmount],
)

const [scope, animate] = useAnimate()

useEffect(() => {
const offsets = getStepOffsets(steps.length, STEP_HEIGHT, STEP_SPACING)
const valueElements = [
...(scope.current as HTMLElement).querySelectorAll("[data-step-value]"),
].slice(0, -1)

const sequence = [
["[data-steps-list]", { y: offsets[0] }, TRANSITION],
[
"[data-container]",
{
clipPath: `polygon(0 0, 100% 0, 100% ${CONTAINER_HEIGHT}px, 0 ${CONTAINER_HEIGHT}px)`,
},
{ at: "<", ...TRANSITION },
],

[valueElements[0], { scale: VALUE_SCALE }, { at: "<", ...TRANSITION }],
["[data-close-button]", { opacity: 1 }, { at: "<", ...TRANSITION }],

// TODO: Uncomment when the leaderboard feature is ready

// ["[data-steps-list]", { y: offsets[1] }, TRANSITION],
// [valueElements[1], { scale: VALUE_SCALE }, { at: "<", ...TRANSITION }],

// ["[data-steps-list]", { y: offsets[2] }, TRANSITION],
// [valueElements[2], { scale: VALUE_SCALE }, { at: "<", ...TRANSITION }],
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved
] as AnimationSequence

const handleAnimation = async () => {
await animate(sequence)
}

logPromiseFailure(handleAnimation())
}, [scope, animate, steps])

const [isCofettiExploded, setIsCofettiExploded] = useState(false)

useTimeout(closeModal, AUTOCLOSE_DELAY)

return (
<ModalBody gap={0} p={0} pos="relative" ref={scope}>
<MotionBox
data-container
clipPath={`polygon(0 0, 100% 0, 100% ${INITIAL_CONTAINER_HEIGHT}px, 0 ${INITIAL_CONTAINER_HEIGHT}px)`}
overflow="hidden"
>
<VStack data-steps-list spacing={8}>
{steps.map(([currentStepLabel, currentStepValue]) => (
<Box key={currentStepLabel}>
<TextXl
fontWeight="semibold"
mb="5.25rem" // 84px
>
{currentStepLabel}
</TextXl>

<Box
data-step-value
transformOrigin="bottom"
fontSize="8xl"
lineHeight="6.25rem" // 100px
fontWeight="bold"
color="grey.700"
>
{currentStepValue}
</Box>
</Box>
))}
</VStack>
</MotionBox>

<Button
opacity={0}
onClick={closeModal}
data-close-button
variant="outline"
>
Yay!
</Button>

{!isCofettiExploded && (
<Box
pos="absolute"
top={0}
left="50%"
translateX="-50%"
transform="auto"
>
<ConfettiExplosion
zIndex={1410} // Chakra's modal has a z-index of 1400
width={768}
height="100vh"
duration={CONFETTI_DURATION}
force={0.2}
onComplete={() => setIsCofettiExploded(true)}
/>
</Box>
)}
</ModalBody>
)
}

const AcrePointsClaimModal = withBaseModal(AcrePointsClaimModalBase, {
returnFocusOnClose: false,
variant: "unstyled",
size: "full",
})

export default AcrePointsClaimModal
4 changes: 1 addition & 3 deletions dapp/src/components/MezoBeehiveModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ function MezoBeehiveModalBase() {
<VStack spacing={1}>
{data && (
<HStack>
<H6 fontWeight="bold">
{numberToLocaleString(data.totalMats, 0)}
</H6>
<H6 fontWeight="bold">{numberToLocaleString(data.totalMats)}</H6>
<TextLg fontWeight="bold">MATS</TextLg>
</HStack>
)}
Expand Down
2 changes: 2 additions & 0 deletions dapp/src/components/ModalRoot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import WelcomeModal from "../WelcomeModal"
import MezoBeehiveModal from "../MezoBeehiveModal"
import ConnectWalletModal from "../ConnectWalletModal"
import UnexpectedErrorModal from "../UnexpectedErrorModal"
import AcrePointsClaimModal from "../AcrePointsClaimModal"

const MODALS: Record<ModalType, ElementType> = {
STAKE: TransactionModal,
Expand All @@ -14,6 +15,7 @@ const MODALS: Record<ModalType, ElementType> = {
MEZO_BEEHIVE: MezoBeehiveModal,
CONNECT_WALLET: ConnectWalletModal,
UNEXPECTED_ERROR: UnexpectedErrorModal,
ACRE_POINTS_CLAIM: AcrePointsClaimModal,
} as const

export default function ModalRoot() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useMemo, useState } from "react"
import {
HStack,
Icon,
Menu,
MenuButton,
MenuItem,
MenuList,
StackProps,
VStack,
} from "@chakra-ui/react"
import { H4, TextMd } from "#/components/shared/Typography"
import { numberToLocaleString } from "#/utils"
import { IconChevronDown } from "@tabler/icons-react"
import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import {
ONE_MONTH_IN_DAYS,
ONE_WEEK_IN_DAYS,
ONE_YEAR_IN_DAYS,
} from "#/constants"

const ACRE_POINTS_DATA = {
week: {
label: "Per week",
multipler: ONE_WEEK_IN_DAYS,
},
month: {
label: "Per month",
multipler: ONE_MONTH_IN_DAYS,
},
year: {
label: "Per year",
multipler: ONE_YEAR_IN_DAYS,
},
}

function AcrePointsRewardEstimation(props: StackProps) {
const [selectedTierItem, setSelectedTierItem] = useState(
ACRE_POINTS_DATA.week,
)

const tierItems = [
selectedTierItem,
...Object.values(ACRE_POINTS_DATA).filter(
({ label, multipler }) =>
label !== selectedTierItem.label &&
multipler !== selectedTierItem.multipler,
),
]

const { value = 0n } = useTokenAmountField()
const baseReward = Number(value)
const pointsRate = 10000

const estimatedReward = useMemo(
() => (selectedTierItem.multipler * baseReward) / pointsRate,
[baseReward, selectedTierItem],
)

return (
<VStack spacing={2} {...props}>
<HStack>
<TextMd fontWeight="semibold">Acre points you&apos;ll earn</TextMd>

<Menu gutter={0} matchWidth offset={[0, -32]}>
{({ isOpen }) => (
<>
<MenuButton
type="button"
h="auto"
px={3}
py={1}
rounded="2xl"
bg="gold.300"
_hover={{ bg: "gold.200" }}
>
<HStack spacing={1}>
<TextMd>{selectedTierItem.label}</TextMd>
<Icon
as={IconChevronDown}
boxSize={4}
color="brand.400"
zIndex={2}
rotate={isOpen ? 180 : 0}
transform="auto"
/>
</HStack>
</MenuButton>

<MenuList
p={0}
minW={0}
rounded="2xl"
shadow="none"
bg="gold.300"
border="none"
overflow="hidden"
motionProps={{
variants: {},
}}
>
{tierItems.map((tierItem) => (
<MenuItem
type="button"
px={3}
py={1}
rounded="2xl"
bg="gold.300"
_active={{ bg: "gold.200" }}
_hover={{ bg: "gold.200" }}
key={tierItem.label}
onClick={() => setSelectedTierItem(tierItem)}
fontWeight="semibold"
>
{tierItem.label}
</MenuItem>
))}
</MenuList>
</>
)}
</Menu>
</HStack>

<H4>+{numberToLocaleString(estimatedReward)} PTS</H4>
</VStack>
)
}

export default AcrePointsRewardEstimation
Loading
Loading