From b6dcc1ac989fb5ce49c6c8196e78f9357bbef7da Mon Sep 17 00:00:00 2001 From: Megha <100185149+Megha-Dev-19@users.noreply.github.com> Date: Fri, 9 Aug 2024 23:41:18 +0530 Subject: [PATCH] Refactor proposal feed component (#912) * fix solution action * fix preview for proposals * reused feed component * delete components * remove comments * update test * move all props to one file --- instances/devhub.near/widget/app.jsx | 30 +- instances/devhub.near/widget/config/css.jsx | 26 + instances/devhub.near/widget/config/data.jsx | 97 +++ instances/devhub.near/widget/config/theme.jsx | 5 + .../widget/devhub/entity/proposal/Feed.jsx | 234 ++++--- .../devhub/entity/proposal/LikeButton.jsx | 3 +- .../proposal/MultiSelectLabelsDropdown.jsx | 193 ++++++ .../feature/proposal-search/by-author.jsx | 7 +- .../feature/proposal-search/by-category.jsx | 66 +- .../widget/devhub/page/proposals.jsx | 4 +- .../events-committee.near/widget/app.jsx | 30 +- .../widget/config/css.jsx | 17 + .../widget/config/data.jsx | 52 ++ .../widget/config/theme.jsx | 5 + .../widget/devhub/entity/proposal/Feed.jsx | 582 ----------------- .../feature/proposal-search/by-author.jsx | 42 -- .../feature/proposal-search/by-category.jsx | 51 -- .../feature/proposal-search/by-sort.jsx | 27 - .../feature/proposal-search/by-stage.jsx | 30 - .../widget/devhub/page/proposals.jsx | 6 +- .../widget/app.jsx | 37 +- .../widget/components/proposals/Feed.jsx | 612 ------------------ .../widget/components/proposals/Proposals.jsx | 8 + .../widget/config/css.jsx | 22 + .../widget/config/data.jsx | 71 ++ .../widget/config/theme.jsx | 5 + package.json | 1 + .../tests/proposal/proposals.spec.js | 2 +- 28 files changed, 691 insertions(+), 1574 deletions(-) create mode 100644 instances/devhub.near/widget/config/css.jsx create mode 100644 instances/devhub.near/widget/config/data.jsx create mode 100644 instances/devhub.near/widget/config/theme.jsx create mode 100644 instances/devhub.near/widget/devhub/entity/proposal/MultiSelectLabelsDropdown.jsx create mode 100644 instances/events-committee.near/widget/config/css.jsx create mode 100644 instances/events-committee.near/widget/config/data.jsx create mode 100644 instances/events-committee.near/widget/config/theme.jsx delete mode 100644 instances/events-committee.near/widget/devhub/entity/proposal/Feed.jsx delete mode 100644 instances/events-committee.near/widget/devhub/feature/proposal-search/by-author.jsx delete mode 100644 instances/events-committee.near/widget/devhub/feature/proposal-search/by-category.jsx delete mode 100644 instances/events-committee.near/widget/devhub/feature/proposal-search/by-sort.jsx delete mode 100644 instances/events-committee.near/widget/devhub/feature/proposal-search/by-stage.jsx delete mode 100644 instances/infrastructure-committee.near/widget/components/proposals/Feed.jsx create mode 100644 instances/infrastructure-committee.near/widget/components/proposals/Proposals.jsx create mode 100644 instances/infrastructure-committee.near/widget/config/css.jsx create mode 100644 instances/infrastructure-committee.near/widget/config/data.jsx create mode 100644 instances/infrastructure-committee.near/widget/config/theme.jsx diff --git a/instances/devhub.near/widget/app.jsx b/instances/devhub.near/widget/app.jsx index 83b9e40aa..be0619899 100644 --- a/instances/devhub.near/widget/app.jsx +++ b/instances/devhub.near/widget/app.jsx @@ -14,27 +14,13 @@ const { AppLayout } = VM.require( "${REPL_DEVHUB}/widget/devhub.components.templates.AppLayout" ); -if (!AppLayout) { +const { CssContainer } = VM.require("${REPL_DEVHUB}/widget/config.css"); +const { Theme } = VM.require("${REPL_DEVHUB}/widget/config.theme"); + +if (!AppLayout || !Theme || !CssContainer) { return

Loading modules...

; } -// CSS styles to be used across the app. -// Define fonts here, as well as any other global styles. -const Theme = styled.div` - a { - color: inherit; - } - - .attractable { - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; - transition: box-shadow 0.6s; - - &:hover { - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; - } - } -`; - if (!page) { // If no page is specified, we default to the feed page TEMP page = "home"; @@ -256,8 +242,10 @@ function Page() { return ( - - - + + + + + ); diff --git a/instances/devhub.near/widget/config/css.jsx b/instances/devhub.near/widget/config/css.jsx new file mode 100644 index 000000000..56ba8b991 --- /dev/null +++ b/instances/devhub.near/widget/config/css.jsx @@ -0,0 +1,26 @@ +const CssContainer = styled.div` + .theme-btn { + background-color: var(--theme-color) !important; + border: none; + color: white; + + &:active { + color: white; + } + } + + a { + color: inherit; + } + + .attractable { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; + transition: box-shadow 0.6s; + + &:hover { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; + } + } +`; + +return { CssContainer }; diff --git a/instances/devhub.near/widget/config/data.jsx b/instances/devhub.near/widget/config/data.jsx new file mode 100644 index 000000000..ce40e902a --- /dev/null +++ b/instances/devhub.near/widget/config/data.jsx @@ -0,0 +1,97 @@ +const proposalFeedAnnouncement = ( +
+

