Skip to content

Commit db2fcb7

Browse files
authored
Merge pull request #1 from xdevguild/improve-roles-setup
Improve roles, properties management, and token inputs.
2 parents 1cf802b + dbe71f4 commit db2fcb7

29 files changed

Lines changed: 722 additions & 285 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### [0.12.0](https://github.com/xdevguild/buildo.dev/releases/tag/v0.12.0) (2024-01-01)
2+
- improve roles and properties selectors to make them less confusing
3+
- use tokenId selectors for fungible ids and collections
4+
15
### [0.11.1](https://github.com/xdevguild/buildo.dev/releases/tag/v0.11.1) (2023-12-29)
26
- update dependencies (some improvements in useElven)
37

app/page.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const Home: NextPage = () => {
99
<h1 className="text-2xl md:text-4xl lg:text-6xl font-black text-center mb-8 max-w-2xl lg:max-w-4xl m-auto">
1010
Buildo is your companion through the MultiversX!
1111
</h1>
12-
<h2 className="text-sm md:text-xl lg:text-2xl font-light text-center max-w-2xl lg:max-w-4xl m-auto mb-8">
12+
<h2 className="text-sm md:text-xl lg:text-2xl font-light text-center max-w-2xl lg:max-w-4xl m-auto sm:mb-8">
1313
Buildo.dev is a{' '}
1414
<a
1515
href="https://multiversx.com"
@@ -21,7 +21,7 @@ const Home: NextPage = () => {
2121
app that helps with blockchain interactions, like issuing tokens and
2222
querying smart contracts.
2323
</h2>
24-
<h3 className="text-xs md:text-lg font-extralight m-auto max-w-2xl lg:max-w-4xl text-center">
24+
<h3 className="hidden sm:block text-xs md:text-sm font-extralight m-auto max-w-2xl lg:max-w-4xl text-center">
2525
If you like to work with CLI tools, check the{' '}
2626
<a
2727
href="https://github.com/xdevguild/buildo-begins"
@@ -30,16 +30,24 @@ const Home: NextPage = () => {
3030
>
3131
Buildo Begins
3232
</a>{' '}
33-
CLI!
34-
<br />
35-
Check the{' '}
33+
CLI! Check the{' '}
3634
<a
3735
href="https://chat.openai.com/g/g-GN0Zq0iZP-buildo-expert"
3836
target="_blank"
3937
className="underline"
4038
>
4139
Buildo Expert GPT Assistant
4240
</a>
41+
<br />
42+
Remember that there are different{' '}
43+
<a
44+
className="underline"
45+
href="https://github.com/multiversx/mx-api-service/blob/main/src/utils/cache.info.ts"
46+
target="_blank"
47+
>
48+
cache strategies for API
49+
</a>
50+
, so not all changes will be visible immediately.
4351
</h3>
4452
<OperationsAuthCheck />
4553
</div>

components/home-cards.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,6 @@ export const HomeCards = () => {
170170
onClick: () => {},
171171
disabled: true,
172172
},
173-
{
174-
title: 'Guardians related operations',
175-
description: 'Set/unset a guardian, guard an account',
176-
onClick: () => {},
177-
disabled: true,
178-
},
179173
]}
180174
/>
181175
<HomeCard
@@ -218,6 +212,12 @@ export const HomeCards = () => {
218212
setDialogState('utilities', 'verifySignature');
219213
},
220214
},
215+
{
216+
title: 'Hash functions',
217+
description: 'Hash data using different hash functions',
218+
onClick: () => {},
219+
disabled: true,
220+
},
221221
{
222222
title: 'Read account storage',
223223
description: 'Check account storage by key',

components/operations/common/change-properties.tsx

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
ContractCallPayloadBuilder,
66
ContractFunction,
77
} from '@multiversx/sdk-core';
8-
import { useForm } from 'react-hook-form';
8+
import { useForm, useWatch } from 'react-hook-form';
99
import { zodResolver } from '@hookform/resolvers/zod';
1010
import { Form } from '@/components/ui/form';
1111
import {
@@ -21,18 +21,23 @@ import {
2121
builtInSC,
2222
TokenPropertyOrRole,
2323
} from '@/components/operations/constants';
24-
import { OperationsInputField } from '@/components/operations/operations-input-field';
2524
import { OperationsCheckboxGroup } from '@/components/operations/operations-checkbox-group';
2625
import { OperationsSubmitButton } from '@/components/operations/operations-submit-button';
27-
import { useContext } from 'react';
26+
import { useContext, useEffect } from 'react';
2827
import { OperationsStateDialogContext } from '@/components/operations/operations-status-dialog';
2928
import { CommonOpertationContentProps } from '@/components/operations/operations-common-types';
29+
import { OperationsSelectField } from '@/components/operations/operations-select-field';
30+
import { useCreatorTokens } from '@/hooks/use-creator-tokens';
3031

3132
const formSchema = z.object({
3233
tokenId: z.string().min(1, 'The field is required'),
3334
properties: z.array(z.string()),
3435
});
3536

37+
type CreatorTokens = {
38+
ticker: string;
39+
};
40+
3641
const propertiesMap: Record<
3742
CommonOpertationContentProps['tokenType'],
3843
TokenPropertyOrRole[]
@@ -52,14 +57,20 @@ export const ChangeProperties = ({
5257
OperationsStateDialogContext
5358
);
5459

60+
const { tokens } = useCreatorTokens<CreatorTokens>({
61+
tokenType,
62+
});
63+
5564
const form = useForm<z.infer<typeof formSchema>>({
5665
resolver: zodResolver(formSchema),
5766
defaultValues: {
5867
tokenId: '',
59-
properties: propertiesMap[tokenType].map((property) => property.name),
68+
properties: [],
6069
},
6170
});
6271

72+
const watchTokenId = useWatch({ control: form.control, name: 'tokenId' });
73+
6374
const onSubmit = ({ tokenId, properties }: z.infer<typeof formSchema>) => {
6475
const args: TypedValue[] = [BytesValue.fromUTF8(tokenId.trim())];
6576

@@ -91,6 +102,21 @@ export const ChangeProperties = ({
91102
close();
92103
};
93104

105+
useEffect(() => {
106+
const tokenData = tokens?.find((token) => token.ticker === watchTokenId);
107+
if (tokenData) {
108+
const properties = propertiesMap[tokenType].filter((property) => {
109+
const key = property.name as keyof typeof tokenData;
110+
return tokenData[key];
111+
});
112+
form.setValue(
113+
'properties',
114+
properties.map((property) => property.name)
115+
);
116+
}
117+
// eslint-disable-next-line react-hooks/exhaustive-deps
118+
}, [tokenType, tokens, watchTokenId]);
119+
94120
return (
95121
<>
96122
<DialogHeader className="p-8 pb-0">
@@ -109,11 +135,18 @@ export const ChangeProperties = ({
109135
className="space-y-8"
110136
>
111137
<div className="flex-1 overflow-auto p-1">
112-
<OperationsInputField
138+
<OperationsSelectField
113139
name="tokenId"
114140
label="Token id"
115-
placeholder="Example: MyToken-23432"
116141
description="Please provide your token id"
142+
options={
143+
tokens
144+
? tokens?.map((token) => ({
145+
value: token.ticker,
146+
label: token.ticker,
147+
}))
148+
: []
149+
}
117150
/>
118151
<OperationsCheckboxGroup
119152
items={propertiesMap[tokenType]}

components/operations/common/stop-creation.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ import {
1818
commonOpertationsGasLimit,
1919
builtInSC,
2020
} from '@/components/operations/constants';
21-
import { OperationsInputField } from '@/components/operations/operations-input-field';
2221
import { OperationsSubmitButton } from '@/components/operations/operations-submit-button';
2322
import { useContext } from 'react';
2423
import { OperationsStateDialogContext } from '@/components/operations/operations-status-dialog';
25-
import { OperationContentProps } from '@/components/operations/operations-common-types';
24+
import { CommonOpertationContentProps } from '@/components/operations/operations-common-types';
25+
import { OperationsTokenIdInput } from '../operations-tokenid-input';
2626

2727
const formSchema = z.object({
2828
tokenId: z.string().min(1, 'The field is required!'),
2929
});
3030

31-
export const StopCreation = ({ triggerTx, close }: OperationContentProps) => {
31+
export const StopCreation = ({
32+
triggerTx,
33+
close,
34+
tokenType,
35+
}: CommonOpertationContentProps) => {
3236
const { setOpen: setTxStatusDialogOpen } = useContext(
3337
OperationsStateDialogContext
3438
);
@@ -77,12 +81,7 @@ export const StopCreation = ({ triggerTx, close }: OperationContentProps) => {
7781
className="space-y-8"
7882
>
7983
<div className="flex-1 overflow-auto p-1">
80-
<OperationsInputField
81-
name="tokenId"
82-
label="Token id"
83-
placeholder="Example: MyToken-23432"
84-
description="Please provide your token id"
85-
/>
84+
<OperationsTokenIdInput tokenType={tokenType} />
8685
</div>
8786
</form>
8887
</Form>

components/operations/common/toggle-special-roles.tsx

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ContractCallPayloadBuilder,
88
ContractFunction,
99
} from '@multiversx/sdk-core';
10-
import { useForm } from 'react-hook-form';
10+
import { useForm, useWatch } from 'react-hook-form';
1111

1212
import { zodResolver } from '@hookform/resolvers/zod';
1313
import { Form } from '@/components/ui/form';
@@ -27,11 +27,14 @@ import {
2727
} from '@/components/operations/constants';
2828
import { OperationsInputField } from '@/components/operations/operations-input-field';
2929
import { OperationsCheckboxGroup } from '@/components/operations/operations-checkbox-group';
30-
import { OperationsSubmitButton } from '../operations-submit-button';
31-
import { useContext } from 'react';
30+
import { OperationsSubmitButton } from '@/components/operations/operations-submit-button';
31+
import { useContext, useEffect, useState } from 'react';
3232
import { OperationsStateDialogContext } from '@/components/operations/operations-status-dialog';
3333
import { CommonOpertationContentProps } from '@/components/operations/operations-common-types';
34-
import { OperationsRadioGroup } from '../operations-radio-group';
34+
import { OperationsRadioGroup } from '@/components/operations/operations-radio-group';
35+
import { useAccount } from '@useelven/core';
36+
import { useTokenRolesByAccount } from '@/hooks/use-token-roles-by-account';
37+
import { OperationsTokenIdInput } from '@/components/operations/operations-tokenid-input';
3538

3639
const formSchema = z.object({
3740
tokenId: z.string().min(1, 'The field is required'),
@@ -57,20 +60,52 @@ export const ToggleSpecialRoles = ({
5760
close,
5861
tokenType,
5962
}: CommonOpertationContentProps) => {
63+
const { address } = useAccount();
6064
const { setOpen: setTxStatusDialogOpen } = useContext(
6165
OperationsStateDialogContext
6266
);
6367

68+
const [disabledRoles, setDisabledRoles] = useState<string[]>();
69+
6470
const form = useForm<z.infer<typeof formSchema>>({
6571
resolver: zodResolver(formSchema),
6672
defaultValues: {
6773
tokenId: '',
68-
address: '',
74+
address,
6975
type: 'set',
70-
roles: ['ESDTRoleNFTCreate', 'ESDTRoleNFTBurn'],
76+
roles: [],
7177
},
7278
});
7379

80+
const watchTokenId = useWatch({ name: 'tokenId', control: form.control });
81+
const watchAddress = useWatch({ name: 'address', control: form.control });
82+
const watchType = useWatch({ name: 'type', control: form.control });
83+
84+
const { roles } = useTokenRolesByAccount({
85+
tokenId: watchTokenId,
86+
tokenType,
87+
address: watchAddress,
88+
});
89+
90+
// Handle selected roles in form
91+
useEffect(() => {
92+
const roleNames = rolesMap[tokenType].map((role) => role.name);
93+
94+
if (!watchTokenId || !watchAddress) {
95+
setDisabledRoles(roleNames);
96+
return;
97+
}
98+
99+
let disabledRoles: string[] = [];
100+
if (watchType === 'set') {
101+
disabledRoles = roles;
102+
} else {
103+
disabledRoles = roleNames.filter((role) => !roles?.includes(role));
104+
}
105+
setDisabledRoles(disabledRoles);
106+
// eslint-disable-next-line react-hooks/exhaustive-deps
107+
}, [roles, watchType, watchTokenId, watchAddress]);
108+
74109
const onSubmit = ({
75110
tokenId,
76111
address,
@@ -109,6 +144,17 @@ export const ToggleSpecialRoles = ({
109144
close();
110145
};
111146

147+
const rolesDescription = () => {
148+
if (!watchAddress || !watchTokenId) {
149+
return 'Please set tokenId and address, then you can choose roles!';
150+
}
151+
return `Disabled ones are ${
152+
form.getValues('type') === 'set'
153+
? "set, so you can't set them"
154+
: "not set, so you can't unset them"
155+
}.`;
156+
};
157+
112158
return (
113159
<>
114160
<DialogHeader className="p-8 pb-0">
@@ -137,23 +183,19 @@ export const ToggleSpecialRoles = ({
137183
label="Operation type"
138184
description="Please choose the type of the operation. Set or Unset."
139185
/>
140-
<OperationsInputField
141-
name="tokenId"
142-
label="Token id"
143-
placeholder="Example: MyToken-23432"
144-
description="Please provide your token id"
145-
/>
186+
<OperationsTokenIdInput tokenType={tokenType} />
146187
<OperationsInputField
147188
name="address"
148189
label="Address"
149190
placeholder="Example: erd1..."
150-
description="Please provide the address for which the roles will be assigned"
191+
description="Please provide the address for which the roles will be assigned. By default your own."
151192
/>
152193
<OperationsCheckboxGroup
153194
items={rolesMap[tokenType]}
195+
disabledItems={disabledRoles}
154196
name="roles"
155197
label="Roles"
156-
description="Special roles available for basic ESDT tokens."
198+
description={`Special roles available for basic ESDT tokens. ${rolesDescription()}`}
157199
/>
158200
</div>
159201
</form>

components/operations/common/transfer-creation-role.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import { OperationsInputField } from '@/components/operations/operations-input-f
2424
import { OperationsSubmitButton } from '@/components/operations/operations-submit-button';
2525
import { useContext } from 'react';
2626
import { OperationsStateDialogContext } from '@/components/operations/operations-status-dialog';
27-
import { OperationContentProps } from '@/components/operations/operations-common-types';
27+
import { CommonOpertationContentProps } from '@/components/operations/operations-common-types';
28+
import { OperationsTokenIdInput } from '../operations-tokenid-input';
2829

2930
const formSchema = z.object({
3031
addressWithRole: z.string().min(1, 'The field is required'),
@@ -35,7 +36,8 @@ const formSchema = z.object({
3536
export const TransferCreationRole = ({
3637
triggerTx,
3738
close,
38-
}: OperationContentProps) => {
39+
tokenType,
40+
}: CommonOpertationContentProps) => {
3941
const { setOpen: setTxStatusDialogOpen } = useContext(
4042
OperationsStateDialogContext
4143
);
@@ -95,12 +97,7 @@ export const TransferCreationRole = ({
9597
className="space-y-8"
9698
>
9799
<div className="flex-1 overflow-auto p-1">
98-
<OperationsInputField
99-
name="tokenId"
100-
label="Token id"
101-
placeholder="Example: MyToken-23432"
102-
description="Please provide your token id"
103-
/>
100+
<OperationsTokenIdInput tokenType={tokenType} />
104101
<OperationsInputField
105102
name="addressWithRole"
106103
label="Current address with create role"

0 commit comments

Comments
 (0)