Skip to content

Commit

Permalink
feat: Add proposal edit (#4256)
Browse files Browse the repository at this point in the history
* Add proposal edit

* Update s.js

* Fix

* Only show if pending state

* Fix disable not supported fields for edit

* Fix button text
  • Loading branch information
samuveth authored Oct 6, 2023
1 parent 4a2a54e commit a00f836
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 148 deletions.
260 changes: 131 additions & 129 deletions src/components/SpaceCreateVoting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const props = defineProps<{
dateStart: number;
dateEnd: number;
osnap: { enabled: boolean; selection: boolean };
isEditing: boolean;
}>();
const {
Expand Down Expand Up @@ -54,16 +55,13 @@ watch(
{ immediate: true }
);
// we need to watch for selection change to properly update the voting form stae
watch(
() => props.osnap.selection,
() => {
// If using osnap, we can only allow basic voting, for, against, abstain
if (props.osnap.selection) {
form.value.type = 'basic';
} else {
// Initialize the proposal type if set in space
if (props.space?.voting?.type) form.value.type = props.space.voting.type;
} else if (props.space?.voting?.type) {
form.value.type = props.space.voting.type;
}
}
);
Expand All @@ -85,135 +83,139 @@ defineEmits<{
</script>

<template>
<div v-if="osnap.enabled" class="mb-4">
<div v-if="space?.voting?.type && space.voting.type !== 'basic'">
<h6>Where is oSnap?</h6>
<p class="mb-3">
oSnap is currently disabled because your space's voting settings
disallow the basic voting type which is a requirement for oSnap to work
properly.
</p>
<p>
Have your admin visit your
<a :href="`#/${space.id}/settings`">settings page</a> under Voting ->
Type, and make sure either "Any" or "Basic Voting" is selected. This
will allow you to create oSnap proposals.
</p>
</div>
<div v-else>
<h6>oSnap Proposal</h6>
<p>
Are you planning for this proposal to initiate a transaction that your
organizations Safe will execute if approved? (Remember, oSnap enables
trustless and permissionless execution)
</p>
<br />
<input
id="toggleOsnap"
type="checkbox"
:checked="osnap.selection"
@change="$emit('osnapToggle')"
/>
<label for="toggleOsnap">
Yes, use oSnap for transactions (this will restrict voting type to
Basic).
</label>
<div>
<div v-if="osnap.enabled" class="mb-4">
<div v-if="space?.voting?.type && space.voting.type !== 'basic'">
<h6>Where is oSnap?</h6>
<p class="mb-3">
oSnap is currently disabled because your space's voting settings
disallow the basic voting type which is a requirement for oSnap to
work properly.
</p>
<p>
Have your admin visit your
<a :href="`#/${space.id}/settings`">settings page</a> under Voting ->
Type, and make sure either "Any" or "Basic Voting" is selected. This
will allow you to create oSnap proposals.
</p>
</div>
<div v-else>
<h6>oSnap Proposal</h6>
<p>
Are you planning for this proposal to initiate a transaction that your
organizations Safe will execute if approved? (Remember, oSnap enables
trustless and permissionless execution)
</p>
<br />
<input
id="toggleOsnap"
type="checkbox"
:checked="osnap.selection"
@change="$emit('osnapToggle')"
/>
<label for="toggleOsnap">
Yes, use oSnap for transactions (this will restrict voting type to
Basic).
</label>
</div>
</div>
</div>
<div class="mb-5 space-y-4">
<BaseBlock :title="$t('create.voting')">
<InputSelectVoteType
:type="space.voting?.type || form.type"
:disabled="!!space.voting?.type || osnap.selection"
@update:type="value => (form.type = value)"
/>

<h4 class="mb-1 mt-3" v-text="$t('create.choices')" />
<div class="flex">
<div class="w-full overflow-hidden">
<draggable
v-model="form.choices"
v-bind="{ animation: 200 }"
:disabled="disableChoiceEdit"
item-key="id"
handle=".drag-handle"
class="space-y-2"
>
<div class="mb-5 space-y-4">
<BaseBlock :title="$t('create.voting')">
<InputSelectVoteType
:type="space.voting?.type || form.type"
:disabled="!!space.voting?.type || osnap.selection"
@update:type="value => (form.type = value)"
/>

<h4 class="mb-1 mt-3" v-text="$t('create.choices')" />
<div class="flex">
<div class="w-full overflow-hidden">
<draggable
v-model="form.choices"
v-bind="{ animation: 200 }"
:disabled="disableChoiceEdit"
item-key="id"
handle=".drag-handle"
class="space-y-2"
>
<template #item="{ element, index }">
<UiInput
v-model="element.text"
maxlength="32"
:disabled="disableChoiceEdit"
:placeholder="index > 0 ? $t('optional') : ''"
class="group"
:focus-on-mount="index === 0"
:data-testid="`input-proposal-choice-${index}`"
>
<template #label>
<div
class="drag-handle flex cursor-grab items-center active:cursor-grabbing"
:class="{
'cursor-not-allowed active:cursor-not-allowed':
disableChoiceEdit
}"
>
<BaseIcon name="draggable" size="16" class="mr-[12px]" />
{{ $tc('create.choice', [index + 1]) }}
</div>
</template>
<template #info>
<span
class="hidden text-xs text-skin-text group-focus-within:block"
>
{{ `${element.text.length}/32` }}
</span>
</template>
</UiInput>
</template>
</draggable>
</div>
<div v-if="!disableChoiceEdit" class="ml-2 flex w-[48px] items-end">
<BaseButtonRound
v-if="!disableChoiceEdit"
size="42px"
@click="addChoices(1)"
>
<i-ho-plus-sm class="text-skin-link" />
</BaseButtonRound>
<template #item="{ element, index }">
<UiInput
v-model="element.text"
maxlength="32"
:disabled="disableChoiceEdit"
:placeholder="index > 0 ? $t('optional') : ''"
class="group"
:focus-on-mount="index === 0"
:data-testid="`input-proposal-choice-${index}`"
>
<template #label>
<div
class="drag-handle flex cursor-grab items-center active:cursor-grabbing"
:class="{
'cursor-not-allowed active:cursor-not-allowed':
disableChoiceEdit
}"
>
<BaseIcon name="draggable" size="16" class="mr-[12px]" />
{{ $tc('create.choice', [index + 1]) }}
</div>
</template>
<template #info>
<span
class="hidden text-xs text-skin-text group-focus-within:block"
>
{{ `${element.text.length}/32` }}
</span>
</template>
</UiInput>
</template>
</draggable>
</div>
<div v-if="!disableChoiceEdit" class="ml-2 flex w-[48px] items-end">
<BaseButtonRound
v-if="!disableChoiceEdit"
size="42px"
@click="addChoices(1)"
>
<i-ho-plus-sm class="text-skin-link" />
</BaseButtonRound>
</div>
</div>
</div>
</BaseBlock>

<BaseBlock
:title="$t('create.period')"
:information="$t('create.votingPeriodExplainer')"
>
<div class="space-y-2 md:flex md:space-x-3 md:space-y-0">
<SpaceCreateVotingDateStart
:delay="space.voting?.delay"
:date="dateStart"
@select="value => setDateStart(value)"
/>

<SpaceCreateVotingDateEnd
:period="space.voting?.period"
:date="dateEnd"
@select="value => setDateEnd(value)"
/>
</div>
</BaseBlock>
</BaseBlock>

<BaseBlock v-if="$route.query.snapshot" :title="$t('snapshot')">
<UiInput
v-model="form.snapshot"
:number="true"
:placeholder="$t('create.snapshotBlock')"
<BaseBlock
:title="$t('create.period')"
:information="$t('create.votingPeriodExplainer')"
>
<template #label>
{{ $t('snapshot') }}
</template>
</UiInput>
</BaseBlock>
<div class="space-y-2 md:flex md:space-x-3 md:space-y-0">
<SpaceCreateVotingDateStart
:delay="space.voting?.delay"
:date="dateStart"
:disabled="isEditing"
@select="value => setDateStart(value)"
/>

<SpaceCreateVotingDateEnd
:period="space.voting?.period"
:date="dateEnd"
:disabled="isEditing"
@select="value => setDateEnd(value)"
/>
</div>
</BaseBlock>

<BaseBlock v-if="$route.query.snapshot" :title="$t('snapshot')">
<UiInput
v-model="form.snapshot"
:number="true"
:placeholder="$t('create.snapshotBlock')"
>
<template #label>
{{ $t('snapshot') }}
</template>
</UiInput>
</BaseBlock>
</div>
</div>
</template>
18 changes: 12 additions & 6 deletions src/components/SpaceProposalHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ const { web3Account } = useWeb3();
const isCreator = computed(() => props.proposal?.author === web3Account.value);
const threeDotItems = computed(() => {
const items = [
{ text: t('duplicate'), action: 'duplicate' },
];
const items: { text: string; action: string }[] = [];
if (isCreator.value && props.proposal.state === 'pending')
items.push({ text: t('edit'), action: 'edit' });
items.push({ text: t('duplicate'), action: 'duplicate' });
if ((props.isAdmin || props.isModerator) && !props.proposal.flagged) {
items.push({ text: t('flag'), action: 'flag' });
} else {
Expand Down Expand Up @@ -64,14 +66,15 @@ async function handleSelect(e) {
proposal: props.proposal
});
}
if (e === 'duplicate') {
if (e === 'duplicate' || e === 'edit') {
resetForm();
router.push({
name: 'spaceCreate',
params: {
key: props.proposal.space.id,
sourceProposal: props.proposal.id
}
},
query: { editing: e === 'edit' ? 'true' : undefined }
});
}
}
Expand Down Expand Up @@ -158,8 +161,11 @@ watch(
</template>
<template #item="{ item }">
<div class="flex items-center gap-2">
<i-ho-pencil v-if="item.action === 'edit'" />
<i-ho-document-duplicate v-if="item.action === 'duplicate'" />
<i-ho-flag v-if="item.action === 'report' || item.action === 'flag'" />
<i-ho-flag
v-if="item.action === 'report' || item.action === 'flag'"
/>
<i-ho-trash v-if="item.action === 'delete'" />
{{ item.text }}
</div>
Expand Down
22 changes: 18 additions & 4 deletions src/composables/useClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ export function useClient() {
}

async function sendEIP712(space: { id: string }, type: string, payload: any) {
let plugins = {};
if (
payload.metadata?.plugins &&
Object.keys(payload.metadata?.plugins).length !== 0
)
plugins = payload.metadata.plugins;
const client = clientEIP712;
if (type === 'proposal') {
let plugins = {};
if (Object.keys(payload.metadata?.plugins).length !== 0)
plugins = payload.metadata.plugins;
if (type === 'create-proposal') {
return client.proposal(auth.web3, web3.value.account, {
space: space.id,
type: payload.type,
Expand All @@ -53,6 +56,17 @@ export function useClient() {
plugins: JSON.stringify(plugins),
app: DEFINED_APP
});
} else if (type === 'update-proposal') {
return client.updateProposal(auth.web3, web3.value.account, {
proposal: payload.id,
space: space.id,
type: payload.type,
title: payload.name,
body: payload.body,
discussion: payload.discussion,
choices: payload.choices,
plugins: JSON.stringify(plugins)
});
} else if (type === 'vote') {
return client.vote(auth.web3, web3.value.account, {
space: space.id,
Expand Down
2 changes: 1 addition & 1 deletion src/composables/useFormSpaceProposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function useFormSpaceProposal() {
isBodySet: boolean;
}>(`snapshot.proposal.${route.params.key}`, clone(EMPTY_PROPOSAL_DRAFT));

const sourceProposal = computed(() => route.params.sourceProposal);
const sourceProposal = computed(() => route.params.sourceProposal as string);

function resetForm() {
formDraft.value = clone(EMPTY_PROPOSAL_DRAFT);
Expand Down
1 change: 1 addition & 0 deletions src/locales/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@
"delegationSuccess": "Delegation successful",
"delegationRemoved": "Delegation removed",
"proposalCreated": "Proposal created",
"proposalUpdated": "Proposal updated",
"waitingForOtherSigners": "Waiting for other signers",
"voteSuccessful": "Your vote is in!",
"ensSet": "ENS text record was successfully set",
Expand Down
Loading

1 comment on commit a00f836

@vercel
Copy link

@vercel vercel bot commented on a00f836 Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.