+

+ +
+
+ + Welcome to + + DevDAO’s New Proposal Feed! + + + This dedicated space replaces the + + old activity feed + + , making it easier to submit and track funding requests from DevDAO, the + primary organization behind DevHub. To submit a formal proposal, click + New Proposal. See our{" "} + + guidelines + + for details. For discussions and brainstorming, please utilize the + relevant{" "} + + communities + + . +
+

+
+); + +const categoryOptions = [ + { + title: "DevDAO Operations", + value: "DevDAO Operations", + }, + { + title: "DevDAO Platform", + value: "DevDAO Platform", + }, + { + title: "Events & Hackathons", + value: "Events & Hackathons", + }, + { + title: "Engagement & Awareness", + value: "Engagement & Awareness", + }, + { + title: "Decentralized DevRel", + value: "Decentralized DevRel", + }, + { + title: "Universities & Bootcamps", + value: "Universities & Bootcamps", + }, + { + title: "Tooling & Infrastructure", + value: "Tooling & Infrastructure", + }, + { + title: "Other", + value: "Other", + }, +]; + +return { + contract: "devhub.near", + proposalFeedIndexerQueryName: + "polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot", + indexerHasuraRole: "polyprogrammist_near", + isDevhub: true, + proposalFeedAnnouncement, + availableCategoryOptions: categoryOptions, +}; diff --git a/instances/devhub.near/widget/config/theme.jsx b/instances/devhub.near/widget/config/theme.jsx new file mode 100644 index 000000000..b5432c913 --- /dev/null +++ b/instances/devhub.near/widget/config/theme.jsx @@ -0,0 +1,5 @@ +const Theme = styled.div` + --theme-color: rgb(4, 164, 110); +`; + +return { Theme }; diff --git a/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx b/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx index 87cf5f5fc..17455a438 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx @@ -1,7 +1,33 @@ -const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url"); +const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url") || { + href: () => {}, +}; + +const instance = props.instance ?? ""; + +const { + contract, + rfpFeedIndexerQueryName, + proposalFeedAnnouncement, + availableCategoryOptions, + proposalFeedIndexerQueryName, + indexerHasuraRole, + isDevhub, + isInfra, + isEvents, +} = VM.require(`${instance}/widget/config.data`); + +const loader = ( +
+ +
+); + +if (!contract) { + return loader; +} -if (!href) { - return

Loading modules...

; +function isNumber(v) { + return typeof v === "number"; } const Container = styled.div` @@ -49,18 +75,8 @@ const Container = styled.div` } } - .green-btn { - background-color: #04a46e !important; - border: none; - color: white; - - &:active { - color: white; - } - } - @media screen and (max-width: 768px) { - .green-btn { + .theme-btn { padding: 0.5rem 0.8rem !important; min-height: 32px; } @@ -115,14 +131,17 @@ const FeedItem = ({ proposal, index }) => { const blockHeight = parseInt(proposal.social_db_post_block_height); const item = { type: "social", - path: `${REPL_DEVHUB_CONTRACT}/post/main`, + path: `${contract}/post/main`, blockHeight: blockHeight, }; + const isLinked = isNumber(proposal.linked_rfp); + const rfpData = proposal.rfpData; + return ( {
{proposal.name}
- + {(isInfra || isEvents) && ( + {}, + availableOptions: availableCategoryOptions, + }} + /> + )} + {isDevhub && ( + + )}
+ {isLinked && rfpData && ( +
+ + In response to RFP : + + {rfpData.name} + +
+ )}
#{proposal.proposal_id} ・
@@ -174,6 +229,7 @@ const FeedItem = ({ proposal, index }) => { item, proposalId: proposal.id, notifyAccountId: accountId, + instance, }} /> @@ -202,7 +258,7 @@ const FeedItem = ({ proposal, index }) => { }; const getProposal = (proposal_id) => { - return Near.asyncView("${REPL_DEVHUB_CONTRACT}", "get_proposal", { + return Near.asyncView(contract, "get_proposal", { proposal_id, }); }; @@ -224,9 +280,8 @@ const FeedPage = () => { currentlyDisplaying: 0, }); - const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}"; - const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) { - ${queryName}( + const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${proposalFeedIndexerQueryName}_bool_exp = {}) { + ${proposalFeedIndexerQueryName}( offset: $offset limit: $limit order_by: {proposal_id: desc} @@ -242,8 +297,10 @@ const FeedPage = () => { ts timeline views + labels + linked_rfp } - ${queryName}_aggregate( + ${proposalFeedIndexerQueryName}_aggregate( order_by: {proposal_id: desc} where: $where ) { @@ -256,7 +313,7 @@ const FeedPage = () => { function fetchGraphQL(operationsDoc, operationName, variables) { return asyncFetch(QUERYAPI_ENDPOINT, { method: "POST", - headers: { "x-hasura-role": "${REPL_INDEXER_HASURA_ROLE}" }, + headers: { "x-hasura-role": indexerHasuraRole }, body: JSON.stringify({ query: operationsDoc, variables: variables, @@ -284,7 +341,11 @@ const FeedPage = () => { } if (state.category) { - where = { category: { _eq: state.category }, ...where }; + if (isInfra || isEvents) { + where = { labels: { _contains: state.label }, ...where }; + } else { + where = { category: { _eq: state.category }, ...where }; + } } if (state.stage) { @@ -335,11 +396,25 @@ const FeedPage = () => { fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => { if (result.status === 200) { if (result.body.data) { - const data = result.body.data[queryName]; - const totalResult = result.body.data[`${queryName}_aggregate`]; - State.update({ aggregatedCount: totalResult.aggregate.count }); - // Parse timeline - fetchBlockHeights(data, offset); + const data = result.body.data[proposalFeedIndexerQueryName]; + const totalResult = + result.body.data[`${proposalFeedIndexerQueryName}_aggregate`]; + const promises = data.map((item) => { + if (isNumber(item.linked_rfp)) { + return fetchGraphQL(rfpQuery, "GetLatestSnapshot", {}).then( + (result) => { + const rfpData = result.body.data?.[rfpQueryName]; + return { ...item, rfpData: rfpData[0] }; + } + ); + } else { + return Promise.resolve(item); + } + }); + Promise.all(promises).then((res) => { + State.update({ aggregatedCount: totalResult.aggregate.count }); + fetchBlockHeights(res, offset); + }); } } }); @@ -394,14 +469,6 @@ const FeedPage = () => { }); }; - const loader = ( -
- -
- ); - useEffect(() => { const handler = setTimeout(() => { fetchProposals(); @@ -416,7 +483,7 @@ const FeedPage = () => {
- DevDAO Proposals + Proposals ({state.aggregatedCount ?? state.data.length}){" "} @@ -446,16 +513,19 @@ const FeedPage = () => { }} />
- { - State.update({ category: select.value }); - }, - }} - /> + {!isInfra && ( + { + State.update({ category: select.value }); + }, + }} + /> + )} { "${REPL_DEVHUB}/widget/devhub.feature.proposal-search.by-author" } props={{ + contract, onAuthorChange: (select) => { State.update({ author: select.value }); }, @@ -481,7 +552,7 @@ const FeedPage = () => {
@@ -493,10 +564,10 @@ const FeedPage = () => {
- New Proposal + Submit Proposal
), - classNames: { root: "green-btn" }, + classNames: { root: "theme-btn" }, }} /> @@ -508,56 +579,7 @@ const FeedPage = () => { ) : (
-
-

-

- -
-
- - Welcome to - - DevDAO’s New Proposal Feed! - - - This dedicated space replaces the - - old activity feed - - , making it easier to submit and track funding requests from - DevDAO, the primary organization behind DevHub. To submit a - formal proposal, click New Proposal. See our{" "} - - guidelines - - for details. For discussions and brainstorming, please - utilize the relevant{" "} - - communities - - . -
-

-
+ {proposalFeedAnnouncement}
{state.aggregatedCount === 0 ? ( -
- {state.aggregatedCount === null ? ( - loader - ) : ( -
-
-
-

-

- -
-
- - Welcome to the - {/* */} - Events Committee Proposal Feed! - {/* */} - - This dedicated space makes it easy to submit and track - funding proposals from the Events Committee, the cross-team - organization responsible for hosting and sponsoring - developer-focused events. You are welcome to respond to any - RFPs that are accepting submissions or submit an independent - proposal. -
-

-
-
- {state.aggregatedCount === 0 ? ( - - ) : state.aggregatedCount > 0 ? ( - state.data.length} - loader={loader} - useWindow={false} - threshold={50} - > - {renderedItems} - - ) : ( - loader - )} -
-
-
- )} -
- - ); -}; - -return FeedPage(props); diff --git a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-author.jsx b/instances/events-committee.near/widget/devhub/feature/proposal-search/by-author.jsx deleted file mode 100644 index 62f61c989..000000000 --- a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-author.jsx +++ /dev/null @@ -1,42 +0,0 @@ -const [authorsOptions, setAuthorsOptions] = useState([]); -const [selectedAuthor, setSelectedAuthor] = useState(null); - -if (!authorsOptions.length) { - const data = [{ label: "All", value: "" }]; - const authors = Near.view( - "${REPL_EVENTS_CONTRACT}", - "get_all_proposal_authors", - {} - ); - - if (Array.isArray(authors)) { - for (const author of authors) { - data.push({ label: author, value: author }); - } - setAuthorsOptions(data); - } -} - -const Container = styled.div` - .dropdown-menu { - max-height: 400px; - overflow-x: auto; - } -`; - -return ( - - { - setSelectedAuthor(v); - props.onAuthorChange(v); - }, - selectedValue: props.author, - }} - /> - -); diff --git a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-category.jsx b/instances/events-committee.near/widget/devhub/feature/proposal-search/by-category.jsx deleted file mode 100644 index 5028f0ab2..000000000 --- a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-category.jsx +++ /dev/null @@ -1,51 +0,0 @@ -const options = [ - { - label: "All", - value: "", - }, - { - label: "Bounty", - value: "Bounty", - }, - { - label: "Bounty booster", - value: "Bounty booster", - }, - { - label: "Hackathon", - value: "Hackathon", - }, - { - label: "Hackbox", - value: "Hackbox", - }, - { - label: "Event sponsorship", - value: "Event sponsorship", - }, - { - label: "Meetup", - value: "Meetup", - }, - { - label: "Travel expenses", - value: "Travel expenses", - }, -]; - -const setSelected = props.onStateChange ?? (() => {}); - -return ( -
- { - setSelected(v); - }, - }} - /> -
-); diff --git a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-sort.jsx b/instances/events-committee.near/widget/devhub/feature/proposal-search/by-sort.jsx deleted file mode 100644 index e628373b5..000000000 --- a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-sort.jsx +++ /dev/null @@ -1,27 +0,0 @@ -const options = [ - { label: "All", value: "" }, - { label: "Most recent", value: "proposal_id" }, // proposal_id desc - { label: "Most viewed", value: "views" }, // views desc - // TODO add track_comments function to devhub to track it in the indexer - // { label: "Most commented", value: "" }, // comments desc - // { label: "Unanswered", value: "" }, // where comments = 0 - // where comments = 0 -]; - -const setSelected = props.onStateChange ?? (() => {}); - -return ( -
- { - setSelected(v); - }, - }} - /> -
-); diff --git a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-stage.jsx b/instances/events-committee.near/widget/devhub/feature/proposal-search/by-stage.jsx deleted file mode 100644 index 40f80df3b..000000000 --- a/instances/events-committee.near/widget/devhub/feature/proposal-search/by-stage.jsx +++ /dev/null @@ -1,30 +0,0 @@ -// The timeline is stored in jsonb. The value is used to search for as part of -// that json so it doesn't need to be an exact match. -const options = [ - { label: "All", value: "" }, - { label: "Draft", value: "DRAFT" }, - { label: "Review", value: "REVIEW" }, - { label: "Approved", value: "APPROVED" }, - { label: "Approved - Conditional", value: "CONDITIONAL" }, - { label: "Rejected", value: "REJECTED" }, - { label: "Cancelled", value: "CANCELLED" }, - { label: "Payment Processing", value: "PAYMENT" }, - { label: "Funded", value: "FUNDED" }, -]; - -const setSelected = props.onStateChange ?? (() => {}); - -return ( -
- { - setSelected(v); - }, - }} - /> -
-); diff --git a/instances/events-committee.near/widget/devhub/page/proposals.jsx b/instances/events-committee.near/widget/devhub/page/proposals.jsx index 4033d0993..3b4a79dec 100644 --- a/instances/events-committee.near/widget/devhub/page/proposals.jsx +++ b/instances/events-committee.near/widget/devhub/page/proposals.jsx @@ -1,6 +1,8 @@ return ( ); diff --git a/instances/infrastructure-committee.near/widget/app.jsx b/instances/infrastructure-committee.near/widget/app.jsx index a07dce898..a808c2b8a 100644 --- a/instances/infrastructure-committee.near/widget/app.jsx +++ b/instances/infrastructure-committee.near/widget/app.jsx @@ -10,26 +10,17 @@ const { AppLayout } = VM.require( `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/components.template.AppLayout` ); -if (!AppLayout) { - return

Loading modules...

; -} - -// CSS styles to be used across the app. -// Define fonts here, as well as any other global styles. -const Theme = styled.div` - a { - color: inherit; - } +const { Theme } = VM.require( + `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/config.theme` +); - .attractable { - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; - transition: box-shadow 0.6s; +const { CssContainer } = VM.require( + `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/config.css` +); - &:hover { - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; - } - } -`; +if (!AppLayout || !Theme || !CssContainer) { + return

