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

WIP: Feat: Offchain DAO creation #957

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6200ab6
Create votingType selector
selankon Jul 20, 2023
b794a88
Create ProposalCreation component
selankon Jul 20, 2023
bf8db4f
Add missing CreateDaoFormData property
selankon Jul 20, 2023
3323272
Implement add executive commite wallets
selankon Jul 21, 2023
0b8ffb2
Implement committee minimum approval
selankon Jul 25, 2023
cec4f20
Implement execution expiration time
selankon Jul 26, 2023
c8cb8cc
Implement defineCommitteeIsValid
selankon Jul 26, 2023
c937ed0
Add confirm dao creation step
selankon Jul 27, 2023
05be15d
Use `hideWizard` for votingType offChain
selankon Jul 28, 2023
4b8e6ce
Fix validation when adding committee members
selankon Jul 28, 2023
9881234
Add committee section to goLive
selankon Aug 1, 2023
eb6b5e8
Create vocdoni census3
selankon Aug 7, 2023
6643076
Implement dao creation only for token holders
selankon Aug 10, 2023
d7a5bf0
Update rebased components
selankon Aug 29, 2023
5270429
Create committee address modal
selankon Aug 29, 2023
c10bcd9
Implement committee review view
selankon Aug 29, 2023
33f4156
Enable code
selankon Sep 1, 2023
273f7b9
Disable early execution and vote change for offChain voting
selankon Sep 4, 2023
12b1708
Fix validation errors
selankon Sep 4, 2023
9ed6dd0
Check census3 supported chains
selankon Sep 15, 2023
c543643
Install offchain plugin
selankon Sep 15, 2023
7835288
WIP disable committee wallet validation
selankon Sep 19, 2023
aaf3568
Implement census3 create token
selankon Sep 20, 2023
0f57ec3
Fix plugin address
selankon Sep 20, 2023
4f3d4e5
Disable commite member must be holder
selankon Sep 21, 2023
264140e
Fix types
selankon Sep 27, 2023
2ea8af3
Add timeout
selankon Oct 16, 2023
c0459b0
Fix new types on min sdk
selankon Oct 16, 2023
26c7ced
Fix if consistency
selankon Oct 16, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ subgraph.yaml

