Skip to content

Commit 2765352

Browse files
authored
Merge pull request #32 from lidofinance/feature/si-1877-permission-form-restore-icon
2 parents bdc693a + ea03f71 commit 2765352

File tree

18 files changed

+186
-96
lines changed

18 files changed

+186
-96
lines changed

features/create-vault/create-vault-form/confirmation/confirmation-data/confirmation-data-item/confirm-address.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { FC } from 'react';
2-
import { Address } from 'viem';
32

43
import { AddressBadge } from 'shared/components';
54
import { AddressList } from './styles';
@@ -10,8 +9,14 @@ export const ConfirmAddress: FC<ConfirmDataItemProps> = ({ payload }) => {
109
if (Array.isArray(payload)) {
1110
return (
1211
<AddressList>
13-
{(payload as Address[]).map((address) => {
14-
return <AddressBadge key={address} address={address} symbols={6} />;
12+
{payload.map((field) => {
13+
return (
14+
<AddressBadge
15+
key={field.account}
16+
address={field.account}
17+
symbols={6}
18+
/>
19+
);
1520
})}
1621
</AddressList>
1722
);

features/create-vault/create-vault-form/confirmation/confirmation-data/confirmation-data-item/confirm-number.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { FC } from 'react';
33
import { TextBold } from './styles';
44

55
import { ConfirmDataItemProps } from './types';
6+
import invariant from 'tiny-invariant';
67

78
export const ConfirmNumber: FC<ConfirmDataItemProps> = ({ payload }) => {
9+
invariant(
10+
typeof payload === 'string',
11+
'[ConfirmNumber] payload should be a string',
12+
);
813
return <TextBold>{payload}</TextBold>;
914
};

features/create-vault/create-vault-form/confirmation/confirmation-data/confirmation-data-item/confirm-percent.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { FC } from 'react';
33
import { TextBold } from './styles';
44

55
import { ConfirmDataItemProps } from './types';
6+
import invariant from 'tiny-invariant';
67

78
export const ConfirmPercent: FC<ConfirmDataItemProps> = ({ payload }) => {
9+
invariant(
10+
typeof payload === 'string',
11+
'[ConfirmPercent] payload should be a string',
12+
);
813
return <TextBold>{payload}%</TextBold>;
914
};

features/create-vault/create-vault-form/confirmation/confirmation-data/confirmation-data-item/confirm-time.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { FC } from 'react';
33
import { TextBold } from './styles';
44

55
import { ConfirmDataItemProps } from './types';
6+
import invariant from 'tiny-invariant';
67

78
export const ConfirmTime: FC<ConfirmDataItemProps> = ({ payload }) => {
9+
invariant(
10+
typeof payload === 'string',
11+
'[ConfirmTime] payload should be a string',
12+
);
813
return <TextBold>{payload} hours</TextBold>;
914
};
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { PermissionField } from 'features/create-vault/types';
2+
13
export interface ConfirmDataItemProps {
2-
payload: string | string[];
4+
payload: string | PermissionField[];
35
}

features/create-vault/create-vault-form/create-vault-form-context/create-vault-form-context.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { SubmitStepEnum } from 'features/create-vault/types';
3636
import { formatCreateVaultData } from 'features/create-vault/utils/format-data';
3737
import { simulateCreateVault } from 'modules/vaults/contracts/vault-factory';
38+
import { Address } from 'viem';
3839

3940
const CreateVaultDataContext =
4041
createContext<CreateVaultDataContextValue | null>(null);
@@ -85,11 +86,11 @@ export const CreateFormProvider: FC<PropsWithChildren> = ({ children }) => {
8586

8687
const formObject = useForm<CreateVaultSchema>({
8788
defaultValues: {
88-
nodeOperator: '',
89-
nodeOperatorManager: '',
89+
nodeOperator: '' as Address,
90+
nodeOperatorManager: '' as Address,
9091
nodeOperatorFeeBP: 5,
9192
confirmExpiry: 36,
92-
defaultAdmin: '',
93+
defaultAdmin: '' as Address,
9394
roles: {},
9495
},
9596
resolver: validateFormWithZod(createVaultSchema),
@@ -111,8 +112,8 @@ export const CreateFormProvider: FC<PropsWithChildren> = ({ children }) => {
111112

112113
setSubmitStep({ step: SubmitStepEnum.confirming });
113114
try {
114-
const response = await callCreateVault(payload);
115-
setSubmitStep({ step: SubmitStepEnum.success, tx: response });
115+
const { address, tx } = await callCreateVault(payload);
116+
setSubmitStep({ step: SubmitStepEnum.success, tx, address });
116117
} catch (err) {
117118
console.error('[CreateFormProvider] Error sending create vault', err);
118119

features/create-vault/create-vault-form/create-vault-form-context/validation.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ const validateAddress = (value: string) => isValidAnyAddress(value);
3232

3333
const addressSchema = z
3434
.string()
35-
.refine(validateAddress, { message: INVALID_ADDRESS_MESSAGE });
35+
.refine(validateAddress, { message: INVALID_ADDRESS_MESSAGE })
36+
.transform((value) => value as Address);
37+
38+
const permissionSchema = z.object({
39+
state: z.union([z.literal('restore'), z.literal('grant')]),
40+
account: addressSchema,
41+
});
3642

3743
const roleKeys = [
3844
...Object.keys(VAULTS_OWNER_ROLES_MAP),
@@ -53,9 +59,11 @@ export const createVaultSchema = z.object({
5359
defaultAdmin: addressSchema,
5460
roles: z.object(
5561
Object.fromEntries(
56-
roleKeys.map((key) => [key, z.array(addressSchema).optional()]),
62+
roleKeys.map((key) => [key, z.array(permissionSchema).optional()]),
5763
) as unknown as {
58-
[key in PermissionKeys]: z.ZodOptional<z.ZodArray<z.ZodString>>;
64+
[key in PermissionKeys]: z.ZodOptional<
65+
z.ZodArray<typeof permissionSchema>
66+
>;
5967
},
6068
),
6169
});
@@ -68,24 +76,28 @@ export const formatCreateVaultData = (
6876
values: CreateVaultSchema,
6977
): VaultFactoryArgs => {
7078
return {
71-
defaultAdmin: values.defaultAdmin as Address,
72-
nodeOperator: values.nodeOperator as Address,
73-
nodeOperatorManager: values.nodeOperatorManager as Address,
79+
defaultAdmin: values.defaultAdmin,
80+
nodeOperator: values.nodeOperator,
81+
nodeOperatorManager: values.nodeOperatorManager,
7482
nodeOperatorFeeBP: BigInt(values.nodeOperatorFeeBP),
7583
confirmExpiry: BigInt(values.confirmExpiry * 60 * 60),
7684

77-
roles: Object.entries(values.roles).flatMap(([roleName, roleAddresses]) => {
78-
const roleHash = JOINT_ROLE_MAP[roleName as PermissionKeys];
79-
invariant(
80-
roleHash,
81-
`[formatCreateVaultData] no role hash found for ${roleName}`,
82-
);
83-
if (!roleAddresses) return [];
84-
return roleAddresses.map((address) => ({
85-
role: roleHash,
86-
account: address as Address,
87-
}));
88-
}),
85+
roles: Object.entries(values.roles)
86+
.filter(([_, roleData]) => {
87+
return roleData.filter((item) => item.state === 'grant');
88+
})
89+
.flatMap(([roleName, roleData]) => {
90+
const roleHash = JOINT_ROLE_MAP[roleName as PermissionKeys];
91+
invariant(
92+
roleHash,
93+
`[formatCreateVaultData] no role hash found for ${roleName}`,
94+
);
95+
if (!roleData) return [];
96+
return roleData.map((item) => ({
97+
role: roleHash,
98+
account: item.account,
99+
}));
100+
}),
89101
};
90102
};
91103

features/create-vault/create-vault-form/permissions/address-list/address-block/address-block.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,31 @@
1-
import { FC, useCallback } from 'react';
2-
import { useFormContext, useFieldArray } from 'react-hook-form';
1+
import { FC } from 'react';
2+
import { useFormContext } from 'react-hook-form';
33

44
import { AddressItem } from 'features/create-vault/create-vault-form/permissions/address-list/address-item';
55
import { Wrapper } from './styles';
6-
import { FieldName, PermissionKeys } from 'features/create-vault/types';
6+
import { PermissionField, PermissionKeys } from 'features/create-vault/types';
77

88
export interface AddressBlockProps {
99
permission: PermissionKeys;
1010
}
1111

1212
export const AddressBlock: FC<AddressBlockProps> = ({ permission }) => {
13-
const { control, watch } = useFormContext();
14-
const { remove } = useFieldArray({ control, name: permission });
15-
const fieldsWatch = watch(`roles.${permission}`) as FieldName[];
16-
17-
const handleRemove = useCallback(
18-
(index: number) => {
19-
remove(index);
20-
},
21-
[remove],
22-
);
13+
const { watch } = useFormContext();
14+
const fieldsWatch = watch(`roles.${permission}`) as PermissionField[];
2315

2416
if (!fieldsWatch?.length) {
2517
return null;
2618
}
2719

2820
return (
2921
<Wrapper>
30-
{fieldsWatch.map((value, index) => {
22+
{fieldsWatch.map((field, index) => {
3123
return (
3224
<AddressItem
33-
key={value}
34-
address={value}
25+
key={field.account}
3526
index={index}
36-
remove={handleRemove}
27+
permission={permission}
28+
field={field}
3729
/>
3830
);
3931
})}

features/create-vault/create-vault-form/permissions/address-list/address-item/address-item.tsx

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { FC, useState, useRef } from 'react';
2-
import { Address } from 'viem';
1+
import { FC, useState, useRef, useMemo, MouseEvent } from 'react';
32

43
import { Copy, External, ToastSuccess } from '@lidofinance/lido-ui';
54
import {
@@ -16,28 +15,57 @@ import {
1615
} from './styles';
1716

1817
import { truncateAddress } from 'utils/truncate-address';
18+
import { useFormContext } from 'react-hook-form';
19+
import { PermissionField, PermissionKeys } from 'features/create-vault/types';
1920

2021
export interface AddressItemProps {
2122
index: number;
22-
address: string;
23-
remove: (index: number) => void;
23+
permission: PermissionKeys;
24+
field: PermissionField;
2425
}
2526

2627
export const AddressItem: FC<AddressItemProps> = ({
2728
index,
28-
address,
29-
remove,
29+
permission,
30+
field,
3031
}) => {
32+
const { setValue } = useFormContext();
33+
const { account, state } = field;
3134
const [showPopover, showPopoverVisibility] = useState(false);
3235
const badgeRef = useRef<HTMLDivElement>(null);
3336

37+
const isTextCrossed = state === 'restore';
38+
const bgColor = useMemo(() => {
39+
if (isTextCrossed) return 'error';
40+
if (state === 'grant') return 'success';
41+
return 'default';
42+
}, [isTextCrossed, state]);
43+
44+
const handleUpdateFormItem = (e: MouseEvent<HTMLButtonElement>) => {
45+
e.stopPropagation();
46+
e.preventDefault();
47+
48+
const newState = state === 'grant' ? 'restore' : 'grant';
49+
50+
setValue(
51+
`roles.${permission}.${index}`,
52+
{
53+
account,
54+
state: newState,
55+
},
56+
{ shouldDirty: true },
57+
);
58+
};
59+
3460
const handleShowPopover = () => {
3561
showPopoverVisibility(true);
3662
};
3763

3864
const handleCopyLink = () => {
39-
void navigator.clipboard.writeText(address);
40-
ToastSuccess(`Address ${truncateAddress({ address })} have been copied`);
65+
void navigator.clipboard.writeText(account);
66+
ToastSuccess(
67+
`Address ${truncateAddress({ address: account })} have been copied`,
68+
);
4169
};
4270

4371
const handleClosePopover = () => {
@@ -48,12 +76,11 @@ export const AddressItem: FC<AddressItemProps> = ({
4876
<>
4977
<AddressBadge
5078
ref={badgeRef}
51-
address={address}
52-
onRemove={(e) => {
53-
e.preventDefault();
54-
e.stopPropagation();
55-
remove(index);
56-
}}
79+
crossedText={isTextCrossed}
80+
bgColor={bgColor}
81+
address={account}
82+
isActive={showPopover}
83+
onToggle={handleUpdateFormItem}
5784
onClick={handleShowPopover}
5885
/>
5986
{!!badgeRef?.current && (
@@ -68,15 +95,15 @@ export const AddressItem: FC<AddressItemProps> = ({
6895
open={showPopover}
6996
>
7097
<PopoverContent>
71-
<AddressBadge address={address} symbols={21} />
98+
<AddressBadge address={account} symbols={21} />
7299
<ActionGroup>
73100
<ActionWrapper>
74101
<Copy fill="var(--lido-color-primary)" />
75102
<ButtonLink onClick={handleCopyLink}>Copy address</ButtonLink>
76103
</ActionWrapper>
77104
<ActionWrapper>
78105
<External fill="var(--lido-color-primary)" />
79-
<AddressLinkEtherscan address={address as Address} />
106+
<AddressLinkEtherscan address={account} />
80107
</ActionWrapper>
81108
</ActionGroup>
82109
</PopoverContent>

features/create-vault/create-vault-form/permissions/address-list/input-block/input-item/input-item.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@ export const InputItem: FC<InputItemProps> = ({
5757
const output = await trigger(inputKey);
5858
if (output) {
5959
const value = (e.currentTarget || (e.target as HTMLInputElement)).value;
60-
setValue(`roles.${permission}.${values?.length ?? 0}`, value);
60+
setValue(
61+
`roles.${permission}.${values?.length ?? 0}`,
62+
{
63+
account: value,
64+
state: 'grant',
65+
},
66+
{ shouldDirty: true },
67+
);
6168
remove(index);
6269
}
6370
}

0 commit comments

Comments
 (0)