Skip to content

Commit

Permalink
Merge branch 'master' into feat-add-link-to-v1
Browse files Browse the repository at this point in the history
  • Loading branch information
wa0x6e authored Dec 10, 2024
2 parents 62dafdf + 96d9bfa commit 79ca9f5
Show file tree
Hide file tree
Showing 9 changed files with 675 additions and 24 deletions.
151 changes: 151 additions & 0 deletions apps/ui/src/components/EditorTimeline.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<script setup lang="ts">
import { _d } from '@/helpers/utils';
import { offchainNetworks } from '@/networks';
import { Draft, Space } from '@/types';
type EditModalSettings = {
open: boolean;
editProperty: 'start' | 'min_end';
min?: number;
selected: number;
};
const MIN_VOTING_PERIOD = 60;
const proposal = defineModel<Draft>({
required: true
});
const props = defineProps<{
space: Space;
editable: boolean;
created: number;
start: number;
// eslint-disable-next-line vue/prop-name-casing
min_end: number;
// eslint-disable-next-line vue/prop-name-casing
max_end: number;
}>();
const { getDurationFromCurrent } = useMetaStore();
const editModalSettings = reactive<EditModalSettings>({
open: false,
editProperty: 'start',
selected: 0
});
const isOffchainSpace = computed(() =>
offchainNetworks.includes(props.space.network)
);
const minDates = computed(() => {
return {
start: props.created,
min_end: props.start + MIN_VOTING_PERIOD
};
});
function handleEditClick(type: 'start' | 'min_end') {
editModalSettings.selected = props[type];
editModalSettings.min = minDates.value[type];
editModalSettings.editProperty = type;
editModalSettings.open = true;
}
function handleDatePick(timestamp: number) {
if (
editModalSettings.editProperty === 'start' &&
proposal.value.min_end &&
timestamp >= proposal.value.min_end
) {
const customVotingPeriod = props.min_end - props.start;
proposal.value.min_end = timestamp + customVotingPeriod;
}
proposal.value[editModalSettings.editProperty] = timestamp;
}
function formatVotingDuration(
type: 'voting_delay' | 'min_voting_period' | 'max_voting_period'
): string {
const duration = getDurationFromCurrent(
props.space.network,
props.space[type]
);
const roundedDuration = Math.round(duration / 60) * 60;
return _d(roundedDuration);
}
</script>