Loading modules...

; +} if (!page) { // If no page is specified, we default to the feed page TEMP @@ -84,7 +75,7 @@ function Page() { case "proposals": { return ( ); @@ -122,8 +113,10 @@ function Page() { return ( - - - + + + + + ); diff --git a/instances/infrastructure-committee.near/widget/components/proposals/Feed.jsx b/instances/infrastructure-committee.near/widget/components/proposals/Feed.jsx deleted file mode 100644 index 496df6eba..000000000 --- a/instances/infrastructure-committee.near/widget/components/proposals/Feed.jsx +++ /dev/null @@ -1,612 +0,0 @@ -const { fetchGraphQL, parseJSON, isNumber } = VM.require( - `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/core.common` -) || { parseJSON: () => {}, isNumber: () => {} }; - -const { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`); -href || (href = () => {}); - -const { getGlobalLabels } = VM.require( - `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/components.core.lib.contract` -) || { getGlobalLabels: () => {} }; - -const Container = styled.div` - .full-width-div { - width: 100vw; - position: relative; - left: 50%; - right: 50%; - margin-left: -50vw; - margin-right: -50vw; - } - - .card.no-border { - border-left: none !important; - border-right: none !important; - margin-bottom: -3.5rem; - } - - @media screen and (max-width: 768px) { - font-size: 13px; - } - - .text-sm { - font-size: 13px; - } - - .bg-grey { - background-color: #f4f4f4; - } - - .border-bottom { - border-bottom: 1px solid grey; - } - - .cursor-pointer { - cursor: pointer; - } - - .proposal-card { - border-left: none !important; - border-right: none !important; - border-bottom: none !important; - &:hover { - background-color: #f4f4f4; - } - } - - .blue-btn { - background-color: #3c697d !important; - border: none; - color: white; - - &:active { - color: white; - } - } - - @media screen and (max-width: 768px) { - .blue-btn { - padding: 0.5rem 0.8rem !important; - min-height: 32px; - } - } - - a.no-space { - display: inline-block; - } - - .text-wrap { - overflow: hidden; - white-space: normal; - } - - .bg-blue { - background-image: linear-gradient(to bottom, #4b7a93, #213236); - color: white; - } -`; - -const Heading = styled.div` - font-size: 24px; - font-weight: 700; - width: 100%; - - .text-normal { - font-weight: normal !important; - } - - @media screen and (max-width: 768px) { - font-size: 18px; - } -`; - -const rfpLabelOptions = getGlobalLabels(); - -const FeedItem = ({ proposal, index }) => { - const accountId = proposal.author_id; - proposal.timeline = parseJSON(proposal.timeline); - const profile = Social.get(`${accountId}/profile/**`, "final"); - // We will have to get the proposal from the contract to get the block height. - const blockHeight = parseInt(proposal.social_db_post_block_height); - const item = { - type: "social", - path: `${REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT}/post/main`, - blockHeight: blockHeight, - }; - - const isLinked = isNumber(proposal.linked_rfp); - const rfpData = proposal.rfpData; - - return ( - e.stopPropagation()} - style={{ textDecoration: "none" }} - > -
-
- -
-
-
{proposal.name}
- {}, - availableOptions: rfpLabelOptions, - }} - /> -
- {isLinked && rfpData && ( -
- )} -
-
#{proposal.proposal_id} ・
-
- By {profile.name ?? accountId} ・{" "} -
- -
-
- - - {}, - }} - /> -
-
-
-
- -
-
- - ); -}; - -const getProposal = (proposal_id) => { - return Near.asyncView( - `${REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT}`, - "get_proposal", - { - proposal_id, - } - ); -}; - -const FeedPage = () => { - State.init({ - data: [], - cachedItems: {}, - stage: "", - sort: "", - label: "", - input: "", - loading: false, - loadingMore: false, - aggregatedCount: null, - currentlyDisplaying: 0, - isFiltered: false, - }); - - const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}"; - const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) { - ${queryName}( - offset: $offset - limit: $limit - order_by: {proposal_id: desc} - where: $where - ) { - author_id - block_height - name - labels - summary - editor_id - proposal_id - ts - timeline - views - linked_rfp - } - ${queryName}_aggregate( - order_by: {proposal_id: desc} - where: $where - ) { - aggregate { - count - } - } - }`; - - const rfpQueryName = "${REPL_RFP_FEED_INDEXER_QUERY_NAME}"; - const rfpQuery = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${rfpQueryName}_bool_exp = {}) { - ${rfpQueryName}( - offset: $offset - limit: $limit - order_by: {rfp_id: desc} - where: $where - ) { - name - rfp_id - } - }`; - - function separateNumberAndText(str) { - const numberRegex = /\d+/; - - if (numberRegex.test(str)) { - const number = str.match(numberRegex)[0]; - const text = str.replace(numberRegex, "").trim(); - return { number: parseInt(number), text }; - } else { - return { number: null, text: str.trim() }; - } - } - - const buildWhereClause = () => { - let where = {}; - - if (state.label) { - where = { labels: { _contains: state.label }, ...where }; - } - - if (state.stage) { - // timeline is stored as jsonb - where = { - timeline: { _cast: { String: { _regex: `${state.stage}` } } }, - ...where, - }; - } - if (state.input) { - const { number, text } = separateNumberAndText(state.input); - if (number) { - where = { proposal_id: { _eq: number }, ...where }; - } - - if (text) { - where = { - _or: [ - { name: { _iregex: `${text}` } }, - { summary: { _iregex: `${text}` } }, - { description: { _iregex: `${text}` } }, - ], - ...where, - }; - } - } - State.update({ isFiltered: Object.keys(where).length > 0 }); - return where; - }; - - const buildOrderByClause = () => { - /** - * TODO - * Most commented -> edit contract and indexer - * Unanswered -> 0 comments - */ - }; - - const makeMoreItems = () => { - if (state.aggregatedCount <= state.currentlyDisplaying) return; - fetchProposals(state.data.length); - }; - - const fetchProposals = (offset) => { - if (!offset) { - offset = 0; - } - if (state.loading) return; - const FETCH_LIMIT = 10; - const variables = { - limit: FETCH_LIMIT, - offset, - where: buildWhereClause(), - }; - if (typeof fetchGraphQL !== "function") { - return; - } - fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => { - if (result.status === 200) { - if (result.body.data) { - const data = result.body.data?.[queryName]; - const totalResult = result.body.data?.[`${queryName}_aggregate`]; - const promises = data.map((item) => { - if (isNumber(item.linked_rfp)) { - return fetchGraphQL(rfpQuery, "GetLatestSnapshot", {}).then( - (result) => { - const rfpData = result.body.data?.[rfpQueryName]; - return { ...item, rfpData: rfpData[0] }; - } - ); - } else { - return Promise.resolve(item); - } - }); - Promise.all(promises).then((res) => { - State.update({ aggregatedCount: totalResult.aggregate.count }); - fetchBlockHeights(res, offset); - }); - } - } - }); - }; - - const renderItem = (item, index) => ( -
- -
- ); - const cachedRenderItem = (item, index) => { - if (props.term) { - return renderItem(item, { - searchKeywords: [props.term], - }); - } - - const key = JSON.stringify(item); - - if (!(key in state.cachedItems)) { - state.cachedItems[key] = renderItem(item, index); - State.update(); - } - return state.cachedItems[key]; - }; - - useEffect(() => { - fetchProposals(); - }, [state.sort, state.label, state.stage]); - - const mergeItems = (newItems) => { - const items = [ - ...new Set([...newItems, ...state.data].map((i) => JSON.stringify(i))), - ].map((i) => JSON.parse(i)); - // Sorting in the front end - if (state.sort === "proposal_id" || state.sort === "") { - items.sort((a, b) => b.proposal_id - a.proposal_id); - } else if (state.sort === "views") { - items.sort((a, b) => b.views - a.views); - } - - return items; - }; - - const fetchBlockHeights = (data, offset) => { - let promises = data.map((item) => getProposal(item.proposal_id)); - Promise.all(promises).then((blockHeights) => { - data = data.map((item, index) => ({ - ...item, - timeline: JSON.parse(item.timeline), - social_db_post_block_height: - blockHeights[index].social_db_post_block_height, - })); - if (offset) { - let newData = mergeItems(data); - State.update({ - data: newData, - currentlyDisplaying: newData.length, - loading: false, - }); - } else { - State.update({ - data, - currentlyDisplaying: data.length, - loading: false, - }); - } - }); - }; - - const loader = ( -
- -
- ); - - const renderedItems = state.data ? state.data.map(cachedRenderItem) : null; - - return ( - -
- - Proposals - - ({state.aggregatedCount ?? state.data.length}){" "} - - -
- { - State.update({ input }); - fetchProposals(); - }, - onEnter: () => { - fetchProposals(); - }, - }} - /> - { - State.update({ sort: select.value }); - }, - }} - /> -
- { - State.update({ label: select.value }); - }, - availableOptions: rfpLabelOptions, - }} - /> - { - State.update({ stage: select.value }); - }, - }} - /> -
-
-
- - -
- -
- Submit Proposal -
- ), - classNames: { root: "blue-btn" }, - }} - /> - -
-
-
- {!Array.isArray(state.data) ? ( - loader - ) : ( -
-
-
-

-

- -
-
- - Welcome to the Infrastructure Committee Proposal Feed! - - This dedicated space makes it easy to submit and track - funding proposals from the Infrastructure Committee, the - primary organization overseeing improvements pertaining to - wallets, indexers, RPC services, explorers, oracles, - bridges, NEAR Protocol features, and related ecosystem - upgrades. You are welcome to respond to any RFPs that are - accepting submissions or submit an independent proposal. -
-

-
-
- {state.aggregatedCount === 0 ? ( -
- {state.isFiltered ? ( - - ) : ( - - )} -
- ) : state.aggregatedCount > 0 ? ( - state.data.length} - loader={loader} - useWindow={false} - threshold={100} - > - {renderedItems} - - ) : ( - loader - )} -
-
-
- )} -
- - ); -}; - -return FeedPage(props); diff --git a/instances/infrastructure-committee.near/widget/components/proposals/Proposals.jsx b/instances/infrastructure-committee.near/widget/components/proposals/Proposals.jsx new file mode 100644 index 000000000..eb97875f5 --- /dev/null +++ b/instances/infrastructure-committee.near/widget/components/proposals/Proposals.jsx @@ -0,0 +1,8 @@ +return ( + +); diff --git a/instances/infrastructure-committee.near/widget/config/css.jsx b/instances/infrastructure-committee.near/widget/config/css.jsx new file mode 100644 index 000000000..4925f7dfe --- /dev/null +++ b/instances/infrastructure-committee.near/widget/config/css.jsx @@ -0,0 +1,22 @@ +const CssContainer = styled.div` + .theme-btn { + background-color: var(--theme-color) !important; + border: none; + color: white; + + &:active { + color: white; + } + } + + a { + color: inherit; + } + + .bg-blue { + background-image: linear-gradient(to bottom, #4b7a93, #213236); + color: white; + } +`; + +return { CssContainer }; diff --git a/instances/infrastructure-committee.near/widget/config/data.jsx b/instances/infrastructure-committee.near/widget/config/data.jsx new file mode 100644 index 000000000..b1a84eaa2 --- /dev/null +++ b/instances/infrastructure-committee.near/widget/config/data.jsx @@ -0,0 +1,71 @@ +const { getGlobalLabels } = VM.require( + `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/components.core.lib.contract` +) || { getGlobalLabels: () => {} }; + +const proposalFeedAnnouncement = ( +
+

+

+ +
+
+ + Welcome to the Infrastructure Committee Proposal Feed! + + This dedicated space makes it easy to submit and track funding proposals + from the Infrastructure Committee, the primary organization overseeing + improvements pertaining to wallets, indexers, RPC services, explorers, + oracles, bridges, NEAR Protocol features, and related ecosystem + upgrades. You are welcome to respond to any RFPs that are accepting + submissions or submit an independent proposal. +
+

+
+); + +const categoryOptions = [ + { + title: "DevDAO Operations", + value: "DevDAO Operations", + }, + { + title: "DevDAO Platform", + value: "DevDAO Platform", + }, + { + title: "Events & Hackathons", + value: "Events & Hackathons", + }, + { + title: "Engagement & Awareness", + value: "Engagement & Awareness", + }, + { + title: "Decentralized DevRel", + value: "Decentralized DevRel", + }, + { + title: "Universities & Bootcamps", + value: "Universities & Bootcamps", + }, + { + title: "Tooling & Infrastructure", + value: "Tooling & Infrastructure", + }, + { + title: "Other", + value: "Other", + }, +]; + +return { + contract: "infrastructure-committee.near", + proposalFeedIndexerQueryName: + "polyprogrammist_near_devhub_ic_v1_proposals_with_latest_snapshot", + rfpFeedIndexerQueryName: + "polyprogrammist_near_devhub_ic_v1_rfps_with_latest_snapshot", + indexerHasuraRole: "polyprogrammist_near", + isInfra: true, + proposalFeedAnnouncement, + aavailableCategoryOptions: getGlobalLabels(), +}; diff --git a/instances/infrastructure-committee.near/widget/config/theme.jsx b/instances/infrastructure-committee.near/widget/config/theme.jsx new file mode 100644 index 000000000..137c824a0 --- /dev/null +++ b/instances/infrastructure-committee.near/widget/config/theme.jsx @@ -0,0 +1,5 @@ +const Theme = styled.div` + --theme-color: rgb(60, 105, 125); +`; + +return { Theme }; diff --git a/package.json b/package.json index 67aeb4d60..2a0e226bb 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'", "build": "npm run fmt", "bw": "bos-workspace", + "bw:dev:instances": "bw ws dev", "bw:dev:devhub": "bw dev instances/devhub.near", "bw:build:instance": "npm run bw build instances/$npm_config_instance build/$npm_config_instance && mv build/$npm_config_instance/src/widget/* build/$npm_config_instance/src/ && rm -Rf build/$npm_config_instance/src/widget", "bw:build:devhub": "npm run bw:build:instance --instance=devhub.near", diff --git a/playwright-tests/tests/proposal/proposals.spec.js b/playwright-tests/tests/proposal/proposals.spec.js index 4736a6363..be3a971fa 100644 --- a/playwright-tests/tests/proposal/proposals.spec.js +++ b/playwright-tests/tests/proposal/proposals.spec.js @@ -94,7 +94,7 @@ test.describe("Don't ask again enabled", () => { contractId: account, }); - await page.getByRole("button", { name: " New Proposal" }).click(); + await page.getByRole("button", { name: " Submit Proposal" }).click(); const titleArea = await page.getByRole("textbox").first(); await titleArea.fill("Test proposal 123456");