@@ -49,10 +52,14 @@ export const AllocationAmount = ({ balance, errorMessage, onPercentageSet }: All
labelProps={{ className: 'text-base leading-4 font-normal' }}
name="allocated-amount"
fullWidth
- onChange={onChange}
- value={allocatedAmount.toString()}
- errorMessage={errorMessage}
- hint={hint}
+ onChange={handleOnChange}
+ value={formatEther(totalAllocation)}
+ errorMessage={
+ cumulativeAllocation > totalAllocation && cumulativeAllocation < balance
+ ? ALLOCATION_EXCEED_AMOUNT_ERROR
+ : ''
+ }
+ hint={Number(totalAllocation - cumulativeAllocation) < 0 ? : undefined}
/>
diff --git a/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx b/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx
new file mode 100644
index 00000000..04b6529b
--- /dev/null
+++ b/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx
@@ -0,0 +1,68 @@
+'use client'
+
+import { Paragraph } from '@/components/Typography'
+import { useContext } from 'react'
+import { formatEther } from 'viem'
+import { AllocationsContext } from '@/app/collective-rewards/allocations/context'
+
+type ValueProps = {
+ value: string
+}
+
+type MetricsProps = ValueProps & {
+ name: string
+}
+
+const Metric = ({ name, value }: MetricsProps) => {
+ return (
+
+ )
+}
+
+const Column = ({ children }: { children: React.ReactNode }) => {
+ return
{children}
+}
+
+const Balance = ({ value }: ValueProps) => {
+ return
+}
+
+const AllocatedAmount = ({ value }: ValueProps) => {
+ return
+}
+
+const UnallocatedAmount = ({ value }: ValueProps) => {
+ return
+}
+
+export const AllocationMetrics = () => {
+ const {
+ initialState: {
+ backer: { totalAllocation, balance },
+ },
+ } = useContext(AllocationsContext)
+
+ const balanceValue = `${formatEther(balance)} stRIF`
+
+ const allocatedAmountValue = `${formatEther(totalAllocation)} stRIF`
+
+ const unallocatedAmount = formatEther(balance - totalAllocation)
+
+ const unallocatedAmountValue = `${unallocatedAmount} stRIF`
+ return (
+
+ )
+}
diff --git a/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx b/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx
new file mode 100644
index 00000000..a6fa5048
--- /dev/null
+++ b/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx
@@ -0,0 +1,51 @@
+import { AllocationsContext } from '@/app/collective-rewards/allocations/context'
+import { Builder } from '@/app/collective-rewards/types'
+import { Input } from '@/components/Input'
+import { Slider } from '@/components/Slider'
+import { Label } from '@/components/Typography'
+import { useContext } from 'react'
+import { formatEther, parseEther } from 'viem'
+import { BuilderAllocationHeader, BuilderAllocationHeaderProps } from './BuilderAllocationHeader'
+
+export type BuilderAllocationProps = BuilderAllocationHeaderProps &
+ Pick
& {
+ index: number
+ currentAllocation: bigint
+ }
+
+export const BuilderAllocation = (builder: BuilderAllocationProps) => {
+ const {
+ state: {
+ backer: { totalAllocation, cumulativeAllocation },
+ },
+ actions: { updateAllocation },
+ } = useContext(AllocationsContext)
+ const allocationLeft = totalAllocation - cumulativeAllocation
+ const { currentAllocation, kickback, address } = builder
+ const onInputChange = (value: string) => {
+ updateAllocation(builder.index, parseEther(value))
+ }
+
+ const onSliderValueChange = (value: number[]) => {
+ updateAllocation(builder.index, BigInt(value[0]))
+ }
+
+ return (
+
+
+
+ 0 ? formatEther(allocationLeft) : '0'} stRIF`}
+ onChange={onInputChange}
+ value={formatEther(currentAllocation)}
+ />
+
+
+ )
+}
diff --git a/src/app/collective-rewards/allocations/BuilderAllocationHeader.tsx b/src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx
similarity index 63%
rename from src/app/collective-rewards/allocations/BuilderAllocationHeader.tsx
rename to src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx
index 9437f299..05e1e22f 100644
--- a/src/app/collective-rewards/allocations/BuilderAllocationHeader.tsx
+++ b/src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx
@@ -1,17 +1,19 @@
+import { Builder, BuilderStatusActive, BuilderStatusShown } from '@/app/collective-rewards/types'
+import { crStatusColorClasses } from '@/app/collective-rewards/user'
import { AddressOrAlias } from '@/components/Address'
import { Badge } from '@/components/Badge'
import { Jdenticon } from '@/components/Header/Jdenticon'
import { Paragraph, Typography } from '@/components/Typography'
-import { BuilderStatusActive } from '../types'
-import { crStatusColorClasses } from '../user'
-import { BuilderAllocationProps } from './types'
+import { FC } from 'react'
-export const BuilderAllocationHeader = ({
+export type BuilderAllocationHeaderProps = Pick
+
+export const BuilderAllocationHeader: FC = ({
address,
builderName,
status,
joiningDate,
-}: BuilderAllocationProps) => {
+}) => {
return (
@@ -20,7 +22,10 @@ export const BuilderAllocationHeader = ({
{status !== BuilderStatusActive && (
-
+
)}
{status === BuilderStatusActive && (
Joined {joiningDate}
diff --git a/src/app/collective-rewards/allocations/Header.tsx b/src/app/collective-rewards/allocations/components/Header.tsx
similarity index 100%
rename from src/app/collective-rewards/allocations/Header.tsx
rename to src/app/collective-rewards/allocations/components/Header.tsx
diff --git a/src/app/collective-rewards/allocations/StakeHint.tsx b/src/app/collective-rewards/allocations/components/StakeHint.tsx
similarity index 100%
rename from src/app/collective-rewards/allocations/StakeHint.tsx
rename to src/app/collective-rewards/allocations/components/StakeHint.tsx
diff --git a/src/app/collective-rewards/allocations/components/index.ts b/src/app/collective-rewards/allocations/components/index.ts
new file mode 100644
index 00000000..e471eb16
--- /dev/null
+++ b/src/app/collective-rewards/allocations/components/index.ts
@@ -0,0 +1,5 @@
+export * from './AllocationAmount'
+export * from './AllocationMetrics'
+export * from './BuilderAllocation'
+export * from './Header'
+export * from './StakeHint'
diff --git a/src/app/collective-rewards/allocations/context/AllocationsContext.tsx b/src/app/collective-rewards/allocations/context/AllocationsContext.tsx
new file mode 100644
index 00000000..486c4aa7
--- /dev/null
+++ b/src/app/collective-rewards/allocations/context/AllocationsContext.tsx
@@ -0,0 +1,249 @@
+import {
+ useBackerTotalAllocation,
+ useGetAllAllocationOf,
+ useGetVotingPower,
+} from '@/app/collective-rewards/allocations/hooks'
+import { Builder, BuilderInfo } from '@/app/collective-rewards/types'
+import { useBuilderContext, useGetBuilders } from '@/app/collective-rewards/user'
+import { createContext, FC, ReactNode, useEffect, useMemo, useState } from 'react'
+import { zeroAddress } from 'viem'
+import { useAccount } from 'wagmi'
+import { createActions } from './allocationsActions'
+
+export type Allocations = Record
+
+export interface Backer {
+ totalAllocation: bigint
+ balance: bigint
+ allocationCount: number
+ cumulativeAllocation: bigint
+}
+
+type AllocationsContextValue = {
+ selections: number[]
+ allocations: Allocations
+ backer: Backer
+ isContextLoading: boolean
+ contextError: Error | null
+ getBuilder: (index: number) => Builder | null
+}
+
+export type AllocationsActions = {
+ toggleSelectedBuilder: (builderIndex: number) => void
+ updateAllocation: (builderIndex: number, value: bigint) => void
+ updateAllocations: (values: bigint[]) => void
+ updateTotalAllocation: (value: bigint) => void
+ resetAllocations: () => void
+}
+
+export type InitialState = Pick
+
+type AllocationsContext = {
+ initialState: InitialState
+ state: AllocationsContextValue
+ actions: AllocationsActions
+}
+
+const DEFAULT_CONTEXT: AllocationsContext = {
+ initialState: {
+ backer: {
+ balance: BigInt(0),
+ totalAllocation: BigInt(0),
+ allocationCount: 0,
+ cumulativeAllocation: BigInt(0),
+ },
+ allocations: {},
+ },
+ state: {
+ selections: [],
+ allocations: {},
+ backer: {
+ balance: BigInt(0),
+ totalAllocation: BigInt(0),
+ allocationCount: 0,
+ cumulativeAllocation: BigInt(0),
+ },
+ isContextLoading: true,
+ contextError: null,
+ getBuilder: () => null,
+ },
+ actions: {
+ toggleSelectedBuilder: () => {},
+ updateAllocation: () => {},
+ updateAllocations: () => {},
+ updateTotalAllocation: () => {},
+ resetAllocations: () => {},
+ },
+}
+
+export const AllocationsContext = createContext(DEFAULT_CONTEXT)
+
+export const AllocationsContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
+ const { address: backerAddress } = useAccount()
+
+ /**
+ * Selections are the indexes of the builders that the backer has selected
+ */
+ const [selections, setSelections] = useState(DEFAULT_CONTEXT.state.selections)
+
+ /**
+ * Allocations are the amounts that the backer has allocated to each builder either in the current session or in the past
+ * The key is the index of the builder
+ * The value is the amount allocated
+ */
+ const [allocations, setAllocations] = useState(DEFAULT_CONTEXT.state.allocations)
+
+ /**
+ * Cumulative allocation is the total amount that the backer has allocated to all builders in current session
+ */
+ const [isContextLoading, setIsContextLoading] = useState(DEFAULT_CONTEXT.state.isContextLoading)
+ const [contextError, setContextError] = useState(DEFAULT_CONTEXT.state.contextError)
+
+ const [backer, setBacker] = useState(DEFAULT_CONTEXT.state.backer)
+
+ // TODO: review this part
+ const {
+ data: buildersFromContext,
+ isLoading: isLoadingBuilders,
+ error: buildersError,
+ } = useBuilderContext()
+ const builders = buildersFromContext.map(
+ ({ builderName, status, address, gauge, joiningDate }) =>
+ ({
+ builderName,
+ status,
+ address,
+ gauge,
+ joiningDate,
+ // TODO: retrieve the kickback
+ kickback: 0,
+ }) as Builder,
+ )
+
+ const {
+ data: allAllocations,
+ isLoading: isAllAllocationsLoading,
+ error: allAllocationsError,
+ } = useGetAllAllocationOf(
+ backerAddress ?? zeroAddress,
+ builders.map(builder => builder.gauge),
+ )
+
+ const {
+ data: totalAllocation,
+ isLoading: isTotalAllocationLoading,
+ error: totalAllocationError,
+ } = useBackerTotalAllocation(backerAddress ?? zeroAddress)
+
+ const { data: votingPower, isLoading: isVotingPowerLoading, error: votingPowerError } = useGetVotingPower()
+
+ useEffect(() => {
+ if (isContextLoading) {
+ return
+ }
+ if (!backerAddress || !allAllocations) {
+ return
+ }
+
+ const [allocations, newCumulativeAllocation] = createInitialAllocations(allAllocations, selections)
+
+ setAllocations(allocations)
+ setBacker(prevBacker => ({
+ ...prevBacker,
+ allocationCount: builders.length,
+ cumulativeAllocation: newCumulativeAllocation,
+ }))
+ }, [allAllocations, backerAddress, selections, isContextLoading, builders.length])
+
+ useEffect(() => {
+ if (totalAllocation) {
+ setBacker(prevBacker => ({
+ ...prevBacker,
+ totalAllocation,
+ }))
+ }
+ }, [totalAllocation])
+
+ useEffect(() => {
+ if (votingPower) {
+ setBacker(prevBacker => ({
+ ...prevBacker,
+ balance: votingPower,
+ }))
+ }
+ }, [votingPower])
+
+ useEffect(() => {
+ setContextError(buildersError ?? allAllocationsError ?? totalAllocationError ?? votingPowerError)
+ }, [allAllocationsError, buildersError, totalAllocationError, votingPowerError])
+
+ useEffect(() => {
+ setIsContextLoading(
+ isLoadingBuilders || isAllAllocationsLoading || isTotalAllocationLoading || isVotingPowerLoading,
+ )
+ }, [isLoadingBuilders, isAllAllocationsLoading, isTotalAllocationLoading, isVotingPowerLoading])
+
+ const initialState: InitialState = useMemo(() => {
+ if (isContextLoading) {
+ return {
+ backer: DEFAULT_CONTEXT.initialState.backer,
+ allocations: DEFAULT_CONTEXT.initialState.allocations,
+ }
+ }
+
+ const [initialAllocations, initialCumulativeAllocations] = createInitialAllocations(
+ allAllocations || [],
+ selections,
+ )
+
+ return {
+ backer: {
+ balance: votingPower ?? BigInt(0),
+ totalAllocation: totalAllocation ?? BigInt(0),
+ allocationCount: builders.length,
+ cumulativeAllocation: initialCumulativeAllocations,
+ },
+ allocations: initialAllocations,
+ }
+ }, [allAllocations, builders, totalAllocation, votingPower, isContextLoading, selections])
+
+ const state: AllocationsContextValue = {
+ selections,
+ allocations,
+ backer,
+ isContextLoading,
+ contextError,
+ getBuilder: (index: number) => (index >= 0 && index < builders.length ? builders[index] : null),
+ }
+
+ const actions: AllocationsActions = useMemo(
+ () => createActions(setSelections, setAllocations, setBacker, initialState),
+ [initialState],
+ )
+
+ return (
+
+ {children}
+
+ )
+}
+
+function createInitialAllocations(allAllocations: bigint[], selections: number[]): [Allocations, bigint] {
+ return allAllocations.reduce(
+ (acc, allocation, index) => {
+ if (allocation || selections.includes(index)) {
+ acc[0][index] = allocation
+ acc[1] += allocation
+ }
+
+ return acc
+ },
+ [{} as Allocations, BigInt(0)],
+ )
+}
diff --git a/src/app/collective-rewards/allocations/context/allocationsActions.ts b/src/app/collective-rewards/allocations/context/allocationsActions.ts
new file mode 100644
index 00000000..668ef777
--- /dev/null
+++ b/src/app/collective-rewards/allocations/context/allocationsActions.ts
@@ -0,0 +1,57 @@
+import { Dispatch, SetStateAction } from 'react'
+import { Allocations, AllocationsActions, Backer, InitialState } from './AllocationsContext'
+
+export const createActions = (
+ setSelections: Dispatch>,
+ setAllocations: Dispatch>>,
+ setBacker: Dispatch>,
+ initialState: InitialState,
+): AllocationsActions => ({
+ toggleSelectedBuilder: (builderIndex: number) => {
+ setSelections(prevSelections =>
+ prevSelections.includes(builderIndex)
+ ? prevSelections.filter(index => index !== builderIndex)
+ : [...prevSelections, builderIndex],
+ )
+ },
+ updateAllocation: (builderIndex: number, value: bigint) => {
+ setAllocations(prevAllocations => {
+ const newAllocations = { ...prevAllocations, [builderIndex]: value }
+
+ setBacker(prevBacker => ({
+ ...prevBacker,
+ cumulativeAllocation:
+ prevBacker.cumulativeAllocation + value - (prevAllocations[builderIndex] ?? BigInt(0)),
+ }))
+
+ return newAllocations
+ })
+ },
+ updateAllocations: (values: bigint[]) => {
+ const [newAllocations, newCumulativeAllocation] = values.reduce(
+ (acc, value, index) => {
+ acc[0][index] = value
+ acc[1] += value
+
+ return acc
+ },
+ [{} as Allocations, BigInt(0)],
+ )
+ setAllocations(newAllocations)
+
+ setBacker(prevBacker => ({
+ ...prevBacker,
+ cumulativeAllocation: newCumulativeAllocation,
+ }))
+ },
+ updateTotalAllocation: (value: bigint) => {
+ setBacker(prevBacker => ({
+ ...prevBacker,
+ totalAllocation: value,
+ }))
+ },
+ resetAllocations: () => {
+ setAllocations(initialState.allocations)
+ setBacker(initialState.backer)
+ },
+})
diff --git a/src/app/collective-rewards/allocations/context/index.ts b/src/app/collective-rewards/allocations/context/index.ts
new file mode 100644
index 00000000..0697bbaa
--- /dev/null
+++ b/src/app/collective-rewards/allocations/context/index.ts
@@ -0,0 +1 @@
+export * from './AllocationsContext'
diff --git a/src/app/collective-rewards/allocations/hooks/index.ts b/src/app/collective-rewards/allocations/hooks/index.ts
new file mode 100644
index 00000000..3dffbdee
--- /dev/null
+++ b/src/app/collective-rewards/allocations/hooks/index.ts
@@ -0,0 +1,2 @@
+export * from './useBackerTotalAllocation'
+export * from './useGetVotingPower'
diff --git a/src/app/collective-rewards/allocations/useBackerTotalAllocation.tsx b/src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts
similarity index 51%
rename from src/app/collective-rewards/allocations/useBackerTotalAllocation.tsx
rename to src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts
index b7239801..f686e97d 100644
--- a/src/app/collective-rewards/allocations/useBackerTotalAllocation.tsx
+++ b/src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts
@@ -1,8 +1,9 @@
import { BackersManagerAbi } from '@/lib/abis/v2/BackersManagerAbi'
+import { GaugeAbi } from '@/lib/abis/v2/GaugeAbi'
import { AVERAGE_BLOCKTIME } from '@/lib/constants'
import { BackersManagerAddress } from '@/lib/contracts'
import { Address } from 'viem'
-import { useReadContract } from 'wagmi'
+import { useReadContract, useReadContracts } from 'wagmi'
export const useBackerTotalAllocation = (backer: Address) => {
const { data, isLoading, error } = useReadContract({
@@ -22,3 +23,23 @@ export const useBackerTotalAllocation = (backer: Address) => {
error,
}
}
+
+export const useGetAllAllocationOf = (backer: Address, gauges: Address[]) => {
+ const { data, isLoading, error } = useReadContracts({
+ contracts: gauges.map(gauge => ({
+ abi: GaugeAbi,
+ address: gauge,
+ functionName: 'allocationOf',
+ args: [backer],
+ })),
+ query: {
+ refetchInterval: AVERAGE_BLOCKTIME,
+ },
+ })
+
+ return {
+ data: data?.map(({ result }) => result as bigint),
+ isLoading,
+ error,
+ }
+}
diff --git a/src/app/collective-rewards/allocations/hooks/useGetVotingPower.ts b/src/app/collective-rewards/allocations/hooks/useGetVotingPower.ts
new file mode 100644
index 00000000..e46d2b88
--- /dev/null
+++ b/src/app/collective-rewards/allocations/hooks/useGetVotingPower.ts
@@ -0,0 +1,24 @@
+import { StRIFTokenAbi } from '@/lib/abis/StRIFTokenAbi'
+import { AVERAGE_BLOCKTIME } from '@/lib/constants'
+import { tokenContracts } from '@/lib/contracts'
+import { useAccount, useReadContract, useReadContracts } from 'wagmi'
+
+export const useGetVotingPower = () => {
+ const { address } = useAccount()
+
+ const { data, isLoading, error } = useReadContract({
+ abi: StRIFTokenAbi,
+ address: tokenContracts.stRIF,
+ functionName: 'balanceOf',
+ args: [address!],
+ query: {
+ refetchInterval: AVERAGE_BLOCKTIME,
+ },
+ })
+
+ return {
+ data,
+ isLoading,
+ error,
+ }
+}
diff --git a/src/app/collective-rewards/allocations/index.ts b/src/app/collective-rewards/allocations/index.ts
new file mode 100644
index 00000000..1db759f2
--- /dev/null
+++ b/src/app/collective-rewards/allocations/index.ts
@@ -0,0 +1,2 @@
+export * from './context'
+export * from './hooks'
diff --git a/src/app/collective-rewards/allocations/page.tsx b/src/app/collective-rewards/allocations/page.tsx
index 6323acda..70d1ec44 100644
--- a/src/app/collective-rewards/allocations/page.tsx
+++ b/src/app/collective-rewards/allocations/page.tsx
@@ -1,77 +1,36 @@
'use client'
+import { Button } from '@/components/Button'
import { MainContainer } from '@/components/MainContainer/MainContainer'
import { Typography } from '@/components/Typography'
-import { AllocationAmount } from './AllocationAmount'
-import { AllocationMetrics } from './AllocationMetrics'
-import { BuilderAllocation } from './BuilderAllocation'
-import { Header } from './Header'
-import { BuilderAllocationProps } from './types'
-import { useState } from 'react'
-
-const ALLOCATION_EXCEED_AMOUNT_ERROR = 'Builder allocations exceeds amount to allocate'
+import { useRouter } from 'next/navigation'
+import { useContext } from 'react'
+import { Builder } from '../types'
+import {
+ AllocationAmount,
+ AllocationMetrics,
+ BuilderAllocation,
+ BuilderAllocationProps,
+ Header,
+} from './components'
+import { AllocationsContext } from './context'
export default function Allocations() {
- /* TODO: Error message is set when
- * - the cumulative amount exceeds the allocation amount (ALLOCATION_EXCEED_AMOUNT_ERROR)
- */
- const [errorMessage, setErrorMessage] = useState('')
- const builders: BuilderAllocationProps[] = [
- {
- builderName: 'Builder 1',
- address: '0x1234567890abcdef1234567890abcdef12345678',
- status: 'Active',
- joiningDate: '2021-10-01',
- allocationLeft: 50000000000000000000n,
- backerRewards: 10,
- currentAllocation: 70,
- },
- {
- builderName: 'Builder 2',
- address: '0x1234567890abcdef1234567890abcdef12345678',
- status: 'Active',
- joiningDate: '2021-10-01',
- allocationLeft: 50000000000000000000n,
- backerRewards: 8,
- currentAllocation: 50,
- },
- {
- builderName: 'Builder 3',
- address: '0x1234567890abcdef1234567890abcdef12345678',
- status: 'Deactivated',
- joiningDate: '2021-10-01',
- allocationLeft: 50000000000000000000n,
- backerRewards: 100,
- currentAllocation: 20,
- },
- {
- builderName: 'Builder 4',
- address: '0x1234567890abcdef1234567890abcdef12345678',
- status: 'Paused',
- joiningDate: '2021-10-01',
- allocationLeft: 50000000000000000000n,
- backerRewards: 99,
- currentAllocation: 100,
- },
- {
- builderName: 'Builder 5',
- address: '0x1234567890abcdef1234567890abcdef12345678',
- status: 'Deactivated',
- joiningDate: '2021-10-01',
- allocationLeft: 50000000000000000000n,
- backerRewards: 99,
- currentAllocation: 100,
- },
- {
- builderName: 'Builder 6',
- address: '0x1234567890abcdef1234567890abcdef12345678',
- status: 'Active',
- joiningDate: '2021-10-01',
- allocationLeft: 50000000000000000000n,
- backerRewards: 99,
- currentAllocation: 100,
- },
- ]
+ const router = useRouter()
+ const {
+ state: { allocations, getBuilder },
+ actions: { resetAllocations },
+ } = useContext(AllocationsContext)
+
+ const saveAllocations = () => {
+ // TODO: save current allocations
+ }
+
+ const cancel = () => {
+ resetAllocations()
+ router.back()
+ }
+
return (
@@ -80,20 +39,45 @@ export default function Allocations() {
Selected Builders
- {builders.map((builder, index) => (
-
- ))}
+ {Object.entries(allocations).map(([key, currentAllocation]) => {
+ const index = Number(key)
+ const builderInfo = getBuilder(index) as Builder
+ const builder: BuilderAllocationProps = {
+ ...builderInfo,
+ index,
+ currentAllocation,
+ }
+ return
+ })}
+
+
+
+ {/* TODO: review disabled statuses */}
+
+
+
+
+
diff --git a/src/app/collective-rewards/allocations/types.ts b/src/app/collective-rewards/allocations/types.ts
deleted file mode 100644
index 9d2242ee..00000000
--- a/src/app/collective-rewards/allocations/types.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Address } from 'viem'
-import { BuilderStatusShown } from '../types'
-
-export type BuilderStatus = BuilderStatusShown | 'Paused' | 'Deactivated'
-
-export type BuilderAllocationProps = {
- builderName: string
- address: Address
- status: BuilderStatus
- joiningDate: string
- allocationLeft: BigInt
- // TODO: what's the value we expect here? (e.g. 8% or 8.123456%)
- backerRewards: number
- currentAllocation: number
-}
diff --git a/src/app/collective-rewards/context/PaginatedDataContext.tsx b/src/app/collective-rewards/context/PaginatedDataContext.tsx
new file mode 100644
index 00000000..0ba1ca86
--- /dev/null
+++ b/src/app/collective-rewards/context/PaginatedDataContext.tsx
@@ -0,0 +1,91 @@
+import { Context, createContext, FC, useContext, useEffect, useMemo, useState } from 'react'
+
+type PaginationConfig = {
+ data: T[]
+ pageSize: number
+ currentPage: number
+}
+
+type State = PaginationConfig & {
+ pageCount: number
+}
+
+type Actions = {
+ getDataIndex: (index: number) => number
+ getPage: (page?: number) => T[]
+ updateData: (data: T[]) => void
+ updatePageSize: (pageSize: number) => void
+ updateCurrentPage: (currentPage: number) => void
+}
+
+type PageContext = State & Actions
+
+export const PaginatedDataContext = createContext>({
+ data: [],
+ pageSize: 0,
+ currentPage: 0,
+ pageCount: 0,
+ getDataIndex: () => 0,
+ getPage: () => [],
+ updateData: () => {},
+ updatePageSize: () => {},
+ updateCurrentPage: () => {},
+})
+
+export const usePaginatedDataContext = () => {
+ const context = useContext>(PaginatedDataContext)
+ if (!context) {
+ throw new Error('usePaginatedDataContext must be used within a PaginatedDataContextProvider')
+ }
+ return context
+}
+
+type PaginatedDataContextProviderProps = {
+ children: React.ReactNode
+ config: PaginationConfig
+}
+
+export const PaginatedDataContextProvider: FC = ({ children, config }) => {
+ const [data, setData] = useState(config.data)
+ const [pageSize, setPageSize] = useState(config.pageSize)
+ const [currentPage, setCurrentPage] = useState(config.currentPage)
+
+ const state = {
+ data,
+ pageSize,
+ currentPage,
+ pageCount: useMemo(() => Math.ceil(data.length / pageSize), [data, pageSize]),
+ }
+ const actions = {
+ getDataIndex: (pagedIndex: number) => pagedIndex + pageSize * currentPage,
+ getPage: (page?: number) => {
+ const pageIndex = page ?? currentPage
+ const start = pageIndex * pageSize
+ const end = start + pageSize
+
+ return data.slice(start, end)
+ },
+ updateData: (newData: typeof data) => setData(newData),
+ updatePageSize: (newPageSize: number) => setPageSize(newPageSize),
+ updateCurrentPage: (newCurrentPage: number) => setCurrentPage(newCurrentPage),
+ }
+
+ return (
+ {children}
+ )
+}
+
+export const withPagination = (Component: FC) => {
+ const PaginatedComponent: FC<
+ PaginationConfig & {
+ props: TProps
+ }
+ > = ({ props, ...rest }) => {
+ return (
+
+
+
+ )
+ }
+ return PaginatedComponent
+}
diff --git a/src/app/collective-rewards/leaderboard/BuildersLeaderBoardTable.tsx b/src/app/collective-rewards/leaderboard/BuildersLeaderBoardTable.tsx
index 79934c85..da9dd9f9 100644
--- a/src/app/collective-rewards/leaderboard/BuildersLeaderBoardTable.tsx
+++ b/src/app/collective-rewards/leaderboard/BuildersLeaderBoardTable.tsx
@@ -21,6 +21,7 @@ import {
} from '@/app/collective-rewards/shared'
import { getAddress } from 'viem'
import { tokenContracts } from '@/lib/contracts'
+import { useRouter } from 'next/navigation'
enum RewardsColumnKeyEnum {
builder = 'builder',
@@ -174,9 +175,10 @@ const BuildersLeaderBoardTable: FC = ({ tokens, c
}
export const BuildersLeaderBoard = () => {
+ const router = useRouter()
+
const onManageAllocations = () => {
- // TODO: fill the allocation context if necessary and change the route
- console.log('Manage allocations')
+ router.push('collective-rewards/allocations')
}
// TODO: check where to store this information
diff --git a/src/app/collective-rewards/page.tsx b/src/app/collective-rewards/page.tsx
index eccdbd8f..a50083ad 100644
--- a/src/app/collective-rewards/page.tsx
+++ b/src/app/collective-rewards/page.tsx
@@ -2,7 +2,7 @@
import { BuildersLeaderBoard } from '@/app/collective-rewards/leaderboard'
import { Metrics } from '@/app/collective-rewards/metrics'
-import { WhitelistContextProviderWithBuilders, WhitelistSection } from '@/app/collective-rewards/whitelist'
+import { WhitelistContextProvider, WhitelistSection } from '@/app/collective-rewards/whitelist'
import { MainContainer } from '@/components/MainContainer/MainContainer'
export default function BuildersIncentiveMarket() {
@@ -10,9 +10,9 @@ export default function BuildersIncentiveMarket() {
-
+
-
+
diff --git a/src/app/collective-rewards/rewards/MyRewards.tsx b/src/app/collective-rewards/rewards/MyRewards.tsx
index 8b1b52de..5a54cd69 100644
--- a/src/app/collective-rewards/rewards/MyRewards.tsx
+++ b/src/app/collective-rewards/rewards/MyRewards.tsx
@@ -48,7 +48,7 @@ export const Rewards: FC<{ builder: Address }> = ({ builder }) => {
{
- console.error('Not implemented')
+ router.push('/collective-rewards/allocations')
}}
title="Backer Rewards"
subtext="Monitor your rewards balances and claim."
diff --git a/src/app/collective-rewards/shared/components/Pagination.tsx b/src/app/collective-rewards/shared/components/Pagination.tsx
new file mode 100644
index 00000000..7684ed9d
--- /dev/null
+++ b/src/app/collective-rewards/shared/components/Pagination.tsx
@@ -0,0 +1,33 @@
+import { FC, useContext } from 'react'
+import { PaginatedDataContext } from '@/app/collective-rewards/context/PaginatedDataContext'
+import { PaginationButton } from './PaginationButton'
+import { ChevronLeft, ChevronRight } from 'lucide-react'
+
+export const Pagination: FC = () => {
+ const { currentPage, pageCount, updateCurrentPage } = useContext(PaginatedDataContext)
+
+ return (
+
+
}
+ onClick={() => updateCurrentPage(currentPage - 1)}
+ disabled={currentPage === 0}
+ />
+ {Array(pageCount)
+ .fill(1)
+ .map((_, pageNumber) => (
+
updateCurrentPage(pageNumber)}
+ text={pageNumber + 1}
+ isActive={pageNumber === currentPage}
+ />
+ ))}
+ }
+ onClick={() => updateCurrentPage(currentPage + 1)}
+ disabled={currentPage === pageCount - 1}
+ />
+
+ )
+}
diff --git a/src/app/collective-rewards/shared/components/PaginationButton.tsx b/src/app/collective-rewards/shared/components/PaginationButton.tsx
new file mode 100644
index 00000000..2be3f259
--- /dev/null
+++ b/src/app/collective-rewards/shared/components/PaginationButton.tsx
@@ -0,0 +1,13 @@
+import { Button } from '@/components/Button'
+import { FC, ReactNode } from 'react'
+
+export const PaginationButton: FC<{
+ text: ReactNode
+ onClick: () => void
+ disabled?: boolean
+ isActive?: boolean
+}> = ({ text, onClick, disabled, isActive }) => (
+
+)
diff --git a/src/app/collective-rewards/shared/components/index.ts b/src/app/collective-rewards/shared/components/index.ts
index a86149cb..6a2dbd3a 100644
--- a/src/app/collective-rewards/shared/components/index.ts
+++ b/src/app/collective-rewards/shared/components/index.ts
@@ -1 +1,3 @@
+export * from './Pagination'
+export * from './PaginationButton'
export * from './Table'
diff --git a/src/app/collective-rewards/types.ts b/src/app/collective-rewards/types.ts
index 24d879cd..7580dff5 100644
--- a/src/app/collective-rewards/types.ts
+++ b/src/app/collective-rewards/types.ts
@@ -3,10 +3,14 @@ import { ProposalState } from '@/shared/types'
import { Address } from 'viem'
export const BuilderStatusActive = 'Active'
+export const BuilderStatusPaused = 'Paused'
+export const BuilderStatusDeactivated = 'Deactivated'
export const BuilderStatusInProgress = 'In progress'
export const BuilderStatusProposalCreatedMVP = 'In progress - mvp'
export const builderStatusOptions = [
BuilderStatusActive,
+ BuilderStatusPaused,
+ BuilderStatusDeactivated,
BuilderStatusInProgress,
BuilderStatusProposalCreatedMVP,
] as const
@@ -21,4 +25,14 @@ export type BuilderInfo = {
gauge: Address
}
+// TODO: refactor BuilderInfo & BuilderProposal
+export type Builder = {
+ address: Address
+ status: BuilderStatus
+ gauge: Address
+ kickback: number
+ builderName: string
+ joiningDate: string
+}
+
export type ProposalsToState = Record
diff --git a/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.tsx b/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.tsx
index 5647a610..29693082 100644
--- a/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.tsx
+++ b/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.tsx
@@ -1,4 +1,3 @@
-import { BuilderStatus } from '@/app/collective-rewards/allocations/types'
import { BuilderInfo, BuilderStatusShown } from '@/app/collective-rewards/types'
import { BuilderContextProvider, useBuilderContext } from '@/app/collective-rewards/user'
import { useAlertContext } from '@/app/providers/AlertProvider'
@@ -25,7 +24,10 @@ const BuilderRegistrationButton = () => {
)
}
-export const crStatusColorClasses: Record['className']> = {
+export const crStatusColorClasses: Record<
+ BuilderStatusShown,
+ HtmlHTMLAttributes['className']
+> = {
Active: 'bg-[#DBFEE5] text-secondary',
'In progress': 'bg-[#4B5CF0] color-text-primary',
Paused: 'bg-[#F9E1FF] text-secondary',
@@ -45,6 +47,8 @@ const StatusBadge: FC = ({ builderStatus }) => {
return {
'In progress': InProgressComponent,
Active: WhitelistedComponent,
+ Paused: InProgressComponent, // TODO: Change to PausedComponent
+ Deactivated: InProgressComponent, // TODO: Change to DeactivatedComponent
undefined: BuilderRegistrationButton,
}[builderStatus as BuilderStatusShown]
}
@@ -79,9 +83,5 @@ export const BecomeABuilderHandler = ({ address }: { address: Address }) => {
}
export const BecomeABuilderButton = ({ address }: { address: Address }) => {
- return (
-
-
-
- )
+ return
}
diff --git a/src/app/collective-rewards/user/context/BuilderContext.tsx b/src/app/collective-rewards/user/context/BuilderContext.tsx
index b1ae7b35..cd97795d 100644
--- a/src/app/collective-rewards/user/context/BuilderContext.tsx
+++ b/src/app/collective-rewards/user/context/BuilderContext.tsx
@@ -1,6 +1,7 @@
import { createContext, FC, ReactNode, useContext, useMemo } from 'react'
import { Address } from 'viem'
import {
+ BuilderInfo,
BuilderStatus,
BuilderStatusProposalCreatedMVP,
BuilderStatusShown,
@@ -12,6 +13,7 @@ import { DateTime } from 'luxon'
import { splitCombinedName } from '@/app/proposals/shared/utils'
import { withPricesContextProvider } from '@/shared/context/PricesContext'
+// TODO: rename BuilderProposal and perhaps rewrite the type to Modify
export type BuilderProposal = {
builderName: string
status: BuilderStatusShown
@@ -57,7 +59,7 @@ export const BuilderContextProvider: FC = ({ children }) =
error: proposalsStateMapError,
} = useGetProposalsState(buildersProposals)
- const filteredBuilders = useMemo(() => {
+ const builderWithProposal = useMemo(() => {
return builders.reduce((acc, builder) => {
const { status, address, gauge } = builder
const proposal = getMostAdvancedProposal(builder, proposalsStateMap)
@@ -90,11 +92,11 @@ export const BuilderContextProvider: FC = ({ children }) =
const error = buildersError ?? proposalsStateMapError
const getBuilderByAddress = (address: Address): BuilderProposal | undefined => {
- return filteredBuilders[address]
+ return builderWithProposal[address]
}
const valueOfContext: BuilderContextValue = {
- data: Object.values(filteredBuilders),
+ data: Object.values(builderWithProposal),
isLoading,
error,
getBuilderByAddress,
diff --git a/src/app/collective-rewards/user/hooks/useGetBuilders.ts b/src/app/collective-rewards/user/hooks/useGetBuilders.ts
index a34ed37f..bc25dce9 100644
--- a/src/app/collective-rewards/user/hooks/useGetBuilders.ts
+++ b/src/app/collective-rewards/user/hooks/useGetBuilders.ts
@@ -5,6 +5,8 @@ import {
BuilderStatusInProgress,
BuilderStatusProposalCreatedMVP,
} from '@/app/collective-rewards/types'
+import { useGetGaugesArray } from '@/app/collective-rewards/user/hooks/useGetGaugesArray'
+import { BuilderStateStruct } from '@/app/collective-rewards/utils/getBuilderGauge'
import { useFetchCreateBuilderProposals } from '@/app/proposals/hooks/useFetchLatestProposals'
import { BuilderRegistryAbi } from '@/lib/abis/v2/BuilderRegistryAbi'
import { AVERAGE_BLOCKTIME } from '@/lib/constants'
@@ -12,8 +14,6 @@ import { BackersManagerAddress } from '@/lib/contracts'
import { useMemo } from 'react'
import { Address, getAddress } from 'viem'
import { useReadContracts } from 'wagmi'
-import { useGetGaugesArray } from '@/app/collective-rewards/user/hooks/useGetGaugesArray'
-import { BuilderStateStruct } from '@/app/collective-rewards/utils'
export type BuilderLoader = {
data?: BuilderInfo
@@ -50,7 +50,6 @@ const getCombinedBuilderStatus = (builderState: BuilderStateStruct): BuilderStat
// Default case: used to filter out builders
return EXCLUDED_BUILDER_STATUS
}
-
export const useGetBuilders = (): BuildersLoader => {
/*
* get Gauges
@@ -126,6 +125,7 @@ export const useGetBuilders = (): BuildersLoader => {
} = useFetchCreateBuilderProposals()
const data = useMemo(() => {
+ // we need to add the kickback and the joining date
return Object.entries(buildersProposalsMap ?? {}).map(([builder, proposals]) => ({
address: getAddress(builder),
status:
diff --git a/src/app/collective-rewards/whitelist/context/WhitelistContext.tsx b/src/app/collective-rewards/whitelist/context/WhitelistContext.tsx
index 0441f7bb..cee83b67 100644
--- a/src/app/collective-rewards/whitelist/context/WhitelistContext.tsx
+++ b/src/app/collective-rewards/whitelist/context/WhitelistContext.tsx
@@ -38,7 +38,7 @@ interface WhitelistProviderProps {
children: ReactNode
}
-const WhitelistContextProvider: FC = ({ children }) => {
+export const WhitelistContextProvider: FC = ({ children }) => {
const [search, setSearch] = useState('')
const [filterBy, setFilterBy] = useState(initialFilterByValue)
const { data, isLoading, error } = useGetFilteredBuilders({ builderName: search, status: filterBy })
diff --git a/src/app/collective-rewards/whitelist/hooks/useGetFilteredBuilders.ts b/src/app/collective-rewards/whitelist/hooks/useGetFilteredBuilders.ts
index f70b0ffe..5d68f82f 100644
--- a/src/app/collective-rewards/whitelist/hooks/useGetFilteredBuilders.ts
+++ b/src/app/collective-rewards/whitelist/hooks/useGetFilteredBuilders.ts
@@ -3,14 +3,14 @@ import { BuilderStatusFilter } from '@/app/collective-rewards/whitelist'
import { BuilderProposal, useBuilderContext } from '@/app/collective-rewards/user'
type FetchWhitelistedBuildersFilter = {
- builderName: string
+ builderName?: string
status: BuilderStatusFilter
}
const lowerCaseCompare = (a: string, b: string) => a?.toLowerCase().includes(b?.toLowerCase())
export const useGetFilteredBuilders = ({
- builderName: filterBuilderName,
+ builderName: filterBuilderName = '',
status: filterStatus,
}: FetchWhitelistedBuildersFilter) => {
const [data, setData] = useState([])
diff --git a/src/app/providers/ContextProviders.tsx b/src/app/providers/ContextProviders.tsx
index 17519f95..b758801e 100644
--- a/src/app/providers/ContextProviders.tsx
+++ b/src/app/providers/ContextProviders.tsx
@@ -5,6 +5,8 @@ import { ReactNode } from 'react'
import { WagmiProvider } from 'wagmi'
import { AlertProvider } from './AlertProvider'
import ErrorBoundary from '@/components/ErrorPage/ErrorBoundary'
+import { AllocationsContextProvider } from '../collective-rewards/allocations'
+import { BuilderContextProviderWithPrices } from '../collective-rewards/user'
interface Props {
children: ReactNode
@@ -16,7 +18,11 @@ export const ContextProviders = ({ children }: Props) => {
- {children}
+
+
+ {children}
+
+
diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx
index e04f3fa9..c4babe3c 100644
--- a/src/components/Slider/Slider.tsx
+++ b/src/components/Slider/Slider.tsx
@@ -7,21 +7,12 @@ export const Slider = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>((props, forwardedRef) => {
- const initialValue = props.value || props.defaultValue
- const [value, setValue] = React.useState(initialValue)
-
- const onPercentageClick = (percentage: number) => {
- setValue([percentage])
- console.log('percentage', percentage)
- }
-
return (
<>
setValue(nextValue)}
>
@@ -32,14 +23,13 @@ export const Slider = React.forwardRef<
onPercentageClick(v)}
>
{v}%
))}
- {value?.map((_, i) => (
+ {props.value?.map((_, i) => (