tests/e2e/videos/*
test.http
.idea
50 changes: 50 additions & 0 deletions src/components/addCommitteeMembers/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import styled from 'styled-components';
import {Label} from '@aragon/ods';
import {useTranslation} from 'react-i18next';

type WalletsFooterProps = {
totalAddresses: number;
};

const AddWalletsFooter: React.FC<WalletsFooterProps> = ({totalAddresses}) => {
const {t} = useTranslation();

return (
<Container>
<FooterItem1>
<Label label={t('labels.summary')} />
</FooterItem1>
<FooterRow>
<FooterItem1>
<StyledLabel>{t('labels.whitelistWallets.totalWallets')}</StyledLabel>
</FooterItem1>
<FooterItem2>
<StyledLabel>{totalAddresses}</StyledLabel>
</FooterItem2>
</FooterRow>
</Container>
);
};

export default AddWalletsFooter;

export const Container = styled.div.attrs({
className: 'hidden tablet:flex tablet:flex-col p-2 bg-ui-0',
})``;

const FooterRow = styled.div.attrs({
className: 'flex',
})``;

export const FooterItem1 = styled.div.attrs({
className: 'flex-1',
})``;

export const FooterItem2 = styled.div.attrs({
className: 'w-8 text-right',
})``;

const StyledLabel = styled.p.attrs({
className: 'text-ui-800',
})``;
26 changes: 26 additions & 0 deletions src/components/addCommitteeMembers/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import styled from 'styled-components';
import {Label} from '@aragon/ods';
import {useTranslation} from 'react-i18next';

const AddWalletsHeader: React.FC = () => {
const {t} = useTranslation();

return (
<Container>
<HeaderItem>
<Label label={t('labels.whitelistWallets.address')} />
</HeaderItem>
</Container>
);
};

export default AddWalletsHeader;

export const Container = styled.div.attrs({
className: 'hidden tablet:flex p-2 space-x-2 bg-ui-0',
})``;

export const HeaderItem = styled.div.attrs({
className: 'flex-1',
})``;
126 changes: 126 additions & 0 deletions src/components/addCommitteeMembers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
AlertInline,
ButtonIcon,
ButtonText,
Dropdown,
IconMenuVertical,
ListItemAction,
} from '@aragon/ods';
import React, {useEffect} from 'react';
import {useFieldArray, useFormContext, useWatch} from 'react-hook-form';
import {useTranslation} from 'react-i18next';
import styled from 'styled-components';

import {useAlertContext} from 'context/alert';
import {useWallet} from 'hooks/useWallet';
import Footer from './footer';
import Header from './header';
import Row from './row';

const AddCommitteeMembers: React.FC = () => {
const {t} = useTranslation();
const {address, ensName} = useWallet();

const {control, trigger} = useFormContext();
const wallets = useWatch({name: 'committee', control: control});
const {fields, append, remove} = useFieldArray({
name: 'committee',
control,
});
const {alert} = useAlertContext();

const controlledFields = fields.map((field, index) => {
return {
...field,
...(wallets && {...wallets[index]}),
};
});

useEffect(() => {
if (address && !wallets) {
// uncomment when minting to treasury is ready
// insert(1, {address: address, amount: '0'});
append({address, ensName, amount: 1});
}
}, [address, append, ensName, wallets]);

// setTimeout added because instant trigger not working
const handleAddWallet = () => {
append({address: '', ensName: '', amount: 1});
setTimeout(() => {
trigger(`committee.${controlledFields.length}`);
}, 50);
};
selankon marked this conversation as resolved.
Show resolved Hide resolved

const handleDeleteRow = (index: number) => {
remove(index);
setTimeout(() => {
trigger('committee');
});
};

return (
<Container>
<ListGroup>
{controlledFields.length > 0 && <Header />}
{controlledFields.map((field, index) => {
return (
<Row key={field.id} index={index} onDelete={handleDeleteRow} />
);
})}
<Footer totalAddresses={fields.length || 0} />
</ListGroup>
<ActionsWrapper>
<ButtonText
label={t('labels.addWallet')}
mode="secondary"
size="large"
onClick={handleAddWallet}
/>
<Dropdown
align="start"
trigger={
<ButtonIcon
mode="ghost"
size="large"
bgWhite
icon={<IconMenuVertical />}
data-testid="trigger"
/>
}
sideOffset={8}
listItems={[
{
component: (
<ListItemAction
title={t('labels.deleteAllAddresses')}
bgWhite
/>
),
callback: () => {
remove();
alert(t('alert.chip.removedAllAddresses'));
},
},
]}
/>
</ActionsWrapper>
<AlertInline
label={t('alert.committee.automaticallyAdded') as string}
mode="neutral"
/>
</Container>
);
};

export default AddCommitteeMembers;

const Container = styled.div.attrs({className: 'space-y-1.5'})``;

const ListGroup = styled.div.attrs({
className: 'flex flex-col overflow-hidden space-y-0.25 rounded-xl',
})``;

const ActionsWrapper = styled.div.attrs({
className: 'flex justify-between',
})``;
169 changes: 169 additions & 0 deletions src/components/addCommitteeMembers/row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
ButtonIcon,
Dropdown,
IconMenuVertical,
Label,
ListItemAction,
InputValue as WalletInputValue,
} from '@aragon/ods';
import React, {useCallback} from 'react';
import {Controller, useFormContext, useWatch} from 'react-hook-form';
import {useTranslation} from 'react-i18next';
import styled from 'styled-components';

import {WrappedWalletInput} from 'components/wrappedWalletInput';
import {useAlertContext} from 'context/alert';
import {useProviders} from 'context/providers';
import {Web3Address} from 'utils/library';
import {validateWeb3Address} from 'utils/validators';
import {MultisigWalletField} from 'components/multisigWallets/row';

type WalletRowProps = {
index: number;
onDelete?: (index: number) => void;
};

const WalletRow: React.FC<WalletRowProps> = ({index, onDelete}) => {
const {t} = useTranslation();
const {alert} = useAlertContext();
const {api: provider} = useProviders();

const {control} = useFormContext();
const [committee, multisigWallets, wallets, membership]: [
MultisigWalletField[],
MultisigWalletField[],
MultisigWalletField[],
'multisig' | 'token'
] = useWatch({
name: ['committee', 'multisigWallets', 'wallets', 'membership'],
control,
});

const addressValidator = useCallback(
async ({address, ensName}: WalletInputValue, index: number) => {
const web3Address = new Web3Address(provider, address, ensName);

// check if address is valid
let validationResult = await validateWeb3Address(
web3Address,
t('errors.required.walletAddress'),
t
);

if (validationResult && validationResult !== true) {
return validationResult;
selankon marked this conversation as resolved.
Show resolved Hide resolved
}

if (
committee?.some(
(wallet, walletIndex) =>
((web3Address.address &&
wallet.address.toLowerCase() ===
web3Address.address.toLowerCase()) ||
(web3Address.ensName &&
wallet.ensName.toLowerCase() ===
web3Address.ensName.toLowerCase())) &&
walletIndex !== index
)
) {
validationResult = t('errors.duplicateAddress');
}
return validationResult;
},
[provider, t, committee]
);

const handleOnChange = useCallback(
// to avoid nesting the InputWallet value, add the existing amount
// when the value of the address/ens changes
(e: unknown, onChange: (e: unknown) => void) => {
onChange({
...(e as WalletInputValue),
});
},
[index, committee]
);

return (
<Container>
<Controller
defaultValue={{address: '', ensName: ''}}
name={`committee.${index}`}
control={control}
rules={{validate: value => addressValidator(value, index)}}
render={({
field: {name, ref, value, onBlur, onChange},
fieldState: {error},
}) => (
<AddressWrapper>
<LabelWrapper>
<Label label={t('labels.whitelistWallets.address')} />
</LabelWrapper>
<WrappedWalletInput
state={error && 'critical'}
value={value}
onBlur={onBlur}
onChange={e => handleOnChange(e, onChange)}
error={error?.message}
resolveLabels="onBlur"
ref={ref}
name={name}
/>
</AddressWrapper>
)}
/>

<DropdownMenuWrapper>
{/* Disable index 0 when minting to DAO Treasury is supported */}
<Dropdown
align="start"
trigger={
<ButtonIcon
mode="ghost"
size="large"
bgWhite
icon={<IconMenuVertical />}
data-testid="trigger"
/>
}
sideOffset={8}
listItems={[
{
component: (
<ListItemAction
title={t('labels.removeWallet')}
{...(typeof onDelete !== 'function' && {mode: 'disabled'})}
bgWhite
/>
),
callback: () => {
if (typeof onDelete === 'function') {
onDelete(index);
alert(t('alert.chip.removedAddress') as string);
}
},
},
]}
/>
</DropdownMenuWrapper>
</Container>
);
};

export default WalletRow;

const Container = styled.div.attrs({
className: 'flex flex-wrap gap-x-2 gap-y-1.5 p-2 bg-ui-0',
})``;

const LabelWrapper = styled.div.attrs({
className: 'tablet:hidden mb-0.5',
})``;

const AddressWrapper = styled.div.attrs({
className: 'flex-1 order-1',
})``;

const DropdownMenuWrapper = styled.div.attrs({
className: 'flex order-2 tablet:order-5 mt-3.5 tablet:mt-0 w-6',
})``;
2 changes: 1 addition & 1 deletion src/components/addWallets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const AddWallets: React.FC = () => {
remove(index);
setTimeout(() => {
trigger('wallets');
});
}, 50);
};

return (
Expand Down
Loading