Skip to content

Commit

Permalink
feat: support offchain space creation
Browse files Browse the repository at this point in the history
  • Loading branch information
wa0x6e committed Jan 20, 2025
1 parent 86f772a commit 18d144e
Show file tree
Hide file tree
Showing 16 changed files with 1,360 additions and 151 deletions.
67 changes: 67 additions & 0 deletions apps/ui/src/components/FormEnsRegistration.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts" setup>
import { clone } from '@/helpers/utils';
import { getValidator } from '@/helpers/validation';
const VALID_EXTENSIONS = [
'eth',
'xyz',
'com',
'org',
'io',
'app',
'art',
'id'
] as const;
const DOMAIN_DEFINITION = {
type: 'string',
pattern: `^[a-zA-Z0-9\-\.]+\.(${VALID_EXTENSIONS.join('|')})$`,
title: 'ENS name',
examples: ['dao-name.eth'],
errorMessage: {
pattern: `Must be a valid domain ending with ${VALID_EXTENSIONS.join(', ')}`
}
} as const;
const definition = {
type: 'object',
additionalProperties: false,
required: [],
properties: {
domain: DOMAIN_DEFINITION
}
};
const emit = defineEmits<{
(e: 'submit');
}>();
const form = ref(clone({ domain: '' }));
const formErrors = computed(() => {
const validator = getValidator(definition);
return validator.validate(form.value, { skipEmptyOptionalFields: true });
});
const formValid = computed(() => {
return form.value.domain && Object.keys(formErrors.value).length === 0;
});
</script>

