Skip to content

Commit

Permalink
Merge pull request #13 from calimero-network/feat-set-context-variables
Browse files Browse the repository at this point in the history
feat: proposal action UI parsing
  • Loading branch information
alenmestrov authored Dec 27, 2024
2 parents bfc491d + a4b99a6 commit dc99093
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 120 deletions.
5 changes: 5 additions & 0 deletions app/src/api/contractApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export interface ApprovalsCount {
num_approvals: number;
}

export interface ContextVariables {
key: string;
value: string;
}

export interface ContractApi {
//Contract
getContractProposals(
Expand Down
60 changes: 44 additions & 16 deletions app/src/api/dataSource/ContractApiDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ApiResponse } from '@calimero-is-near/calimero-p2p-sdk';
import {
ApprovalsCount,
ContextDetails,
ContextVariables,
ContractApi,
ContractProposal,
Members,
Expand Down Expand Up @@ -103,23 +104,50 @@ export class ContextApiDataSource implements ContractApi {
}
}

getContextDetails(): ApiResponse<ContextDetails> {
// try {
// const headers: Header | null = await createAuthHeader(
// contextId,
// getNearEnvironment(),
// );
// const response = await this.client.get<ApiContext>(
// `${getAppEndpointKey()}/admin-api/contexts/${contextId}`,
// headers ?? {},
// );
// return response;
// } catch (error) {
// console.error('Error fetching context:', error);
// return { error: { code: 500, message: 'Failed to fetch context data.' } };
// }
throw new Error('Method not implemented.');
async getContextVariables(): ApiResponse<ContextVariables[]> {
try {
const { jwtObject, error } = getConfigAndJwt();
if (error) {
return { error };
}

const apiEndpoint = `${getStorageAppEndpointKey()}/admin-api/contexts/${jwtObject.context_id}/proposals/context-storage-entries`;
const body = {
offset: 0,
limit: 10,
};

const response = await axios.post(apiEndpoint, body, {
headers: {
'Content-Type': 'application/json',
},
});

if (!response.data.data) {
return {
data: [],
error: null,
};
}

// Convert both key and value from Vec<u8> to string
const parsedData = response.data.data.map((item: any) => ({
key: new TextDecoder().decode(new Uint8Array(item.key)),
value: new TextDecoder().decode(new Uint8Array(item.value)),
}));

return {
data: parsedData ?? [],
error: null,
};
} catch (error) {
return {
data: null,
error: error as Error,
};
}
}

getContextMembers(): ApiResponse<Members[]> {
throw new Error('Method not implemented.');
}
Expand Down
185 changes: 185 additions & 0 deletions app/src/components/proposal/Actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React from 'react';
import { styled } from 'styled-components';

interface FunctionCallProps {
receiver_id: string;
method_name: string;
args: string;
deposit: number;
gas: number;
}

interface ContextValueProps {
key: number[];
value: number[];
}

interface ApprovalsParams {
num_approvals: number;
}

interface LimitParams {
active_proposals_limit: number;
}

interface TransferParams {
receiver_id: string;
amount: number;
}

interface Action {
scope: string;
params:
| LimitParams
| TransferParams
| ApprovalsParams
| ContextValueProps
| FunctionCallProps;
}

interface ActionsProps {
actions: Action[];
}

interface TableHeaderProps {
columns: number;
bgColor?: boolean;
}

const GridList = styled.div<TableHeaderProps>`
display: grid;
grid-template-columns: repeat(${(props) => props.columns}, 1fr);
gap: 0.5rem;
background: ${(props) => (props.bgColor ? '#ffa500' : 'transparent')};
`;

export default function Actions({ actions }: ActionsProps) {
const getColumnCount = (scope: string) => {
switch (scope) {
case 'Transfer':
case 'SetContextValue':
return 3;
case 'SetActiveProposalsLimit':
case 'SetNumApprovals':
return 2;
case 'ExternalFunctionCall':
return 5;
default:
return 1;
}
};

const renderActionContent = (action: Action) => {
switch (action.scope) {
case 'Transfer':
return (
<>
<div>Scope</div>
<div>Amount</div>
<div>Receiver ID</div>
</>
);
case 'SetContextValue':
return (
<>
<div>Scope</div>
<div>Key</div>
<div>Vaue ID</div>
</>
);
case 'SetActiveProposalsLimit':
return (
<>
<div>Scope</div>
<div>Active Proposals Limit</div>
</>
);
case 'SetNumApprovals':
return (
<>
<div>Scope</div>
<div>Number of Approvals</div>
</>
);
case 'ExternalFunctionCall':
return (
<>
<div>Scope</div>
<div>Receiver ID</div>
<div>Method</div>
<div>Deposit</div>
<div>Gas</div>
</>
);
default:
return <div>Scope</div>;
}
};

const renderActionValues = (action: Action) => {
switch (action.scope) {
case 'Transfer':
const transferParams = action.params as TransferParams;
return (
<>
<div>{action.scope}</div>
<div>{transferParams.amount}</div>
<div>{transferParams.receiver_id}</div>
</>
);
case 'SetContextValue':
const contextValueParams = action.params as ContextValueProps;
return (
<>
<div>{action.scope}</div>
<div>{String.fromCharCode(...contextValueParams.key)}</div>
<div>{String.fromCharCode(...contextValueParams.value)}</div>
</>
);
case 'SetActiveProposalsLimit':
const limitParams = action.params as LimitParams;
return (
<>
<div>{action.scope}</div>
<div>{limitParams.active_proposals_limit}</div>
</>
);
case 'SetNumApprovals':
const approvalParams = action.params as ApprovalsParams;
return (
<>
<div>{action.scope}</div>
<div>{approvalParams.num_approvals}</div>
</>
);
case 'ExternalFunctionCall':
const functionParams = action.params as FunctionCallProps;
return (
<>
<div>{action.scope}</div>
<div>{functionParams.receiver_id}</div>
<div>{functionParams.method_name}</div>
<div>{functionParams.deposit}</div>
<div>{functionParams.gas}</div>
</>
);
default:
return <div>{action.scope}</div>;
}
};

return (
<>
<GridList columns={getColumnCount(actions[0].scope)} bgColor>
{renderActionContent(actions[0])}
</GridList>
<div>
{actions.map((action, index) => (
<GridList key={index} columns={getColumnCount(action.scope)}>
{renderActionValues(action)}
</GridList>
))}
</div>
</>
);
}
2 changes: 1 addition & 1 deletion app/src/components/proposals/ChangeApprovalsNeededForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function ChangeApprovalsNeededForm({
type="number"
id="minApprovals"
name="minApprovals"
placeholder='2'
placeholder="2"
value={proposalForm.minApprovals}
onChange={handleInputChange}
min="1"
Expand Down
7 changes: 2 additions & 5 deletions app/src/components/proposals/CreateProposalPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const ModalContent = styled.div`
color: white;
h2 {
padding-bottom: 0.5rem;
margin: 0;
padding-bottom: 0.5rem;
margin: 0;
}
`;

Expand Down Expand Up @@ -76,7 +76,6 @@ export const ButtonSm = styled.button`

export interface ProposalData {
actionType: string;
protocol: string;
contractId: string;
methodName: string;
arguments: { key: string; value: string }[];
Expand All @@ -99,7 +98,6 @@ export default function CreateProposalPopup({
}: CreateProposalPopupProps) {
const [proposalForm, setProposalForm] = useState({
actionType: 'Cross contract call',
protocol: 'NEAR',
contractId: '',
methodName: '',
arguments: [{ key: '', value: '' }],
Expand Down Expand Up @@ -195,7 +193,6 @@ export default function CreateProposalPopup({

setProposalForm({
actionType: 'Cross contract call',
protocol: 'NEAR',
contractId: '',
methodName: '',
arguments: [{ key: '', value: '' }],
Expand Down
18 changes: 1 addition & 17 deletions app/src/components/proposals/CrossContractCallForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,6 @@ export default function CrossContractCallForm({
}: CrossContractCallFormProps) {
return (
<>
<FormGroup>
<label htmlFor="protocol">Protocol</label>
<select
id="protocol"
name="protocol"
value={proposalForm.protocol}
onChange={handleInputChange}
required
>
<option value="NEAR">NEAR</option>
<option value="Starknet">Starknet</option>
</select>
</FormGroup>
<FormGroup>
<label htmlFor="contractId">Contract ID</label>
<input
Expand All @@ -68,10 +55,7 @@ export default function CrossContractCallForm({
/>
</FormGroup>
<FormGroup>
<label htmlFor="deposit">
Deposit{' '}
{proposalForm.protocol === 'NEAR' ? '(in octoNEAR)' : '(in STRK)'}
</label>
<label htmlFor="deposit">Deposit</label>
<input
type="text"
id="deposit"
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/proposals/MaxActiveProposalsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function MaxActiveProposalsForm({
type="number"
id="maxActiveProposals"
name="maxActiveProposals"
placeholder='10'
placeholder="10"
value={proposalForm.maxActiveProposals}
onChange={handleInputChange}
min="1"
Expand Down
Loading

0 comments on commit dc99093

Please sign in to comment.