Search
Search

Transaction: 2yv7J8W...btok

Signed by
Receiver
Status
Succeeded
Transaction Fee
0.0017 
Deposit Value
0.0035 
Gas Used
17 Tgas
Attached Gas
300 Tgas
Created
June 07, 2024 at 7:10:27pm
Hash
2yv7J8WFNx8FteKFy3x4eC4sjySyCyVyycUJgh3rbtok

Actions

Called method: 'set' in contract: social.near
Arguments:
{ "data": { "megha19.near": { "widget": { "near-prpsls-bos.components.proposals.Editor": { "": "/*\nLicense: MIT\nAuthor: devhub.near\nHomepage: https://github.com/NEAR-DevHub/near-prpsls-bos#readme\n*/\n/* INCLUDE: \"includes/common.jsx\" */\nconst REPL_DEVHUB = \"devhub.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE = \"megha19.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT = \"truedove38.near\";\nconst REPL_RPC_URL = \"https://rpc.mainnet.near.org\";\nconst REPL_NEAR = \"near\";\nconst REPL_SOCIAL_CONTRACT = \"social.near\";\nconst RFP_IMAGE =\n \"https://ipfs.near.social/ipfs/bafkreicbygt4kajytlxij24jj6tkg2ppc2dw3dlqhkermkjjfgdfnlizzy\";\n\nconst RFP_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfps_with_latest_snapshot\";\n\nconst RFP_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfp_snapshots\";\n\nconst PROPOSAL_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposals_with_latest_snapshot\";\n\nconst PROPOSAL_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposal_snapshots\";\nconst RFP_TIMELINE_STATUS = {\n ACCEPTING_SUBMISSIONS: \"ACCEPTING_SUBMISSIONS\",\n EVALUATION: \"EVALUATION\",\n PROPOSAL_SELECTED: \"PROPOSAL_SELECTED\",\n CANCELLED: \"CANCELLED\",\n};\n\nconst PROPOSAL_TIMELINE_STATUS = {\n DRAFT: \"DRAFT\",\n REVIEW: \"REVIEW\",\n APPROVED: \"APPROVED\",\n REJECTED: \"REJECTED\",\n CANCELED: \"CANCELLED\",\n APPROVED_CONDITIONALLY: \"APPROVED_CONDITIONALLY\",\n PAYMENT_PROCESSING: \"PAYMENT_PROCESSING\",\n FUNDED: \"FUNDED\",\n};\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\n\nasync function fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `polyprogrammist_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst CANCEL_RFP_OPTIONS = {\n CANCEL_PROPOSALS: \"CANCEL_PROPOSALS\",\n UNLINK_PROPOSALS: \"UNLINK_PROPOSALSS\",\n NONE: \"NONE\",\n};\n\nfunction parseJSON(json) {\n if (typeof json === \"string\") {\n try {\n return JSON.parse(json);\n } catch (error) {\n return json;\n }\n } else {\n return json;\n }\n}\n\nfunction isNumber(value) {\n return typeof value === \"number\";\n}\n\nconst PROPOSALS_APPROVED_STATUS_ARRAY = [\n PROPOSAL_TIMELINE_STATUS.APPROVED,\n PROPOSAL_TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n PROPOSAL_TIMELINE_STATUS.PAYMENT_PROCESSING,\n PROPOSAL_TIMELINE_STATUS.FUNDED,\n];\n\nfunction getLinkUsingCurrentGateway(url) {\n const data = fetch(`https://httpbin.org/headers`);\n const gatewayURL = data?.body?.headers?.Origin ?? \"\";\n return `https://${\n gatewayURL.includes(\"near.org\") ? \"dev.near.org\" : \"near.social\"\n }/${url}`;\n}\n/* END_INCLUDE: \"includes/common.jsx\" */\n\nconst { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`);\nhref || (href = () => {});\n\nconst { getGlobalLabels } = VM.require(\n `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.core.lib.contract`\n) || { getGlobalLabels: () => {} };\n\nconst { id, timestamp, rfp_id } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Funding-Process-%E2%80%90-Company\";\nconst ToCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Terms-&-Conditions\";\nconst CoCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Code-Of-Conduct\";\nif (!author) {\n return (\n <Widget src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.LoginScreen`} />\n );\n}\nlet editProposalData = null;\nlet draftProposalData = null;\nconst draftKey = \"INFRA_PROPOSAL_EDIT\";\n\nconst rfpLabelOptions = getGlobalLabels();\n\nif (isEditPage) {\n editProposalData = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_proposal\",\n {\n proposal_id: parseInt(id),\n }\n );\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !important;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n textarea {\n font-size: 14px !important;\n }\n\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .text-sm {\n font-size: 13px;\n }\n\n @media screen and (max-width: 768px) {\n .h6 {\n font-size: 14px !important;\n }\n\n .h5 {\n font-size: 16px !important;\n }\n\n .text-sm {\n font-size: 11px;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .border-bottom {\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n }\n\n .text-xs {\n font-size: 10px;\n }\n\n .flex-2 {\n flex: 2;\n }\n\n .flex-1 {\n flex: 1;\n }\n .bg-grey {\n background-color: #f4f4f4;\n }\n\n .border-bottom {\n border-bottom: 1px solid grey;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .proposal-card {\n &:hover {\n background-color: #f4f4f4;\n }\n }\n\n .border-1 {\n border: 1px solid #e2e6ec;\n }\n .green-btn {\n background-color: #03ba16 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .drop-btn {\n max-width: none !important;\n }\n\n .dropdown-menu {\n width: 100%;\n border-radius: 0.375rem !important;\n }\n\n .input-icon {\n display: flex;\n height: 100%;\n align-items: center;\n border-right: 1px solid #dee2e6;\n padding-right: 10px;\n }\n\n /* Tooltip container */\n .custom-tooltip {\n position: relative;\n display: inline-block;\n }\n\n /* Tooltip text */\n .custom-tooltip .tooltiptext {\n visibility: hidden;\n width: 250px;\n background-color: #fff;\n color: #6c757d;\n text-align: center;\n padding: 10px;\n border-radius: 6px;\n font-size: 12px;\n border: 0.2px solid #6c757d;\n\n /* Position the tooltip text */\n position: absolute;\n z-index: 1;\n bottom: 125%;\n left: -30px;\n\n /* Fade in tooltip */\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n /* Tooltip arrow */\n .custom-tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 15%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #555 transparent transparent transparent;\n }\n\n /* Show the tooltip text when you mouse over the tooltip container */\n .custom-tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n a.no-space {\n display: inline-block;\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst tokensOptions = [\n { label: \"NEAR\", value: \"NEAR\" },\n { label: \"USDT\", value: \"USDT\" },\n {\n label: \"USDC\",\n value: \"USDC\",\n },\n {\n label: \"Other\",\n value: \"OTHER\",\n },\n];\n\nconst [linkedRfp, setLinkedRfp] = useState(rfp_id ? parseInt(rfp_id) : null);\nconst [labels, setLabels] = useState([]);\nconst [title, setTitle] = useState(null);\nconst [description, setDescription] = useState(null);\nconst [summary, setSummary] = useState(null);\nconst [consent, setConsent] = useState({ toc: false, coc: false });\nconst [linkedProposals, setLinkedProposals] = useState([]);\nconst [receiverAccount, setReceiverAccount] = useState(context.accountId);\nconst [requestedSponsorshipAmount, setRequestedSponsorshipAmount] =\n useState(null);\nconst [requestedSponsorshipToken, setRequestedSponsorshipToken] = useState(\n tokensOptions[2]\n);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [loading, setLoading] = useState(true);\nconst [disabledSubmitBtn, setDisabledSubmitBtn] = useState(false);\nconst [isDraftBtnOpen, setDraftBtnOpen] = useState(false);\nconst [selectedStatus, setSelectedStatus] = useState(\"draft\");\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\n\nconst [showProposalViewModal, setShowProposalViewModal] = useState(false); // when user creates/edit a proposal and confirm the txn, this is true\nconst [proposalId, setProposalId] = useState(null);\nconst [proposalIdsArray, setProposalIdsArray] = useState(null);\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [oldProposalData, setOldProposalData] = useState(null);\nconst [supervisor, setSupervisor] = useState(null);\n\nif (allowDraft) {\n draftProposalData = Storage.privateGet(draftKey);\n}\n\nconst isModerator = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"is_allowed_to_write_rfps\",\n {\n editor: context.accountId,\n }\n);\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editProposalData.id ?? null,\n snapshot: {\n linked_rfp: linkedRfp,\n name: title,\n description: description,\n labels: labels,\n summary: summary,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n },\n }),\n [\n linkedRfp,\n title,\n summary,\n description,\n labels,\n requestedSponsorshipAmount,\n requestedSponsorshipToken,\n receiverAccount,\n ]\n);\n\nuseEffect(() => {\n if (allowDraft) {\n let data = editProposalData || JSON.parse(draftProposalData);\n let snapshot = data.snapshot;\n if (data) {\n if (timestamp) {\n snapshot =\n data.snapshot_history.find((item) => item.timestamp === timestamp) ??\n data.snapshot;\n }\n if (\n draftProposalData &&\n editProposalData &&\n editProposalData.id === JSON.parse(draftProposalData).id\n ) {\n snapshot = {\n ...editProposalData.snapshot,\n ...JSON.parse(draftProposalData).snapshot,\n };\n }\n if (!isNumber(linkedRfp)) {\n setLinkedRfp(snapshot.linked_rfp);\n }\n setLabels(snapshot.labels ?? []);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setReceiverAccount(snapshot.receiver_account);\n setRequestedSponsorshipAmount(snapshot.requested_sponsorship_usd_amount);\n setSupervisor(snapshot.supervisor);\n\n const token = tokensOptions.find(\n (item) => item.value === snapshot.requested_sponsorship_paid_in_currency\n );\n setRequestedSponsorshipToken(token ?? tokensOptions[2]);\n if (isEditPage) {\n setConsent({ toc: true, coc: true });\n }\n }\n }\n}, [editProposalData, draftProposalData, allowDraft]);\n\n// show loader until LS data is set in state\nuseEffect(() => {\n const handler = setTimeout(() => {\n setAllowDraft(false);\n setLoading(false);\n }, 500);\n\n return () => clearTimeout(handler);\n}, []);\n\nuseEffect(() => {\n if (showProposalViewModal) {\n return;\n }\n setDisabledSubmitBtn(\n isTxnCreated ||\n !title ||\n !description ||\n !summary ||\n !(labels ?? []).length ||\n !requestedSponsorshipAmount ||\n !receiverAccount ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 10000);\n\n return () => clearTimeout(handler);\n}, [\n memoizedDraftData,\n draftKey,\n draftProposalData,\n consent,\n isTxnCreated,\n showProposalViewModal,\n]);\n\n// set RFP labels, disable link rfp change when linked rfp is past accepting stage\nconst [disabledLinkRFP, setDisableLinkRFP] = useState(false);\n\nuseEffect(() => {\n if (linkedRfp) {\n Near.asyncView(REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT, \"get_rfp\", {\n rfp_id: linkedRfp.value ?? linkedRfp,\n }).then((i) => {\n const timeline = parseJSON(i.snapshot.timeline);\n setDisableLinkRFP(\n !isModerator &&\n timeline.status !== RFP_TIMELINE_STATUS.ACCEPTING_SUBMISSIONS\n );\n setLabels(i.snapshot.labels);\n });\n }\n}, [linkedRfp]);\n\nuseEffect(() => {\n if (\n editProposalData &&\n editProposalData?.snapshot?.linked_proposals?.length > 0\n ) {\n editProposalData.snapshot.linked_proposals.map((item) => {\n useCache(\n () =>\n Near.asyncView(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_proposal\",\n {\n proposal_id: parseInt(item),\n }\n ).then((proposal) => {\n setLinkedProposals([\n ...linkedProposals,\n {\n label: \"# \" + proposal.id + \" : \" + proposal.snapshot.name,\n value: proposal.id,\n },\n ]);\n }),\n item + \"linked_proposals\",\n { subscribe: false }\n );\n });\n }\n}, [editProposalData]);\n\nconst InputContainer = ({ heading, description, children }) => {\n return (\n <div className=\"d-flex flex-column gap-1 gap-sm-2 w-100\">\n <b className=\"h6 mb-0\">{heading}</b>\n {description && (\n <div className=\"text-muted w-100 text-sm\">{description}</div>\n )}\n {children}\n </div>\n );\n};\n\n// show proposal created after txn approval for popup wallet\nuseEffect(() => {\n if (isTxnCreated) {\n if (editProposalData) {\n setOldProposalData(editProposalData);\n if (\n editProposalData &&\n typeof editProposalData === \"object\" &&\n oldProposalData &&\n typeof oldProposalData === \"object\" &&\n JSON.stringify(editProposalData) !== JSON.stringify(oldProposalData)\n ) {\n setCreateTxn(false);\n setProposalId(editProposalData.id);\n setShowProposalViewModal(true);\n }\n } else {\n const proposalIds = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_proposal_ids\"\n );\n if (Array.isArray(proposalIds) && !proposalIdsArray) {\n setProposalIdsArray(proposalIds);\n }\n if (\n Array.isArray(proposalIds) &&\n Array.isArray(proposalIdsArray) &&\n proposalIds.length !== proposalIdsArray.length\n ) {\n setCreateTxn(false);\n setProposalId(proposalIds[proposalIds.length - 1]);\n setShowProposalViewModal(true);\n }\n }\n }\n setLoading(false);\n});\n\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(REPL_RPC_URL, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_post_transaction =\n transaction_method_name == \"add_proposal\" ||\n transaction_method_name == \"edit_proposal\";\n\n if (is_edit_or_add_post_transaction) {\n setShowProposalViewModal(true);\n Storage.privateSet(draftKey, null);\n }\n // show the latest created proposal to user\n if (transaction_method_name == \"add_proposal\") {\n useCache(\n () =>\n Near.asyncView(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_proposal_ids\"\n ).then((proposalIdsArray) => {\n setProposalId(\n proposalIdsArray?.[proposalIdsArray?.length - 1]\n );\n }),\n props.transactionHashes + \"proposalIds\",\n { subscribe: false }\n );\n } else {\n setProposalId(id);\n }\n setLoading(false);\n }),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n } else {\n if (showProposalViewModal) {\n setShowProposalViewModal(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst DropdowntBtnContainer = styled.div`\n font-size: 13px;\n min-width: 150px;\n\n .custom-select {\n position: relative;\n }\n\n .select-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n border: 1px solid #ccc;\n border-radius-top: 5px;\n cursor: pointer;\n background-color: #fff;\n border-radius: 5px;\n }\n\n .no-border {\n border: none !important;\n }\n\n .options-card {\n position: absolute;\n top: 100%;\n left: 0;\n width: 200%;\n border: 1px solid #ccc;\n background-color: #fff;\n padding: 0.5rem;\n z-index: 99;\n font-size: 13px;\n border-radius:0.375rem !important;\n }\n\n .left {\n right: 0 !important;\n left: auto !important;\n }\n\n @media screen and (max-width: 768px) {\n .options-card {\n right: 0 !important;\n left: auto !important;\n }\n }\n\n .option {\n margin-block: 5px;\n padding: 10px;\n cursor: pointer;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.3s ease;\n border-radius: 0.375rem !important;\n }\n\n .option:hover {\n background-color: #f0f0f0; /* Custom hover effect color */\n }\n\n .option:last-child {\n border-bottom: none;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .disabled {\n background-color: #f4f4f4 !important;\n cursor: not-allowed !important;\n font-weight: 500;\n color: #b3b3b3;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n .green {\n background-color: #04a46e;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n}\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-proposal-draft-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst SubmitBtn = () => {\n const btnOptions = [\n {\n iconColor: \"grey\",\n label: \"Submit Draft\",\n description:\n \"The author can still edit the proposal and build consensus before sharing it with sponsors.\",\n value: \"draft\",\n },\n {\n iconColor: \"green\",\n label: \"Ready for Review\",\n description:\n \"Start the official review process with sponsors. This will lock the editing function, but comments are still open.\",\n value: \"review\",\n },\n ];\n\n const handleOptionClick = (option) => {\n setDraftBtnOpen(false);\n setSelectedStatus(option.value);\n handleSubmit(option.value);\n };\n\n const toggleDropdown = () => {\n setDraftBtnOpen(!isDraftBtnOpen);\n };\n\n const handleSubmit = (status) => {\n const isDraft = status === \"draft\";\n if (isDraft) {\n onSubmit({ isDraft });\n cleanDraft();\n } else {\n setReviewModal(true);\n }\n };\n\n const selectedOption = btnOptions.find((i) => i.value === selectedStatus);\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => setDraftBtnOpen(false)}\n >\n <div\n className={\n \"select-header d-flex gap-1 align-items-center submit-draft-button \" +\n (disabledSubmitBtn && \"disabled\")\n }\n >\n <div\n onClick={() => !disabledSubmitBtn && handleSubmit(selectedStatus)}\n className=\"p-2 d-flex gap-2 align-items-center \"\n >\n {isTxnCreated ? (\n LoadingButtonSpinner\n ) : (\n <div className={\"circle \" + selectedOption.iconColor}></div>\n )}\n <div className={`selected-option`}>{selectedOption.label}</div>\n </div>\n <div\n className=\"h-100 p-2\"\n style={{ borderLeft: \"1px solid #ccc\" }}\n onClick={!disabledSubmitBtn && toggleDropdown}\n >\n <i class={`bi bi-chevron-${isDraftBtnOpen ? \"up\" : \"down\"}`}></i>\n </div>\n </div>\n\n {isDraftBtnOpen && (\n <div className=\"options-card\">\n {btnOptions.map((option) => (\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <div className={\"circle \" + option.iconColor}></div>\n <div className=\"fw-bold\">{option.label}</div>\n </div>\n <div className=\"text-muted text-xs\">{option.description}</div>\n </div>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nconst onSubmit = ({ isDraft, isCancel }) => {\n setCreateTxn(true);\n console.log(\"submitting transaction\");\n const linkedProposalsIds = linkedProposals.map((item) => item.value) ?? [];\n const body = {\n proposal_body_version: \"V1\",\n linked_rfp: linkedRfp?.value,\n category: \"Infrastructure Committee\",\n name: title,\n description: description,\n summary: summary,\n linked_proposals: linkedProposalsIds,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n requested_sponsor: \"infrastructure-committee.near\",\n supervisor: supervisor,\n timeline: isCancel\n ? {\n status: \"CANCELLED\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n }\n : isDraft\n ? { status: \"DRAFT\" }\n : {\n status: \"REVIEW\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n };\n const args = {\n labels: linkedRfp ? [] : (labels ?? []).map((i) => i.value ?? i),\n body: body,\n };\n if (isEditPage) {\n args[\"id\"] = editProposalData.id;\n }\n\n Near.call([\n {\n contractName: REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n methodName: isEditPage ? \"edit_proposal\" : \"add_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nfunction cleanDraft() {\n Storage.privateSet(draftKey, null);\n}\n\nif (loading) {\n return (\n <div\n style={{ height: \"50vh\" }}\n className=\"d-flex justify-content-center align-items-center w-100\"\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Spinner`}\n />\n </div>\n );\n}\n\nconst [collapseState, setCollapseState] = useState({});\n\nconst CollapsibleContainer = ({ title, children, noPaddingTop }) => {\n return (\n <div\n className={\n \"border-bottom py-4 \" +\n (noPaddingTop && \"pt-0 \") +\n (collapseState[title] && \" pb-0\")\n }\n >\n <div className={\"d-flex justify-content-between \"}>\n <div className=\"h5 text-muted mb-2 mb-sm-3\">{title}</div>\n <div\n className=\"d-flex d-sm-none cursor-pointer\"\n onClick={() =>\n setCollapseState((prevState) => ({\n ...prevState,\n [title]: !prevState[title],\n }))\n }\n >\n {!collapseState[title] ? (\n <i class=\"bi bi-chevron-up h4\"></i>\n ) : (\n <i class=\"bi bi-chevron-down h4\"></i>\n )}\n </div>\n </div>\n <div className={!collapseState[title] ? \"\" : \"d-none\"}>{children}</div>\n </div>\n );\n};\n\nconst CategoryDropdown = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.MultiSelectCategoryDropdown`}\n props={{\n selected: labels,\n onChange: (v) => setLabels(v),\n disabled: linkedRfp, // when RFP is linked, labels are disabled\n linkedRfp: linkedRfp,\n\n availableOptions: rfpLabelOptions,\n }}\n />\n );\n}, [draftProposalData, linkedRfp, labels]);\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: title,\n onBlur: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SummaryComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onBlur: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 500,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst DescriptionComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.Compose`}\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n showProposalIdAutoComplete: true,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst ConsentComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-2\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"toc\",\n label: (\n <>\n I’ve agree to{\" \"}\n <a\n href={ToCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Infrastructure Committee’s Terms and Conditions\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.toc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n toc: value,\n })),\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"coc\",\n label: (\n <>\n I’ve read{\" \"}\n <a\n href={CoCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Infrastructure Committee’s Code of Conduct\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.coc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n coc: value,\n })),\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ProfileComponent = useMemo(() => {\n return (\n <Widget\n src=\"mob.near/widget/Profile.ShortInlineBlock\"\n props={{\n accountId: author,\n }}\n />\n );\n}, []);\n\nconst LinkRFPComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-1\">\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.LinkedRfpDropdown`}\n props={{\n onChange: setLinkedRfp,\n linkedRfp: linkedRfp,\n disabled: disabledLinkRFP,\n onDeleteRfp: () => setLabels([]),\n }}\n />\n </div>\n );\n}, [draftProposalData, disabledLinkRFP]);\n\nconst LinkedProposalsComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-1\">\n <div className=\"text-muted w-100 text-sm\">\n Link any relevant proposals (e.g. previous milestones).\n </div>\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.LinkedProposalsDropdown`}\n props={{\n onChange: setLinkedProposals,\n linkedProposals: linkedProposals,\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ReceiverAccountComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.AccountInput`}\n props={{\n value: receiverAccount,\n placeholder: \"Enter Address\",\n onUpdate: setReceiverAccount,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst AmountComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: requestedSponsorshipAmount,\n onChange: (e) => {\n setRequestedSponsorshipAmount(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n type: \"text\",\n prefix: \"$\",\n inputmode: \"numeric\",\n pattern: \"[0-9]*\",\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst CurrencyComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.DropDown`}\n props={{\n options: tokensOptions,\n selectedValue: requestedSponsorshipToken,\n onUpdate: (v) => {\n setRequestedSponsorshipToken(v);\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nreturn (\n <Container className=\"w-100 py-4 px-0 px-sm-2 d-flex flex-column gap-3\">\n <Heading className=\"px-2 px-sm-0\">\n {isEditPage ? \"Edit\" : \"Create\"} Proposal\n </Heading>\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.proposals.ViewProposalModal`}\n props={{\n isOpen: showProposalViewModal,\n isEdit: isEditPage,\n proposalId: proposalId,\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.ConfirmReviewModal`}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n cleanDraft();\n onSubmit({ isDraft: false });\n },\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.ConfirmCancelModal`}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n onSubmit({ isCancel: true });\n },\n }}\n />\n <div className=\"card no-border rounded-0 px-2 p-lg-0 full-width-div\">\n <div className=\"container-xl py-4 d-flex flex-wrap gap-6 w-100\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-2 w-100 order-2 order-md-1\"\n >\n <div className=\"d-flex gap-2 w-100\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.Profile`}\n props={{\n accountId: author,\n }}\n />\n </div>\n <div className=\"d-flex flex-column gap-4 w-100\">\n <div className=\"border-bottom pb-4\">\n <InputContainer\n heading=\"Link RFP (Optional)\"\n description={\n \"Link this proposal if it is a response to a specific RFP. You can only link to active RFPs in the “Accepting Submission” stage. You can only link to one RFP.\"\n }\n >\n {LinkRFPComponent}\n </InputContainer>\n </div>\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the relevant categories that best align with your\n contribution to the NEAR developer community. Need guidance?\n See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs\n </a>\n .\n </>\n }\n >\n {CategoryDropdown}\n </InputContainer>\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your proposal in a few words. This will appear on your proposal’s detail page and the main proposal feed. Keep it short, please :)\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Summary\"\n description=\"Explain your proposal briefly. This is your chance to make a good first impression on the community. Include what needs or goals your work will address, your solution, and the benefit for the NEAR developer community.\"\n >\n {SummaryComponent}\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description={\n <>\n Expand on your summary with any relevant details like your\n contribution timeline, key milestones, team background, and\n a clear breakdown of how the funds will be used. Proposals\n should be simple and clear (e.g. 1 month). For more complex\n projects, treat each milestone as a separate proposal.\n </>\n }\n >\n {DescriptionComponent}\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n {ConsentComponent}\n </InputContainer>\n <div className=\"d-flex justify-content-between gap-2 align-items-center\">\n <div>\n {isEditPage && (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0 btn-sm\",\n },\n label: (\n <div className=\"d-flex align-items-center gap-1\">\n <i class=\"bi bi-trash3\"></i> Cancel Proposal\n </div>\n ),\n onClick: () => setCancelModal(true),\n }}\n />\n )}\n </div>\n <div\n className=\"d-flex gap-2\"\n style={{\n height: isDraftBtnOpen ? \"25vh\" : \"auto\",\n alignItems: isDraftBtnOpen ? \"flex-start\" : \"center\",\n }}\n >\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"proposal\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"proposals\",\n },\n })\n }\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 text-muted fw-bold btn-outline shadow-none border-0 btn-sm\",\n },\n label: \"Discard Changes\",\n onClick: cleanDraft,\n }}\n />\n </Link>\n <SubmitBtn />\n </div>\n </div>\n </div>\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-1 w-100 order-1 order-md-2\"\n >\n <CollapsibleContainer noPaddingTop={true} title=\"Author Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer heading=\"Author\">\n {ProfileComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Link Proposals (Optional)\">\n {LinkedProposalsComponent}\n </CollapsibleContainer>\n </div>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Funding Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer\n heading=\"Recipient NEAR Wallet Address\"\n description=\"Enter the address that will receive the funds. We’ll need this to send a test transaction once your proposal is approved.\"\n >\n {ReceiverAccountComponent}\n </InputContainer>\n <InputContainer\n heading={\n <div className=\"d-flex gap-2 align-items-center\">\n Recipient Verification Status\n <div className=\"custom-tooltip\">\n <i class=\"bi bi-info-circle-fill\"></i>\n <span class=\"tooltiptext\">\n To get approved and receive payments on our platform,\n you must complete KYC/KYB verification using Fractal,\n a trusted identity verification solution. This helps\n others trust transactions with your account. Click\n \"Get Verified\" to start. <br />\n <br />\n Once verified, your profile will display a badge,\n which is valid for 365 days from the date of your\n verification. You must renew your verification upon\n expiration OR if any of your personal information\n changes.\n </span>\n </div>\n </div>\n }\n description=\"\"\n >\n <div className=\"border border-1 p-3 rounded-2\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.VerificationStatus`}\n props={{\n receiverAccount: receiverAccount,\n showGetVerifiedBtn: true,\n imageSize: 30,\n }}\n />\n </div>\n </InputContainer>\n <InputContainer\n heading=\"Total Amount (USD)\"\n description={\n <>\n Enter the exact amount you are seeking. See\n <a\n href={FundingDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Documentation\n </a>\n for guidelines.\n </>\n }\n >\n {AmountComponent}\n </InputContainer>\n <InputContainer\n heading=\"Currency\"\n description=\"Select your preferred currency for receiving funds. Note: The exchange rate for NEAR tokens will be the closing rate at the day of the invoice.\"\n >\n {CurrencyComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "near-prpsls-bos.components.rfps.Editor": { "": "/*\nLicense: MIT\nAuthor: devhub.near\nHomepage: https://github.com/NEAR-DevHub/near-prpsls-bos#readme\n*/\n/* INCLUDE: \"includes/common.jsx\" */\nconst REPL_DEVHUB = \"devhub.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE = \"megha19.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT = \"truedove38.near\";\nconst REPL_RPC_URL = \"https://rpc.mainnet.near.org\";\nconst REPL_NEAR = \"near\";\nconst REPL_SOCIAL_CONTRACT = \"social.near\";\nconst RFP_IMAGE =\n \"https://ipfs.near.social/ipfs/bafkreicbygt4kajytlxij24jj6tkg2ppc2dw3dlqhkermkjjfgdfnlizzy\";\n\nconst RFP_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfps_with_latest_snapshot\";\n\nconst RFP_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfp_snapshots\";\n\nconst PROPOSAL_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposals_with_latest_snapshot\";\n\nconst PROPOSAL_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposal_snapshots\";\nconst RFP_TIMELINE_STATUS = {\n ACCEPTING_SUBMISSIONS: \"ACCEPTING_SUBMISSIONS\",\n EVALUATION: \"EVALUATION\",\n PROPOSAL_SELECTED: \"PROPOSAL_SELECTED\",\n CANCELLED: \"CANCELLED\",\n};\n\nconst PROPOSAL_TIMELINE_STATUS = {\n DRAFT: \"DRAFT\",\n REVIEW: \"REVIEW\",\n APPROVED: \"APPROVED\",\n REJECTED: \"REJECTED\",\n CANCELED: \"CANCELLED\",\n APPROVED_CONDITIONALLY: \"APPROVED_CONDITIONALLY\",\n PAYMENT_PROCESSING: \"PAYMENT_PROCESSING\",\n FUNDED: \"FUNDED\",\n};\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\n\nasync function fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `polyprogrammist_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst CANCEL_RFP_OPTIONS = {\n CANCEL_PROPOSALS: \"CANCEL_PROPOSALS\",\n UNLINK_PROPOSALS: \"UNLINK_PROPOSALSS\",\n NONE: \"NONE\",\n};\n\nfunction parseJSON(json) {\n if (typeof json === \"string\") {\n try {\n return JSON.parse(json);\n } catch (error) {\n return json;\n }\n } else {\n return json;\n }\n}\n\nfunction isNumber(value) {\n return typeof value === \"number\";\n}\n\nconst PROPOSALS_APPROVED_STATUS_ARRAY = [\n PROPOSAL_TIMELINE_STATUS.APPROVED,\n PROPOSAL_TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n PROPOSAL_TIMELINE_STATUS.PAYMENT_PROCESSING,\n PROPOSAL_TIMELINE_STATUS.FUNDED,\n];\n\nfunction getLinkUsingCurrentGateway(url) {\n const data = fetch(`https://httpbin.org/headers`);\n const gatewayURL = data?.body?.headers?.Origin ?? \"\";\n return `https://${\n gatewayURL.includes(\"near.org\") ? \"dev.near.org\" : \"near.social\"\n }/${url}`;\n}\n/* END_INCLUDE: \"includes/common.jsx\" */\n\nconst { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`);\n\nconst draftKey = \"INFRA_RFP_EDIT\";\nhref || (href = () => {});\n\nconst { getGlobalLabels } = VM.require(\n `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.core.lib.contract`\n) || { getGlobalLabels: () => {} };\nconst { id, timestamp } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Funding-Process-%E2%80%90-Company\";\nconst ToCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Terms-&-Conditions\";\nconst CoCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Code-Of-Conduct\";\n\nconst rfpLabelOptions = getGlobalLabels();\nconst isAllowedToWriteRfp = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"is_allowed_to_write_rfps\",\n {\n editor: context.accountId,\n }\n);\n\nif (!author || !isAllowedToWriteRfp) {\n return (\n <Widget src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.LoginScreen`} />\n );\n}\n\nlet editRfpData = null;\nlet draftRfpData = null;\n\nif (isEditPage) {\n editRfpData = Near.view(\n `${REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT}`,\n \"get_rfp\",\n {\n rfp_id: parseInt(id),\n }\n );\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !important;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n textarea {\n font-size: 14px !important;\n }\n\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .text-sm {\n font-size: 13px;\n }\n\n .h5 {\n font-size: 18px !important;\n }\n\n @media screen and (max-width: 768px) {\n .h6 {\n font-size: 14px !important;\n }\n\n .h5 {\n font-size: 16px !important;\n }\n\n .text-sm {\n font-size: 11px;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .border-bottom {\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n }\n\n .text-xs {\n font-size: 10px;\n }\n\n .flex-2 {\n flex: 2;\n }\n\n .flex-1 {\n flex: 1;\n }\n .bg-grey {\n background-color: #f4f4f4;\n }\n\n .border-bottom {\n border-bottom: 1px solid grey;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .border-1 {\n border: 1px solid #e2e6ec;\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .drop-btn {\n max-width: none !important;\n }\n\n .dropdown-menu {\n width: 100%;\n border-radius: 0.375rem !important;\n }\n\n .input-icon {\n display: flex;\n height: 100%;\n align-items: center;\n border-right: 1px solid #dee2e6;\n padding-right: 10px;\n }\n\n /* Tooltip container */\n .custom-tooltip {\n position: relative;\n display: inline-block;\n }\n\n /* Tooltip text */\n .custom-tooltip .tooltiptext {\n visibility: hidden;\n width: 250px;\n background-color: #fff;\n color: #6c757d;\n text-align: center;\n padding: 10px;\n border-radius: 6px;\n font-size: 12px;\n border: 0.2px solid #6c757d;\n\n /* Position the tooltip text */\n position: absolute;\n z-index: 1;\n bottom: 125%;\n left: -30px;\n\n /* Fade in tooltip */\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n /* Tooltip arrow */\n .custom-tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 15%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #555 transparent transparent transparent;\n }\n\n /* Show the tooltip text when you mouse over the tooltip container */\n .custom-tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n a.no-space {\n display: inline-block;\n }\n\n .fw-light-bold {\n font-weight: 600 !important;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n @media screen and (max-width: 970px) {\n .gap-6 {\n gap: 1.5rem !important;\n }\n }\n\n @media screen and (max-width: 570px) {\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nfunction getTimestamp(date) {\n // in nanoseconds\n const parsedDate = date ? new Date(date) : new Date();\n return Math.floor(parsedDate.getTime() * 1000000).toString();\n}\n\nfunction getDate(timestamp) {\n const stamp =\n !timestamp || timestamp === \"0\" || timestamp === \"NaN\" ? null : timestamp;\n return new Date(parseFloat(stamp / 1000000)).toISOString().split(\"T\")[0];\n}\n\nconst [labels, setLabels] = useState([]);\nconst [title, setTitle] = useState(null);\nconst [description, setDescription] = useState(null);\nconst [summary, setSummary] = useState(null);\nconst [consent, setConsent] = useState({ toc: false, coc: false });\nconst [submissionDeadline, setSubmissionDeadline] = useState(null);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [loading, setLoading] = useState(true);\nconst [disabledSubmitBtn, setDisabledSubmitBtn] = useState(false);\nconst [isDraftBtnOpen, setDraftBtnOpen] = useState(false);\n\nconst [showRfpViewModal, setShowRfpViewModal] = useState(false); // when user creates/edit a RFP and confirm the txn, this is true\nconst [rfpId, setRfpId] = useState(null);\nconst [rfpIdsArray, setRfpIdsArray] = useState(null);\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [oldRfpData, setOldRfpData] = useState(null);\nconst [timeline, setTimeline] = useState({\n status: RFP_TIMELINE_STATUS.ACCEPTING_SUBMISSIONS,\n});\n\nif (allowDraft) {\n draftRfpData = Storage.privateGet(draftKey);\n}\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editRfpData.id ?? null,\n snapshot: {\n name: title,\n description: description,\n labels: labels,\n summary: summary,\n submission_deadline: getTimestamp(submissionDeadline),\n },\n }),\n [title, summary, description, submissionDeadline, labels]\n);\n\nuseEffect(() => {\n if (allowDraft) {\n let data = editRfpData || JSON.parse(draftRfpData);\n let snapshot = data.snapshot;\n if (data) {\n if (timestamp) {\n snapshot =\n data.snapshot_history.find((item) => item.timestamp === timestamp) ??\n data.snapshot;\n }\n if (\n draftRfpData &&\n editRfpData &&\n editRfpData.id === JSON.parse(draftRfpData).id\n ) {\n snapshot = {\n ...editRfpData.snapshot,\n ...JSON.parse(draftRfpData).snapshot,\n };\n }\n setRfpId(data.id);\n setLabels(snapshot.labels);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setSubmissionDeadline(getDate(snapshot.submission_deadline));\n setTimeline(parseJSON(snapshot.timeline));\n if (isEditPage) {\n setConsent({ toc: true, coc: true });\n }\n }\n }\n}, [editRfpData, draftRfpData, allowDraft]);\n\n// show loader until LS data is set in state\nuseEffect(() => {\n const handler = setTimeout(() => {\n setAllowDraft(false);\n setLoading(false);\n }, 200);\n\n return () => clearTimeout(handler);\n}, []);\n\nuseEffect(() => {\n if (showRfpViewModal) {\n return;\n }\n setDisabledSubmitBtn(\n !title ||\n !description ||\n !summary ||\n !(labels ?? []).length ||\n !submissionDeadline ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 10000);\n\n return () => clearTimeout(handler);\n}, [\n memoizedDraftData,\n draftKey,\n draftRfpData,\n consent,\n isTxnCreated,\n showRfpViewModal,\n]);\n\nconst InputContainer = ({ heading, description, children }) => {\n return (\n <div className=\"d-flex flex-column gap-1 gap-sm-2 w-100\">\n <b className=\"h6 mb-0\">{heading}</b>\n {description && (\n <div className=\"text-muted w-100 text-sm\">{description}</div>\n )}\n {children}\n </div>\n );\n};\n\n// show RFP created after txn approval for popup wallet\nuseEffect(() => {\n if (isTxnCreated) {\n if (editRfpData) {\n setOldRfpData(editRfpData);\n if (\n editRfpData &&\n typeof editRfpData === \"object\" &&\n oldRfpData &&\n typeof oldRfpData === \"object\" &&\n JSON.stringify(editRfpData) !== JSON.stringify(oldRfpData)\n ) {\n setCreateTxn(false);\n setRfpId(editRfpData.id);\n setShowRfpViewModal(true);\n }\n } else {\n const rfpIds = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_rfp_ids\"\n );\n if (Array.isArray(rfpIds) && !rfpIdsArray) {\n setRfpIdsArray(rfpIds);\n }\n if (\n Array.isArray(rfpIds) &&\n Array.isArray(rfpIdsArray) &&\n rfpIds.length !== rfpIdsArray.length\n ) {\n setCreateTxn(false);\n setRfpId(rfpIds[rfpIds.length - 1]);\n setShowRfpViewModal(true);\n }\n }\n }\n});\n\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(REPL_RPC_URL, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_rfp_transaction =\n transaction_method_name == \"add_rfp\" ||\n transaction_method_name == \"edit_rfp\";\n\n if (is_edit_or_add_rfp_transaction) {\n setShowRfpViewModal(true);\n Storage.privateSet(draftKey, null);\n }\n // show the latest created rfp to user\n if (transaction_method_name == \"add_rfp\") {\n useCache(\n () =>\n Near.asyncView(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_rfp_ids\"\n ).then((rfpIdsArray) => {\n setRfpId(rfpIdsArray?.[rfpIdsArray?.length - 1]);\n }),\n props.transactionHashes + \"rfpIds\",\n { subscribe: false }\n );\n } else {\n setRfpId(id);\n }\n setLoading(false);\n }),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n } else {\n if (showRfpViewModal) {\n setShowRfpViewModal(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst LoadingButtonSpinner = (\n <span\n className=\"submit-rfp-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst onSubmit = () => {\n setCreateTxn(true);\n const body = {\n rfp_body_version: \"V0\",\n name: title,\n description: description,\n summary: summary,\n submission_deadline: getTimestamp(submissionDeadline),\n timeline: timeline,\n };\n const args = { labels: (labels ?? []).map((i) => i.value), body: body };\n if (isEditPage) {\n args[\"id\"] = editRfpData.id;\n }\n\n Near.call([\n {\n contractName: REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n methodName: isEditPage ? \"edit_rfp\" : \"add_rfp\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nfunction cleanDraft() {\n Storage.privateSet(draftKey, null);\n}\n\nif (loading) {\n return (\n <div\n style={{ height: \"50vh\" }}\n className=\"d-flex justify-content-center align-items-center w-100\"\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Spinner`}\n />\n </div>\n );\n}\n\nconst [collapseState, setCollapseState] = useState({});\n\nconst CollapsibleContainer = ({ title, children, noPaddingTop }) => {\n return (\n <div\n className={\n \"border-bottom py-4 \" +\n (noPaddingTop && \"pt-0 \") +\n (collapseState[title] && \" pb-0\")\n }\n >\n <div className={\"d-flex justify-content-between \"}>\n <div className=\"h5 text-muted mb-2 mb-sm-3\">{title}</div>\n <div\n className=\"d-flex d-sm-none cursor-pointer\"\n onClick={() =>\n setCollapseState((prevState) => ({\n ...prevState,\n [title]: !prevState[title],\n }))\n }\n >\n {!collapseState[title] ? (\n <i className=\"bi bi-chevron-up h4\"></i>\n ) : (\n <i className=\"bi bi-chevron-down h4\"></i>\n )}\n </div>\n </div>\n <div className={!collapseState[title] ? \"\" : \"d-none\"}>{children}</div>\n </div>\n );\n};\n\nconst CategoryDropdown = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.MultiSelectCategoryDropdown`}\n props={{\n selected: labels,\n onChange: (v) => setLabels(v),\n disabled: false,\n availableOptions: rfpLabelOptions,\n }}\n />\n );\n}, [draftRfpData]);\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: title,\n onBlur: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n },\n }}\n />\n );\n}, [draftRfpData]);\n\nconst SummaryComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onBlur: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 500,\n },\n }}\n />\n );\n}, [draftRfpData]);\n\nconst DescriptionComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.Compose`}\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n }}\n />\n );\n}, [draftRfpData]);\n\nconst ConsentComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-2\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"toc\",\n label: (\n <>\n I’ve agree to{\" \"}\n <a\n href={ToCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Terms and Conditions\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.toc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n toc: value,\n })),\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"coc\",\n label: (\n <>\n I’ve read{\" \"}\n <a\n href={CoCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Code of Conduct\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.coc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n coc: value,\n })),\n }}\n />\n </div>\n );\n}, [draftRfpData]);\n\nconst SubmissionDeadline = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: submissionDeadline,\n onBlur: (e) => {\n setSubmissionDeadline(e.target.value);\n },\n skipPaddingGap: true,\n type: \"date\",\n inputProps: {\n required: true,\n },\n }}\n />\n );\n}, [draftRfpData]);\n\nreturn (\n <Container className=\"w-100 py-2 px-0 px-sm-2 d-flex flex-column gap-3\">\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.ViewRfpModal`}\n props={{\n isOpen: showRfpViewModal,\n isEdit: isEditPage,\n rfpId: rfpId,\n }}\n />\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.ConfirmCancelModal`}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => {\n setCancelModal(false);\n setTimeline({ status: RFP_TIMELINE_STATUS.EVALUATION });\n },\n onConfirmClick: (value) => {\n setCancelModal(false);\n onCancelRFP(value);\n },\n linkedProposalIds: editRfpData.snapshot.linked_proposals,\n }}\n />\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.WarningModal`}\n props={{\n isOpen: isWarningModalOpen,\n onConfirmClick: () => {\n setWarningModal(false);\n setTimeline({ status: RFP_TIMELINE_STATUS.EVALUATION });\n },\n }}\n />\n <Heading className=\"px-2 px-sm-0\">\n {isEditPage ? \"Edit\" : \"Create\"} RFP\n </Heading>\n <div className=\"card no-border rounded-0 px-2 p-lg-0 full-width-div\">\n <div className=\"container-xl py-4 d-flex flex-wrap gap-6 w-100\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-2 w-100 order-2 order-md-1\"\n >\n <div className=\"d-flex gap-3 w-100\">\n <div className=\"d-none d-sm-flex\">\n <img src={RFP_IMAGE} height={35} width={35} />\n </div>\n <div className=\"d-flex flex-column gap-4 w-100\">\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the relevant categories to help users quickly\n understand the nature of the need. Need guidance? See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs\n </a>\n .\n </>\n }\n >\n {CategoryDropdown}\n </InputContainer>\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your RFP in a few words. This will appear on your RFP’s detail page and the main RFP feed. Keep it short, please :)\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Summary\"\n description=\"Explain your RFP briefly. What is the problem or need, desired outcome, and benefit to the NEAR developer community.\"\n >\n {SummaryComponent}\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description={\n \"Expand on your summary with any relevant details like a detailed explanation of the problem and the expected solution, scope, and deliverables. Also include an estimate range for the project if you have a specific budget. And the selection criteria.\"\n }\n >\n {DescriptionComponent}\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n {ConsentComponent}\n </InputContainer>\n <div className=\"d-flex justify-content-end gap-2 align-items-center\">\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"rfp\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"rfps\",\n },\n })\n }\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 text-muted fw-bold btn-outline shadow-none border-0 btn-sm\",\n },\n label: \"Discard Changes\",\n onClick: cleanDraft,\n }}\n />\n </Link>\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 fw-light-bold btn-outline shadow-none border-1\",\n },\n label: (\n <div className=\"d-flex align-items-center gap-2\">\n <div className=\"circle grey\"></div> <div>Submit</div>\n </div>\n ),\n onClick: onSubmit,\n disabled: disabledSubmitBtn,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-1 w-100 order-1 order-md-2\"\n >\n <CollapsibleContainer noPaddingTop={true}>\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer\n heading=\"Submission Deadline\"\n description=\"Enter the deadline for submitting proposals.\"\n >\n {SubmissionDeadline}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Timeline\">\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.TimelineConfigurator`}\n props={{\n timeline: timeline,\n setTimeline: (v) => {\n if (editRfpData.snapshot.timeline.status === v.status) {\n return;\n }\n // if proposal selected timeline is selected and no approved proposals exist, show warning\n if (\n v.status === RFP_TIMELINE_STATUS.PROPOSAL_SELECTED &&\n Array.isArray(approvedProposals) &&\n !approvedProposals.length\n ) {\n setWarningModal(true);\n }\n\n if (v.status === RFP_TIMELINE_STATUS.CANCELLED) {\n setCancelModal(true);\n }\n setTimeline(v);\n },\n disabled: isEditPage ? false : true,\n }}\n />\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" } } } } }