<template>
<div class="s-box">
<UiForm :model-value="form" :error="formErrors" :definition="definition" />
<UiButton
class="w-full"
:disabled="!formValid"
:to="
formValid
? `https://app.ens.domains/name/${form.domain}/register`
: undefined
"
@click="emit('submit')"
>
Register ENS name
</UiButton>
</div>
</template>
18 changes: 11 additions & 7 deletions apps/ui/src/components/FormSpaceStrategies.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { MAX_STRATEGIES } from '@/helpers/turbo';
import { StrategyConfig } from '@/networks/types';
import { NetworkID, Space } from '@/types';
import { NetworkID } from '@/types';
const snapshotChainId = defineModel<number>('snapshotChainId', {
required: true
Expand All @@ -10,11 +10,15 @@ const strategies = defineModel<StrategyConfig[]>('strategies', {
required: true
});
const props = defineProps<{
networkId: NetworkID;
isTicketValid: boolean;
space: Space;
}>();
const props = withDefaults(
defineProps<{
networkId: NetworkID;
isTicketValid: boolean;
space: { turbo: boolean; verified: boolean };
withNetworkSelector?: boolean;
}>(),
{ withNetworkSelector: true }
);
const strategiesLimit = computed(() => {
const spaceType = props.space.turbo
Expand All @@ -29,7 +33,7 @@ const strategiesLimit = computed(() => {

<template>
<h4 class="eyebrow mb-2 font-medium">Strategies</h4>
<div class="s-box mb-4">
<div v-if="withNetworkSelector" class="s-box mb-4">
<UiSelectorNetwork
v-model="snapshotChainId"
:definition="{
Expand Down
47 changes: 38 additions & 9 deletions apps/ui/src/components/FormVoting.vue
Original file line number Diff line number Diff line change
@@ -1,40 +1,69 @@
<script setup lang="ts">
import { validateForm } from '@/helpers/validation';
import { offchainNetworks } from '@/networks';
import { NetworkID } from '@/types';
const props = defineProps<{
form: any;
selectedNetworkId: NetworkID;
title: string;
title?: string;
description?: string;
}>();
const emit = defineEmits<{
(e: 'errors', value: any);
}>();
const isOffchainNetwork = computed(() =>
offchainNetworks.includes(props.selectedNetworkId)
);
const definition = computed(() => {
return {
type: 'object',
title: 'SpaceSettings',
additionalProperties: true,
required: ['votingDelay', 'minVotingDuration', 'maxVotingDuration'],
required: [
'votingDelay',
'minVotingDuration',
!isOffchainNetwork ? 'maxVotingDuration' : undefined
].filter(Boolean),
properties: {
votingDelay: {
type: 'number',
format: 'duration',
title: 'Voting delay'
title: 'Voting delay',
...(isOffchainNetwork
? {
maximum: 2592000,
errorMessage: {
maximum: 'Delay must be less than 30 days'
}
}
: {})
},
minVotingDuration: {
type: 'number',
format: 'duration',
title: 'Min. voting duration'
title: isOffchainNetwork ? 'Voting period' : 'Min. voting duration',
...(isOffchainNetwork
? {
maximum: 15552000,
errorMessage: {
maximum: 'Period must be less than 180 days'
}
}
: {})
},
maxVotingDuration: {
type: 'number',
format: 'duration',
title: 'Max. voting duration'
}
...(isOffchainNetwork
? {}
: {
maxVotingDuration: {
type: 'number',
format: 'duration',
title: 'Max. voting duration'
}
})
}
};
});
Expand Down
51 changes: 51 additions & 0 deletions apps/ui/src/components/Modal/SelectTokenStandard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script lang="ts" setup>
import { TokenStandard } from '@/components/SetupStrategiesOffchain.vue';
const ITEMS: Record<TokenStandard, { label: string; description: string }> = {
ERC20: {
label: 'ERC-20',
description: 'For tokens e.g. ETH, USDC, UNI, etc...'
},
ERC721: {
label: 'ERC-721',
description: 'For non-fungible tokens e.g. CryptoKitties, Doodle, etc...'
},
ERC1155: {
label: 'ERC-1155',
description:
'For both fungibles and non-fungibles tokens, e.g. MANA, Decentraland, etc...'
}
} as const;
defineProps<{
open: boolean;
initialState?: string;
}>();
const emit = defineEmits<{
(e: 'close');
(e: 'save', value: TokenStandard);
}>();
</script>

<template>
<UiModal :open="open" @close="emit('close')">
<template #header>
<h3>Select token standard</h3>
</template>
<div class="p-4 flex flex-col gap-2.5">
<UiSelector
v-for="(standard, id) in ITEMS"
:key="id"
:is-active="initialState === id"
class="w-full"
@click="emit('save', id)"
>
<div>
<h4 class="text-skin-link" v-text="standard.label" />
<div v-text="standard.description" />
</div>
</UiSelector>
</div>
</UiModal>
</template>
33 changes: 23 additions & 10 deletions apps/ui/src/components/Modal/SelectValidation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ const STRATEGIES_WITHOUT_PARAMS: ValidationDetails['key'][] = [
'only-members'
];
const props = defineProps<{
open: boolean;
networkId: NetworkID;
defaultChainId: ChainId;
space: Space;
type: 'voting' | 'proposal';
current?: Validation;
}>();
const props = withDefaults(
defineProps<{
open: boolean;
networkId: NetworkID;
defaultChainId: ChainId;
space?: Space;
type: 'voting' | 'proposal';
current?: Validation;
skipMenu?: boolean;
}>(),
{ skipMenu: false }
);
const emit = defineEmits<{
(e: 'save', type: Validation);
Expand Down Expand Up @@ -227,14 +231,23 @@ function handleApply() {
watch(
() => props.open,
value => {
async value => {
if (value) {
selectedValidation.value = null;
fetchValidations();
await fetchValidations();
if (props.current) {
form.value = clone(props.current.params);
rawParams.value = JSON.stringify(props.current.params, null, 2);
if (props.skipMenu) {
const selectedValidationDetail = filteredValidations.value.find(
v => v.key === props.current.name
);
if (selectedValidationDetail) {
handleSelect(selectedValidationDetail);
}
}
}
}
},
Expand Down
96 changes: 96 additions & 0 deletions apps/ui/src/components/ProposalValidationConfigurator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script lang="ts" setup>
import UiSelector from '@/components/Ui/Selector.vue';
import { VALIDATION_TYPES_INFO } from '@/helpers/constants';
import { NetworkID, Validation } from '@/types';
type ValidationDetailId = keyof typeof VALIDATION_TYPES_INFO;
type ValidationDetails = typeof VALIDATION_TYPES_INFO & {
key: ValidationDetailId;
tag?: string;
};
type ValidationRecords = Partial<Record<ValidationDetailId, ValidationDetails>>;
const PROPOSAL_VALIDATION_TYPES_ID: ValidationDetailId[] = [
'only-members',
'basic',
'passport-gated',
'arbitrum',
'karma-eas-attestation'
] as const;
const VALIDATIONS_WITHOUT_PARAMS: ValidationDetailId[] = [
'only-members'
] as const;
const PROPOSAL_VALIDATION_TAGS = {
'passport-gated': 'Beta'
} as const;
const validation = defineModel<Validation | null>({ required: true });
defineProps<{
networkId: NetworkID;
snapshotChainId: number;
}>();
const isSelectValidationModalOpen = ref(false);
const initialValidation = ref<Validation | undefined>();
const availableValidations = computed<ValidationRecords>(() => {
return PROPOSAL_VALIDATION_TYPES_ID.reduce((acc, id) => {
acc[id] = {
...VALIDATION_TYPES_INFO[id],
key: id,
tag: PROPOSAL_VALIDATION_TAGS[id]
};
return acc;
}, {});
});
function handleClick(validationId: ValidationDetailId) {
const selectedValidation = {
name: validationId,
params: {}
};
if (VALIDATIONS_WITHOUT_PARAMS.includes(validationId)) {
validation.value = selectedValidation;
return;
}
initialValidation.value =
validation.value?.name === validationId
? validation.value
: selectedValidation;
isSelectValidationModalOpen.value = true;
}
</script>

<template>
<div>
<div class="space-y-3">
<UiSelectorCard
:is="UiSelector"
v-for="(type, id) in availableValidations"
:key="id"
:item="type"
:selected="validation?.name === id"
:is-active="validation?.name === id"
@click="handleClick(id)"
/>
</div>
<teleport to="#modal">
<ModalSelectValidation
type="proposal"
:open="isSelectValidationModalOpen"
:network-id="networkId"
:default-chain-id="snapshotChainId"
:current="initialValidation"
:skip-menu="true"
@close="isSelectValidationModalOpen = false"
@save="value => (validation = value)"
/>
</teleport>
</div>
</template>
Loading

0 comments on commit 18d144e

Please sign in to comment.