<template>
<div>
<h4 class="eyebrow mb-2.5" v-text="'Timeline'" />
<ProposalTimeline
:data="
isOffchainSpace || !editable
? {
...space,
created,
start,
min_end,
max_end
}
: space
"
>
<template v-if="editable" #start-date-suffix>
<UiTooltip
v-if="space.voting_delay"
:title="`This space has enforced a ${formatVotingDuration('voting_delay')} voting delay`"
>
<IH-exclamation-circle />
</UiTooltip>
<button
v-else-if="isOffchainSpace"
class="text-skin-link"
@click="handleEditClick('start')"
v-text="'Edit'"
/>
</template>
<template v-if="editable" #end-date-suffix>
<UiTooltip
v-if="space.min_voting_period"
:title="`This space has enforced a ${formatVotingDuration('min_voting_period')} voting period`"
>
<IH-exclamation-circle />
</UiTooltip>
<button
v-else-if="isOffchainSpace"
class="text-skin-link"
@click="handleEditClick('min_end')"
v-text="'Edit'"
/>
</template>
<template v-if="editable && space.min_voting_period" #min_end-date-suffix>
<UiTooltip
:title="`This space has enforced a ${formatVotingDuration('min_voting_period')} minimum voting period`"
>
<IH-exclamation-circle />
</UiTooltip>
</template>
<template v-if="editable && space.max_voting_period" #max_end-date-suffix>
<UiTooltip
:title="`This space has enforced a ${formatVotingDuration('max_voting_period')} maximum voting period`"
>
<IH-exclamation-circle />
</UiTooltip>
</template>
</ProposalTimeline>
<teleport to="#modal">
<ModalDateTime
:min="editModalSettings.min"
:selected="editModalSettings.selected"
:open="editModalSettings.open"
@pick="handleDatePick"
@close="editModalSettings.open = false"
/>
</teleport>
</div>
</template>
151 changes: 151 additions & 0 deletions apps/ui/src/components/Modal/DateTime.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<script setup lang="ts">
import dayjs from 'dayjs';
const TIME_FORMAT = 'HH:mm';
const STEPS = {
date: { id: 'date', title: 'Select date' },
time: { id: 'time', title: 'Select time' }
};
const props = defineProps<{
open: boolean;
min?: number;
selected: number;
}>();
const emit = defineEmits<{
(e: 'close'): void;
(e: 'pick', timestamp: number): void;
}>();
const { current, isCurrent, goTo, goToNext, goToPrevious, isFirst, isLast } =
useStepper(STEPS);
const date = ref<number>(props.selected);
const time = ref<string>(getBaseTime(props.selected));
const formError = ref<null | string>(null);
function handleClose() {
emit('close');
}
function handleSubmit() {
handleClose();
emit('pick', date.value);
}
function handleDateUpdate(ts: number) {
date.value = ts;
time.value = getBaseTime(ts);
goToNext();
}
function getBaseTime(ts: number): string {
const originalDate = dayjs.unix(props.selected);
const selectedDate = dayjs
.unix(ts)
.set('hour', originalDate.get('hour'))
.set('minute', originalDate.get('minute'));
if (props.min) {
const minDate = dayjs.unix(props.min);
if (selectedDate.isBefore(minDate, 'minute')) {
return minDate.format(TIME_FORMAT);
}
}
return selectedDate.format(TIME_FORMAT);
}
function updateDateWithTime() {
const [hours, minutes] = time.value.split(':');
date.value = dayjs
.unix(date.value)
.set('hour', +hours)
.set('minute', +minutes)
.startOf('minute')
.unix();
}
function validateForm() {
if (!props.min) return;
const minDate = dayjs.unix(props.min).startOf('minute');
if (date.value < minDate.unix()) {
formError.value = `Time must be equal or greater than ${minDate.format(TIME_FORMAT)}`;
return;
}
formError.value = null;
}
watch([() => current.value.id, time], ([stepId]) => {
if (stepId !== 'time') return;
updateDateWithTime();
validateForm();
});
watch(
() => props.open,
open => {
if (open) {
goTo('date');
date.value = props.selected;
time.value = getBaseTime(props.selected);
}
}
);
</script>

<template>
<UiModal :open="open" @close="handleClose">
<template #header>
<h3 v-text="current.title" />
</template>
<div :class="['!m-4 text-center', { 's-error': formError }]">
<UiCalendar
v-if="isCurrent('date')"
:min="min"
:selected="date"
@pick="handleDateUpdate"
/>
<template v-else-if="isCurrent('time')">
<input
v-model="time"
type="time"
class="s-input mx-auto max-w-[140px] text-center text-lg"
/>
<span
v-if="formError"
class="s-input-error-message"
v-text="formError"
/>
</template>
</div>
<template #footer>
<div class="flex space-x-3">
<UiButton v-if="isFirst" class="w-full" @click="handleClose">
Cancel
</UiButton>
<UiButton v-else class="w-full" @click="goToPrevious">
Previous
</UiButton>
<UiButton v-if="!isLast" class="primary w-full" @click="goToNext">
Next
</UiButton>
<UiButton
v-else
class="primary w-full"
:disabled="!!formError"
@click="handleSubmit"
>
Confirm
</UiButton>
</div>
</template>
</UiModal>
</template>
5 changes: 4 additions & 1 deletion apps/ui/src/components/ProposalTimeline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ function isInThePast(timestamp: number): boolean {
class="mb-3 last:mb-0 h-[44px]"
>
<h4 v-text="LABELS[state.id]" />
{{ _t(state.value) }}
<div class="flex gap-2 items-center">
<div v-text="_t(state.value)" />
<slot :name="`${state.id}-date-suffix`" />
</div>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 79ca9f5

Please sign in to comment.