Transaction Execution Plan

Convert Transaction To Receipt
Gas Burned:
465 Ggas
Tokens Burned:
0.00005 
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
16 Tgas
Tokens Burned:
0.00165 
Called method: 'set' in contract: social.near
Arguments:
{ "data": { "megha19.near": { "widget": { "near-prpsls-bos.components.proposals.Editor": { "": "/*\nLicense: MIT\nAuthor: devhub.near\nHomepage: https://github.com/NEAR-DevHub/near-prpsls-bos#readme\n*/\n/* INCLUDE: \"includes/common.jsx\" */\nconst REPL_DEVHUB = \"devhub.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE = \"megha19.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT = \"truedove38.near\";\nconst REPL_RPC_URL = \"https://rpc.mainnet.near.org\";\nconst REPL_NEAR = \"near\";\nconst REPL_SOCIAL_CONTRACT = \"social.near\";\nconst RFP_IMAGE =\n \"https://ipfs.near.social/ipfs/bafkreicbygt4kajytlxij24jj6tkg2ppc2dw3dlqhkermkjjfgdfnlizzy\";\n\nconst RFP_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfps_with_latest_snapshot\";\n\nconst RFP_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfp_snapshots\";\n\nconst PROPOSAL_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposals_with_latest_snapshot\";\n\nconst PROPOSAL_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposal_snapshots\";\nconst RFP_TIMELINE_STATUS = {\n ACCEPTING_SUBMISSIONS: \"ACCEPTING_SUBMISSIONS\",\n EVALUATION: \"EVALUATION\",\n PROPOSAL_SELECTED: \"PROPOSAL_SELECTED\",\n CANCELLED: \"CANCELLED\",\n};\n\nconst PROPOSAL_TIMELINE_STATUS = {\n DRAFT: \"DRAFT\",\n REVIEW: \"REVIEW\",\n APPROVED: \"APPROVED\",\n REJECTED: \"REJECTED\",\n CANCELED: \"CANCELLED\",\n APPROVED_CONDITIONALLY: \"APPROVED_CONDITIONALLY\",\n PAYMENT_PROCESSING: \"PAYMENT_PROCESSING\",\n FUNDED: \"FUNDED\",\n};\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\n\nasync function fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `polyprogrammist_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst CANCEL_RFP_OPTIONS = {\n CANCEL_PROPOSALS: \"CANCEL_PROPOSALS\",\n UNLINK_PROPOSALS: \"UNLINK_PROPOSALSS\",\n NONE: \"NONE\",\n};\n\nfunction parseJSON(json) {\n if (typeof json === \"string\") {\n try {\n return JSON.parse(json);\n } catch (error) {\n return json;\n }\n } else {\n return json;\n }\n}\n\nfunction isNumber(value) {\n return typeof value === \"number\";\n}\n\nconst PROPOSALS_APPROVED_STATUS_ARRAY = [\n PROPOSAL_TIMELINE_STATUS.APPROVED,\n PROPOSAL_TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n PROPOSAL_TIMELINE_STATUS.PAYMENT_PROCESSING,\n PROPOSAL_TIMELINE_STATUS.FUNDED,\n];\n\nfunction getLinkUsingCurrentGateway(url) {\n const data = fetch(`https://httpbin.org/headers`);\n const gatewayURL = data?.body?.headers?.Origin ?? \"\";\n return `https://${\n gatewayURL.includes(\"near.org\") ? \"dev.near.org\" : \"near.social\"\n }/${url}`;\n}\n/* END_INCLUDE: \"includes/common.jsx\" */\n\nconst { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`);\nhref || (href = () => {});\n\nconst { getGlobalLabels } = VM.require(\n `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.core.lib.contract`\n) || { getGlobalLabels: () => {} };\n\nconst { id, timestamp, rfp_id } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Funding-Process-%E2%80%90-Company\";\nconst ToCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Terms-&-Conditions\";\nconst CoCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Code-Of-Conduct\";\nif (!author) {\n return (\n <Widget src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.LoginScreen`} />\n );\n}\nlet editProposalData = null;\nlet draftProposalData = null;\nconst draftKey = \"INFRA_PROPOSAL_EDIT\";\n\nconst rfpLabelOptions = getGlobalLabels();\n\nif (isEditPage) {\n editProposalData = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_proposal\",\n {\n proposal_id: parseInt(id),\n }\n );\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !important;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n textarea {\n font-size: 14px !important;\n }\n\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .text-sm {\n font-size: 13px;\n }\n\n @media screen and (max-width: 768px) {\n .h6 {\n font-size: 14px !important;\n }\n\n .h5 {\n font-size: 16px !important;\n }\n\n .text-sm {\n font-size: 11px;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .border-bottom {\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n }\n\n .text-xs {\n font-size: 10px;\n }\n\n .flex-2 {\n flex: 2;\n }\n\n .flex-1 {\n flex: 1;\n }\n .bg-grey {\n background-color: #f4f4f4;\n }\n\n .border-bottom {\n border-bottom: 1px solid grey;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .proposal-card {\n &:hover {\n background-color: #f4f4f4;\n }\n }\n\n .border-1 {\n border: 1px solid #e2e6ec;\n }\n .green-btn {\n background-color: #03ba16 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .drop-btn {\n max-width: none !important;\n }\n\n .dropdown-menu {\n width: 100%;\n border-radius: 0.375rem !important;\n }\n\n .input-icon {\n display: flex;\n height: 100%;\n align-items: center;\n border-right: 1px solid #dee2e6;\n padding-right: 10px;\n }\n\n /* Tooltip container */\n .custom-tooltip {\n position: relative;\n display: inline-block;\n }\n\n /* Tooltip text */\n .custom-tooltip .tooltiptext {\n visibility: hidden;\n width: 250px;\n background-color: #fff;\n color: #6c757d;\n text-align: center;\n padding: 10px;\n border-radius: 6px;\n font-size: 12px;\n border: 0.2px solid #6c757d;\n\n /* Position the tooltip text */\n position: absolute;\n z-index: 1;\n bottom: 125%;\n left: -30px;\n\n /* Fade in tooltip */\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n /* Tooltip arrow */\n .custom-tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 15%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #555 transparent transparent transparent;\n }\n\n /* Show the tooltip text when you mouse over the tooltip container */\n .custom-tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n a.no-space {\n display: inline-block;\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst tokensOptions = [\n { label: \"NEAR\", value: \"NEAR\" },\n { label: \"USDT\", value: \"USDT\" },\n {\n label: \"USDC\",\n value: \"USDC\",\n },\n {\n label: \"Other\",\n value: \"OTHER\",\n },\n];\n\nconst [linkedRfp, setLinkedRfp] = useState(rfp_id ? parseInt(rfp_id) : null);\nconst [labels, setLabels] = useState([]);\nconst [title, setTitle] = useState(null);\nconst [description, setDescription] = useState(null);\nconst [summary, setSummary] = useState(null);\nconst [consent, setConsent] = useState({ toc: false, coc: false });\nconst [linkedProposals, setLinkedProposals] = useState([]);\nconst [receiverAccount, setReceiverAccount] = useState(context.accountId);\nconst [requestedSponsorshipAmount, setRequestedSponsorshipAmount] =\n useState(null);\nconst [requestedSponsorshipToken, setRequestedSponsorshipToken] = useState(\n tokensOptions[2]\n);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [loading, setLoading] = useState(true);\nconst [disabledSubmitBtn, setDisabledSubmitBtn] = useState(false);\nconst [isDraftBtnOpen, setDraftBtnOpen] = useState(false);\nconst [selectedStatus, setSelectedStatus] = useState(\"draft\");\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\n\nconst [showProposalViewModal, setShowProposalViewModal] = useState(false); // when user creates/edit a proposal and confirm the txn, this is true\nconst [proposalId, setProposalId] = useState(null);\nconst [proposalIdsArray, setProposalIdsArray] = useState(null);\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [oldProposalData, setOldProposalData] = useState(null);\nconst [supervisor, setSupervisor] = useState(null);\n\nif (allowDraft) {\n draftProposalData = Storage.privateGet(draftKey);\n}\n\nconst isModerator = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"is_allowed_to_write_rfps\",\n {\n editor: context.accountId,\n }\n);\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editProposalData.id ?? null,\n snapshot: {\n linked_rfp: linkedRfp,\n name: title,\n description: description,\n labels: labels,\n summary: summary,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n },\n }),\n [\n linkedRfp,\n title,\n summary,\n description,\n labels,\n requestedSponsorshipAmount,\n requestedSponsorshipToken,\n receiverAccount,\n ]\n);\n\nuseEffect(() => {\n if (allowDraft) {\n let data = editProposalData || JSON.parse(draftProposalData);\n let snapshot = data.snapshot;\n if (data) {\n if (timestamp) {\n snapshot =\n data.snapshot_history.find((item) => item.timestamp === timestamp) ??\n data.snapshot;\n }\n if (\n draftProposalData &&\n editProposalData &&\n editProposalData.id === JSON.parse(draftProposalData).id\n ) {\n snapshot = {\n ...editProposalData.snapshot,\n ...JSON.parse(draftProposalData).snapshot,\n };\n }\n if (!isNumber(linkedRfp)) {\n setLinkedRfp(snapshot.linked_rfp);\n }\n setLabels(snapshot.labels ?? []);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setReceiverAccount(snapshot.receiver_account);\n setRequestedSponsorshipAmount(snapshot.requested_sponsorship_usd_amount);\n setSupervisor(snapshot.supervisor);\n\n const token = tokensOptions.find(\n (item) => item.value === snapshot.requested_sponsorship_paid_in_currency\n );\n setRequestedSponsorshipToken(token ?? tokensOptions[2]);\n if (isEditPage) {\n setConsent({ toc: true, coc: true });\n }\n }\n }\n}, [editProposalData, draftProposalData, allowDraft]);\n\n// show loader until LS data is set in state\nuseEffect(() => {\n const handler = setTimeout(() => {\n setAllowDraft(false);\n setLoading(false);\n }, 500);\n\n return () => clearTimeout(handler);\n}, []);\n\nuseEffect(() => {\n if (showProposalViewModal) {\n return;\n }\n setDisabledSubmitBtn(\n isTxnCreated ||\n !title ||\n !description ||\n !summary ||\n !(labels ?? []).length ||\n !requestedSponsorshipAmount ||\n !receiverAccount ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 10000);\n\n return () => clearTimeout(handler);\n}, [\n memoizedDraftData,\n draftKey,\n draftProposalData,\n consent,\n isTxnCreated,\n showProposalViewModal,\n]);\n\n// set RFP labels, disable link rfp change when linked rfp is past accepting stage\nconst [disabledLinkRFP, setDisableLinkRFP] = useState(false);\n\nuseEffect(() => {\n if (linkedRfp) {\n Near.asyncView(REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT, \"get_rfp\", {\n rfp_id: linkedRfp.value ?? linkedRfp,\n }).then((i) => {\n const timeline = parseJSON(i.snapshot.timeline);\n setDisableLinkRFP(\n !isModerator &&\n timeline.status !== RFP_TIMELINE_STATUS.ACCEPTING_SUBMISSIONS\n );\n setLabels(i.snapshot.labels);\n });\n }\n}, [linkedRfp]);\n\nuseEffect(() => {\n if (\n editProposalData &&\n editProposalData?.snapshot?.linked_proposals?.length > 0\n ) {\n editProposalData.snapshot.linked_proposals.map((item) => {\n useCache(\n () =>\n Near.asyncView(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_proposal\",\n {\n proposal_id: parseInt(item),\n }\n ).then((proposal) => {\n setLinkedProposals([\n ...linkedProposals,\n {\n label: \"# \" + proposal.id + \" : \" + proposal.snapshot.name,\n value: proposal.id,\n },\n ]);\n }),\n item + \"linked_proposals\",\n { subscribe: false }\n );\n });\n }\n}, [editProposalData]);\n\nconst InputContainer = ({ heading, description, children }) => {\n return (\n <div className=\"d-flex flex-column gap-1 gap-sm-2 w-100\">\n <b className=\"h6 mb-0\">{heading}</b>\n {description && (\n <div className=\"text-muted w-100 text-sm\">{description}</div>\n )}\n {children}\n </div>\n );\n};\n\n// show proposal created after txn approval for popup wallet\nuseEffect(() => {\n if (isTxnCreated) {\n if (editProposalData) {\n setOldProposalData(editProposalData);\n if (\n editProposalData &&\n typeof editProposalData === \"object\" &&\n oldProposalData &&\n typeof oldProposalData === \"object\" &&\n JSON.stringify(editProposalData) !== JSON.stringify(oldProposalData)\n ) {\n setCreateTxn(false);\n setProposalId(editProposalData.id);\n setShowProposalViewModal(true);\n }\n } else {\n const proposalIds = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_proposal_ids\"\n );\n if (Array.isArray(proposalIds) && !proposalIdsArray) {\n setProposalIdsArray(proposalIds);\n }\n if (\n Array.isArray(proposalIds) &&\n Array.isArray(proposalIdsArray) &&\n proposalIds.length !== proposalIdsArray.length\n ) {\n setCreateTxn(false);\n setProposalId(proposalIds[proposalIds.length - 1]);\n setShowProposalViewModal(true);\n }\n }\n }\n setLoading(false);\n});\n\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(REPL_RPC_URL, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_post_transaction =\n transaction_method_name == \"add_proposal\" ||\n transaction_method_name == \"edit_proposal\";\n\n if (is_edit_or_add_post_transaction) {\n setShowProposalViewModal(true);\n Storage.privateSet(draftKey, null);\n }\n // show the latest created proposal to user\n if (transaction_method_name == \"add_proposal\") {\n useCache(\n () =>\n Near.asyncView(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_proposal_ids\"\n ).then((proposalIdsArray) => {\n setProposalId(\n proposalIdsArray?.[proposalIdsArray?.length - 1]\n );\n }),\n props.transactionHashes + \"proposalIds\",\n { subscribe: false }\n );\n } else {\n setProposalId(id);\n }\n setLoading(false);\n }),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n } else {\n if (showProposalViewModal) {\n setShowProposalViewModal(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst DropdowntBtnContainer = styled.div`\n font-size: 13px;\n min-width: 150px;\n\n .custom-select {\n position: relative;\n }\n\n .select-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n border: 1px solid #ccc;\n border-radius-top: 5px;\n cursor: pointer;\n background-color: #fff;\n border-radius: 5px;\n }\n\n .no-border {\n border: none !important;\n }\n\n .options-card {\n position: absolute;\n top: 100%;\n left: 0;\n width: 200%;\n border: 1px solid #ccc;\n background-color: #fff;\n padding: 0.5rem;\n z-index: 99;\n font-size: 13px;\n border-radius:0.375rem !important;\n }\n\n .left {\n right: 0 !important;\n left: auto !important;\n }\n\n @media screen and (max-width: 768px) {\n .options-card {\n right: 0 !important;\n left: auto !important;\n }\n }\n\n .option {\n margin-block: 5px;\n padding: 10px;\n cursor: pointer;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.3s ease;\n border-radius: 0.375rem !important;\n }\n\n .option:hover {\n background-color: #f0f0f0; /* Custom hover effect color */\n }\n\n .option:last-child {\n border-bottom: none;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .disabled {\n background-color: #f4f4f4 !important;\n cursor: not-allowed !important;\n font-weight: 500;\n color: #b3b3b3;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n .green {\n background-color: #04a46e;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n}\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-proposal-draft-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst SubmitBtn = () => {\n const btnOptions = [\n {\n iconColor: \"grey\",\n label: \"Submit Draft\",\n description:\n \"The author can still edit the proposal and build consensus before sharing it with sponsors.\",\n value: \"draft\",\n },\n {\n iconColor: \"green\",\n label: \"Ready for Review\",\n description:\n \"Start the official review process with sponsors. This will lock the editing function, but comments are still open.\",\n value: \"review\",\n },\n ];\n\n const handleOptionClick = (option) => {\n setDraftBtnOpen(false);\n setSelectedStatus(option.value);\n handleSubmit(option.value);\n };\n\n const toggleDropdown = () => {\n setDraftBtnOpen(!isDraftBtnOpen);\n };\n\n const handleSubmit = (status) => {\n const isDraft = status === \"draft\";\n if (isDraft) {\n onSubmit({ isDraft });\n cleanDraft();\n } else {\n setReviewModal(true);\n }\n };\n\n const selectedOption = btnOptions.find((i) => i.value === selectedStatus);\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => setDraftBtnOpen(false)}\n >\n <div\n className={\n \"select-header d-flex gap-1 align-items-center submit-draft-button \" +\n (disabledSubmitBtn && \"disabled\")\n }\n >\n <div\n onClick={() => !disabledSubmitBtn && handleSubmit(selectedStatus)}\n className=\"p-2 d-flex gap-2 align-items-center \"\n >\n {isTxnCreated ? (\n LoadingButtonSpinner\n ) : (\n <div className={\"circle \" + selectedOption.iconColor}></div>\n )}\n <div className={`selected-option`}>{selectedOption.label}</div>\n </div>\n <div\n className=\"h-100 p-2\"\n style={{ borderLeft: \"1px solid #ccc\" }}\n onClick={!disabledSubmitBtn && toggleDropdown}\n >\n <i class={`bi bi-chevron-${isDraftBtnOpen ? \"up\" : \"down\"}`}></i>\n </div>\n </div>\n\n {isDraftBtnOpen && (\n <div className=\"options-card\">\n {btnOptions.map((option) => (\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <div className={\"circle \" + option.iconColor}></div>\n <div className=\"fw-bold\">{option.label}</div>\n </div>\n <div className=\"text-muted text-xs\">{option.description}</div>\n </div>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nconst onSubmit = ({ isDraft, isCancel }) => {\n setCreateTxn(true);\n console.log(\"submitting transaction\");\n const linkedProposalsIds = linkedProposals.map((item) => item.value) ?? [];\n const body = {\n proposal_body_version: \"V1\",\n linked_rfp: linkedRfp?.value,\n category: \"Infrastructure Committee\",\n name: title,\n description: description,\n summary: summary,\n linked_proposals: linkedProposalsIds,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n requested_sponsor: \"infrastructure-committee.near\",\n supervisor: supervisor,\n timeline: isCancel\n ? {\n status: \"CANCELLED\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n }\n : isDraft\n ? { status: \"DRAFT\" }\n : {\n status: \"REVIEW\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n };\n const args = {\n labels: linkedRfp ? [] : (labels ?? []).map((i) => i.value ?? i),\n body: body,\n };\n if (isEditPage) {\n args[\"id\"] = editProposalData.id;\n }\n\n Near.call([\n {\n contractName: REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n methodName: isEditPage ? \"edit_proposal\" : \"add_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nfunction cleanDraft() {\n Storage.privateSet(draftKey, null);\n}\n\nif (loading) {\n return (\n <div\n style={{ height: \"50vh\" }}\n className=\"d-flex justify-content-center align-items-center w-100\"\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Spinner`}\n />\n </div>\n );\n}\n\nconst [collapseState, setCollapseState] = useState({});\n\nconst CollapsibleContainer = ({ title, children, noPaddingTop }) => {\n return (\n <div\n className={\n \"border-bottom py-4 \" +\n (noPaddingTop && \"pt-0 \") +\n (collapseState[title] && \" pb-0\")\n }\n >\n <div className={\"d-flex justify-content-between \"}>\n <div className=\"h5 text-muted mb-2 mb-sm-3\">{title}</div>\n <div\n className=\"d-flex d-sm-none cursor-pointer\"\n onClick={() =>\n setCollapseState((prevState) => ({\n ...prevState,\n [title]: !prevState[title],\n }))\n }\n >\n {!collapseState[title] ? (\n <i class=\"bi bi-chevron-up h4\"></i>\n ) : (\n <i class=\"bi bi-chevron-down h4\"></i>\n )}\n </div>\n </div>\n <div className={!collapseState[title] ? \"\" : \"d-none\"}>{children}</div>\n </div>\n );\n};\n\nconst CategoryDropdown = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.MultiSelectCategoryDropdown`}\n props={{\n selected: labels,\n onChange: (v) => setLabels(v),\n disabled: linkedRfp, // when RFP is linked, labels are disabled\n linkedRfp: linkedRfp,\n\n availableOptions: rfpLabelOptions,\n }}\n />\n );\n}, [draftProposalData, linkedRfp, labels]);\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: title,\n onBlur: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SummaryComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onBlur: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 500,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst DescriptionComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.Compose`}\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n showProposalIdAutoComplete: true,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst ConsentComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-2\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"toc\",\n label: (\n <>\n I’ve agree to{\" \"}\n <a\n href={ToCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Infrastructure Committee’s Terms and Conditions\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.toc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n toc: value,\n })),\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"coc\",\n label: (\n <>\n I’ve read{\" \"}\n <a\n href={CoCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Infrastructure Committee’s Code of Conduct\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.coc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n coc: value,\n })),\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ProfileComponent = useMemo(() => {\n return (\n <Widget\n src=\"mob.near/widget/Profile.ShortInlineBlock\"\n props={{\n accountId: author,\n }}\n />\n );\n}, []);\n\nconst LinkRFPComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-1\">\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.LinkedRfpDropdown`}\n props={{\n onChange: setLinkedRfp,\n linkedRfp: linkedRfp,\n disabled: disabledLinkRFP,\n onDeleteRfp: () => setLabels([]),\n }}\n />\n </div>\n );\n}, [draftProposalData, disabledLinkRFP]);\n\nconst LinkedProposalsComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-1\">\n <div className=\"text-muted w-100 text-sm\">\n Link any relevant proposals (e.g. previous milestones).\n </div>\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.LinkedProposalsDropdown`}\n props={{\n onChange: setLinkedProposals,\n linkedProposals: linkedProposals,\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ReceiverAccountComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.AccountInput`}\n props={{\n value: receiverAccount,\n placeholder: \"Enter Address\",\n onUpdate: setReceiverAccount,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst AmountComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: requestedSponsorshipAmount,\n onChange: (e) => {\n setRequestedSponsorshipAmount(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n type: \"text\",\n prefix: \"$\",\n inputmode: \"numeric\",\n pattern: \"[0-9]*\",\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst CurrencyComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.DropDown`}\n props={{\n options: tokensOptions,\n selectedValue: requestedSponsorshipToken,\n onUpdate: (v) => {\n setRequestedSponsorshipToken(v);\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nreturn (\n <Container className=\"w-100 py-4 px-0 px-sm-2 d-flex flex-column gap-3\">\n <Heading className=\"px-2 px-sm-0\">\n {isEditPage ? \"Edit\" : \"Create\"} Proposal\n </Heading>\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.proposals.ViewProposalModal`}\n props={{\n isOpen: showProposalViewModal,\n isEdit: isEditPage,\n proposalId: proposalId,\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.ConfirmReviewModal`}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n cleanDraft();\n onSubmit({ isDraft: false });\n },\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.ConfirmCancelModal`}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n onSubmit({ isCancel: true });\n },\n }}\n />\n <div className=\"card no-border rounded-0 px-2 p-lg-0 full-width-div\">\n <div className=\"container-xl py-4 d-flex flex-wrap gap-6 w-100\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-2 w-100 order-2 order-md-1\"\n >\n <div className=\"d-flex gap-2 w-100\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.Profile`}\n props={{\n accountId: author,\n }}\n />\n </div>\n <div className=\"d-flex flex-column gap-4 w-100\">\n <div className=\"border-bottom pb-4\">\n <InputContainer\n heading=\"Link RFP (Optional)\"\n description={\n \"Link this proposal if it is a response to a specific RFP. You can only link to active RFPs in the “Accepting Submission” stage. You can only link to one RFP.\"\n }\n >\n {LinkRFPComponent}\n </InputContainer>\n </div>\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the relevant categories that best align with your\n contribution to the NEAR developer community. Need guidance?\n See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs\n </a>\n .\n </>\n }\n >\n {CategoryDropdown}\n </InputContainer>\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your proposal in a few words. This will appear on your proposal’s detail page and the main proposal feed. Keep it short, please :)\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Summary\"\n description=\"Explain your proposal briefly. This is your chance to make a good first impression on the community. Include what needs or goals your work will address, your solution, and the benefit for the NEAR developer community.\"\n >\n {SummaryComponent}\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description={\n <>\n Expand on your summary with any relevant details like your\n contribution timeline, key milestones, team background, and\n a clear breakdown of how the funds will be used. Proposals\n should be simple and clear (e.g. 1 month). For more complex\n projects, treat each milestone as a separate proposal.\n </>\n }\n >\n {DescriptionComponent}\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n {ConsentComponent}\n </InputContainer>\n <div className=\"d-flex justify-content-between gap-2 align-items-center\">\n <div>\n {isEditPage && (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0 btn-sm\",\n },\n label: (\n <div className=\"d-flex align-items-center gap-1\">\n <i class=\"bi bi-trash3\"></i> Cancel Proposal\n </div>\n ),\n onClick: () => setCancelModal(true),\n }}\n />\n )}\n </div>\n <div\n className=\"d-flex gap-2\"\n style={{\n height: isDraftBtnOpen ? \"25vh\" : \"auto\",\n alignItems: isDraftBtnOpen ? \"flex-start\" : \"center\",\n }}\n >\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"proposal\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"proposals\",\n },\n })\n }\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 text-muted fw-bold btn-outline shadow-none border-0 btn-sm\",\n },\n label: \"Discard Changes\",\n onClick: cleanDraft,\n }}\n />\n </Link>\n <SubmitBtn />\n </div>\n </div>\n </div>\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-1 w-100 order-1 order-md-2\"\n >\n <CollapsibleContainer noPaddingTop={true} title=\"Author Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer heading=\"Author\">\n {ProfileComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Link Proposals (Optional)\">\n {LinkedProposalsComponent}\n </CollapsibleContainer>\n </div>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Funding Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer\n heading=\"Recipient NEAR Wallet Address\"\n description=\"Enter the address that will receive the funds. We’ll need this to send a test transaction once your proposal is approved.\"\n >\n {ReceiverAccountComponent}\n </InputContainer>\n <InputContainer\n heading={\n <div className=\"d-flex gap-2 align-items-center\">\n Recipient Verification Status\n <div className=\"custom-tooltip\">\n <i class=\"bi bi-info-circle-fill\"></i>\n <span class=\"tooltiptext\">\n To get approved and receive payments on our platform,\n you must complete KYC/KYB verification using Fractal,\n a trusted identity verification solution. This helps\n others trust transactions with your account. Click\n \"Get Verified\" to start. <br />\n <br />\n Once verified, your profile will display a badge,\n which is valid for 365 days from the date of your\n verification. You must renew your verification upon\n expiration OR if any of your personal information\n changes.\n </span>\n </div>\n </div>\n }\n description=\"\"\n >\n <div className=\"border border-1 p-3 rounded-2\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.VerificationStatus`}\n props={{\n receiverAccount: receiverAccount,\n showGetVerifiedBtn: true,\n imageSize: 30,\n }}\n />\n </div>\n </InputContainer>\n <InputContainer\n heading=\"Total Amount (USD)\"\n description={\n <>\n Enter the exact amount you are seeking. See\n <a\n href={FundingDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Documentation\n </a>\n for guidelines.\n </>\n }\n >\n {AmountComponent}\n </InputContainer>\n <InputContainer\n heading=\"Currency\"\n description=\"Select your preferred currency for receiving funds. Note: The exchange rate for NEAR tokens will be the closing rate at the day of the invoice.\"\n >\n {CurrencyComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "near-prpsls-bos.components.rfps.Editor": { "": "/*\nLicense: MIT\nAuthor: devhub.near\nHomepage: https://github.com/NEAR-DevHub/near-prpsls-bos#readme\n*/\n/* INCLUDE: \"includes/common.jsx\" */\nconst REPL_DEVHUB = \"devhub.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE = \"megha19.near\";\nconst REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT = \"truedove38.near\";\nconst REPL_RPC_URL = \"https://rpc.mainnet.near.org\";\nconst REPL_NEAR = \"near\";\nconst REPL_SOCIAL_CONTRACT = \"social.near\";\nconst RFP_IMAGE =\n \"https://ipfs.near.social/ipfs/bafkreicbygt4kajytlxij24jj6tkg2ppc2dw3dlqhkermkjjfgdfnlizzy\";\n\nconst RFP_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfps_with_latest_snapshot\";\n\nconst RFP_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_rfp_snapshots\";\n\nconst PROPOSAL_FEED_INDEXER_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposals_with_latest_snapshot\";\n\nconst PROPOSAL_QUERY_NAME =\n \"polyprogrammist_near_devhub_objects_s_proposal_snapshots\";\nconst RFP_TIMELINE_STATUS = {\n ACCEPTING_SUBMISSIONS: \"ACCEPTING_SUBMISSIONS\",\n EVALUATION: \"EVALUATION\",\n PROPOSAL_SELECTED: \"PROPOSAL_SELECTED\",\n CANCELLED: \"CANCELLED\",\n};\n\nconst PROPOSAL_TIMELINE_STATUS = {\n DRAFT: \"DRAFT\",\n REVIEW: \"REVIEW\",\n APPROVED: \"APPROVED\",\n REJECTED: \"REJECTED\",\n CANCELED: \"CANCELLED\",\n APPROVED_CONDITIONALLY: \"APPROVED_CONDITIONALLY\",\n PAYMENT_PROCESSING: \"PAYMENT_PROCESSING\",\n FUNDED: \"FUNDED\",\n};\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\n\nasync function fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `polyprogrammist_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst CANCEL_RFP_OPTIONS = {\n CANCEL_PROPOSALS: \"CANCEL_PROPOSALS\",\n UNLINK_PROPOSALS: \"UNLINK_PROPOSALSS\",\n NONE: \"NONE\",\n};\n\nfunction parseJSON(json) {\n if (typeof json === \"string\") {\n try {\n return JSON.parse(json);\n } catch (error) {\n return json;\n }\n } else {\n return json;\n }\n}\n\nfunction isNumber(value) {\n return typeof value === \"number\";\n}\n\nconst PROPOSALS_APPROVED_STATUS_ARRAY = [\n PROPOSAL_TIMELINE_STATUS.APPROVED,\n PROPOSAL_TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n PROPOSAL_TIMELINE_STATUS.PAYMENT_PROCESSING,\n PROPOSAL_TIMELINE_STATUS.FUNDED,\n];\n\nfunction getLinkUsingCurrentGateway(url) {\n const data = fetch(`https://httpbin.org/headers`);\n const gatewayURL = data?.body?.headers?.Origin ?? \"\";\n return `https://${\n gatewayURL.includes(\"near.org\") ? \"dev.near.org\" : \"near.social\"\n }/${url}`;\n}\n/* END_INCLUDE: \"includes/common.jsx\" */\n\nconst { href } = VM.require(`${REPL_DEVHUB}/widget/core.lib.url`);\n\nconst draftKey = \"INFRA_RFP_EDIT\";\nhref || (href = () => {});\n\nconst { getGlobalLabels } = VM.require(\n `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.core.lib.contract`\n) || { getGlobalLabels: () => {} };\nconst { id, timestamp } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Funding-Process-%E2%80%90-Company\";\nconst ToCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Terms-&-Conditions\";\nconst CoCDocs =\n \"https://github.com/near/Infrastructure-Working-Group/wiki/Code-Of-Conduct\";\n\nconst rfpLabelOptions = getGlobalLabels();\nconst isAllowedToWriteRfp = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"is_allowed_to_write_rfps\",\n {\n editor: context.accountId,\n }\n);\n\nif (!author || !isAllowedToWriteRfp) {\n return (\n <Widget src={`${REPL_DEVHUB}/widget/devhub.entity.proposal.LoginScreen`} />\n );\n}\n\nlet editRfpData = null;\nlet draftRfpData = null;\n\nif (isEditPage) {\n editRfpData = Near.view(\n `${REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT}`,\n \"get_rfp\",\n {\n rfp_id: parseInt(id),\n }\n );\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !important;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n textarea {\n font-size: 14px !important;\n }\n\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .text-sm {\n font-size: 13px;\n }\n\n .h5 {\n font-size: 18px !important;\n }\n\n @media screen and (max-width: 768px) {\n .h6 {\n font-size: 14px !important;\n }\n\n .h5 {\n font-size: 16px !important;\n }\n\n .text-sm {\n font-size: 11px;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .border-bottom {\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n }\n\n .text-xs {\n font-size: 10px;\n }\n\n .flex-2 {\n flex: 2;\n }\n\n .flex-1 {\n flex: 1;\n }\n .bg-grey {\n background-color: #f4f4f4;\n }\n\n .border-bottom {\n border-bottom: 1px solid grey;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .border-1 {\n border: 1px solid #e2e6ec;\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .drop-btn {\n max-width: none !important;\n }\n\n .dropdown-menu {\n width: 100%;\n border-radius: 0.375rem !important;\n }\n\n .input-icon {\n display: flex;\n height: 100%;\n align-items: center;\n border-right: 1px solid #dee2e6;\n padding-right: 10px;\n }\n\n /* Tooltip container */\n .custom-tooltip {\n position: relative;\n display: inline-block;\n }\n\n /* Tooltip text */\n .custom-tooltip .tooltiptext {\n visibility: hidden;\n width: 250px;\n background-color: #fff;\n color: #6c757d;\n text-align: center;\n padding: 10px;\n border-radius: 6px;\n font-size: 12px;\n border: 0.2px solid #6c757d;\n\n /* Position the tooltip text */\n position: absolute;\n z-index: 1;\n bottom: 125%;\n left: -30px;\n\n /* Fade in tooltip */\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n /* Tooltip arrow */\n .custom-tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 15%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #555 transparent transparent transparent;\n }\n\n /* Show the tooltip text when you mouse over the tooltip container */\n .custom-tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n a.no-space {\n display: inline-block;\n }\n\n .fw-light-bold {\n font-weight: 600 !important;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n @media screen and (max-width: 970px) {\n .gap-6 {\n gap: 1.5rem !important;\n }\n }\n\n @media screen and (max-width: 570px) {\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nfunction getTimestamp(date) {\n // in nanoseconds\n const parsedDate = date ? new Date(date) : new Date();\n return Math.floor(parsedDate.getTime() * 1000000).toString();\n}\n\nfunction getDate(timestamp) {\n const stamp =\n !timestamp || timestamp === \"0\" || timestamp === \"NaN\" ? null : timestamp;\n return new Date(parseFloat(stamp / 1000000)).toISOString().split(\"T\")[0];\n}\n\nconst [labels, setLabels] = useState([]);\nconst [title, setTitle] = useState(null);\nconst [description, setDescription] = useState(null);\nconst [summary, setSummary] = useState(null);\nconst [consent, setConsent] = useState({ toc: false, coc: false });\nconst [submissionDeadline, setSubmissionDeadline] = useState(null);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [loading, setLoading] = useState(true);\nconst [disabledSubmitBtn, setDisabledSubmitBtn] = useState(false);\nconst [isDraftBtnOpen, setDraftBtnOpen] = useState(false);\n\nconst [showRfpViewModal, setShowRfpViewModal] = useState(false); // when user creates/edit a RFP and confirm the txn, this is true\nconst [rfpId, setRfpId] = useState(null);\nconst [rfpIdsArray, setRfpIdsArray] = useState(null);\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [oldRfpData, setOldRfpData] = useState(null);\nconst [timeline, setTimeline] = useState({\n status: RFP_TIMELINE_STATUS.ACCEPTING_SUBMISSIONS,\n});\n\nif (allowDraft) {\n draftRfpData = Storage.privateGet(draftKey);\n}\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editRfpData.id ?? null,\n snapshot: {\n name: title,\n description: description,\n labels: labels,\n summary: summary,\n submission_deadline: getTimestamp(submissionDeadline),\n },\n }),\n [title, summary, description, submissionDeadline, labels]\n);\n\nuseEffect(() => {\n if (allowDraft) {\n let data = editRfpData || JSON.parse(draftRfpData);\n let snapshot = data.snapshot;\n if (data) {\n if (timestamp) {\n snapshot =\n data.snapshot_history.find((item) => item.timestamp === timestamp) ??\n data.snapshot;\n }\n if (\n draftRfpData &&\n editRfpData &&\n editRfpData.id === JSON.parse(draftRfpData).id\n ) {\n snapshot = {\n ...editRfpData.snapshot,\n ...JSON.parse(draftRfpData).snapshot,\n };\n }\n setRfpId(data.id);\n setLabels(snapshot.labels);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setSubmissionDeadline(getDate(snapshot.submission_deadline));\n setTimeline(parseJSON(snapshot.timeline));\n if (isEditPage) {\n setConsent({ toc: true, coc: true });\n }\n }\n }\n}, [editRfpData, draftRfpData, allowDraft]);\n\n// show loader until LS data is set in state\nuseEffect(() => {\n const handler = setTimeout(() => {\n setAllowDraft(false);\n setLoading(false);\n }, 200);\n\n return () => clearTimeout(handler);\n}, []);\n\nuseEffect(() => {\n if (showRfpViewModal) {\n return;\n }\n setDisabledSubmitBtn(\n !title ||\n !description ||\n !summary ||\n !(labels ?? []).length ||\n !submissionDeadline ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 10000);\n\n return () => clearTimeout(handler);\n}, [\n memoizedDraftData,\n draftKey,\n draftRfpData,\n consent,\n isTxnCreated,\n showRfpViewModal,\n]);\n\nconst InputContainer = ({ heading, description, children }) => {\n return (\n <div className=\"d-flex flex-column gap-1 gap-sm-2 w-100\">\n <b className=\"h6 mb-0\">{heading}</b>\n {description && (\n <div className=\"text-muted w-100 text-sm\">{description}</div>\n )}\n {children}\n </div>\n );\n};\n\n// show RFP created after txn approval for popup wallet\nuseEffect(() => {\n if (isTxnCreated) {\n if (editRfpData) {\n setOldRfpData(editRfpData);\n if (\n editRfpData &&\n typeof editRfpData === \"object\" &&\n oldRfpData &&\n typeof oldRfpData === \"object\" &&\n JSON.stringify(editRfpData) !== JSON.stringify(oldRfpData)\n ) {\n setCreateTxn(false);\n setRfpId(editRfpData.id);\n setShowRfpViewModal(true);\n }\n } else {\n const rfpIds = Near.view(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_rfp_ids\"\n );\n if (Array.isArray(rfpIds) && !rfpIdsArray) {\n setRfpIdsArray(rfpIds);\n }\n if (\n Array.isArray(rfpIds) &&\n Array.isArray(rfpIdsArray) &&\n rfpIds.length !== rfpIdsArray.length\n ) {\n setCreateTxn(false);\n setRfpId(rfpIds[rfpIds.length - 1]);\n setShowRfpViewModal(true);\n }\n }\n }\n});\n\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(REPL_RPC_URL, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_rfp_transaction =\n transaction_method_name == \"add_rfp\" ||\n transaction_method_name == \"edit_rfp\";\n\n if (is_edit_or_add_rfp_transaction) {\n setShowRfpViewModal(true);\n Storage.privateSet(draftKey, null);\n }\n // show the latest created rfp to user\n if (transaction_method_name == \"add_rfp\") {\n useCache(\n () =>\n Near.asyncView(\n REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n \"get_all_rfp_ids\"\n ).then((rfpIdsArray) => {\n setRfpId(rfpIdsArray?.[rfpIdsArray?.length - 1]);\n }),\n props.transactionHashes + \"rfpIds\",\n { subscribe: false }\n );\n } else {\n setRfpId(id);\n }\n setLoading(false);\n }),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n } else {\n if (showRfpViewModal) {\n setShowRfpViewModal(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst LoadingButtonSpinner = (\n <span\n className=\"submit-rfp-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst onSubmit = () => {\n setCreateTxn(true);\n const body = {\n rfp_body_version: \"V0\",\n name: title,\n description: description,\n summary: summary,\n submission_deadline: getTimestamp(submissionDeadline),\n timeline: timeline,\n };\n const args = { labels: (labels ?? []).map((i) => i.value), body: body };\n if (isEditPage) {\n args[\"id\"] = editRfpData.id;\n }\n\n Near.call([\n {\n contractName: REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT,\n methodName: isEditPage ? \"edit_rfp\" : \"add_rfp\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nfunction cleanDraft() {\n Storage.privateSet(draftKey, null);\n}\n\nif (loading) {\n return (\n <div\n style={{ height: \"50vh\" }}\n className=\"d-flex justify-content-center align-items-center w-100\"\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Spinner`}\n />\n </div>\n );\n}\n\nconst [collapseState, setCollapseState] = useState({});\n\nconst CollapsibleContainer = ({ title, children, noPaddingTop }) => {\n return (\n <div\n className={\n \"border-bottom py-4 \" +\n (noPaddingTop && \"pt-0 \") +\n (collapseState[title] && \" pb-0\")\n }\n >\n <div className={\"d-flex justify-content-between \"}>\n <div className=\"h5 text-muted mb-2 mb-sm-3\">{title}</div>\n <div\n className=\"d-flex d-sm-none cursor-pointer\"\n onClick={() =>\n setCollapseState((prevState) => ({\n ...prevState,\n [title]: !prevState[title],\n }))\n }\n >\n {!collapseState[title] ? (\n <i className=\"bi bi-chevron-up h4\"></i>\n ) : (\n <i className=\"bi bi-chevron-down h4\"></i>\n )}\n </div>\n </div>\n <div className={!collapseState[title] ? \"\" : \"d-none\"}>{children}</div>\n </div>\n );\n};\n\nconst CategoryDropdown = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.MultiSelectCategoryDropdown`}\n props={{\n selected: labels,\n onChange: (v) => setLabels(v),\n disabled: false,\n availableOptions: rfpLabelOptions,\n }}\n />\n );\n}, [draftRfpData]);\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: title,\n onBlur: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n },\n }}\n />\n );\n}, [draftRfpData]);\n\nconst SummaryComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onBlur: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 500,\n },\n }}\n />\n );\n}, [draftRfpData]);\n\nconst DescriptionComponent = useMemo(() => {\n return (\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.molecule.Compose`}\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n }}\n />\n );\n}, [draftRfpData]);\n\nconst ConsentComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-2\">\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"toc\",\n label: (\n <>\n I’ve agree to{\" \"}\n <a\n href={ToCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Terms and Conditions\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.toc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n toc: value,\n })),\n }}\n />\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Checkbox`}\n props={{\n value: \"coc\",\n label: (\n <>\n I’ve read{\" \"}\n <a\n href={CoCDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Code of Conduct\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.coc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n coc: value,\n })),\n }}\n />\n </div>\n );\n}, [draftRfpData]);\n\nconst SubmissionDeadline = useMemo(() => {\n return (\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n value: submissionDeadline,\n onBlur: (e) => {\n setSubmissionDeadline(e.target.value);\n },\n skipPaddingGap: true,\n type: \"date\",\n inputProps: {\n required: true,\n },\n }}\n />\n );\n}, [draftRfpData]);\n\nreturn (\n <Container className=\"w-100 py-2 px-0 px-sm-2 d-flex flex-column gap-3\">\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.ViewRfpModal`}\n props={{\n isOpen: showRfpViewModal,\n isEdit: isEditPage,\n rfpId: rfpId,\n }}\n />\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.ConfirmCancelModal`}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => {\n setCancelModal(false);\n setTimeline({ status: RFP_TIMELINE_STATUS.EVALUATION });\n },\n onConfirmClick: (value) => {\n setCancelModal(false);\n onCancelRFP(value);\n },\n linkedProposalIds: editRfpData.snapshot.linked_proposals,\n }}\n />\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.WarningModal`}\n props={{\n isOpen: isWarningModalOpen,\n onConfirmClick: () => {\n setWarningModal(false);\n setTimeline({ status: RFP_TIMELINE_STATUS.EVALUATION });\n },\n }}\n />\n <Heading className=\"px-2 px-sm-0\">\n {isEditPage ? \"Edit\" : \"Create\"} RFP\n </Heading>\n <div className=\"card no-border rounded-0 px-2 p-lg-0 full-width-div\">\n <div className=\"container-xl py-4 d-flex flex-wrap gap-6 w-100\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-2 w-100 order-2 order-md-1\"\n >\n <div className=\"d-flex gap-3 w-100\">\n <div className=\"d-none d-sm-flex\">\n <img src={RFP_IMAGE} height={35} width={35} />\n </div>\n <div className=\"d-flex flex-column gap-4 w-100\">\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the relevant categories to help users quickly\n understand the nature of the need. Need guidance? See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs\n </a>\n .\n </>\n }\n >\n {CategoryDropdown}\n </InputContainer>\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your RFP in a few words. This will appear on your RFP’s detail page and the main RFP feed. Keep it short, please :)\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Summary\"\n description=\"Explain your RFP briefly. What is the problem or need, desired outcome, and benefit to the NEAR developer community.\"\n >\n {SummaryComponent}\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description={\n \"Expand on your summary with any relevant details like a detailed explanation of the problem and the expected solution, scope, and deliverables. Also include an estimate range for the project if you have a specific budget. And the selection criteria.\"\n }\n >\n {DescriptionComponent}\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n {ConsentComponent}\n </InputContainer>\n <div className=\"d-flex justify-content-end gap-2 align-items-center\">\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"rfp\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: `${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.pages.app`,\n params: {\n page: \"rfps\",\n },\n })\n }\n >\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 text-muted fw-bold btn-outline shadow-none border-0 btn-sm\",\n },\n label: \"Discard Changes\",\n onClick: cleanDraft,\n }}\n />\n </Link>\n <Widget\n src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 fw-light-bold btn-outline shadow-none border-1\",\n },\n label: (\n <div className=\"d-flex align-items-center gap-2\">\n <div className=\"circle grey\"></div> <div>Submit</div>\n </div>\n ),\n onClick: onSubmit,\n disabled: disabledSubmitBtn,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-1 w-100 order-1 order-md-2\"\n >\n <CollapsibleContainer noPaddingTop={true}>\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer\n heading=\"Submission Deadline\"\n description=\"Enter the deadline for submitting proposals.\"\n >\n {SubmissionDeadline}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Timeline\">\n <Widget\n src={`${REPL_INFRASTRUCTURE_COMMITTEE}/widget/near-prpsls-bos.components.rfps.TimelineConfigurator`}\n props={{\n timeline: timeline,\n setTimeline: (v) => {\n if (editRfpData.snapshot.timeline.status === v.status) {\n return;\n }\n // if proposal selected timeline is selected and no approved proposals exist, show warning\n if (\n v.status === RFP_TIMELINE_STATUS.PROPOSAL_SELECTED &&\n Array.isArray(approvedProposals) &&\n !approvedProposals.length\n ) {\n setWarningModal(true);\n }\n\n if (v.status === RFP_TIMELINE_STATUS.CANCELLED) {\n setCancelModal(true);\n }\n setTimeline(v);\n },\n disabled: isEditPage ? false : true,\n }}\n />\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" } } } } }
Result:
{ "block_height": "120656226" }
No logs
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 0.18651  to megha19.near
Empty result
No logs