diff --git a/apps/ui/src/components/EditorTimeline.vue b/apps/ui/src/components/EditorTimeline.vue
new file mode 100644
index 000000000..ba532eac3
--- /dev/null
+++ b/apps/ui/src/components/EditorTimeline.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/src/components/Modal/DateTime.vue b/apps/ui/src/components/Modal/DateTime.vue
new file mode 100644
index 000000000..3602f3903
--- /dev/null
+++ b/apps/ui/src/components/Modal/DateTime.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Previous
+
+
+ Next
+
+
+ Confirm
+
+
+
+
+
diff --git a/apps/ui/src/components/ProposalTimeline.vue b/apps/ui/src/components/ProposalTimeline.vue
index 0ef8d86ef..f90d51d10 100644
--- a/apps/ui/src/components/ProposalTimeline.vue
+++ b/apps/ui/src/components/ProposalTimeline.vue
@@ -122,7 +122,10 @@ function isInThePast(timestamp: number): boolean {
class="mb-3 last:mb-0 h-[44px]"
>
- {{ _t(state.value) }}
+
diff --git a/apps/ui/src/components/Ui/Calendar.vue b/apps/ui/src/components/Ui/Calendar.vue
new file mode 100644
index 000000000..b2a28d2aa
--- /dev/null
+++ b/apps/ui/src/components/Ui/Calendar.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/src/helpers/boost.ts b/apps/ui/src/helpers/boost.ts
new file mode 100644
index 000000000..849fab6e4
--- /dev/null
+++ b/apps/ui/src/helpers/boost.ts
@@ -0,0 +1,139 @@
+import { ApolloClient, gql, InMemoryCache } from '@apollo/client/core';
+
+type BoostSubgraph = {
+ id: string;
+ strategyURI: string;
+ poolSize: string;
+ guard: string;
+ start: string;
+ end: string;
+ owner: string;
+ chainId: string;
+ currentBalance: string;
+ transaction: string;
+ token: {
+ id: string;
+ name: string;
+ symbol: string;
+ decimals: string;
+ };
+ strategy: {
+ id: string;
+ version: string;
+ name: string;
+ proposal: string;
+ eligibility: {
+ type: 'incentive' | 'prediction' | 'bribe';
+ choice: string | null;
+ };
+ distribution: {
+ type: 'weighted' | 'lottery';
+ limit: string | null;
+ numWinners: string | null;
+ };
+ };
+};
+
+const TWO_WEEKS = 1209600;
+
+const SUBGRAPH_URLS = {
+ '1': 'https://subgrapher.snapshot.org/subgraph/arbitrum/A6EEuSAB7mFrWvLBnL1HZXwfiGfqFYnFJjc14REtMNkd',
+ '11155111':
+ 'https://subgrapher.snapshot.org/subgraph/arbitrum/6T64qrPe7S46zhArSoBF8CAmc5cG3PyKa92Nt4Jhymcy',
+ '137':
+ 'https://subgrapher.snapshot.org/subgraph/arbitrum/CkNpf5gY7XPCinJWP1nh8K7u6faXwDjchGGV4P9rgJ7',
+ '8453':
+ 'https://subgrapher.snapshot.org/subgraph/arbitrum/52uVpyUHkkMFieRk1khbdshUw26CNHWAEuqLojZzcyjd'
+};
+
+const SUPPORTED_NETWORKS = Object.keys(SUBGRAPH_URLS);
+
+const boostQuery = gql(`query Boosts($proposalIds: [String!]) {
+ boosts (where: {strategy_: {proposal_in: $proposalIds}}) {
+ id
+ strategyURI
+ poolSize
+ guard
+ start
+ end
+ owner
+ currentBalance
+ transaction
+ token {
+ id
+ name
+ symbol
+ decimals
+ }
+ strategy {
+ id
+ name
+ version
+ proposal
+ eligibility {
+ type
+ choice
+ }
+ distribution {
+ type
+ limit
+ numWinners
+ }
+ }
+ }
+}`);
+
+async function getBoosts(proposalIds: string[]) {
+ async function query(chainId: string) {
+ const client = new ApolloClient({
+ uri: SUBGRAPH_URLS[chainId],
+ cache: new InMemoryCache()
+ });
+
+ const { data } = await client.query({
+ query: boostQuery,
+ variables: { proposalIds }
+ });
+
+ if (!data?.boosts) return { boosts: [] };
+ return {
+ boosts: data.boosts.map((boost: BoostSubgraph) => ({
+ ...boost,
+ chainId
+ }))
+ };
+ }
+
+ const requests = SUPPORTED_NETWORKS.map(chainId => query(chainId));
+ const responses: { boosts: BoostSubgraph[] }[] = await Promise.all(requests);
+
+ return responses
+ .map(response => response.boosts)
+ .filter(boost => boost)
+ .flat();
+}
+function sanitizeBoosts(
+ boosts: BoostSubgraph[],
+ bribeEnabled: boolean,
+ proposalEnd: number
+) {
+ return boosts.filter(
+ boost =>
+ !(
+ (bribeEnabled && boost.strategy.eligibility.type === 'bribe') ||
+ Number(boost.start) !== proposalEnd ||
+ Number(boost.end) - Number(boost.start) !== TWO_WEEKS
+ )
+ );
+}
+
+export async function getBoostsCount(
+ proposalID: string,
+ bribeEnabled: boolean,
+ proposalEnd: number
+): Promise {
+ const response = await getBoosts([proposalID]);
+ const sanitizedBoosts = sanitizeBoosts(response, bribeEnabled, proposalEnd);
+
+ return sanitizedBoosts.length;
+}
diff --git a/apps/ui/src/types.ts b/apps/ui/src/types.ts
index 7623c7afd..2bf39c641 100644
--- a/apps/ui/src/types.ts
+++ b/apps/ui/src/types.ts
@@ -373,6 +373,10 @@ export type Draft = {
labels: string[];
executions: Record;
updatedAt: number;
+ created?: number;
+ start?: number;
+ min_end?: number;
+ max_end?: number;
};
export type Metadata = {
diff --git a/apps/ui/src/views/Proposal.vue b/apps/ui/src/views/Proposal.vue
index a0f6427fa..20d3acb27 100644
--- a/apps/ui/src/views/Proposal.vue
+++ b/apps/ui/src/views/Proposal.vue
@@ -1,4 +1,5 @@