Skip to content

Commit

Permalink
✨Implemented change fee reciever in edit modal
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPoblete committed Nov 18, 2024
1 parent 677b6a0 commit 5515b72
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 38 deletions.
2 changes: 1 addition & 1 deletion apps/dapp/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function RootLayout({
children: ReactNode,
}) {
return (
<html lang='en' className="dark" style={{ colorScheme: "dark" }}>
<html lang='en' className="dark" style={{ colorScheme: "dark" }} suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
Expand Down
10 changes: 4 additions & 6 deletions apps/dapp/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { Container } from '@chakra-ui/react'
import { Container, HStack } from '@chakra-ui/react'
import { useSorobanReact } from '@soroban-react/core'
import ManageVaults from '@/components/ManageVaults/ManageVaults';
import {
Expand All @@ -13,10 +13,8 @@ ChartJS.register(ArcElement);
export default function Home() {
const { address } = useSorobanReact()
return (
<Container mt={16} mx={0} px={0} minW={'100vw'}>
<Container centerContent textAlign={'center'} minW={'100vw'}>
<ManageVaults />
</Container>
</Container>
<HStack w={'full'} alignContent={'center'} justifyContent={'center'}>
<ManageVaults />
</HStack>
);
}
46 changes: 24 additions & 22 deletions apps/dapp/src/components/DeployVault/VaultPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,29 @@ interface VaultPreviewProps {
setFormControl: (args: FormControlInterface) => any;

}

export const dropdownData = {
strategies: {
title: 'Strategies',
description: 'A strategy is a set of steps to be followed to execute an investment in one or several protocols.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/01-introduction#core-concepts'
},
manager: {
title: 'Manager',
description: 'The Manager can rebalance the Vault, emergency withdraw and invest IDLE funds in strategies.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract#management'
},
emergencyManager: {
title: 'Emergency manager',
description: 'The Emergency Manager has the authority to withdraw assets from the DeFindex in case of an emergency.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract#emergency-management'
},
feeReceiver: {
title: 'Fee receiver',
description: ' Fee Receiver could be the manager using the same address, or it could be a different entity such as a streaming contract, a DAO, or another party.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract#fee-collection'
}
}
export const VaultPreview: React.FC<VaultPreviewProps> = ({ data, accordionValue, setAccordionValue, formControl, setFormControl }) => {

const dispatch = useAppDispatch()
Expand Down Expand Up @@ -248,28 +271,7 @@ export const VaultPreview: React.FC<VaultPreviewProps> = ({ data, accordionValue
dispatch(setVaultShare(input * 100))
}

const dropdownData = {
strategies: {
title: 'Strategies',
description: 'A strategy is a set of steps to be followed to execute an investment in one or several protocols.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/01-introduction#core-concepts'
},
manager: {
title: 'Manager',
description: 'The Manager can rebalance the Vault, emergency withdraw and invest IDLE funds in strategies.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract#management'
},
emergencyManager: {
title: 'Emergency manager',
description: 'The Emergency Manager has the authority to withdraw assets from the DeFindex in case of an emergency.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract#emergency-management'
},
feeReceiver: {
title: 'Fee receiver',
description: ' Fee Receiver could be the manager using the same address, or it could be a different entity such as a streaming contract, a DAO, or another party.',
href: 'https://docs.defindex.io/whitepaper/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract#fee-collection'
}
}

return (
<>
<AccordionRoot value={accordionValue} onValueChange={(e: any) => setAccordionValue(e.value)}>
Expand Down
240 changes: 240 additions & 0 deletions apps/dapp/src/components/InteractWithVault/EditVault.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import React, { useContext, useEffect, useState } from 'react'
import { Address, xdr } from '@stellar/stellar-sdk'
import { useSorobanReact } from '@soroban-react/core'

import { useAppDispatch, useAppSelector } from '@/store/lib/storeHooks'
import { setVaultFeeReceiver } from '@/store/lib/features/walletStore'
import { setFeeReceiver } from '@/store/lib/features/vaultStore'

import { ModalContext } from '@/contexts'
import { VaultMethod, useVaultCallback, useVault } from '@/hooks/useVault'
import { isValidAddress } from '@/helpers/address'

import { DialogBody, DialogContent, DialogHeader } from '../ui/dialog'
import { InputGroup } from '../ui/input-group'
import {
Input,
Text,
Stack,
HStack,
Fieldset,
Link,
IconButton,
} from '@chakra-ui/react'
import { InfoTip } from '../ui/toggle-tip'
import { Tooltip } from '../ui/tooltip'
import { FaRegPaste } from 'react-icons/fa6'
import { dropdownData } from '../DeployVault/VaultPreview'
import { Button } from '../ui/button'

const CustomInputField = ({
label,
value,
onChange,
handleClick,
placeholder,
invalid,
description,
href,
}: {
label: string,
value: string,
onChange: (e: any) => void,
handleClick: (address: string) => void,
placeholder: string,
invalid: boolean,
description?: string,
href?: string,
}) => {
const { address } = useSorobanReact()
return (
<Fieldset.Root invalid={invalid}>
<Fieldset.Legend>{label}
<InfoTip content={
<>
<Text fontSize={'xs'} maxW={'25vw'}>{description}</Text>
<Link
href={href ?? ''}
colorPalette={'blue'}
target='_blank'
>
Learn more.
</Link>

</>
} />
</Fieldset.Legend>
<Stack>
<InputGroup endElement={
<Tooltip content='Use connected wallet'>
<IconButton
css={{ "--bg": "{colors.red.400/40}" }}
aria-label='Connected address'
size={'sm'}
onClick={() => handleClick(address!)}
>
<FaRegPaste />
</IconButton>
</Tooltip>
}
>
<Input
value={value}
onChange={onChange}
placeholder={placeholder}
/>
</InputGroup>
</Stack>
<Fieldset.ErrorText>A valid Stellar / Soroban address is required.</Fieldset.ErrorText>
</Fieldset.Root>
)
}

export const EditVaultModal = () => {
const selectedVault = useAppSelector(state => state.wallet.vaults.selectedVault)
const vaultMethod = selectedVault?.method

const { address } = useSorobanReact();
const vaultCB = useVaultCallback()
const vault = useVault()
const dispatch = useAppDispatch()
const { transactionStatusModal: statusModal, interactWithVaultModal: interactModal, inspectVaultModal: inspectModal } = useContext(ModalContext)
const [formControl, setFormControl] = useState({
feeReceiver: {
value: selectedVault?.feeReceiver ?? '',
isValid: false,
needsUpdate: false,
isLoading: false,
}
})




const handleFeeReceiverChange = (input: string) => {
const isValid = isValidAddress(input)
while (!isValid) {
setFormControl({
...formControl,
feeReceiver: {
...formControl.feeReceiver,
value: input,
isValid: false,
}
})
dispatch(setFeeReceiver(''))
return
}
setFormControl({
...formControl,
feeReceiver: {
...formControl.feeReceiver,
value: input,
isValid: true,
}
})
};

useEffect(() => {
setFormControl({
...formControl,
feeReceiver: {
isValid: isValidAddress(selectedVault?.feeReceiver ?? ''),
value: selectedVault?.feeReceiver ?? '',
needsUpdate: false,
isLoading: false,
}
})
}, [selectedVault])
enum Values {
FEERECIEVER = 'feeReceiver',
MANAGER = 'manager',
EMERGENCYMANAGER = 'emergencyManager'
}

const updateValue = async (value: Values) => {
if (!address || !selectedVault) return;
let result: any;
if (value === Values.FEERECIEVER) {
setFormControl({ feeReceiver: { ...formControl.feeReceiver, isLoading: true } })
statusModal.initModal()
console.log('Updating fee receiver')
const caller = new Address(address);
const feeReceiver = new Address(formControl.feeReceiver.value);
const createDefindexParams: xdr.ScVal[] = [
caller.toScVal(),
feeReceiver.toScVal(),
];
try {
result = await vaultCB(VaultMethod.SETFEERECIEVER, selectedVault.address, createDefindexParams, true).then((res) => {
console.log(res)
statusModal.handleSuccess(res.txHash)
dispatch(setVaultFeeReceiver(formControl.feeReceiver.value))
})
} catch (error: any) {
console.error('Error:', error)
statusModal.handleError(error.toString())
} finally {
setFormControl({ feeReceiver: { ...formControl.feeReceiver, isLoading: false } })
}

};
}

useEffect(() => {
if (!selectedVault?.feeReceiver) return
if (formControl.feeReceiver.value !== selectedVault.feeReceiver && formControl.feeReceiver.isValid) {
setFormControl({
...formControl,
feeReceiver: {
...formControl.feeReceiver,
needsUpdate: true,
}
})
} else if (formControl.feeReceiver.value === selectedVault.feeReceiver && formControl.feeReceiver.isValid) {
setFormControl({
...formControl,
feeReceiver: {
...formControl.feeReceiver,
needsUpdate: false,
}
})
}
}, [formControl.feeReceiver.value, formControl.feeReceiver.isValid])

if (!selectedVault) return null
return (
<>
<DialogContent zIndex={'docked'}>
<DialogHeader>
<Text fontSize='xl'>Manage {selectedVault.name}</Text>
</DialogHeader>
<DialogBody zIndex={'docked'}>
<HStack align={'baseline'}>
<CustomInputField
label={dropdownData.feeReceiver.title}
value={formControl.feeReceiver.value}
href={dropdownData.feeReceiver.href}
onChange={(e) => handleFeeReceiverChange(e.target.value)}
handleClick={(address) => setFormControl({ feeReceiver: { ...formControl.feeReceiver, isValid: true, value: address } })}
placeholder='GAFS3TLVM...'
invalid={!formControl.feeReceiver.isValid}
description={dropdownData.feeReceiver.description}
/>
</HStack>
<HStack justifyContent={'end'} mt={4}>
{formControl.feeReceiver.needsUpdate &&
<Button
loading={formControl.feeReceiver.isLoading}
onClick={() => updateValue(Values.FEERECIEVER)}
disabled={!formControl.feeReceiver.isValid}
>
Update fee receiver
</Button>
}
</HStack>
</DialogBody>
</DialogContent>
</>
)
}
13 changes: 8 additions & 5 deletions apps/dapp/src/components/ManageVaults/InspectVault.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Button, Grid, GridItem, HStack, Icon, Stack, Text } from "@chakra-ui/re
import { DialogBody, DialogContent, DialogFooter, DialogHeader } from "../ui/dialog"
import { FaRegEdit } from "react-icons/fa"
import { IoClose } from "react-icons/io5"
import { ModalContext } from "@/contexts"
import { useContext } from "react"


export const InspectVault = ({
Expand All @@ -24,6 +26,7 @@ export const InspectVault = ({
}) => {
const selectedVault: VaultData | undefined = useAppSelector(state => state.wallet.vaults.selectedVault)
const { address } = useSorobanReact()
const { editVaultModal: editModal } = useContext(ModalContext)
if (!selectedVault) return null

return (
Expand All @@ -34,11 +37,11 @@ export const InspectVault = ({
<h2>Inspect {selectedVault?.name ? selectedVault.name : shortenAddress(selectedVault.address)}</h2>
</GridItem>
{address === selectedVault.manager &&
<GridItem colSpan={1}>
<Icon onClick={() => { handleOpenDeployVault('edit_vault', true, selectedVault) }} css={{ cursor: "pointer" }}>
<FaRegEdit />
</Icon>
</GridItem>
<GridItem colSpan={1}>
<Icon onClick={() => { editModal.setIsOpen(true) }} css={{ cursor: "pointer" }}>
<FaRegEdit />
</Icon>
</GridItem>
}
<GridItem colSpan={1}>
<Icon onClick={onClose} css={{ cursor: "pointer" }}>
Expand Down
18 changes: 17 additions & 1 deletion apps/dapp/src/components/ManageVaults/ManageVaults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ import {
Input,
Stack,
} from "@chakra-ui/react"
import { EditVaultModal } from "../InteractWithVault/EditVault"

export const ManageVaults = () => {
const { address, activeChain } = useSorobanReact()
const { inspectVaultModal: inspectModal, deployVaultModal: deployModal, interactWithVaultModal: interactModal, transactionStatusModal: txModal } = useContext(ModalContext)
const {
inspectVaultModal: inspectModal,
deployVaultModal: deployModal,
interactWithVaultModal: interactModal,
transactionStatusModal: txModal,
editVaultModal: editModal
} = useContext(ModalContext)
const dispatch = useAppDispatch()
const modalContext = useContext(ModalContext)
const vaults: VaultData[] = useAppSelector(state => state.wallet.vaults.createdVaults)
Expand Down Expand Up @@ -155,6 +162,15 @@ export const ManageVaults = () => {
onClose={() => { inspectModal.setIsOpen(false) }}
/>
</DialogRoot>
<DialogRoot
open={editModal.isOpen}
onOpenChange={(e) => { editModal.setIsOpen(e.open) }}
size={'lg'}
placement={'center'}
>
<DialogBackdrop backdropFilter='blur(1px)' />
<EditVaultModal />
</DialogRoot>
<DialogRoot
open={modalContext.transactionStatusModal.isOpen}
onOpenChange={(e) => { txModal.setIsOpen(e.open) }}
Expand Down
Loading

0 comments on commit 5515b72

Please sign in to comment.