Search
Search

Transaction: 4acmefq...8igk

Receiver
Status
Succeeded
Transaction Fee
0.00407 
Deposit Value
<0.00001 
Gas Used
40 Tgas
Attached Gas
300 Tgas
Created
March 21, 2024 at 5:52:47am
Hash
4acmefqqpmLbc8iL7DfmsDY5hJj1UG2P1ZHAFEAf8igk

Actions

Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devgovgigs.petersalomonsen.near": { "widget": { "devhub.components.molecule.DropDownWithSearch": { "": "const {\n selectedValue,\n onChange,\n options,\n defaultLabel,\n showSearch,\n searchInputPlaceholder,\n searchByLabel,\n searchByValue,\n} = props;\n\nconst [searchTerm, setSearchTerm] = useState(\"\");\nconst [filteredOptions, setFilteredOptions] = useState(options);\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOption, setSelectedOption] = useState({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n});\n\nuseEffect(() => {\n if (selectedOption.value !== selectedValue) {\n setSelectedOption({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n });\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n setFilteredOptions(options);\n}, [options]);\n\nconst handleSearch = (event) => {\n const term = event.target.value.toLowerCase();\n setSearchTerm(term);\n\n const filteredOptions = options.filter((option) => {\n if (searchByLabel) {\n return option.label.toLowerCase().includes(term);\n }\n if (searchByValue) {\n return option.value.toString().toLowerCase().includes(term);\n }\n });\n\n setFilteredOptions(filteredOptions);\n};\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nconst handleOptionClick = (option) => {\n setSelectedOption(option);\n setIsOpen(false);\n onChange(option);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .custom-select {\n position: relative;\n }\n\n .scroll-box {\n max-height: 200px;\n overflow-y: scroll;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n input {\n background-color: #f8f9fa;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\nlet searchFocused = false;\nreturn (\n <Container>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => {\n setTimeout(() => {\n setIsOpen(searchFocused || false);\n }, 0);\n }}\n >\n <div className=\"dropdown-toggle bg-white border rounded-2 btn drop-btn\">\n <div\n className={`selected-option w-100 text-wrap ${\n selectedOption.label === defaultLabel ? \"text-muted\" : \"\"\n }`}\n onClick={toggleDropdown}\n >\n {selectedOption.label}\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow show\">\n {showSearch && (\n <input\n type=\"text\"\n className=\"form-control mb-2\"\n placeholder={searchInputPlaceholder ?? \"Search options\"}\n value={searchTerm}\n onChange={handleSearch}\n onFocus={() => {\n searchFocused = true;\n }}\n onBlur={() => {\n setTimeout(() => {\n searchFocused = false;\n }, 0);\n }}\n />\n )}\n <div className=\"scroll-box\">\n {filteredOptions.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 text-wrap ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n {option.label}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.Proposal": { "": "const { href } = VM.require(\"devgovgigs.petersalomonsen.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devgovgigs.petersalomonsen.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devgovgigs.petersalomonsen.near/widget/core.lib.common\"\n);\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\n\nconst accountId = context.accountId;\n/*\n---props---\nprops.id: number;\nprops.timestamp: number; optional\n*/\n\nconst 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 Container = styled.div`\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 .description-box {\n font-size: 14px;\n }\n\n .draft-info-container {\n background-color: #ecf8fb;\n }\n\n .review-info-container {\n background-color: #fef6ee;\n }\n\n .text-sm {\n font-size: 13px !important;\n }\n\n .flex-1 {\n flex: 1;\n }\n\n .flex-3 {\n flex: 3;\n }\n\n .circle {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n border: 1px solid grey;\n }\n\n .green-fill {\n background-color: rgb(4, 164, 110) !important;\n border-color: rgb(4, 164, 110) !important;\n color: white !important;\n }\n\n .yellow-fill {\n border-color: #ff7a00 !important;\n }\n\n .vertical-line {\n width: 2px;\n height: 180px;\n background-color: lightgrey;\n }\n\n @media screen and (max-width: 970px) {\n .vertical-line {\n height: 135px !important;\n }\n\n .vertical-line-sm {\n height: 70px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n @media screen and (max-width: 570px) {\n .vertical-line {\n height: 180px !important;\n }\n\n .vertical-line-sm {\n height: 75px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .vertical-line-sm {\n width: 2px;\n height: 70px;\n background-color: lightgrey;\n }\n\n .form-check-input:disabled ~ .form-check-label,\n .form-check-input[disabled] ~ .form-check-label {\n opacity: 1;\n }\n\n .form-check-input {\n border-color: black !important;\n }\n\n .grey-btn {\n background-color: #687076;\n border: none;\n color: white;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\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 }\n\n .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n .border-vertical {\n border-top: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n border-bottom: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n }\n\n button.px-0 {\n padding-inline: 0px !important;\n }\n\n red-icon i {\n color: red;\n }\n`;\n\nconst ProposalContainer = styled.div`\n border: 1px solid lightgrey;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\nconst Text = styled.p`\n display: block;\n margin: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 400;\n color: #687076;\n white-space: nowrap;\n`;\n\nconst Actions = styled.div`\n display: flex;\n align-items: center;\n gap: 12px;\n margin: -6px -6px 6px;\n`;\n\nconst Avatar = styled.div`\n width: 40px;\n height: 40px;\n pointer-events: none;\n\n img {\n object-fit: cover;\n border-radius: 40px;\n width: 100%;\n height: 100%;\n }\n`;\n\nconst stepsArray = [1, 2, 3, 4, 5];\n\nconst { id, timestamp } = props;\nconst proposal = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n});\n\nif (!proposal) {\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={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n}\nif (timestamp && proposal) {\n proposal.snapshot =\n proposal.snapshot_history.find((item) => item.timestamp === timestamp) ??\n proposal.snapshot;\n}\n\nconst { snapshot } = proposal;\n\nconst authorId = proposal.author_id;\nconst blockHeight = parseInt(proposal.social_db_post_block_height);\nconst item = {\n type: \"social\",\n path: `devhub.near/post/main`,\n blockHeight,\n};\nconst proposalURL = `https://near.org/devgovgigs.petersalomonsen.near/widget/app?page=proposal&id=${proposal.id}&timestamp=${snapshot.timestamp}`;\n\nconst KycVerificationStatus = () => {\n const isVerified = true;\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <img\n src={\n isVerified\n ? \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\"\n : \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\"\n }\n height={40}\n />\n <div className=\"d-flex flex-column\">\n <div className=\"h6 mb-0\">KYC Verified</div>\n <div className=\"text-sm\">Expires on Aug 24, 2024</div>\n </div>\n </div>\n );\n};\n\nconst SidePanelItem = ({ title, children, hideBorder }) => {\n return (\n <div\n className={\n \"d-flex flex-column gap-2 pb-3 \" + (!hideBorder && \" border-bottom\")\n }\n >\n <div className=\"h6 mb-0\">{title} </div>\n <div className=\"text-muted\">{children}</div>\n </div>\n );\n};\n\nconst proposalStatusOptions = [\n {\n label: \"Draft\",\n value: { status: TIMELINE_STATUS.DRAFT },\n },\n {\n label: \"Review\",\n value: {\n status: TIMELINE_STATUS.REVIEW,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Approved\",\n value: {\n status: TIMELINE_STATUS.APPROVED,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Approved-Conditionally\",\n value: {\n status: TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Rejected\",\n value: {\n status: TIMELINE_STATUS.REJECTED,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Canceled\",\n value: {\n status: TIMELINE_STATUS.CANCELED,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Payment-processing\",\n value: {\n status: TIMELINE_STATUS.PAYMENT_PROCESSING,\n kyc_verified: false,\n test_transaction_sent: false,\n request_for_trustees_created: false,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Funded\",\n value: {\n status: TIMELINE_STATUS.FUNDED,\n trustees_released_payment: true,\n kyc_verified: true,\n test_transaction_sent: true,\n request_for_trustees_created: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n];\n\nconst LinkedProposals = () => {\n const linkedProposalsData = [];\n snapshot.linked_proposals.map((item) => {\n const data = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: item,\n });\n if (data !== null) {\n linkedProposalsData.push(data);\n }\n });\n\n return (\n <div className=\"d-flex flex-column gap-3\">\n {linkedProposalsData.map((item) => {\n const link = `https://near.org/devhub.near/widget/app?page=proposal&id=${item.id}`;\n return (\n <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: item.snapshot.editor_id,\n }}\n />\n <div className=\"d-flex flex-column\" style={{ maxWidth: 250 }}>\n <b className=\"text-truncate\">{item.snapshot.name}</b>\n <div className=\"text-sm text-muted\">\n created on {readableDate(item.snapshot.timestamp / 1000000)}\n </div>\n </div>\n </div>\n </a>\n );\n })}\n </div>\n );\n};\n\nconst CheckBox = ({ value, isChecked, label, disabled, onClick }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={isChecked}\n disabled={!isModerator || !showTimelineSetting || disabled}\n onChange={(e) => onClick(e.target.checked)}\n />\n <label style={{ width: \"90%\" }} class=\"form-check-label text-black\">\n {label}\n </label>\n </div>\n );\n};\n\nconst RadioButton = ({ value, isChecked, label }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n value={value}\n checked={isChecked}\n disabled={true}\n />\n <label class=\"form-check-label text-black\">{label}</label>\n </div>\n );\n};\n\nconst isAllowedToEditProposal = Near.view(\n \"devhub.near\",\n \"is_allowed_to_edit_proposal\",\n {\n proposal_id: proposal.id,\n editor: accountId,\n }\n);\n\nconst isModerator = Near.view(\"devgovgigs.near\", \"has_moderator\", {\n account_id: accountId,\n});\n\nconst editProposal = ({ timeline }) => {\n const body = {\n proposal_body_version: \"V0\",\n name: snapshot.title,\n description: snapshot.description,\n category: snapshot.category,\n summary: snapshot.summary,\n linked_proposals: snapshot.linked_proposals,\n requested_sponsorship_usd_amount: snapshot.requested_sponsorship_usd_amount,\n requested_sponsorship_paid_in_currency:\n snapshot.requested_sponsorship_paid_in_currency,\n receiver_account: snapshot.receiver_account,\n supervisor: supervisor || null,\n requested_sponsor: snapshot.requested_sponsor,\n timeline: timeline,\n };\n const args = { labels: [], body: body, id: proposal.id };\n\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nconst editProposalStatus = ({ timeline }) => {\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal_timeline\",\n args: {\n id: proposal.id,\n timeline: timeline,\n },\n gas: 270000000000000,\n },\n ]);\n};\n\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\nconst [showTimelineSetting, setShowTimelineSetting] = useState(false);\nconst proposalStatus = useCallback(\n () =>\n proposalStatusOptions.find(\n (i) => i.value.status === snapshot.timeline.status\n ),\n [snapshot]\n);\nconst [updatedProposalStatus, setUpdatedProposalStatus] = useState({\n ...proposalStatus(),\n value: { ...proposalStatus().value, ...snapshot.timeline },\n});\nconst [paymentHashes, setPaymentHashes] = useState([\"\"]);\nconst [supervisor, setSupervisor] = useState(snapshot.supervisor);\n\nconst selectedStatusIndex = useMemo(\n () =>\n proposalStatusOptions.findIndex((i) => {\n return updatedProposalStatus.value.status === i.value.status;\n }),\n [updatedProposalStatus]\n);\n\nconst TimelineItems = ({ title, children, value, values }) => {\n const indexOfCurrentItem = proposalStatusOptions.findIndex((i) =>\n Array.isArray(values)\n ? values.includes(i.value.status)\n : value === i.value.status\n );\n let color = \"transparent\";\n let statusIndex = selectedStatusIndex;\n\n // index 2,3,4,5 is of decision\n if (selectedStatusIndex === 3 || selectedStatusIndex === 2) {\n statusIndex = 2;\n }\n if (statusIndex === indexOfCurrentItem) {\n color = \"#FEF6EE\";\n }\n if (\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status === TIMELINE_STATUS.FUNDED\n ) {\n color = \"#EEFEF0\";\n }\n // reject\n if (statusIndex === 4 && indexOfCurrentItem === 2) {\n color = \"#FF7F7F\";\n }\n // cancelled\n if (statusIndex === 5 && indexOfCurrentItem === 2) {\n color = \"#F4F4F4\";\n }\n\n return (\n <div\n className=\"p-2 rounded-3\"\n style={{\n backgroundColor: color,\n }}\n >\n <div className=\"h6 text-black\"> {title}</div>\n <div className=\"text-sm\">{children}</div>\n </div>\n );\n};\n\nconst link = href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"create-proposal\",\n id: proposal.id,\n timestamp: timestamp,\n },\n});\n\nreturn (\n <Container className=\"d-flex flex-column gap-2 w-100 mt-4\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.ConfirmReviewModal\"}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[1].value });\n },\n }}\n />\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.ConfirmCancelModal\"}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[5].value });\n },\n }}\n />\n <div className=\"d-flex px-3 px-lg-0 justify-content-between\">\n <div className=\"d-flex gap-2 align-items-center h3\">\n <div>{snapshot.name}</div>\n <div className=\"text-muted\">#{proposal.id}</div>\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"near/widget/ShareButton\"\n props={{\n postType: \"post\",\n url: proposalURL,\n }}\n />\n {((isAllowedToEditProposal &&\n snapshot.timeline.status === TIMELINE_STATUS.DRAFT) ||\n isModerator) && (\n <Link to={link} style={{ textDecoration: \"none\" }}>\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Edit\",\n classNames: { root: \"grey-btn btn-sm\" },\n }}\n />\n </Link>\n )}\n </div>\n </div>\n <div className=\"d-flex px-3 px-lg-0 gap-2 align-items-center text-sm pb-3\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n size: \"sm\",\n }}\n />\n <div>\n <b>{authorId} </b> created on{\" \"}\n {readableDate(snapshot.timestamp / 1000000)}\n </div>\n </div>\n <div className=\"card rounded-0 full-width-div px-3 px-lg-0\">\n <div className=\"container-xl py-4\">\n {snapshot.timeline.status === TIMELINE_STATUS.DRAFT &&\n isAllowedToEditProposal && (\n <div className=\"draft-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in draft mode and open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n The author can still refine the proposal and build consensus\n before sharing it with sponsors. Click “Ready for review” when\n you want to start the official review process. This will lock\n the editing function, but comments are still open.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Ready for review\",\n classNames: { root: \"grey-btn btn-sm\" },\n onClick: () => setReviewModal(true),\n }}\n />\n </div>\n </div>\n )}\n {snapshot.timeline.status === TIMELINE_STATUS.REVIEW &&\n isAllowedToEditProposal && (\n <div className=\"review-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in review mode and still open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n You can’t edit the proposal, but comments are open. Only\n moderators can make changes. Click “Cancel Proposal” to cancel\n your proposal. This changes the status to Canceled, signaling\n to sponsors that it’s no longer active or relevant.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\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 classNames: { root: \"btn-outline-danger btn-sm\" },\n onClick: () => setCancelModal(true),\n }}\n />\n </div>\n </div>\n )}\n <div className=\"my-4\">\n <div className=\"d-flex flex-wrap gap-6\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-3 order-2 order-md-1\"\n >\n <div\n className=\"d-flex gap-2 flex-1\"\n style={{\n zIndex: 9999,\n background: \"white\",\n position: \"relative\",\n }}\n >\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: authorId,\n }}\n />\n </div>\n <ProposalContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n {authorId} ・{\" \"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: snapshot.timestamp,\n }}\n />\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: authorId,\n blockHeight: blockHeight,\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"d-flex flex-column gap-1 p-2 px-3 description-box\">\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n PROPOSAL CATEGORY\n </div>\n <div>\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CategoryDropdown\"\n }\n props={{\n selectedValue: snapshot.category,\n disabled: true,\n }}\n />\n </div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n SUMMARY\n </div>\n <div>{snapshot.summary}</div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3 mb-4\">\n DESCRIPTION\n </div>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.MarkdownViewer\"\n props={{ text: snapshot.description }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: authorId,\n }}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CommentIcon\"\n }\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: proposalURL,\n }}\n />\n </div>\n </div>\n </ProposalContainer>\n </div>\n <div className=\"border-bottom pb-4 mt-4\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CommentsAndLogs\"\n }\n props={{\n item: item,\n snapshotHistory: [...proposal.snapshot_history, snapshot],\n }}\n />\n </div>\n <div\n style={{\n position: \"relative\",\n zIndex: 99,\n backgroundColor: \"white\",\n }}\n className=\"pt-4\"\n >\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n ...props,\n item: item,\n notifyAccountId: authorId,\n id: proposal.id,\n }}\n />\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"d-flex flex-column gap-4 flex-1 order-1 order-md-2\"\n >\n <SidePanelItem title=\"Author\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: authorId,\n }}\n />\n </SidePanelItem>\n <SidePanelItem\n title={\n \"Linked Proposals \" + `(${snapshot.linked_proposals.length})`\n }\n >\n <LinkedProposals />\n </SidePanelItem>\n <SidePanelItem title=\"Funding Ask\">\n <div className=\"h4 text-black\">\n {snapshot.requested_sponsorship_usd_amount && (\n <div className=\"d-flex flex-column gap-1\">\n <div>{snapshot.requested_sponsorship_usd_amount} USD</div>\n <div className=\"text-sm text-muted\">\n Requested in{\" \"}\n {snapshot.requested_sponsorship_paid_in_currency}\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Wallet Address\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.receiver_account,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Verification Status\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: snapshot.receiver_account,\n showGetVerifiedBtn:\n accountId === snapshot.receiver_account ||\n accountId === authorId,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Requested Sponsor\">\n {snapshot.requested_sponsor && (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.requested_sponsor,\n }}\n />\n )}\n </SidePanelItem>\n <SidePanelItem title=\"Supervisor\">\n {snapshot.supervisor ? (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.supervisor,\n }}\n />\n ) : (\n \"No Supervisor\"\n )}\n </SidePanelItem>\n <SidePanelItem\n hideBorder={true}\n title={\n <div>\n <div className=\"d-flex justify-content-between align-content-center\">\n Timeline\n {isModerator && (\n <div onClick={() => setShowTimelineSetting(true)}>\n <i class=\"bi bi-gear\"></i>\n </div>\n )}\n </div>\n {showTimelineSetting && (\n <div className=\"mt-2 d-flex flex-column gap-2\">\n <h6 className=\"mb-0\">Proposal Status</h6>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: proposalStatusOptions,\n selectedValue: updatedProposalStatus,\n onUpdate: (v) => {\n setUpdatedProposalStatus({\n ...v,\n value: {\n ...v.value,\n ...updatedProposalStatus.value,\n status: v.value.status,\n },\n });\n },\n }}\n />\n </div>\n )}\n </div>\n }\n >\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"d-flex gap-3 mt-2\">\n <div className=\"d-flex flex-column\">\n {stepsArray.map((_, index) => {\n const indexOfCurrentItem = index;\n let color = \"\";\n let statusIndex = selectedStatusIndex;\n // index 2,3,4 is of decision\n if (\n selectedStatusIndex === 3 ||\n selectedStatusIndex === 2 ||\n selectedStatusIndex === 4 ||\n selectedStatusIndex === 5\n ) {\n statusIndex = 2;\n }\n if (selectedStatusIndex === 6) {\n statusIndex = 3;\n }\n const current = statusIndex === indexOfCurrentItem;\n const completed =\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED;\n return (\n <div className=\"d-flex flex-column align-items-center gap-1\">\n <div\n className={\n \"circle \" +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n >\n {completed && (\n <div\n className=\"d-flex justify-content-center align-items-center\"\n style={{ height: \"110%\" }}\n >\n <i class=\"bi bi-check\"></i>\n </div>\n )}\n </div>\n\n {index !== stepsArray.length - 1 && (\n <div\n className={\n \"vertical-line\" +\n (index === stepsArray.length - 2\n ? \"-sm \"\n : \" \") +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n ></div>\n )}\n </div>\n );\n })}\n </div>\n <div className=\"d-flex flex-column gap-3\">\n <TimelineItems\n title=\"1) Draft\"\n value={TIMELINE_STATUS.DRAFT}\n >\n <div>\n Once an author submits a proposal, it is in draft mode\n and open for community comments. The author can still\n make changes to the proposal during this stage and\n submit it for official review when ready.\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"2) Review\"\n value={TIMELINE_STATUS.REVIEW}\n >\n <div className=\"d-flex flex-column gap-2\">\n Sponsors who agree to consider the proposal may\n request attestations from work groups.\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n sponsor_requested_review: value,\n },\n }))\n }\n label=\"Sponsor provides feedback or requests reviews\"\n isChecked={\n updatedProposalStatus.value\n .sponsor_requested_review\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n label=\"Reviewer completes attestations (Optional)\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n reviewer_completed_attestation: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .reviewer_completed_attestation\n }\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"3) Decision\"\n values={[\n TIMELINE_STATUS.APPROVED,\n TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n TIMELINE_STATUS.REJECTED,\n ]}\n >\n <div className=\"d-flex flex-column gap-2\">\n <div>Sponsor makes a final decision:</div>\n <RadioButton\n value=\"\"\n label={<div className=\"fw-bold\">Approved</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED\n }\n />\n <RadioButton\n value=\"\"\n label={\n <>\n <div className=\"fw-bold\">\n Approved - Conditional{\" \"}\n </div>\n <span>\n Require follow up from recipient after payment\n </span>\n </>\n }\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED_CONDITIONALLY\n }\n />\n <RadioButton\n value=\"Reject\"\n label={<div className=\"fw-bold\">Rejected</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.REJECTED\n }\n />\n <RadioButton\n value=\"Canceled\"\n label={<div className=\"fw-bold\">Canceled</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.CANCELED\n }\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"4) Payment Processing\"\n value={TIMELINE_STATUS.PAYMENT_PROCESSING}\n >\n <div className=\"d-flex flex-column gap-2\">\n <CheckBox\n value={updatedProposalStatus.value.kyc_verified}\n label=\"Sponsor verifies KYC/KYB\"\n disabled={selectedStatusIndex !== 6}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n kyc_verified: value,\n },\n }))\n }\n isChecked={updatedProposalStatus.value.kyc_verified}\n />\n <CheckBox\n value={\n updatedProposalStatus.value.test_transaction_sent\n }\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor confirmed sponsorship and shared funding steps with recipient\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n {/* Not needed for Alpha testing */}\n {/* <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor sends test transaction\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor creates funding request from Trustees\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n request_for_trustees_created: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .request_for_trustees_created\n }\n /> */}\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"5) Funded\"\n value={TIMELINE_STATUS.FUNDED}\n >\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes?.length && paymentHashes[0] ? (\n paymentHashes.map((link) => (\n <a\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i class=\"bi bi-arrow-up-right\"></i>\n </a>\n ))\n ) : updatedProposalStatus.value.payouts.length > 0 ? (\n <div>\n {updatedProposalStatus.value.payouts.map(\n (link) => {\n return (\n <a\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i class=\"bi bi-arrow-up-right\"></i>\n </a>\n );\n }\n )}\n </div>\n ) : (\n \"No Payouts yet\"\n )}\n </div>\n </TimelineItems>\n </div>\n </div>\n {showTimelineSetting && (\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Supervisor</label>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n placeholder: \"Enter Supervisor\",\n onUpdate: setSupervisor,\n }}\n />\n </div>\n {updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED && (\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Payment Link</label>\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes.map((item, index) => (\n <div className=\"d-flex gap-2 justify-content-between align-items-center\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n onChange: (e) => {\n const updatedHashes = [...paymentHashes];\n updatedHashes[index] = e.target.value;\n setPaymentHashes(updatedHashes);\n },\n skipPaddingGap: true,\n placeholder: \"Enter URL\",\n }}\n />\n <div style={{ minWidth: 20 }}>\n {index !== paymentHashes.length - 1 ? (\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none w-100\",\n },\n label: <i class=\"bi bi-trash3 h6\"></i>,\n onClick: () => {\n const updatedHashes = [\n ...paymentHashes,\n ];\n updatedHashes.splice(index, 1);\n setPaymentHashes(updatedHashes);\n },\n }}\n />\n ) : (\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"green-btn shadow-none border-0 w-100\",\n },\n label: <i class=\"bi bi-plus-lg\"></i>,\n onClick: () =>\n setPaymentHashes([\n ...paymentHashes,\n \"\",\n ]),\n }}\n />\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n <div className=\"d-flex gap-2 align-items-center justify-content-end text-sm\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Cancel\",\n classNames: {\n root: \"btn-outline-danger border-0 shadow-none btn-sm\",\n },\n onClick: () => {\n setShowTimelineSetting(false);\n setUpdatedProposalStatus(proposalStatus);\n },\n }}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n disabled: !supervisor,\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n if (!supervisor) {\n return;\n }\n if (snapshot.supervisor !== supervisor) {\n editProposal({\n timeline: updatedProposalStatus.value,\n });\n } else if (\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n ) {\n editProposalStatus({\n timeline: {\n ...updatedProposalStatus.value,\n payouts: !paymentHashes[0]\n ? []\n : paymentHashes.filter(\n (item) => item !== \"\"\n ),\n },\n });\n } else {\n editProposalStatus({\n timeline: updatedProposalStatus.value,\n });\n }\n },\n }}\n />\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n </div>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.CommentsAndLogs": { "": "const snapshotHistory = props.snapshotHistory;\n\nconst Wrapper = styled.div`\n position: relative;\n .log-line {\n position: absolute;\n left: 7%;\n top: -30px;\n bottom: 0;\n z-index: 1;\n width: 1px;\n background-color: var(--bs-border-color);\n z-index: 1;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\n\nconst CommentContainer = styled.div`\n border: 1px solid lightgrey;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\n// check snapshot history all keys and values for differences\nfunction getDifferentKeysWithValues(obj1, obj2) {\n return Object.keys(obj1)\n .filter((key) => {\n if (key !== \"editor_id\" && obj2.hasOwnProperty(key)) {\n const value1 = obj1[key];\n const value2 = obj2[key];\n\n if (typeof value1 === \"object\" && typeof value2 === \"object\") {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else if (Array.isArray(value1) && Array.isArray(value2)) {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else {\n return value1 !== value2;\n }\n }\n return false;\n })\n .map((key) => ({\n key,\n originalValue: obj1[key],\n modifiedValue: obj2[key],\n }));\n}\n\nState.init({\n data: null,\n socialComments: null,\n changedKeysListWithValues: null,\n});\n\nfunction sortTimelineAndComments() {\n const comments = Social.index(\"comment\", props.item);\n\n if (state.changedKeysListWithValues === null) {\n const changedKeysListWithValues = snapshotHistory\n .slice(1)\n .map((item, index) => {\n const startingPoint = snapshotHistory[index]; // Set comparison to the previous item\n return {\n editorId: item.editor_id,\n ...getDifferentKeysWithValues(startingPoint, item),\n };\n });\n State.update({ changedKeysListWithValues });\n }\n\n // sort comments and timeline logs by time\n const snapShotTimeStamp = Array.isArray(snapshotHistory)\n ? snapshotHistory.map((i) => {\n return { blockHeight: null, timestamp: parseFloat(i.timestamp / 1e6) };\n })\n : [];\n\n const commentsTimeStampPromise = Array.isArray(comments)\n ? Promise.all(\n comments.map((item) => {\n return asyncFetch(\n `https://api.near.social/time?blockHeight=${item.blockHeight}`\n ).then((res) => {\n const timeMs = parseFloat(res.body);\n return {\n blockHeight: item.blockHeight,\n timestamp: timeMs,\n };\n });\n })\n ).then((res) => res)\n : Promise.resolve([]);\n\n commentsTimeStampPromise.then((commentsTimeStamp) => {\n const combinedArray = [...snapShotTimeStamp, ...commentsTimeStamp];\n combinedArray.sort((a, b) => a.timestamp - b.timestamp);\n State.update({ data: combinedArray, socialComments: comments });\n });\n}\n\nsortTimelineAndComments();\nconst Comment = ({ commentItem }) => {\n const { accountId, blockHeight } = commentItem;\n const item = {\n type: \"social\",\n path: `${accountId}/post/comment`,\n blockHeight,\n };\n const content = JSON.parse(Social.get(item.path, blockHeight) ?? \"null\");\n\n const link = `https://near.org/mob.near/widget/MainPage.N.Comment.Page?accountId=${accountId}&blockHeight=${blockHeight}`;\n return (\n <div style={{ zIndex: 9999, background: \"white\" }}>\n <div className=\"d-flex gap-2 flex-1\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n </div>\n <CommentContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n <div>\n {accountId} commented\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight: blockHeight,\n }}\n />\n </div>\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: accountId,\n blockHeight: blockHeight,\n contentPath: `/post/comment`,\n contentType: \"comment\",\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"p-2 px-3\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: content.text,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"near/widget/v1.LikeButton\"\n props={{\n item: item,\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: link,\n }}\n />\n </div>\n </div>\n </CommentContainer>\n </div>\n </div>\n );\n};\n\nfunction capitalizeFirstLetter(string) {\n const updated = string.replace(\"_\", \" \");\n return updated.charAt(0).toUpperCase() + updated.slice(1).toLowerCase();\n}\n\nfunction parseTimelineKeyAndValue(timeline, originalValue, modifiedValue) {\n const oldValue = originalValue[timeline];\n const newValue = modifiedValue[timeline];\n switch (timeline) {\n case \"status\":\n return (\n <span>\n moved proposal from {capitalizeFirstLetter(oldValue)} to{\" \"}\n {capitalizeFirstLetter(newValue)} stage\n </span>\n );\n case \"sponsor_requested_review\":\n return !oldValue && newValue && <span>completed review</span>;\n case \"reviewer_completed_attestation\":\n return !oldValue && newValue && <span>completed attestation</span>;\n case \"kyc_verified\":\n return !oldValue && newValue && <span>verified KYC/KYB</span>;\n case \"test_transaction_sent\":\n return (\n !oldValue &&\n newValue && (\n <span>\n confirmed sponsorship and shared funding steps with recipient\n </span>\n )\n );\n // we don't have this step for now\n // case \"request_for_trustees_created\":\n // return !oldValue && newValue && <span>successfully created request for trustees</span>;\n default:\n return null;\n }\n}\n\nconst parseProposalKeyAndValue = (key, modifiedValue, originalValue) => {\n switch (key) {\n case \"name\":\n case \"summary\":\n case \"description\":\n return <span>changed {key}</span>;\n case \"category\":\n return (\n <span>\n changed category from {originalValue} to {modifiedValue}\n </span>\n );\n case \"linked_proposals\":\n return <span>updated linked proposals</span>;\n case \"requested_sponsorship_usd_amount\":\n return (\n <span>\n changed sponsorship amount from {originalValue} to {modifiedValue}\n </span>\n );\n case \"requested_sponsorship_paid_in_currency\":\n return (\n <span>\n changed sponsorship currency from {originalValue} to {modifiedValue}\n </span>\n );\n case \"receiver_account\":\n return (\n <span>\n changed receiver account from {originalValue} to {modifiedValue}\n </span>\n );\n case \"supervisor\":\n return !originalValue && modifiedValue ? (\n <span>added {modifiedValue} as supervisor</span>\n ) : (\n <span>\n changed receiver account from {originalValue} to {modifiedValue}\n </span>\n );\n case \"requested_sponsor\":\n return (\n <span>\n changed sponsor from {originalValue} to {modifiedValue}\n </span>\n );\n case \"timeline\": {\n const modifiedKeys = Object.keys(modifiedValue);\n const originalKeys = Object.keys(originalValue);\n return modifiedKeys.map((i, index) => {\n const text = parseTimelineKeyAndValue(i, originalValue, modifiedValue);\n return (\n <span>\n {text}\n {text &&\n originalKeys.length > 1 &&\n index < modifiedKeys.length - 1 &&\n \"・\"}\n </span>\n );\n });\n }\n default:\n return null;\n }\n};\n\nconst LogIconContainer = styled.div`\n margin-left: 50px;\n z-index: 99;\n\n @media screen and (max-width: 768px) {\n margin-left: 10px;\n }\n`;\n\nconst Log = ({ timestamp }) => {\n const updatedData = useMemo(\n () =>\n state.changedKeysListWithValues.find((obj) =>\n Object.values(obj).some(\n (value) =>\n value && parseFloat(value.modifiedValue / 1e6) === timestamp\n )\n ),\n [state.changedKeysListWithValues, timestamp]\n );\n\n const editorId = updatedData.editorId;\n const valuesArray = Object.values(updatedData ?? {});\n // if valuesArray length is 2 that means it only has timestamp and editorId\n if (!updatedData || valuesArray.length === 2) {\n return <></>;\n }\n\n return (\n <LogIconContainer className=\"d-flex gap-3 align-items-center\">\n <img\n src=\"https://ipfs.near.social/ipfs/bafkreiffqrxdi4xqu7erf46gdlwuodt6dm6rji2jtixs3iionjvga6rhdi\"\n height={30}\n />\n <div className=\"flex-1 w-100 text-wrap\">\n <span\n style={{ display: \"inline-flex\" }}\n className=\"gap-1 align-items-center\"\n >\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: editorId,\n size: \"sm\",\n }}\n />\n {editorId}\n </span>\n {valuesArray.map((i, index) => {\n if (i.key && i.key !== \"timestamp\") {\n return (\n <span key={index}>\n {parseProposalKeyAndValue(\n i.key,\n i.modifiedValue,\n i.originalValue\n )}\n {i.key !== \"timeline\" && \"・\"}\n </span>\n );\n }\n })}\n <span>\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockTimestamp: timestamp * 1000000,\n }}\n />\n </span>\n </div>\n </LogIconContainer>\n );\n};\n\nif (Array.isArray(state.data)) {\n return (\n <Wrapper>\n <div\n className=\"log-line\"\n style={{ height: state.data.length > 4 ? \"120%\" : \"150%\" }}\n ></div>\n <div className=\"d-flex flex-column gap-4\">\n {state.data.map((i, index) => {\n if (i.blockHeight) {\n const item = state.socialComments.find(\n (t) => t.blockHeight === i.blockHeight\n );\n return <Comment commentItem={item} />;\n } else {\n return <Log timestamp={i.timestamp} key={index} />;\n }\n })}\n </div>\n </Wrapper>\n );\n}\n" }, "devhub.entity.proposal.ComposeComment": { "": "const proposalId = props.id;\nconst draftKey = \"COMMENT_DRAFT\" + proposalId;\nconst draftComment = Storage.privateGet(draftKey);\n\nconst ComposeEmbeddCSS = `\n .CodeMirror {\n border: none !important;\n min-height: 50px !important;\n }\n\n .editor-toolbar {\n border: none !important;\n }\n\n .CodeMirror-scroll{\n min-height: 50px !important;\n }\n`;\nconst notifyAccountId = props.notifyAccountId;\nconst accountId = context.accountId;\nconst item = props.item;\nconst [allowGetDraft, setAllowGetDraft] = useState(true);\nconst [comment, setComment] = useState(null);\n\nuseEffect(() => {\n if (draftComment && allowGetDraft) {\n setComment(draftComment);\n setAllowGetDraft(false);\n }\n}, [draftComment]);\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n if (comment !== draftComment) Storage.privateSet(draftKey, comment);\n }, 2000);\n\n return () => {\n clearTimeout(handler);\n };\n}, [comment, draftKey]);\n\nif (!accountId) {\n return (\n <div\n style={{\n marginLeft: 10,\n backgroundColor: \"#ECF8FB\",\n border: \"1px solid #E2E6EC\",\n }}\n className=\"d-flex align-items-center gap-1 p-4 rounded-2\"\n >\n <Link to=\"https://near.org/signup\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"grey-btn\" },\n label: \"Sign up\",\n }}\n />\n </Link>\n <div className=\"fw-bold\">to join this conversation.</div>\n <div>Already have an account?</div>\n <a className=\"text-decoration-underline\" href=\"https://near.org/signin\">\n Log in to comment\n </a>\n </div>\n );\n}\n\nfunction extractMentions(text) {\n const mentionRegex =\n /@((?:(?:[a-z\\d]+[-_])*[a-z\\d]+\\.)*(?:[a-z\\d]+[-_])*[a-z\\d]+)/gi;\n mentionRegex.lastIndex = 0;\n const accountIds = new Set();\n for (const match of text.matchAll(mentionRegex)) {\n if (\n !/[\\w`]/.test(match.input.charAt(match.index - 1)) &&\n !/[/\\w`]/.test(match.input.charAt(match.index + match[0].length)) &&\n match[1].length >= 2 &&\n match[1].length <= 64\n ) {\n accountIds.add(match[1].toLowerCase());\n }\n }\n return [...accountIds];\n}\n\nfunction extractTagNotifications(text, item) {\n return extractMentions(text || \"\")\n .filter((accountId) => accountId !== context.accountId)\n .map((accountId) => ({\n key: accountId,\n value: {\n type: \"mention\",\n item,\n },\n }));\n}\n\nfunction composeData() {\n const data = {\n post: {\n comment: JSON.stringify({\n type: \"md\",\n text: comment,\n item,\n }),\n },\n index: {\n comment: JSON.stringify({\n key: item,\n value: {\n type: \"md\",\n },\n }),\n },\n };\n\n const notifications = extractTagNotifications(comment, {\n type: \"social\",\n path: `${accountId}/post/comment`,\n });\n\n if (notifyAccountId && notifyAccountId !== context.accountId) {\n notifications.push({\n key: notifyAccountId,\n value: {\n type: \"devhub/reply\",\n item,\n proposal: proposalId,\n },\n });\n }\n\n if (notifications.length) {\n data.index.notify = JSON.stringify(\n notifications.length > 1 ? notifications : notifications[0]\n );\n }\n\n Social.set(data, {\n force: true,\n onCommit: () => {\n setComment(\"\");\n },\n onCancel: () => {},\n });\n}\n\nuseEffect(() => {\n if (props.transactionHashes && comment) {\n setComment(\"\");\n }\n}, [props.transactionHashes, comment]);\n\nreturn (\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2 w-100\">\n <b className=\"mt-1\">Add a comment</b>\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Compose\"}\n props={{\n data: comment,\n onChange: setComment,\n autocompleteEnabled: true,\n autoFocus: false,\n placeholder: \"Add your comment here...\",\n height: \"160\",\n embeddCSS: ComposeEmbeddCSS,\n }}\n />\n <div className=\"d-flex gap-2 align-content-center justify-content-end\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Comment\",\n disabled: !comment,\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n composeData();\n },\n }}\n />\n </div>\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.Editor": { "": "const { href } = VM.require(\"devgovgigs.petersalomonsen.near/widget/core.lib.url\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devgovgigs.petersalomonsen.near/widget/core.lib.common\"\n);\nconst draftKey = \"PROPOSAL_EDIT\";\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nhref || (href = () => {});\n\nconst { id, timestamp } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://docs.google.com/document/d/1kR1YbaQE4mmHcf-BHo7NwO7vmGx4EciHK-QjelCufI8/edit?usp=sharing\";\n\nif (!author) {\n return (\n <Widget src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.LoginScreen\"} />\n );\n}\nlet editProposalData = null;\nlet draftProposalData = null;\n\nif (isEditPage) {\n editProposalData = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n });\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !important;\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: #04a46e !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 }\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\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 devdaoAccount = \"neardevdao.near\";\n\nconst [category, setCategory] = useState(null);\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 [requestedSponsor, setRequestedSponsor] = useState(devdaoAccount);\nconst [requestedSponsorshipAmount, setRequestedSponsorshipAmount] =\n useState(null);\nconst [requestedSponsorshipToken, setRequestedSponsorshipToken] = useState(\n tokensOptions[2]\n);\nconst [supervisor, setSupervisor] = useState(null);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [proposalsOptions, setProposalsOptions] = useState([]);\nconst proposalsData = Near.view(\"devhub.near\", \"get_proposals\");\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 [amountError, setAmountError] = useState(null);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\n\nconst [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false);\n\nconst [showProposalPage, setShowProposalPage] = useState(false); // when user creates/edit a proposal and confirm the txn, this is true\nconst [proposalId, setProposalId] = useState(null);\n\nif (allowDraft) {\n draftProposalData = Storage.privateGet(draftKey);\n}\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editProposalData.id ?? null,\n snapshot: {\n name: title,\n description: description,\n category: category,\n summary: summary,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n supervisor: supervisor,\n requested_sponsor: requestedSponsor,\n },\n }),\n [\n title,\n summary,\n description,\n category,\n requestedSponsorshipAmount,\n requestedSponsorshipToken,\n receiverAccount,\n supervisor,\n requestedSponsor,\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 setCategory(snapshot.category);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setReceiverAccount(snapshot.receiver_account);\n setRequestedSponsor(snapshot.requested_sponsor);\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 setLoading(false);\n }\n}, [editProposalData, draftProposalData, allowDraft]);\n\nuseEffect(() => {\n if (draftProposalData) {\n setAllowDraft(false);\n }\n}, [draftProposalData]);\n\nuseEffect(() => {\n if (showProposalPage) {\n return;\n }\n setDisabledSubmitBtn(\n isSubmittingTransaction ||\n amountError ||\n !title ||\n !description ||\n !summary ||\n !category ||\n !requestedSponsorshipAmount ||\n !receiverAccount ||\n !requestedSponsor ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 3000);\n\n return () => {\n clearTimeout(handler);\n };\n}, [\n memoizedDraftData,\n draftKey,\n draftProposalData,\n consent,\n amountError,\n isSubmittingTransaction,\n showProposalPage,\n]);\n\nuseEffect(() => {\n if (\n proposalsOptions.length > 0 &&\n editProposalData &&\n editProposalData?.snapshot?.linked_proposals?.length > 0\n ) {\n let data = [];\n editProposalData.snapshot.linked_proposals.map((item) => {\n data.push(proposalsOptions.find((i) => i.value === item));\n });\n setLinkedProposals(data);\n }\n}, [editProposalData, proposalsOptions]);\n\nuseEffect(() => {\n // Trigger when proposals data change, which will happen on cache invalidation\n setIsSubmittingTransaction(false);\n console.log(\"Proposals data change, assume transaction completed\");\n}, [proposalsData]);\n\nuseEffect(() => {\n if (\n proposalsData !== null &&\n Array.isArray(proposalsData) &&\n !proposalsOptions.length\n ) {\n const data = [];\n for (const prop of proposalsData) {\n data.push({\n label: \"Id \" + prop.id + \" : \" + prop.snapshot.name,\n value: prop.id,\n });\n }\n setProposalsOptions(data);\n }\n}, [proposalsData]);\n\nuseEffect(() => {});\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\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(\"https://rpc.mainnet.near.org\", {\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 setShowProposalPage(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 \"devhub.near\",\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 (showProposalPage) {\n setShowProposalPage(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst CheckBox = ({ value, isChecked, label, onClick }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={isChecked}\n onChange={(e) => onClick(e.target.checked)}\n />\n <label class=\"form-check-label text-sm\">{label}</label>\n </div>\n );\n};\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: 9999;\n font-size: 13px;\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 }\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 };\n\n const toggleDropdown = () => {\n setDraftBtnOpen(!isDraftBtnOpen);\n };\n\n const handleSubmit = () => {\n const isDraft = selectedStatus === \"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()}\n className=\"p-2 d-flex gap-2 align-items-center \"\n >\n {isSubmittingTransaction ? (\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-${isOpen ? \"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 setIsSubmittingTransaction(true);\n console.log(\"submitting transaction\");\n const linkedProposalsIds = linkedProposals.map((item) => item.value) ?? [];\n const body = {\n proposal_body_version: \"V0\",\n name: title,\n description: description,\n category: category,\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 supervisor: supervisor || null,\n requested_sponsor: requestedSponsor,\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 = { labels: [], body: body };\n if (isEditPage) {\n args[\"id\"] = editProposalData.id;\n }\n\n Near.call([\n {\n contractName: \"devhub.near\",\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\nconst descriptionPlaceholder = `**PROJECT DETAILS**\nProvide a clear overview of the scope, deliverables, and expected outcomes. What benefits will it provide to the NEAR community? How will you measure success?\n\n**TIMELINE**\nDescribe the timeline of your project and key milestones, specifying if the work was already complete or not. Include your plans for reporting progress to the community.\n\nOPTIONAL FIELDS\n\n**TEAM**\nProvide a list of who will be working on the project along with their relevant skillset and experience. You may include links to portfolios or profiles to help the community get to know who the DAO will fund and how their backgrounds will contribute to your project’s success.\n\n**BUDGET BREAKDOWN**\nInclude a detailed breakdown on how you will use the funds and include rate justification. Our community values transparency, so be as specific as possible.\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={\"devgovgigs.petersalomonsen.near/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\nif (showProposalPage) {\n return (\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Proposal\"}\n props={{ id: proposalId, ...props }}\n />\n );\n} else\n return (\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={\"devgovgigs.petersalomonsen.near/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={\"devgovgigs.petersalomonsen.near/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 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={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: author,\n }}\n />\n </div>\n <div className=\"d-flex flex-column gap-2 gap-sm-4 w-100\">\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the category that best aligns with your\n contribution to the NEAR developer community. Need\n guidance? See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs.\n </a>\n </>\n }\n >\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CategoryDropdown\"\n }\n props={{\n selectedValue: category,\n onChange: setCategory,\n }}\n />\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: title,\n onChange: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: \"Enter title here.\",\n inputProps: {\n max: 80,\n required: true,\n },\n }}\n />\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onChange: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: \"Enter summary here.\",\n inputProps: {\n max: 500,\n required: true,\n },\n }}\n />\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description=\"Expand on your summary with any relevant details like your contribution timeline, key milestones, team background, and a clear breakdown of how the funds will be used. Proposals should be simple and clear (e.g. 1 month). For more complex projects, treat each milestone as a separate proposal.\"\n >\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Compose\"\n }\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n placeholder: descriptionPlaceholder,\n }}\n />\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n <div className=\"d-flex flex-column gap-2\">\n <CheckBox\n value={consent.toc}\n label={\n <>\n I’ve agree to{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1nRGy7LhpLj56SjN9MseV1x-ubH8O_c6B9DOAZ9qTwMU/edit?usp=sharing\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’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 <CheckBox\n value={consent.coc}\n label={\n <>\n I’ve read{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1c6XV8Sj_BRKw8jnTIsjdLPPN6Al5eEStt1ZLYSuqw9U/edit\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’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 </InputContainer>\n <div className=\"d-flex justify-content-between gap-2 align-items-center\">\n <div>\n {isEditPage && (\n <Widget\n src={`devgovgigs.petersalomonsen.near/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 className=\"d-flex gap-2\">\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposal\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposals\",\n },\n })\n }\n >\n <Widget\n src={`devgovgigs.petersalomonsen.near/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 <Widget\n src=\"mob.near/widget/Profile.ShortInlineBlock\"\n props={{\n accountId: author,\n }}\n />\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Link Proposals (Optional)\">\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 {linkedProposals.map((proposal) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <a\n className=\"text-decoration-underline flex-1\"\n href={href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposal.value,\n },\n })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {proposal.label}\n </a>\n <div\n className=\"cursor-pointer\"\n onClick={() => {\n const updatedLinkedProposals =\n linkedProposals.filter(\n (item) => item.value !== proposal.value\n );\n setLinkedProposals(updatedLinkedProposals);\n }}\n >\n <i class=\"bi bi-trash3-fill\"></i>\n </div>\n </div>\n );\n })}\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.DropDownWithSearch\"\n props={{\n selectedValue: \"\",\n onChange: (v) => {\n if (\n !linkedProposals.some(\n (item) => item.value === v.value\n )\n ) {\n setLinkedProposals([...linkedProposals, v]);\n }\n },\n options: proposalsOptions,\n showSearch: true,\n searchInputPlaceholder: \"Search by Id\",\n defaultLabel: \"Search proposals\",\n searchByValue: true,\n }}\n />\n </div>\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: receiverAccount,\n placeholder: devdaoAccount,\n onUpdate: setReceiverAccount,\n }}\n />\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\n platform, you must complete KYC/KYB verification\n using Fractal, a trusted identity verification\n solution. This helps others trust transactions with\n your account. Click \"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=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: receiverAccount,\n showGetVerifiedBtn: true,\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: requestedSponsorshipAmount,\n onChange: (e) => {\n const inputValue = e.target.value;\n const isValidInput = /^\\d+$/.test(inputValue);\n if (inputValue.trim() === \"\") {\n return;\n }\n if (!isValidInput || Number(inputValue) < 0) {\n setAmountError(\n \"Please enter the nearest positive whole number.\"\n );\n } else {\n setRequestedSponsorshipAmount(inputValue);\n setAmountError(\"\");\n }\n },\n skipPaddingGap: true,\n placeholder: \"Enter amount\",\n inputProps: {\n type: \"number\",\n prefix: \"$\",\n },\n }}\n />\n {amountError && (\n <div style={{ color: \"red\" }} className=\"text-sm\">\n {amountError}\n </div>\n )}\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: tokensOptions,\n selectedValue: requestedSponsorshipToken,\n onUpdate: (v) => {\n setRequestedSponsorshipToken(v);\n },\n }}\n />\n </InputContainer>\n <InputContainer heading=\"Requested Sponsor\" description=\"\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: requestedSponsor,\n placeholder: \"DevDAO\",\n onUpdate: setRequestedSponsor,\n }}\n />\n </InputContainer>\n <InputContainer\n heading=\"Supervisor (Optional)\"\n description=\"\"\n >\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n placeholder: \"Enter Supervisor\",\n onUpdate: setSupervisor,\n }}\n />\n </InputContainer>\n </div>\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n" }, "devhub.entity.proposal.StatusTag": { "": "const timelineStatus = props.timelineStatus;\nconst size = props.size ?? \"md\";\n\nconst getClassNameByStatus = () => {\n switch (timelineStatus) {\n case \"DRAFT\":\n return \"grey\";\n case \"REVIEW\":\n return \"grey\";\n case \"APPROVED\":\n case \"PAYMENT_PROCESSING\":\n case \"APPROVED_CONDITIONALLY\":\n case \"FUNDED\":\n return \"green\";\n case \"REJECTED\":\n case \"CANCELLED\":\n return \"warning\";\n default:\n return \"green\";\n }\n};\n\nconst Container = styled.div`\n font-size: ${({ size }) => {\n switch (size) {\n case \"sm\":\n return \"10px\";\n case \"lg\":\n return \"14px\";\n default:\n return \"12px\";\n }\n }};\n\n .warning-tag {\n border: 1px solid #f40303 !important;\n color: #f40303 !important;\n }\n\n .grey-tag {\n border: 1px solid #555555 !important;\n color: #555555 !important;\n }\n\n .green-tag {\n border: 1px solid #04a46e !important;\n color: #04a46e !important;\n }\n`;\n\nreturn (\n <Container size={size}>\n <div className={getClassNameByStatus() + \"-tag rounded-2 p-1\"}>\n {(timelineStatus ?? \"\").replace(\"_\", \" \")}\n </div>\n </Container>\n);\n" }, "devhub.components.molecule.Input": { "": "const TextInput = ({\n className,\n format,\n inputProps: { className: inputClassName, ...inputProps },\n key,\n label,\n multiline,\n onChange,\n placeholder,\n type,\n value,\n skipPaddingGap,\n style,\n ...otherProps\n}) => {\n State.init({\n data: value,\n });\n\n useEffect(() => {\n const handler = setTimeout(() => {\n onChange({ target: { value: state.data } });\n }, 30);\n\n return () => {\n clearTimeout(handler);\n };\n }, [state.data]);\n\n useEffect(() => {\n if (value !== state.data) {\n State.update({ data: value });\n }\n }, [value]);\n\n const typeAttribute =\n type === \"text\" ||\n type === \"password\" ||\n type === \"number\" ||\n type === \"date\"\n ? type\n : \"text\";\n\n const isValid = () => {\n if (!state.data || state.data.length === 0) {\n return !inputProps.required;\n } else if (inputProps.min && inputProps.min > state.data?.length) {\n return false;\n } else if (inputProps.max && inputProps.max < state.data?.length) {\n return false;\n } else if (\n inputProps.allowCommaAndSpace === false &&\n /^[^,\\s]*$/.test(state.data) === false\n ) {\n return false;\n } else if (\n inputProps.validUrl === true &&\n /^(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/.test(\n state.data\n ) === false\n ) {\n return false;\n }\n return true;\n };\n\n const renderedLabels = [\n (label?.length ?? 0) > 0 ? (\n <span className=\"d-inline-flex gap-1 text-wrap\">\n <span>{label}</span>\n\n {inputProps.required ? <span className=\"text-danger\">*</span> : null}\n </span>\n ) : null,\n\n format === \"markdown\" ? (\n <i class=\"bi bi-markdown text-muted\" title=\"Markdown\" />\n ) : null,\n\n format === \"comma-separated\" ? (\n <span\n className={`d-inline-flex align-items-center ${\n isValid() ? \"text-muted\" : \"text-danger\"\n }`}\n style={{ fontSize: 12 }}\n >\n {format}\n </span>\n ) : null,\n\n (inputProps.max ?? null) !== null ? (\n <span\n className={`d-inline-flex ${isValid() ? \"text-muted\" : \"text-danger\"}`}\n style={{ fontSize: 12 }}\n >{`${state.data?.length ?? 0} / ${inputProps.max}`}</span>\n ) : null,\n ].filter((label) => label !== null);\n\n return (\n <div\n className={[\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\",\n skipPaddingGap ? \"\" : \"gap-1 p-2\",\n className ?? \"\",\n ].join(\" \")}\n style={style}\n {...otherProps}\n >\n {renderedLabels.length > 0 ? (\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n id={key}\n >\n {renderedLabels.map((label) => label)}\n </span>\n ) : null}\n\n {!multiline ? (\n <div className=\"input-group\">\n {inputProps.prefix && (\n <span className=\"input-group-text\">{inputProps.prefix}</span>\n )}\n <input\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\"form-control border\", inputClassName].join(\" \")}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n {...{ placeholder, ...inputProps }}\n />\n </div>\n ) : (\n <textarea\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\"form-control border\", inputClassName].join(\" \")}\n placeholder={\n placeholder + (inputProps.required ? \" ( required )\" : \"\")\n }\n style={{ resize: inputProps.resize ?? \"vertical\" }}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n {...{ placeholder, ...inputProps }}\n />\n )}\n </div>\n );\n};\n\nreturn TextInput(props);\n" }, "devhub.entity.proposal.AccountInput": { "": "const value = props.value;\nconst placeholder = props.placeholder;\nconst onUpdate = props.onUpdate;\n\nconst [showAccountAutocomplete, setAutoComplete] = useState(false);\nconst [isValidAccount, setValidAccount] = useState(true);\nconst AutoComplete = styled.div`\n max-width: 400px;\n margin-top: 1rem;\n`;\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n const valid = value.length === 64 || (value ?? \"\").includes(\".near\");\n setValidAccount(valid);\n setAutoComplete(!valid);\n }, 100);\n\n return () => {\n clearTimeout(handler);\n };\n}, [value]);\n\nreturn (\n <div>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: value,\n onChange: (e) => {\n onUpdate(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: placeholder,\n inputProps: {\n max: 64,\n prefix: \"@\",\n },\n }}\n />\n {value && !isValidAccount && (\n <div style={{ color: \"red\" }} className=\"text-sm mt-1\">\n Please enter valid account ID\n </div>\n )}\n {showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: value,\n onSelect: (id) => {\n onUpdate(id);\n setAutoComplete(false);\n },\n onClose: () => setAutoComplete(false),\n }}\n />\n </AutoComplete>\n )}\n </div>\n);\n" }, "devhub.entity.proposal.CategoryDropdown": { "": "const { selectedValue, onChange, disabled } = props;\n\nonChange = onChange || (() => {});\n\nconst options = [\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiet5w62oeef6msfsakdskq7zkjk33ngogcerfdmqewnsuj74u376e\",\n title: \"DevDAO Operations\",\n description:\n \"Provide core operations and leadership for the DAO or infrastructure support.\",\n value: \"DevDAO Operations\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiengkdru4fczwltjylfqeeypsdf4hb5fdxa6t67l3x2qtqgeo3pzq\",\n title: \"DevDAO Platform\",\n description:\n \"Build & maintain the interface for DevHub’s community & funding activities.\",\n value: \"DevDAO Platform\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreicpt3ulwsmptzdbtkhvxodvo7pcajcpyr35tqcbfdnaipzrx5re7e\",\n title: \"Events & Hackathons\",\n description:\n \"Organize or support events, hackathons, and local meet ups to grow communities.\",\n value: \"Events & Hackathons\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreibdrwhbouuutvrk4qt2udf4kumbyy5ebjkezobbahxvo7fyxo2ec4\",\n title: \"Engagement & Awareness\",\n description:\n \"Create content from social posts to real world swag to drive awareness to NEAR.\",\n value: \"Engagement & Awareness\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiem2vjsp6wu3lkd4zagpm43f32egdjjzchmleky6rr2ydzhlkrxam\",\n title: \"Decentralized DevRel\",\n description:\n \"Provide support, gather feedback, and maintain docs to drive engagement.\",\n value: \"Decentralized DevRel\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreic3prsy52hwueugqj5rwualib4imguelezsbvgrxtezw4u33ldxqq\",\n title: \"Universities & Bootcamps\",\n description:\n \"Engage with students and universities globally to encourage NEAR.\",\n value: \"Universities & Bootcamps\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreigf7j5isssumbjl24zy4pr27ryfqivan3vuwu2uwsofcujhhkk7cq\",\n title: \"Tooling & Infrastructure\",\n description:\n \"Contribute code to NEAR tooling or facilitating technical decisions.\",\n value: \"Tooling & Infrastructure\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreihctatkwnvpmblgqnpw76zggfet3fmpgurqvtj7vbm3cb5r3pp52u\",\n title: \"Other\",\n description: \"Use this category if you are not sure which one to use.\",\n value: \"Other\",\n },\n];\n\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOptionValue, setSelectedValue] = useState(selectedValue);\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nuseEffect(() => {\n if (selectedValue && selectedValue !== selectedOptionValue) {\n setSelectedValue(selectedValue);\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n if (selectedValue !== selectedOptionValue) {\n onChange(selectedOptionValue);\n }\n}, [selectedOptionValue]);\n\nconst handleOptionClick = (option) => {\n setSelectedValue(option.value);\n setIsOpen(false);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 2%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .disabled {\n background-color: #f8f8f8 !important;\n cursor: not-allowed !important;\n border-radius: 5px;\n opacity: inherit !important;\n }\n\n .disabled.dropdown-toggle::after {\n display: none !important;\n }\n\n .custom-select {\n position: relative;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\n\nconst Item = ({ option }) => {\n if (!option) {\n return <div className=\"text-muted\">Select Category</div>;\n }\n return (\n <div className=\"d-flex gap-3 align-items-center w-100\">\n <img src={option.icon} height={30} />\n <div className=\"d-flex flex-column gap-1 w-100 text-wrap\">\n <div className=\"h6 mb-0\"> {option.title}</div>\n <div className=\"text-sm text-muted w-100 text-wrap\">\n {option.description}\n </div>\n </div>\n </div>\n );\n};\n\nconst selectedOption =\n options.find((item) => item.value === selectedOptionValue) ?? null;\n\nreturn (\n <Container>\n <div\n className=\"custom-select w-100\"\n tabIndex=\"0\"\n onBlur={() => setIsOpen(false)}\n >\n <div\n className={\n \"dropdown-toggle bg-white border rounded-2 btn drop-btn w-100 \" +\n (disabled ? \"disabled\" : \"\")\n }\n onClick={!disabled && toggleDropdown}\n >\n <div className={`selected-option`}>\n <Item option={selectedOption} />\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow show w-100\">\n <div>\n {options.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 my-1 ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <Item option={option} />\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.VerificationStatus": { "": "const receiverAccount = props.receiverAccount;\nconst showGetVerifiedBtn = props.showGetVerifiedBtn;\nconst [verificationStatus, setVerificationStatus] = useState(null);\n\nconst WarningImg =\n \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\";\n\nconst SuccessImg =\n \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\";\n\nuseEffect(() => {\n if (\n receiverAccount.length === 64 ||\n (receiverAccount ?? \"\").includes(\".near\")\n ) {\n useCache(\n () =>\n asyncFetch(\n `https://neardevhub-kyc-proxy.shuttleapp.rs/kyc/${receiverAccount}`\n ).then((res) => {\n let displayableText = \"\";\n switch (res.body.kyc_status) {\n case \"Approved\":\n displayableText = \"Verified\";\n break;\n case \"Pending\":\n displayableText = \"Pending\";\n break;\n default:\n displayableText = \"Not Verfied\";\n break;\n }\n setVerificationStatus(displayableText);\n }),\n \"ky-check-proposal\" + receiverAccount,\n { subscribe: false }\n );\n }\n}, [receiverAccount]);\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: 9999;\n font-size: 13px;\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 }\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 .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n`;\n\nconst [kycOptionsOpen, setKycOptions] = useState(false);\n\nconst VerificationBtn = () => {\n const btnOptions = [\n {\n src: \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\",\n label: \"KYC\",\n description: \"Choose this if you are an individual.\",\n value: \"KYC\",\n },\n {\n src: \"https://ipfs.near.social/ipfs/bafkreic5ksax6b45pelvxm6a2v2j465jgbitpzrxtzpmn6zehl23gocwxm\",\n label: \"KYB\",\n description: \"Choose this if you are a business or corporate entity..\",\n value: \"KYB\",\n },\n ];\n\n const toggleDropdown = () => {\n setKycOptions(!kycOptionsOpen);\n };\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => setKycOptions(false)}\n >\n <div className={\"select-header no-border\"}>\n <Widget\n src={`devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"black-btn\" },\n label: (\n <div className=\"d-flex align-items-center gap-1\">\n Get Verified\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </div>\n ),\n onClick: toggleDropdown,\n }}\n />\n </div>\n\n {kycOptionsOpen && (\n <div className=\"options-card left\">\n {btnOptions.map((option) => (\n <a\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n href={\n option.value === \"KYC\"\n ? \"https://go.fractal.id/near-social-kyc\"\n : \"https://go.fractal.id/near-social-kyb\"\n }\n >\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => setKycOptions(false)}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <img src={option.src} height={30} />\n <div>\n <div className=\"fw-bold\">{option.label}</div>\n <div className=\"text-muted text-xs\">\n {option.description}\n </div>\n </div>\n </div>\n </div>\n </a>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nreturn (\n <div>\n <div className=\"d-flex justify-content-between align-items-center\">\n <div className=\"d-flex gap-4 \">\n <img\n className=\"align-self-center\"\n src={verificationStatus === \"Verified\" ? SuccessImg : WarningImg}\n height={30}\n />\n <div className=\"d-flex flex-column justify-content-center\">\n <div className=\"h6 mb-0\">Fractal</div>\n <div className=\"text-muted text-sm\">{verificationStatus}</div>\n </div>\n </div>\n {verificationStatus !== \"Verified\" && showGetVerifiedBtn && (\n <VerificationBtn />\n )}\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.Profile": { "": "const accountId = props.accountId;\nconst size = props.size ?? \"md\";\nconst Avatar = styled.div`\n &.sm {\n min-width: 30px;\n max-width: 30px;\n min-height: 30px;\n max-height: 30px;\n }\n &.md {\n min-width: 40px;\n max-width: 40px;\n min-height: 40px;\n max-height: 40px;\n }\n pointer-events: none;\n flex-shrink: 0;\n border: 1px solid #eceef0;\n overflow: hidden;\n border-radius: 40px;\n transition: border-color 200ms;\n\n img {\n object-fit: cover;\n width: 100%;\n height: 100%;\n margin: 0 !important;\n }\n`;\nconst profile = Social.get(`${accountId}/profile/**`, \"final\");\n\nreturn (\n <Avatar className={size}>\n <Widget\n src=\"mob.near/widget/Image\"\n props={{\n image: profile.image,\n alt: profile.name,\n fallbackUrl:\n \"https://ipfs.near.social/ipfs/bafkreibiyqabm3kl24gcb2oegb7pmwdi6wwrpui62iwb44l7uomnn3lhbi\",\n }}\n />\n </Avatar>\n);\n" }, "devhub.entity.proposal.Feed": { "": "const { href } = VM.require(\"devgovgigs.petersalomonsen.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst Container = styled.div`\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 @media screen and (max-width: 768px) {\n font-size: 13px;\n }\n\n .text-sm {\n font-size: 13px;\n }\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 .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n .green-btn {\n padding: 0.5rem 0.8rem !important;\n min-height: 32px;\n }\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n .text-normal {\n font-weight: normal !important;\n }\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst FeedItem = ({ proposal }) => {\n const { snapshot } = proposal;\n const accountId = proposal.author_id;\n const profile = Social.get(`${accountId}/profile/**`, \"final\");\n const blockHeight = parseInt(proposal.social_db_post_block_height);\n const item = {\n type: \"social\",\n path: `devhub.near/post/main`,\n blockHeight,\n };\n\n return (\n <a\n href={href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposal.id,\n },\n })}\n onClick={(e) => e.stopPropagation()}\n style={{ textDecoration: \"none\" }}\n >\n <div className=\"proposal-card d-flex justify-content-between gap-2 text-muted cursor-pointer p-3\">\n <div className=\"d-flex gap-4\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"d-flex gap-2 align-items-center flex-wrap\">\n <div className=\"h6 mb-0 text-black\">{snapshot.name}</div>\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CategoryTag\"}\n props={{\n category: snapshot.category,\n }}\n />\n </div>\n <div className=\"d-flex gap-2 align-items-center text-sm\">\n <div>By {profile.name ?? accountId} ・ </div>\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: snapshot.timestamp,\n }}\n />\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: accountId,\n }}\n />\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CommentIcon\"}\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n </div>\n </div>\n </div>\n <div className=\"align-self-center\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n }}\n />\n </div>\n </div>\n </a>\n );\n};\n\nconst FeedPage = () => {\n const proposals = Near.view(\"devhub.near\", \"get_proposals\", {});\n\n return (\n <Container className=\"w-100 py-4 px-2 d-flex flex-column gap-3\">\n <div className=\"d-flex justify-content-between flex-wrap gap-2 align-items-center\">\n <Heading>\n DevDAO Proposals{\" \"}\n <span className=\"text-muted text-normal\"> ({proposals.length})</span>\n </Heading>\n {/* Filters aren't supported yet */}\n {/* <div className=\"d-flex gap-4 align-items-center\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-input\"\n }\n props={{}}\n />\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-sort\"}\n props={{}}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-category\"\n }\n props={{}}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-stage\"\n }\n props={{}}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-author\"\n }\n props={{}}\n />\n </div> */}\n <div>\n <Link\n to={href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: { page: \"create-proposal\" },\n })}\n >\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: (\n <div className=\"d-flex gap-2 align-items-center\">\n <div>\n <i class=\"bi bi-plus-circle-fill\"></i>\n </div>\n New Proposal\n </div>\n ),\n classNames: { root: \"green-btn\" },\n }}\n />\n </Link>\n </div>\n </div>\n <div style={{ minHeight: \"50vh\" }}>\n {!Array.isArray(proposals) ? (\n <div className=\"d-flex justify-content-center align-items-center w-100\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n ) : (\n <div className=\"card rounded-0 mt-4 py-3 full-width-div\">\n <div className=\"container-xl\">\n <div className=\"text-muted bg-grey text-sm mt-2 p-3 rounded-3\">\n <p className=\"d-flex gap-4 align-items-center mb-0\">\n <div>\n <i class=\"bi bi-info-circle\"></i>\n </div>\n DevDAO is the primary organization behind DevHub, and we offer\n sponsorships to contributors and projects that align with our\n goal of fostering a self-sufficient community of developers\n for a thriving NEAR ecosystem. Check out our Funding\n Guidelines for more details.\n </p>\n </div>\n <div className=\"mt-4 border rounded-2\">\n {proposals.map((item, index) => {\n return (\n <div\n key={index}\n className={\n (index !== proposals.length - 1 && \"border-bottom \") +\n (index === 0 && \" rounded-top-2\")\n }\n >\n <FeedItem proposal={item} />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n </div>\n </Container>\n );\n};\n\nreturn FeedPage(props);\n" } } } } }

Transaction Execution Plan

Convert Transaction To Receipt
Gas Burned:
2 Tgas
Tokens Burned:
0.00027 
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
37 Tgas
Tokens Burned:
0.0038 
Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devgovgigs.petersalomonsen.near": { "widget": { "devhub.components.molecule.DropDownWithSearch": { "": "const {\n selectedValue,\n onChange,\n options,\n defaultLabel,\n showSearch,\n searchInputPlaceholder,\n searchByLabel,\n searchByValue,\n} = props;\n\nconst [searchTerm, setSearchTerm] = useState(\"\");\nconst [filteredOptions, setFilteredOptions] = useState(options);\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOption, setSelectedOption] = useState({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n});\n\nuseEffect(() => {\n if (selectedOption.value !== selectedValue) {\n setSelectedOption({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n });\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n setFilteredOptions(options);\n}, [options]);\n\nconst handleSearch = (event) => {\n const term = event.target.value.toLowerCase();\n setSearchTerm(term);\n\n const filteredOptions = options.filter((option) => {\n if (searchByLabel) {\n return option.label.toLowerCase().includes(term);\n }\n if (searchByValue) {\n return option.value.toString().toLowerCase().includes(term);\n }\n });\n\n setFilteredOptions(filteredOptions);\n};\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nconst handleOptionClick = (option) => {\n setSelectedOption(option);\n setIsOpen(false);\n onChange(option);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .custom-select {\n position: relative;\n }\n\n .scroll-box {\n max-height: 200px;\n overflow-y: scroll;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n input {\n background-color: #f8f9fa;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\nlet searchFocused = false;\nreturn (\n <Container>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => {\n setTimeout(() => {\n setIsOpen(searchFocused || false);\n }, 0);\n }}\n >\n <div className=\"dropdown-toggle bg-white border rounded-2 btn drop-btn\">\n <div\n className={`selected-option w-100 text-wrap ${\n selectedOption.label === defaultLabel ? \"text-muted\" : \"\"\n }`}\n onClick={toggleDropdown}\n >\n {selectedOption.label}\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow show\">\n {showSearch && (\n <input\n type=\"text\"\n className=\"form-control mb-2\"\n placeholder={searchInputPlaceholder ?? \"Search options\"}\n value={searchTerm}\n onChange={handleSearch}\n onFocus={() => {\n searchFocused = true;\n }}\n onBlur={() => {\n setTimeout(() => {\n searchFocused = false;\n }, 0);\n }}\n />\n )}\n <div className=\"scroll-box\">\n {filteredOptions.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 text-wrap ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n {option.label}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.Proposal": { "": "const { href } = VM.require(\"devgovgigs.petersalomonsen.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devgovgigs.petersalomonsen.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devgovgigs.petersalomonsen.near/widget/core.lib.common\"\n);\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\n\nconst accountId = context.accountId;\n/*\n---props---\nprops.id: number;\nprops.timestamp: number; optional\n*/\n\nconst 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 Container = styled.div`\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 .description-box {\n font-size: 14px;\n }\n\n .draft-info-container {\n background-color: #ecf8fb;\n }\n\n .review-info-container {\n background-color: #fef6ee;\n }\n\n .text-sm {\n font-size: 13px !important;\n }\n\n .flex-1 {\n flex: 1;\n }\n\n .flex-3 {\n flex: 3;\n }\n\n .circle {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n border: 1px solid grey;\n }\n\n .green-fill {\n background-color: rgb(4, 164, 110) !important;\n border-color: rgb(4, 164, 110) !important;\n color: white !important;\n }\n\n .yellow-fill {\n border-color: #ff7a00 !important;\n }\n\n .vertical-line {\n width: 2px;\n height: 180px;\n background-color: lightgrey;\n }\n\n @media screen and (max-width: 970px) {\n .vertical-line {\n height: 135px !important;\n }\n\n .vertical-line-sm {\n height: 70px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n @media screen and (max-width: 570px) {\n .vertical-line {\n height: 180px !important;\n }\n\n .vertical-line-sm {\n height: 75px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .vertical-line-sm {\n width: 2px;\n height: 70px;\n background-color: lightgrey;\n }\n\n .form-check-input:disabled ~ .form-check-label,\n .form-check-input[disabled] ~ .form-check-label {\n opacity: 1;\n }\n\n .form-check-input {\n border-color: black !important;\n }\n\n .grey-btn {\n background-color: #687076;\n border: none;\n color: white;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\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 }\n\n .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n .border-vertical {\n border-top: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n border-bottom: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n }\n\n button.px-0 {\n padding-inline: 0px !important;\n }\n\n red-icon i {\n color: red;\n }\n`;\n\nconst ProposalContainer = styled.div`\n border: 1px solid lightgrey;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\nconst Text = styled.p`\n display: block;\n margin: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 400;\n color: #687076;\n white-space: nowrap;\n`;\n\nconst Actions = styled.div`\n display: flex;\n align-items: center;\n gap: 12px;\n margin: -6px -6px 6px;\n`;\n\nconst Avatar = styled.div`\n width: 40px;\n height: 40px;\n pointer-events: none;\n\n img {\n object-fit: cover;\n border-radius: 40px;\n width: 100%;\n height: 100%;\n }\n`;\n\nconst stepsArray = [1, 2, 3, 4, 5];\n\nconst { id, timestamp } = props;\nconst proposal = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n});\n\nif (!proposal) {\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={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n}\nif (timestamp && proposal) {\n proposal.snapshot =\n proposal.snapshot_history.find((item) => item.timestamp === timestamp) ??\n proposal.snapshot;\n}\n\nconst { snapshot } = proposal;\n\nconst authorId = proposal.author_id;\nconst blockHeight = parseInt(proposal.social_db_post_block_height);\nconst item = {\n type: \"social\",\n path: `devhub.near/post/main`,\n blockHeight,\n};\nconst proposalURL = `https://near.org/devgovgigs.petersalomonsen.near/widget/app?page=proposal&id=${proposal.id}&timestamp=${snapshot.timestamp}`;\n\nconst KycVerificationStatus = () => {\n const isVerified = true;\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <img\n src={\n isVerified\n ? \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\"\n : \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\"\n }\n height={40}\n />\n <div className=\"d-flex flex-column\">\n <div className=\"h6 mb-0\">KYC Verified</div>\n <div className=\"text-sm\">Expires on Aug 24, 2024</div>\n </div>\n </div>\n );\n};\n\nconst SidePanelItem = ({ title, children, hideBorder }) => {\n return (\n <div\n className={\n \"d-flex flex-column gap-2 pb-3 \" + (!hideBorder && \" border-bottom\")\n }\n >\n <div className=\"h6 mb-0\">{title} </div>\n <div className=\"text-muted\">{children}</div>\n </div>\n );\n};\n\nconst proposalStatusOptions = [\n {\n label: \"Draft\",\n value: { status: TIMELINE_STATUS.DRAFT },\n },\n {\n label: \"Review\",\n value: {\n status: TIMELINE_STATUS.REVIEW,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Approved\",\n value: {\n status: TIMELINE_STATUS.APPROVED,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Approved-Conditionally\",\n value: {\n status: TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Rejected\",\n value: {\n status: TIMELINE_STATUS.REJECTED,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Canceled\",\n value: {\n status: TIMELINE_STATUS.CANCELED,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Payment-processing\",\n value: {\n status: TIMELINE_STATUS.PAYMENT_PROCESSING,\n kyc_verified: false,\n test_transaction_sent: false,\n request_for_trustees_created: false,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Funded\",\n value: {\n status: TIMELINE_STATUS.FUNDED,\n trustees_released_payment: true,\n kyc_verified: true,\n test_transaction_sent: true,\n request_for_trustees_created: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n];\n\nconst LinkedProposals = () => {\n const linkedProposalsData = [];\n snapshot.linked_proposals.map((item) => {\n const data = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: item,\n });\n if (data !== null) {\n linkedProposalsData.push(data);\n }\n });\n\n return (\n <div className=\"d-flex flex-column gap-3\">\n {linkedProposalsData.map((item) => {\n const link = `https://near.org/devhub.near/widget/app?page=proposal&id=${item.id}`;\n return (\n <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: item.snapshot.editor_id,\n }}\n />\n <div className=\"d-flex flex-column\" style={{ maxWidth: 250 }}>\n <b className=\"text-truncate\">{item.snapshot.name}</b>\n <div className=\"text-sm text-muted\">\n created on {readableDate(item.snapshot.timestamp / 1000000)}\n </div>\n </div>\n </div>\n </a>\n );\n })}\n </div>\n );\n};\n\nconst CheckBox = ({ value, isChecked, label, disabled, onClick }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={isChecked}\n disabled={!isModerator || !showTimelineSetting || disabled}\n onChange={(e) => onClick(e.target.checked)}\n />\n <label style={{ width: \"90%\" }} class=\"form-check-label text-black\">\n {label}\n </label>\n </div>\n );\n};\n\nconst RadioButton = ({ value, isChecked, label }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n value={value}\n checked={isChecked}\n disabled={true}\n />\n <label class=\"form-check-label text-black\">{label}</label>\n </div>\n );\n};\n\nconst isAllowedToEditProposal = Near.view(\n \"devhub.near\",\n \"is_allowed_to_edit_proposal\",\n {\n proposal_id: proposal.id,\n editor: accountId,\n }\n);\n\nconst isModerator = Near.view(\"devgovgigs.near\", \"has_moderator\", {\n account_id: accountId,\n});\n\nconst editProposal = ({ timeline }) => {\n const body = {\n proposal_body_version: \"V0\",\n name: snapshot.title,\n description: snapshot.description,\n category: snapshot.category,\n summary: snapshot.summary,\n linked_proposals: snapshot.linked_proposals,\n requested_sponsorship_usd_amount: snapshot.requested_sponsorship_usd_amount,\n requested_sponsorship_paid_in_currency:\n snapshot.requested_sponsorship_paid_in_currency,\n receiver_account: snapshot.receiver_account,\n supervisor: supervisor || null,\n requested_sponsor: snapshot.requested_sponsor,\n timeline: timeline,\n };\n const args = { labels: [], body: body, id: proposal.id };\n\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nconst editProposalStatus = ({ timeline }) => {\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal_timeline\",\n args: {\n id: proposal.id,\n timeline: timeline,\n },\n gas: 270000000000000,\n },\n ]);\n};\n\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\nconst [showTimelineSetting, setShowTimelineSetting] = useState(false);\nconst proposalStatus = useCallback(\n () =>\n proposalStatusOptions.find(\n (i) => i.value.status === snapshot.timeline.status\n ),\n [snapshot]\n);\nconst [updatedProposalStatus, setUpdatedProposalStatus] = useState({\n ...proposalStatus(),\n value: { ...proposalStatus().value, ...snapshot.timeline },\n});\nconst [paymentHashes, setPaymentHashes] = useState([\"\"]);\nconst [supervisor, setSupervisor] = useState(snapshot.supervisor);\n\nconst selectedStatusIndex = useMemo(\n () =>\n proposalStatusOptions.findIndex((i) => {\n return updatedProposalStatus.value.status === i.value.status;\n }),\n [updatedProposalStatus]\n);\n\nconst TimelineItems = ({ title, children, value, values }) => {\n const indexOfCurrentItem = proposalStatusOptions.findIndex((i) =>\n Array.isArray(values)\n ? values.includes(i.value.status)\n : value === i.value.status\n );\n let color = \"transparent\";\n let statusIndex = selectedStatusIndex;\n\n // index 2,3,4,5 is of decision\n if (selectedStatusIndex === 3 || selectedStatusIndex === 2) {\n statusIndex = 2;\n }\n if (statusIndex === indexOfCurrentItem) {\n color = \"#FEF6EE\";\n }\n if (\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status === TIMELINE_STATUS.FUNDED\n ) {\n color = \"#EEFEF0\";\n }\n // reject\n if (statusIndex === 4 && indexOfCurrentItem === 2) {\n color = \"#FF7F7F\";\n }\n // cancelled\n if (statusIndex === 5 && indexOfCurrentItem === 2) {\n color = \"#F4F4F4\";\n }\n\n return (\n <div\n className=\"p-2 rounded-3\"\n style={{\n backgroundColor: color,\n }}\n >\n <div className=\"h6 text-black\"> {title}</div>\n <div className=\"text-sm\">{children}</div>\n </div>\n );\n};\n\nconst link = href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"create-proposal\",\n id: proposal.id,\n timestamp: timestamp,\n },\n});\n\nreturn (\n <Container className=\"d-flex flex-column gap-2 w-100 mt-4\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.ConfirmReviewModal\"}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[1].value });\n },\n }}\n />\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.ConfirmCancelModal\"}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[5].value });\n },\n }}\n />\n <div className=\"d-flex px-3 px-lg-0 justify-content-between\">\n <div className=\"d-flex gap-2 align-items-center h3\">\n <div>{snapshot.name}</div>\n <div className=\"text-muted\">#{proposal.id}</div>\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"near/widget/ShareButton\"\n props={{\n postType: \"post\",\n url: proposalURL,\n }}\n />\n {((isAllowedToEditProposal &&\n snapshot.timeline.status === TIMELINE_STATUS.DRAFT) ||\n isModerator) && (\n <Link to={link} style={{ textDecoration: \"none\" }}>\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Edit\",\n classNames: { root: \"grey-btn btn-sm\" },\n }}\n />\n </Link>\n )}\n </div>\n </div>\n <div className=\"d-flex px-3 px-lg-0 gap-2 align-items-center text-sm pb-3\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n size: \"sm\",\n }}\n />\n <div>\n <b>{authorId} </b> created on{\" \"}\n {readableDate(snapshot.timestamp / 1000000)}\n </div>\n </div>\n <div className=\"card rounded-0 full-width-div px-3 px-lg-0\">\n <div className=\"container-xl py-4\">\n {snapshot.timeline.status === TIMELINE_STATUS.DRAFT &&\n isAllowedToEditProposal && (\n <div className=\"draft-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in draft mode and open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n The author can still refine the proposal and build consensus\n before sharing it with sponsors. Click “Ready for review” when\n you want to start the official review process. This will lock\n the editing function, but comments are still open.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Ready for review\",\n classNames: { root: \"grey-btn btn-sm\" },\n onClick: () => setReviewModal(true),\n }}\n />\n </div>\n </div>\n )}\n {snapshot.timeline.status === TIMELINE_STATUS.REVIEW &&\n isAllowedToEditProposal && (\n <div className=\"review-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in review mode and still open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n You can’t edit the proposal, but comments are open. Only\n moderators can make changes. Click “Cancel Proposal” to cancel\n your proposal. This changes the status to Canceled, signaling\n to sponsors that it’s no longer active or relevant.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\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 classNames: { root: \"btn-outline-danger btn-sm\" },\n onClick: () => setCancelModal(true),\n }}\n />\n </div>\n </div>\n )}\n <div className=\"my-4\">\n <div className=\"d-flex flex-wrap gap-6\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-3 order-2 order-md-1\"\n >\n <div\n className=\"d-flex gap-2 flex-1\"\n style={{\n zIndex: 9999,\n background: \"white\",\n position: \"relative\",\n }}\n >\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: authorId,\n }}\n />\n </div>\n <ProposalContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n {authorId} ・{\" \"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: snapshot.timestamp,\n }}\n />\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: authorId,\n blockHeight: blockHeight,\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"d-flex flex-column gap-1 p-2 px-3 description-box\">\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n PROPOSAL CATEGORY\n </div>\n <div>\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CategoryDropdown\"\n }\n props={{\n selectedValue: snapshot.category,\n disabled: true,\n }}\n />\n </div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n SUMMARY\n </div>\n <div>{snapshot.summary}</div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3 mb-4\">\n DESCRIPTION\n </div>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.MarkdownViewer\"\n props={{ text: snapshot.description }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: authorId,\n }}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CommentIcon\"\n }\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: proposalURL,\n }}\n />\n </div>\n </div>\n </ProposalContainer>\n </div>\n <div className=\"border-bottom pb-4 mt-4\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CommentsAndLogs\"\n }\n props={{\n item: item,\n snapshotHistory: [...proposal.snapshot_history, snapshot],\n }}\n />\n </div>\n <div\n style={{\n position: \"relative\",\n zIndex: 99,\n backgroundColor: \"white\",\n }}\n className=\"pt-4\"\n >\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n ...props,\n item: item,\n notifyAccountId: authorId,\n id: proposal.id,\n }}\n />\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"d-flex flex-column gap-4 flex-1 order-1 order-md-2\"\n >\n <SidePanelItem title=\"Author\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: authorId,\n }}\n />\n </SidePanelItem>\n <SidePanelItem\n title={\n \"Linked Proposals \" + `(${snapshot.linked_proposals.length})`\n }\n >\n <LinkedProposals />\n </SidePanelItem>\n <SidePanelItem title=\"Funding Ask\">\n <div className=\"h4 text-black\">\n {snapshot.requested_sponsorship_usd_amount && (\n <div className=\"d-flex flex-column gap-1\">\n <div>{snapshot.requested_sponsorship_usd_amount} USD</div>\n <div className=\"text-sm text-muted\">\n Requested in{\" \"}\n {snapshot.requested_sponsorship_paid_in_currency}\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Wallet Address\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.receiver_account,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Verification Status\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: snapshot.receiver_account,\n showGetVerifiedBtn:\n accountId === snapshot.receiver_account ||\n accountId === authorId,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Requested Sponsor\">\n {snapshot.requested_sponsor && (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.requested_sponsor,\n }}\n />\n )}\n </SidePanelItem>\n <SidePanelItem title=\"Supervisor\">\n {snapshot.supervisor ? (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.supervisor,\n }}\n />\n ) : (\n \"No Supervisor\"\n )}\n </SidePanelItem>\n <SidePanelItem\n hideBorder={true}\n title={\n <div>\n <div className=\"d-flex justify-content-between align-content-center\">\n Timeline\n {isModerator && (\n <div onClick={() => setShowTimelineSetting(true)}>\n <i class=\"bi bi-gear\"></i>\n </div>\n )}\n </div>\n {showTimelineSetting && (\n <div className=\"mt-2 d-flex flex-column gap-2\">\n <h6 className=\"mb-0\">Proposal Status</h6>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: proposalStatusOptions,\n selectedValue: updatedProposalStatus,\n onUpdate: (v) => {\n setUpdatedProposalStatus({\n ...v,\n value: {\n ...v.value,\n ...updatedProposalStatus.value,\n status: v.value.status,\n },\n });\n },\n }}\n />\n </div>\n )}\n </div>\n }\n >\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"d-flex gap-3 mt-2\">\n <div className=\"d-flex flex-column\">\n {stepsArray.map((_, index) => {\n const indexOfCurrentItem = index;\n let color = \"\";\n let statusIndex = selectedStatusIndex;\n // index 2,3,4 is of decision\n if (\n selectedStatusIndex === 3 ||\n selectedStatusIndex === 2 ||\n selectedStatusIndex === 4 ||\n selectedStatusIndex === 5\n ) {\n statusIndex = 2;\n }\n if (selectedStatusIndex === 6) {\n statusIndex = 3;\n }\n const current = statusIndex === indexOfCurrentItem;\n const completed =\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED;\n return (\n <div className=\"d-flex flex-column align-items-center gap-1\">\n <div\n className={\n \"circle \" +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n >\n {completed && (\n <div\n className=\"d-flex justify-content-center align-items-center\"\n style={{ height: \"110%\" }}\n >\n <i class=\"bi bi-check\"></i>\n </div>\n )}\n </div>\n\n {index !== stepsArray.length - 1 && (\n <div\n className={\n \"vertical-line\" +\n (index === stepsArray.length - 2\n ? \"-sm \"\n : \" \") +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n ></div>\n )}\n </div>\n );\n })}\n </div>\n <div className=\"d-flex flex-column gap-3\">\n <TimelineItems\n title=\"1) Draft\"\n value={TIMELINE_STATUS.DRAFT}\n >\n <div>\n Once an author submits a proposal, it is in draft mode\n and open for community comments. The author can still\n make changes to the proposal during this stage and\n submit it for official review when ready.\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"2) Review\"\n value={TIMELINE_STATUS.REVIEW}\n >\n <div className=\"d-flex flex-column gap-2\">\n Sponsors who agree to consider the proposal may\n request attestations from work groups.\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n sponsor_requested_review: value,\n },\n }))\n }\n label=\"Sponsor provides feedback or requests reviews\"\n isChecked={\n updatedProposalStatus.value\n .sponsor_requested_review\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n label=\"Reviewer completes attestations (Optional)\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n reviewer_completed_attestation: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .reviewer_completed_attestation\n }\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"3) Decision\"\n values={[\n TIMELINE_STATUS.APPROVED,\n TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n TIMELINE_STATUS.REJECTED,\n ]}\n >\n <div className=\"d-flex flex-column gap-2\">\n <div>Sponsor makes a final decision:</div>\n <RadioButton\n value=\"\"\n label={<div className=\"fw-bold\">Approved</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED\n }\n />\n <RadioButton\n value=\"\"\n label={\n <>\n <div className=\"fw-bold\">\n Approved - Conditional{\" \"}\n </div>\n <span>\n Require follow up from recipient after payment\n </span>\n </>\n }\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED_CONDITIONALLY\n }\n />\n <RadioButton\n value=\"Reject\"\n label={<div className=\"fw-bold\">Rejected</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.REJECTED\n }\n />\n <RadioButton\n value=\"Canceled\"\n label={<div className=\"fw-bold\">Canceled</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.CANCELED\n }\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"4) Payment Processing\"\n value={TIMELINE_STATUS.PAYMENT_PROCESSING}\n >\n <div className=\"d-flex flex-column gap-2\">\n <CheckBox\n value={updatedProposalStatus.value.kyc_verified}\n label=\"Sponsor verifies KYC/KYB\"\n disabled={selectedStatusIndex !== 6}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n kyc_verified: value,\n },\n }))\n }\n isChecked={updatedProposalStatus.value.kyc_verified}\n />\n <CheckBox\n value={\n updatedProposalStatus.value.test_transaction_sent\n }\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor confirmed sponsorship and shared funding steps with recipient\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n {/* Not needed for Alpha testing */}\n {/* <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor sends test transaction\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor creates funding request from Trustees\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n request_for_trustees_created: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .request_for_trustees_created\n }\n /> */}\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"5) Funded\"\n value={TIMELINE_STATUS.FUNDED}\n >\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes?.length && paymentHashes[0] ? (\n paymentHashes.map((link) => (\n <a\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i class=\"bi bi-arrow-up-right\"></i>\n </a>\n ))\n ) : updatedProposalStatus.value.payouts.length > 0 ? (\n <div>\n {updatedProposalStatus.value.payouts.map(\n (link) => {\n return (\n <a\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i class=\"bi bi-arrow-up-right\"></i>\n </a>\n );\n }\n )}\n </div>\n ) : (\n \"No Payouts yet\"\n )}\n </div>\n </TimelineItems>\n </div>\n </div>\n {showTimelineSetting && (\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Supervisor</label>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n placeholder: \"Enter Supervisor\",\n onUpdate: setSupervisor,\n }}\n />\n </div>\n {updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED && (\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Payment Link</label>\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes.map((item, index) => (\n <div className=\"d-flex gap-2 justify-content-between align-items-center\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n onChange: (e) => {\n const updatedHashes = [...paymentHashes];\n updatedHashes[index] = e.target.value;\n setPaymentHashes(updatedHashes);\n },\n skipPaddingGap: true,\n placeholder: \"Enter URL\",\n }}\n />\n <div style={{ minWidth: 20 }}>\n {index !== paymentHashes.length - 1 ? (\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none w-100\",\n },\n label: <i class=\"bi bi-trash3 h6\"></i>,\n onClick: () => {\n const updatedHashes = [\n ...paymentHashes,\n ];\n updatedHashes.splice(index, 1);\n setPaymentHashes(updatedHashes);\n },\n }}\n />\n ) : (\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"green-btn shadow-none border-0 w-100\",\n },\n label: <i class=\"bi bi-plus-lg\"></i>,\n onClick: () =>\n setPaymentHashes([\n ...paymentHashes,\n \"\",\n ]),\n }}\n />\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n <div className=\"d-flex gap-2 align-items-center justify-content-end text-sm\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Cancel\",\n classNames: {\n root: \"btn-outline-danger border-0 shadow-none btn-sm\",\n },\n onClick: () => {\n setShowTimelineSetting(false);\n setUpdatedProposalStatus(proposalStatus);\n },\n }}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n disabled: !supervisor,\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n if (!supervisor) {\n return;\n }\n if (snapshot.supervisor !== supervisor) {\n editProposal({\n timeline: updatedProposalStatus.value,\n });\n } else if (\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n ) {\n editProposalStatus({\n timeline: {\n ...updatedProposalStatus.value,\n payouts: !paymentHashes[0]\n ? []\n : paymentHashes.filter(\n (item) => item !== \"\"\n ),\n },\n });\n } else {\n editProposalStatus({\n timeline: updatedProposalStatus.value,\n });\n }\n },\n }}\n />\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n </div>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.CommentsAndLogs": { "": "const snapshotHistory = props.snapshotHistory;\n\nconst Wrapper = styled.div`\n position: relative;\n .log-line {\n position: absolute;\n left: 7%;\n top: -30px;\n bottom: 0;\n z-index: 1;\n width: 1px;\n background-color: var(--bs-border-color);\n z-index: 1;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\n\nconst CommentContainer = styled.div`\n border: 1px solid lightgrey;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\n// check snapshot history all keys and values for differences\nfunction getDifferentKeysWithValues(obj1, obj2) {\n return Object.keys(obj1)\n .filter((key) => {\n if (key !== \"editor_id\" && obj2.hasOwnProperty(key)) {\n const value1 = obj1[key];\n const value2 = obj2[key];\n\n if (typeof value1 === \"object\" && typeof value2 === \"object\") {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else if (Array.isArray(value1) && Array.isArray(value2)) {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else {\n return value1 !== value2;\n }\n }\n return false;\n })\n .map((key) => ({\n key,\n originalValue: obj1[key],\n modifiedValue: obj2[key],\n }));\n}\n\nState.init({\n data: null,\n socialComments: null,\n changedKeysListWithValues: null,\n});\n\nfunction sortTimelineAndComments() {\n const comments = Social.index(\"comment\", props.item);\n\n if (state.changedKeysListWithValues === null) {\n const changedKeysListWithValues = snapshotHistory\n .slice(1)\n .map((item, index) => {\n const startingPoint = snapshotHistory[index]; // Set comparison to the previous item\n return {\n editorId: item.editor_id,\n ...getDifferentKeysWithValues(startingPoint, item),\n };\n });\n State.update({ changedKeysListWithValues });\n }\n\n // sort comments and timeline logs by time\n const snapShotTimeStamp = Array.isArray(snapshotHistory)\n ? snapshotHistory.map((i) => {\n return { blockHeight: null, timestamp: parseFloat(i.timestamp / 1e6) };\n })\n : [];\n\n const commentsTimeStampPromise = Array.isArray(comments)\n ? Promise.all(\n comments.map((item) => {\n return asyncFetch(\n `https://api.near.social/time?blockHeight=${item.blockHeight}`\n ).then((res) => {\n const timeMs = parseFloat(res.body);\n return {\n blockHeight: item.blockHeight,\n timestamp: timeMs,\n };\n });\n })\n ).then((res) => res)\n : Promise.resolve([]);\n\n commentsTimeStampPromise.then((commentsTimeStamp) => {\n const combinedArray = [...snapShotTimeStamp, ...commentsTimeStamp];\n combinedArray.sort((a, b) => a.timestamp - b.timestamp);\n State.update({ data: combinedArray, socialComments: comments });\n });\n}\n\nsortTimelineAndComments();\nconst Comment = ({ commentItem }) => {\n const { accountId, blockHeight } = commentItem;\n const item = {\n type: \"social\",\n path: `${accountId}/post/comment`,\n blockHeight,\n };\n const content = JSON.parse(Social.get(item.path, blockHeight) ?? \"null\");\n\n const link = `https://near.org/mob.near/widget/MainPage.N.Comment.Page?accountId=${accountId}&blockHeight=${blockHeight}`;\n return (\n <div style={{ zIndex: 9999, background: \"white\" }}>\n <div className=\"d-flex gap-2 flex-1\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n </div>\n <CommentContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n <div>\n {accountId} commented\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight: blockHeight,\n }}\n />\n </div>\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: accountId,\n blockHeight: blockHeight,\n contentPath: `/post/comment`,\n contentType: \"comment\",\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"p-2 px-3\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: content.text,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"near/widget/v1.LikeButton\"\n props={{\n item: item,\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: link,\n }}\n />\n </div>\n </div>\n </CommentContainer>\n </div>\n </div>\n );\n};\n\nfunction capitalizeFirstLetter(string) {\n const updated = string.replace(\"_\", \" \");\n return updated.charAt(0).toUpperCase() + updated.slice(1).toLowerCase();\n}\n\nfunction parseTimelineKeyAndValue(timeline, originalValue, modifiedValue) {\n const oldValue = originalValue[timeline];\n const newValue = modifiedValue[timeline];\n switch (timeline) {\n case \"status\":\n return (\n <span>\n moved proposal from {capitalizeFirstLetter(oldValue)} to{\" \"}\n {capitalizeFirstLetter(newValue)} stage\n </span>\n );\n case \"sponsor_requested_review\":\n return !oldValue && newValue && <span>completed review</span>;\n case \"reviewer_completed_attestation\":\n return !oldValue && newValue && <span>completed attestation</span>;\n case \"kyc_verified\":\n return !oldValue && newValue && <span>verified KYC/KYB</span>;\n case \"test_transaction_sent\":\n return (\n !oldValue &&\n newValue && (\n <span>\n confirmed sponsorship and shared funding steps with recipient\n </span>\n )\n );\n // we don't have this step for now\n // case \"request_for_trustees_created\":\n // return !oldValue && newValue && <span>successfully created request for trustees</span>;\n default:\n return null;\n }\n}\n\nconst parseProposalKeyAndValue = (key, modifiedValue, originalValue) => {\n switch (key) {\n case \"name\":\n case \"summary\":\n case \"description\":\n return <span>changed {key}</span>;\n case \"category\":\n return (\n <span>\n changed category from {originalValue} to {modifiedValue}\n </span>\n );\n case \"linked_proposals\":\n return <span>updated linked proposals</span>;\n case \"requested_sponsorship_usd_amount\":\n return (\n <span>\n changed sponsorship amount from {originalValue} to {modifiedValue}\n </span>\n );\n case \"requested_sponsorship_paid_in_currency\":\n return (\n <span>\n changed sponsorship currency from {originalValue} to {modifiedValue}\n </span>\n );\n case \"receiver_account\":\n return (\n <span>\n changed receiver account from {originalValue} to {modifiedValue}\n </span>\n );\n case \"supervisor\":\n return !originalValue && modifiedValue ? (\n <span>added {modifiedValue} as supervisor</span>\n ) : (\n <span>\n changed receiver account from {originalValue} to {modifiedValue}\n </span>\n );\n case \"requested_sponsor\":\n return (\n <span>\n changed sponsor from {originalValue} to {modifiedValue}\n </span>\n );\n case \"timeline\": {\n const modifiedKeys = Object.keys(modifiedValue);\n const originalKeys = Object.keys(originalValue);\n return modifiedKeys.map((i, index) => {\n const text = parseTimelineKeyAndValue(i, originalValue, modifiedValue);\n return (\n <span>\n {text}\n {text &&\n originalKeys.length > 1 &&\n index < modifiedKeys.length - 1 &&\n \"・\"}\n </span>\n );\n });\n }\n default:\n return null;\n }\n};\n\nconst LogIconContainer = styled.div`\n margin-left: 50px;\n z-index: 99;\n\n @media screen and (max-width: 768px) {\n margin-left: 10px;\n }\n`;\n\nconst Log = ({ timestamp }) => {\n const updatedData = useMemo(\n () =>\n state.changedKeysListWithValues.find((obj) =>\n Object.values(obj).some(\n (value) =>\n value && parseFloat(value.modifiedValue / 1e6) === timestamp\n )\n ),\n [state.changedKeysListWithValues, timestamp]\n );\n\n const editorId = updatedData.editorId;\n const valuesArray = Object.values(updatedData ?? {});\n // if valuesArray length is 2 that means it only has timestamp and editorId\n if (!updatedData || valuesArray.length === 2) {\n return <></>;\n }\n\n return (\n <LogIconContainer className=\"d-flex gap-3 align-items-center\">\n <img\n src=\"https://ipfs.near.social/ipfs/bafkreiffqrxdi4xqu7erf46gdlwuodt6dm6rji2jtixs3iionjvga6rhdi\"\n height={30}\n />\n <div className=\"flex-1 w-100 text-wrap\">\n <span\n style={{ display: \"inline-flex\" }}\n className=\"gap-1 align-items-center\"\n >\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: editorId,\n size: \"sm\",\n }}\n />\n {editorId}\n </span>\n {valuesArray.map((i, index) => {\n if (i.key && i.key !== \"timestamp\") {\n return (\n <span key={index}>\n {parseProposalKeyAndValue(\n i.key,\n i.modifiedValue,\n i.originalValue\n )}\n {i.key !== \"timeline\" && \"・\"}\n </span>\n );\n }\n })}\n <span>\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockTimestamp: timestamp * 1000000,\n }}\n />\n </span>\n </div>\n </LogIconContainer>\n );\n};\n\nif (Array.isArray(state.data)) {\n return (\n <Wrapper>\n <div\n className=\"log-line\"\n style={{ height: state.data.length > 4 ? \"120%\" : \"150%\" }}\n ></div>\n <div className=\"d-flex flex-column gap-4\">\n {state.data.map((i, index) => {\n if (i.blockHeight) {\n const item = state.socialComments.find(\n (t) => t.blockHeight === i.blockHeight\n );\n return <Comment commentItem={item} />;\n } else {\n return <Log timestamp={i.timestamp} key={index} />;\n }\n })}\n </div>\n </Wrapper>\n );\n}\n" }, "devhub.entity.proposal.ComposeComment": { "": "const proposalId = props.id;\nconst draftKey = \"COMMENT_DRAFT\" + proposalId;\nconst draftComment = Storage.privateGet(draftKey);\n\nconst ComposeEmbeddCSS = `\n .CodeMirror {\n border: none !important;\n min-height: 50px !important;\n }\n\n .editor-toolbar {\n border: none !important;\n }\n\n .CodeMirror-scroll{\n min-height: 50px !important;\n }\n`;\nconst notifyAccountId = props.notifyAccountId;\nconst accountId = context.accountId;\nconst item = props.item;\nconst [allowGetDraft, setAllowGetDraft] = useState(true);\nconst [comment, setComment] = useState(null);\n\nuseEffect(() => {\n if (draftComment && allowGetDraft) {\n setComment(draftComment);\n setAllowGetDraft(false);\n }\n}, [draftComment]);\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n if (comment !== draftComment) Storage.privateSet(draftKey, comment);\n }, 2000);\n\n return () => {\n clearTimeout(handler);\n };\n}, [comment, draftKey]);\n\nif (!accountId) {\n return (\n <div\n style={{\n marginLeft: 10,\n backgroundColor: \"#ECF8FB\",\n border: \"1px solid #E2E6EC\",\n }}\n className=\"d-flex align-items-center gap-1 p-4 rounded-2\"\n >\n <Link to=\"https://near.org/signup\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"grey-btn\" },\n label: \"Sign up\",\n }}\n />\n </Link>\n <div className=\"fw-bold\">to join this conversation.</div>\n <div>Already have an account?</div>\n <a className=\"text-decoration-underline\" href=\"https://near.org/signin\">\n Log in to comment\n </a>\n </div>\n );\n}\n\nfunction extractMentions(text) {\n const mentionRegex =\n /@((?:(?:[a-z\\d]+[-_])*[a-z\\d]+\\.)*(?:[a-z\\d]+[-_])*[a-z\\d]+)/gi;\n mentionRegex.lastIndex = 0;\n const accountIds = new Set();\n for (const match of text.matchAll(mentionRegex)) {\n if (\n !/[\\w`]/.test(match.input.charAt(match.index - 1)) &&\n !/[/\\w`]/.test(match.input.charAt(match.index + match[0].length)) &&\n match[1].length >= 2 &&\n match[1].length <= 64\n ) {\n accountIds.add(match[1].toLowerCase());\n }\n }\n return [...accountIds];\n}\n\nfunction extractTagNotifications(text, item) {\n return extractMentions(text || \"\")\n .filter((accountId) => accountId !== context.accountId)\n .map((accountId) => ({\n key: accountId,\n value: {\n type: \"mention\",\n item,\n },\n }));\n}\n\nfunction composeData() {\n const data = {\n post: {\n comment: JSON.stringify({\n type: \"md\",\n text: comment,\n item,\n }),\n },\n index: {\n comment: JSON.stringify({\n key: item,\n value: {\n type: \"md\",\n },\n }),\n },\n };\n\n const notifications = extractTagNotifications(comment, {\n type: \"social\",\n path: `${accountId}/post/comment`,\n });\n\n if (notifyAccountId && notifyAccountId !== context.accountId) {\n notifications.push({\n key: notifyAccountId,\n value: {\n type: \"devhub/reply\",\n item,\n proposal: proposalId,\n },\n });\n }\n\n if (notifications.length) {\n data.index.notify = JSON.stringify(\n notifications.length > 1 ? notifications : notifications[0]\n );\n }\n\n Social.set(data, {\n force: true,\n onCommit: () => {\n setComment(\"\");\n },\n onCancel: () => {},\n });\n}\n\nuseEffect(() => {\n if (props.transactionHashes && comment) {\n setComment(\"\");\n }\n}, [props.transactionHashes, comment]);\n\nreturn (\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2 w-100\">\n <b className=\"mt-1\">Add a comment</b>\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Compose\"}\n props={{\n data: comment,\n onChange: setComment,\n autocompleteEnabled: true,\n autoFocus: false,\n placeholder: \"Add your comment here...\",\n height: \"160\",\n embeddCSS: ComposeEmbeddCSS,\n }}\n />\n <div className=\"d-flex gap-2 align-content-center justify-content-end\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Comment\",\n disabled: !comment,\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n composeData();\n },\n }}\n />\n </div>\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.Editor": { "": "const { href } = VM.require(\"devgovgigs.petersalomonsen.near/widget/core.lib.url\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devgovgigs.petersalomonsen.near/widget/core.lib.common\"\n);\nconst draftKey = \"PROPOSAL_EDIT\";\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nhref || (href = () => {});\n\nconst { id, timestamp } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://docs.google.com/document/d/1kR1YbaQE4mmHcf-BHo7NwO7vmGx4EciHK-QjelCufI8/edit?usp=sharing\";\n\nif (!author) {\n return (\n <Widget src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.LoginScreen\"} />\n );\n}\nlet editProposalData = null;\nlet draftProposalData = null;\n\nif (isEditPage) {\n editProposalData = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n });\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !important;\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: #04a46e !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 }\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\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 devdaoAccount = \"neardevdao.near\";\n\nconst [category, setCategory] = useState(null);\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 [requestedSponsor, setRequestedSponsor] = useState(devdaoAccount);\nconst [requestedSponsorshipAmount, setRequestedSponsorshipAmount] =\n useState(null);\nconst [requestedSponsorshipToken, setRequestedSponsorshipToken] = useState(\n tokensOptions[2]\n);\nconst [supervisor, setSupervisor] = useState(null);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [proposalsOptions, setProposalsOptions] = useState([]);\nconst proposalsData = Near.view(\"devhub.near\", \"get_proposals\");\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 [amountError, setAmountError] = useState(null);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\n\nconst [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false);\n\nconst [showProposalPage, setShowProposalPage] = useState(false); // when user creates/edit a proposal and confirm the txn, this is true\nconst [proposalId, setProposalId] = useState(null);\n\nif (allowDraft) {\n draftProposalData = Storage.privateGet(draftKey);\n}\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editProposalData.id ?? null,\n snapshot: {\n name: title,\n description: description,\n category: category,\n summary: summary,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n supervisor: supervisor,\n requested_sponsor: requestedSponsor,\n },\n }),\n [\n title,\n summary,\n description,\n category,\n requestedSponsorshipAmount,\n requestedSponsorshipToken,\n receiverAccount,\n supervisor,\n requestedSponsor,\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 setCategory(snapshot.category);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setReceiverAccount(snapshot.receiver_account);\n setRequestedSponsor(snapshot.requested_sponsor);\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 setLoading(false);\n }\n}, [editProposalData, draftProposalData, allowDraft]);\n\nuseEffect(() => {\n if (draftProposalData) {\n setAllowDraft(false);\n }\n}, [draftProposalData]);\n\nuseEffect(() => {\n if (showProposalPage) {\n return;\n }\n setDisabledSubmitBtn(\n isSubmittingTransaction ||\n amountError ||\n !title ||\n !description ||\n !summary ||\n !category ||\n !requestedSponsorshipAmount ||\n !receiverAccount ||\n !requestedSponsor ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 3000);\n\n return () => {\n clearTimeout(handler);\n };\n}, [\n memoizedDraftData,\n draftKey,\n draftProposalData,\n consent,\n amountError,\n isSubmittingTransaction,\n showProposalPage,\n]);\n\nuseEffect(() => {\n if (\n proposalsOptions.length > 0 &&\n editProposalData &&\n editProposalData?.snapshot?.linked_proposals?.length > 0\n ) {\n let data = [];\n editProposalData.snapshot.linked_proposals.map((item) => {\n data.push(proposalsOptions.find((i) => i.value === item));\n });\n setLinkedProposals(data);\n }\n}, [editProposalData, proposalsOptions]);\n\nuseEffect(() => {\n // Trigger when proposals data change, which will happen on cache invalidation\n setIsSubmittingTransaction(false);\n console.log(\"Proposals data change, assume transaction completed\");\n}, [proposalsData]);\n\nuseEffect(() => {\n if (\n proposalsData !== null &&\n Array.isArray(proposalsData) &&\n !proposalsOptions.length\n ) {\n const data = [];\n for (const prop of proposalsData) {\n data.push({\n label: \"Id \" + prop.id + \" : \" + prop.snapshot.name,\n value: prop.id,\n });\n }\n setProposalsOptions(data);\n }\n}, [proposalsData]);\n\nuseEffect(() => {});\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\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(\"https://rpc.mainnet.near.org\", {\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 setShowProposalPage(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 \"devhub.near\",\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 (showProposalPage) {\n setShowProposalPage(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst CheckBox = ({ value, isChecked, label, onClick }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={isChecked}\n onChange={(e) => onClick(e.target.checked)}\n />\n <label class=\"form-check-label text-sm\">{label}</label>\n </div>\n );\n};\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: 9999;\n font-size: 13px;\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 }\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 };\n\n const toggleDropdown = () => {\n setDraftBtnOpen(!isDraftBtnOpen);\n };\n\n const handleSubmit = () => {\n const isDraft = selectedStatus === \"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()}\n className=\"p-2 d-flex gap-2 align-items-center \"\n >\n {isSubmittingTransaction ? (\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-${isOpen ? \"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 setIsSubmittingTransaction(true);\n console.log(\"submitting transaction\");\n const linkedProposalsIds = linkedProposals.map((item) => item.value) ?? [];\n const body = {\n proposal_body_version: \"V0\",\n name: title,\n description: description,\n category: category,\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 supervisor: supervisor || null,\n requested_sponsor: requestedSponsor,\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 = { labels: [], body: body };\n if (isEditPage) {\n args[\"id\"] = editProposalData.id;\n }\n\n Near.call([\n {\n contractName: \"devhub.near\",\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\nconst descriptionPlaceholder = `**PROJECT DETAILS**\nProvide a clear overview of the scope, deliverables, and expected outcomes. What benefits will it provide to the NEAR community? How will you measure success?\n\n**TIMELINE**\nDescribe the timeline of your project and key milestones, specifying if the work was already complete or not. Include your plans for reporting progress to the community.\n\nOPTIONAL FIELDS\n\n**TEAM**\nProvide a list of who will be working on the project along with their relevant skillset and experience. You may include links to portfolios or profiles to help the community get to know who the DAO will fund and how their backgrounds will contribute to your project’s success.\n\n**BUDGET BREAKDOWN**\nInclude a detailed breakdown on how you will use the funds and include rate justification. Our community values transparency, so be as specific as possible.\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={\"devgovgigs.petersalomonsen.near/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\nif (showProposalPage) {\n return (\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Proposal\"}\n props={{ id: proposalId, ...props }}\n />\n );\n} else\n return (\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={\"devgovgigs.petersalomonsen.near/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={\"devgovgigs.petersalomonsen.near/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 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={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: author,\n }}\n />\n </div>\n <div className=\"d-flex flex-column gap-2 gap-sm-4 w-100\">\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the category that best aligns with your\n contribution to the NEAR developer community. Need\n guidance? See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs.\n </a>\n </>\n }\n >\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CategoryDropdown\"\n }\n props={{\n selectedValue: category,\n onChange: setCategory,\n }}\n />\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: title,\n onChange: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: \"Enter title here.\",\n inputProps: {\n max: 80,\n required: true,\n },\n }}\n />\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onChange: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: \"Enter summary here.\",\n inputProps: {\n max: 500,\n required: true,\n },\n }}\n />\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description=\"Expand on your summary with any relevant details like your contribution timeline, key milestones, team background, and a clear breakdown of how the funds will be used. Proposals should be simple and clear (e.g. 1 month). For more complex projects, treat each milestone as a separate proposal.\"\n >\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Compose\"\n }\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n placeholder: descriptionPlaceholder,\n }}\n />\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n <div className=\"d-flex flex-column gap-2\">\n <CheckBox\n value={consent.toc}\n label={\n <>\n I’ve agree to{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1nRGy7LhpLj56SjN9MseV1x-ubH8O_c6B9DOAZ9qTwMU/edit?usp=sharing\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’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 <CheckBox\n value={consent.coc}\n label={\n <>\n I’ve read{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1c6XV8Sj_BRKw8jnTIsjdLPPN6Al5eEStt1ZLYSuqw9U/edit\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’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 </InputContainer>\n <div className=\"d-flex justify-content-between gap-2 align-items-center\">\n <div>\n {isEditPage && (\n <Widget\n src={`devgovgigs.petersalomonsen.near/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 className=\"d-flex gap-2\">\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposal\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposals\",\n },\n })\n }\n >\n <Widget\n src={`devgovgigs.petersalomonsen.near/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 <Widget\n src=\"mob.near/widget/Profile.ShortInlineBlock\"\n props={{\n accountId: author,\n }}\n />\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Link Proposals (Optional)\">\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 {linkedProposals.map((proposal) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <a\n className=\"text-decoration-underline flex-1\"\n href={href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposal.value,\n },\n })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {proposal.label}\n </a>\n <div\n className=\"cursor-pointer\"\n onClick={() => {\n const updatedLinkedProposals =\n linkedProposals.filter(\n (item) => item.value !== proposal.value\n );\n setLinkedProposals(updatedLinkedProposals);\n }}\n >\n <i class=\"bi bi-trash3-fill\"></i>\n </div>\n </div>\n );\n })}\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.DropDownWithSearch\"\n props={{\n selectedValue: \"\",\n onChange: (v) => {\n if (\n !linkedProposals.some(\n (item) => item.value === v.value\n )\n ) {\n setLinkedProposals([...linkedProposals, v]);\n }\n },\n options: proposalsOptions,\n showSearch: true,\n searchInputPlaceholder: \"Search by Id\",\n defaultLabel: \"Search proposals\",\n searchByValue: true,\n }}\n />\n </div>\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: receiverAccount,\n placeholder: devdaoAccount,\n onUpdate: setReceiverAccount,\n }}\n />\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\n platform, you must complete KYC/KYB verification\n using Fractal, a trusted identity verification\n solution. This helps others trust transactions with\n your account. Click \"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=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: receiverAccount,\n showGetVerifiedBtn: true,\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: requestedSponsorshipAmount,\n onChange: (e) => {\n const inputValue = e.target.value;\n const isValidInput = /^\\d+$/.test(inputValue);\n if (inputValue.trim() === \"\") {\n return;\n }\n if (!isValidInput || Number(inputValue) < 0) {\n setAmountError(\n \"Please enter the nearest positive whole number.\"\n );\n } else {\n setRequestedSponsorshipAmount(inputValue);\n setAmountError(\"\");\n }\n },\n skipPaddingGap: true,\n placeholder: \"Enter amount\",\n inputProps: {\n type: \"number\",\n prefix: \"$\",\n },\n }}\n />\n {amountError && (\n <div style={{ color: \"red\" }} className=\"text-sm\">\n {amountError}\n </div>\n )}\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 <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: tokensOptions,\n selectedValue: requestedSponsorshipToken,\n onUpdate: (v) => {\n setRequestedSponsorshipToken(v);\n },\n }}\n />\n </InputContainer>\n <InputContainer heading=\"Requested Sponsor\" description=\"\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: requestedSponsor,\n placeholder: \"DevDAO\",\n onUpdate: setRequestedSponsor,\n }}\n />\n </InputContainer>\n <InputContainer\n heading=\"Supervisor (Optional)\"\n description=\"\"\n >\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n placeholder: \"Enter Supervisor\",\n onUpdate: setSupervisor,\n }}\n />\n </InputContainer>\n </div>\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n" }, "devhub.entity.proposal.StatusTag": { "": "const timelineStatus = props.timelineStatus;\nconst size = props.size ?? \"md\";\n\nconst getClassNameByStatus = () => {\n switch (timelineStatus) {\n case \"DRAFT\":\n return \"grey\";\n case \"REVIEW\":\n return \"grey\";\n case \"APPROVED\":\n case \"PAYMENT_PROCESSING\":\n case \"APPROVED_CONDITIONALLY\":\n case \"FUNDED\":\n return \"green\";\n case \"REJECTED\":\n case \"CANCELLED\":\n return \"warning\";\n default:\n return \"green\";\n }\n};\n\nconst Container = styled.div`\n font-size: ${({ size }) => {\n switch (size) {\n case \"sm\":\n return \"10px\";\n case \"lg\":\n return \"14px\";\n default:\n return \"12px\";\n }\n }};\n\n .warning-tag {\n border: 1px solid #f40303 !important;\n color: #f40303 !important;\n }\n\n .grey-tag {\n border: 1px solid #555555 !important;\n color: #555555 !important;\n }\n\n .green-tag {\n border: 1px solid #04a46e !important;\n color: #04a46e !important;\n }\n`;\n\nreturn (\n <Container size={size}>\n <div className={getClassNameByStatus() + \"-tag rounded-2 p-1\"}>\n {(timelineStatus ?? \"\").replace(\"_\", \" \")}\n </div>\n </Container>\n);\n" }, "devhub.components.molecule.Input": { "": "const TextInput = ({\n className,\n format,\n inputProps: { className: inputClassName, ...inputProps },\n key,\n label,\n multiline,\n onChange,\n placeholder,\n type,\n value,\n skipPaddingGap,\n style,\n ...otherProps\n}) => {\n State.init({\n data: value,\n });\n\n useEffect(() => {\n const handler = setTimeout(() => {\n onChange({ target: { value: state.data } });\n }, 30);\n\n return () => {\n clearTimeout(handler);\n };\n }, [state.data]);\n\n useEffect(() => {\n if (value !== state.data) {\n State.update({ data: value });\n }\n }, [value]);\n\n const typeAttribute =\n type === \"text\" ||\n type === \"password\" ||\n type === \"number\" ||\n type === \"date\"\n ? type\n : \"text\";\n\n const isValid = () => {\n if (!state.data || state.data.length === 0) {\n return !inputProps.required;\n } else if (inputProps.min && inputProps.min > state.data?.length) {\n return false;\n } else if (inputProps.max && inputProps.max < state.data?.length) {\n return false;\n } else if (\n inputProps.allowCommaAndSpace === false &&\n /^[^,\\s]*$/.test(state.data) === false\n ) {\n return false;\n } else if (\n inputProps.validUrl === true &&\n /^(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/.test(\n state.data\n ) === false\n ) {\n return false;\n }\n return true;\n };\n\n const renderedLabels = [\n (label?.length ?? 0) > 0 ? (\n <span className=\"d-inline-flex gap-1 text-wrap\">\n <span>{label}</span>\n\n {inputProps.required ? <span className=\"text-danger\">*</span> : null}\n </span>\n ) : null,\n\n format === \"markdown\" ? (\n <i class=\"bi bi-markdown text-muted\" title=\"Markdown\" />\n ) : null,\n\n format === \"comma-separated\" ? (\n <span\n className={`d-inline-flex align-items-center ${\n isValid() ? \"text-muted\" : \"text-danger\"\n }`}\n style={{ fontSize: 12 }}\n >\n {format}\n </span>\n ) : null,\n\n (inputProps.max ?? null) !== null ? (\n <span\n className={`d-inline-flex ${isValid() ? \"text-muted\" : \"text-danger\"}`}\n style={{ fontSize: 12 }}\n >{`${state.data?.length ?? 0} / ${inputProps.max}`}</span>\n ) : null,\n ].filter((label) => label !== null);\n\n return (\n <div\n className={[\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\",\n skipPaddingGap ? \"\" : \"gap-1 p-2\",\n className ?? \"\",\n ].join(\" \")}\n style={style}\n {...otherProps}\n >\n {renderedLabels.length > 0 ? (\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n id={key}\n >\n {renderedLabels.map((label) => label)}\n </span>\n ) : null}\n\n {!multiline ? (\n <div className=\"input-group\">\n {inputProps.prefix && (\n <span className=\"input-group-text\">{inputProps.prefix}</span>\n )}\n <input\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\"form-control border\", inputClassName].join(\" \")}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n {...{ placeholder, ...inputProps }}\n />\n </div>\n ) : (\n <textarea\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\"form-control border\", inputClassName].join(\" \")}\n placeholder={\n placeholder + (inputProps.required ? \" ( required )\" : \"\")\n }\n style={{ resize: inputProps.resize ?? \"vertical\" }}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n {...{ placeholder, ...inputProps }}\n />\n )}\n </div>\n );\n};\n\nreturn TextInput(props);\n" }, "devhub.entity.proposal.AccountInput": { "": "const value = props.value;\nconst placeholder = props.placeholder;\nconst onUpdate = props.onUpdate;\n\nconst [showAccountAutocomplete, setAutoComplete] = useState(false);\nconst [isValidAccount, setValidAccount] = useState(true);\nconst AutoComplete = styled.div`\n max-width: 400px;\n margin-top: 1rem;\n`;\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n const valid = value.length === 64 || (value ?? \"\").includes(\".near\");\n setValidAccount(valid);\n setAutoComplete(!valid);\n }, 100);\n\n return () => {\n clearTimeout(handler);\n };\n}, [value]);\n\nreturn (\n <div>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: value,\n onChange: (e) => {\n onUpdate(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: placeholder,\n inputProps: {\n max: 64,\n prefix: \"@\",\n },\n }}\n />\n {value && !isValidAccount && (\n <div style={{ color: \"red\" }} className=\"text-sm mt-1\">\n Please enter valid account ID\n </div>\n )}\n {showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: value,\n onSelect: (id) => {\n onUpdate(id);\n setAutoComplete(false);\n },\n onClose: () => setAutoComplete(false),\n }}\n />\n </AutoComplete>\n )}\n </div>\n);\n" }, "devhub.entity.proposal.CategoryDropdown": { "": "const { selectedValue, onChange, disabled } = props;\n\nonChange = onChange || (() => {});\n\nconst options = [\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiet5w62oeef6msfsakdskq7zkjk33ngogcerfdmqewnsuj74u376e\",\n title: \"DevDAO Operations\",\n description:\n \"Provide core operations and leadership for the DAO or infrastructure support.\",\n value: \"DevDAO Operations\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiengkdru4fczwltjylfqeeypsdf4hb5fdxa6t67l3x2qtqgeo3pzq\",\n title: \"DevDAO Platform\",\n description:\n \"Build & maintain the interface for DevHub’s community & funding activities.\",\n value: \"DevDAO Platform\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreicpt3ulwsmptzdbtkhvxodvo7pcajcpyr35tqcbfdnaipzrx5re7e\",\n title: \"Events & Hackathons\",\n description:\n \"Organize or support events, hackathons, and local meet ups to grow communities.\",\n value: \"Events & Hackathons\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreibdrwhbouuutvrk4qt2udf4kumbyy5ebjkezobbahxvo7fyxo2ec4\",\n title: \"Engagement & Awareness\",\n description:\n \"Create content from social posts to real world swag to drive awareness to NEAR.\",\n value: \"Engagement & Awareness\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiem2vjsp6wu3lkd4zagpm43f32egdjjzchmleky6rr2ydzhlkrxam\",\n title: \"Decentralized DevRel\",\n description:\n \"Provide support, gather feedback, and maintain docs to drive engagement.\",\n value: \"Decentralized DevRel\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreic3prsy52hwueugqj5rwualib4imguelezsbvgrxtezw4u33ldxqq\",\n title: \"Universities & Bootcamps\",\n description:\n \"Engage with students and universities globally to encourage NEAR.\",\n value: \"Universities & Bootcamps\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreigf7j5isssumbjl24zy4pr27ryfqivan3vuwu2uwsofcujhhkk7cq\",\n title: \"Tooling & Infrastructure\",\n description:\n \"Contribute code to NEAR tooling or facilitating technical decisions.\",\n value: \"Tooling & Infrastructure\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreihctatkwnvpmblgqnpw76zggfet3fmpgurqvtj7vbm3cb5r3pp52u\",\n title: \"Other\",\n description: \"Use this category if you are not sure which one to use.\",\n value: \"Other\",\n },\n];\n\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOptionValue, setSelectedValue] = useState(selectedValue);\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nuseEffect(() => {\n if (selectedValue && selectedValue !== selectedOptionValue) {\n setSelectedValue(selectedValue);\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n if (selectedValue !== selectedOptionValue) {\n onChange(selectedOptionValue);\n }\n}, [selectedOptionValue]);\n\nconst handleOptionClick = (option) => {\n setSelectedValue(option.value);\n setIsOpen(false);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 2%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .disabled {\n background-color: #f8f8f8 !important;\n cursor: not-allowed !important;\n border-radius: 5px;\n opacity: inherit !important;\n }\n\n .disabled.dropdown-toggle::after {\n display: none !important;\n }\n\n .custom-select {\n position: relative;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\n\nconst Item = ({ option }) => {\n if (!option) {\n return <div className=\"text-muted\">Select Category</div>;\n }\n return (\n <div className=\"d-flex gap-3 align-items-center w-100\">\n <img src={option.icon} height={30} />\n <div className=\"d-flex flex-column gap-1 w-100 text-wrap\">\n <div className=\"h6 mb-0\"> {option.title}</div>\n <div className=\"text-sm text-muted w-100 text-wrap\">\n {option.description}\n </div>\n </div>\n </div>\n );\n};\n\nconst selectedOption =\n options.find((item) => item.value === selectedOptionValue) ?? null;\n\nreturn (\n <Container>\n <div\n className=\"custom-select w-100\"\n tabIndex=\"0\"\n onBlur={() => setIsOpen(false)}\n >\n <div\n className={\n \"dropdown-toggle bg-white border rounded-2 btn drop-btn w-100 \" +\n (disabled ? \"disabled\" : \"\")\n }\n onClick={!disabled && toggleDropdown}\n >\n <div className={`selected-option`}>\n <Item option={selectedOption} />\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow show w-100\">\n <div>\n {options.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 my-1 ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <Item option={option} />\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.VerificationStatus": { "": "const receiverAccount = props.receiverAccount;\nconst showGetVerifiedBtn = props.showGetVerifiedBtn;\nconst [verificationStatus, setVerificationStatus] = useState(null);\n\nconst WarningImg =\n \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\";\n\nconst SuccessImg =\n \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\";\n\nuseEffect(() => {\n if (\n receiverAccount.length === 64 ||\n (receiverAccount ?? \"\").includes(\".near\")\n ) {\n useCache(\n () =>\n asyncFetch(\n `https://neardevhub-kyc-proxy.shuttleapp.rs/kyc/${receiverAccount}`\n ).then((res) => {\n let displayableText = \"\";\n switch (res.body.kyc_status) {\n case \"Approved\":\n displayableText = \"Verified\";\n break;\n case \"Pending\":\n displayableText = \"Pending\";\n break;\n default:\n displayableText = \"Not Verfied\";\n break;\n }\n setVerificationStatus(displayableText);\n }),\n \"ky-check-proposal\" + receiverAccount,\n { subscribe: false }\n );\n }\n}, [receiverAccount]);\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: 9999;\n font-size: 13px;\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 }\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 .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n`;\n\nconst [kycOptionsOpen, setKycOptions] = useState(false);\n\nconst VerificationBtn = () => {\n const btnOptions = [\n {\n src: \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\",\n label: \"KYC\",\n description: \"Choose this if you are an individual.\",\n value: \"KYC\",\n },\n {\n src: \"https://ipfs.near.social/ipfs/bafkreic5ksax6b45pelvxm6a2v2j465jgbitpzrxtzpmn6zehl23gocwxm\",\n label: \"KYB\",\n description: \"Choose this if you are a business or corporate entity..\",\n value: \"KYB\",\n },\n ];\n\n const toggleDropdown = () => {\n setKycOptions(!kycOptionsOpen);\n };\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => setKycOptions(false)}\n >\n <div className={\"select-header no-border\"}>\n <Widget\n src={`devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"black-btn\" },\n label: (\n <div className=\"d-flex align-items-center gap-1\">\n Get Verified\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </div>\n ),\n onClick: toggleDropdown,\n }}\n />\n </div>\n\n {kycOptionsOpen && (\n <div className=\"options-card left\">\n {btnOptions.map((option) => (\n <a\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n href={\n option.value === \"KYC\"\n ? \"https://go.fractal.id/near-social-kyc\"\n : \"https://go.fractal.id/near-social-kyb\"\n }\n >\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => setKycOptions(false)}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <img src={option.src} height={30} />\n <div>\n <div className=\"fw-bold\">{option.label}</div>\n <div className=\"text-muted text-xs\">\n {option.description}\n </div>\n </div>\n </div>\n </div>\n </a>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nreturn (\n <div>\n <div className=\"d-flex justify-content-between align-items-center\">\n <div className=\"d-flex gap-4 \">\n <img\n className=\"align-self-center\"\n src={verificationStatus === \"Verified\" ? SuccessImg : WarningImg}\n height={30}\n />\n <div className=\"d-flex flex-column justify-content-center\">\n <div className=\"h6 mb-0\">Fractal</div>\n <div className=\"text-muted text-sm\">{verificationStatus}</div>\n </div>\n </div>\n {verificationStatus !== \"Verified\" && showGetVerifiedBtn && (\n <VerificationBtn />\n )}\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.Profile": { "": "const accountId = props.accountId;\nconst size = props.size ?? \"md\";\nconst Avatar = styled.div`\n &.sm {\n min-width: 30px;\n max-width: 30px;\n min-height: 30px;\n max-height: 30px;\n }\n &.md {\n min-width: 40px;\n max-width: 40px;\n min-height: 40px;\n max-height: 40px;\n }\n pointer-events: none;\n flex-shrink: 0;\n border: 1px solid #eceef0;\n overflow: hidden;\n border-radius: 40px;\n transition: border-color 200ms;\n\n img {\n object-fit: cover;\n width: 100%;\n height: 100%;\n margin: 0 !important;\n }\n`;\nconst profile = Social.get(`${accountId}/profile/**`, \"final\");\n\nreturn (\n <Avatar className={size}>\n <Widget\n src=\"mob.near/widget/Image\"\n props={{\n image: profile.image,\n alt: profile.name,\n fallbackUrl:\n \"https://ipfs.near.social/ipfs/bafkreibiyqabm3kl24gcb2oegb7pmwdi6wwrpui62iwb44l7uomnn3lhbi\",\n }}\n />\n </Avatar>\n);\n" }, "devhub.entity.proposal.Feed": { "": "const { href } = VM.require(\"devgovgigs.petersalomonsen.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst Container = styled.div`\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 @media screen and (max-width: 768px) {\n font-size: 13px;\n }\n\n .text-sm {\n font-size: 13px;\n }\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 .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n .green-btn {\n padding: 0.5rem 0.8rem !important;\n min-height: 32px;\n }\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n .text-normal {\n font-weight: normal !important;\n }\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst FeedItem = ({ proposal }) => {\n const { snapshot } = proposal;\n const accountId = proposal.author_id;\n const profile = Social.get(`${accountId}/profile/**`, \"final\");\n const blockHeight = parseInt(proposal.social_db_post_block_height);\n const item = {\n type: \"social\",\n path: `devhub.near/post/main`,\n blockHeight,\n };\n\n return (\n <a\n href={href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposal.id,\n },\n })}\n onClick={(e) => e.stopPropagation()}\n style={{ textDecoration: \"none\" }}\n >\n <div className=\"proposal-card d-flex justify-content-between gap-2 text-muted cursor-pointer p-3\">\n <div className=\"d-flex gap-4\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"d-flex gap-2 align-items-center flex-wrap\">\n <div className=\"h6 mb-0 text-black\">{snapshot.name}</div>\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CategoryTag\"}\n props={{\n category: snapshot.category,\n }}\n />\n </div>\n <div className=\"d-flex gap-2 align-items-center text-sm\">\n <div>By {profile.name ?? accountId} ・ </div>\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: snapshot.timestamp,\n }}\n />\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: accountId,\n }}\n />\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.CommentIcon\"}\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n </div>\n </div>\n </div>\n <div className=\"align-self-center\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n }}\n />\n </div>\n </div>\n </a>\n );\n};\n\nconst FeedPage = () => {\n const proposals = Near.view(\"devhub.near\", \"get_proposals\", {});\n\n return (\n <Container className=\"w-100 py-4 px-2 d-flex flex-column gap-3\">\n <div className=\"d-flex justify-content-between flex-wrap gap-2 align-items-center\">\n <Heading>\n DevDAO Proposals{\" \"}\n <span className=\"text-muted text-normal\"> ({proposals.length})</span>\n </Heading>\n {/* Filters aren't supported yet */}\n {/* <div className=\"d-flex gap-4 align-items-center\">\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-input\"\n }\n props={{}}\n />\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-sort\"}\n props={{}}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-category\"\n }\n props={{}}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-stage\"\n }\n props={{}}\n />\n <Widget\n src={\n \"devgovgigs.petersalomonsen.near/widget/devhub.feature.proposal-search.by-author\"\n }\n props={{}}\n />\n </div> */}\n <div>\n <Link\n to={href({\n widgetSrc: \"devgovgigs.petersalomonsen.near/widget/app\",\n params: { page: \"create-proposal\" },\n })}\n >\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: (\n <div className=\"d-flex gap-2 align-items-center\">\n <div>\n <i class=\"bi bi-plus-circle-fill\"></i>\n </div>\n New Proposal\n </div>\n ),\n classNames: { root: \"green-btn\" },\n }}\n />\n </Link>\n </div>\n </div>\n <div style={{ minHeight: \"50vh\" }}>\n {!Array.isArray(proposals) ? (\n <div className=\"d-flex justify-content-center align-items-center w-100\">\n <Widget\n src={\"devgovgigs.petersalomonsen.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n ) : (\n <div className=\"card rounded-0 mt-4 py-3 full-width-div\">\n <div className=\"container-xl\">\n <div className=\"text-muted bg-grey text-sm mt-2 p-3 rounded-3\">\n <p className=\"d-flex gap-4 align-items-center mb-0\">\n <div>\n <i class=\"bi bi-info-circle\"></i>\n </div>\n DevDAO is the primary organization behind DevHub, and we offer\n sponsorships to contributors and projects that align with our\n goal of fostering a self-sufficient community of developers\n for a thriving NEAR ecosystem. Check out our Funding\n Guidelines for more details.\n </p>\n </div>\n <div className=\"mt-4 border rounded-2\">\n {proposals.map((item, index) => {\n return (\n <div\n key={index}\n className={\n (index !== proposals.length - 1 && \"border-bottom \") +\n (index === 0 && \" rounded-top-2\")\n }\n >\n <FeedItem proposal={item} />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n </div>\n </Container>\n );\n};\n\nreturn FeedPage(props);\n" } } } } }
Result:
{ "block_height": "115147160" }
No logs
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 0.18543  to devgo…onsen.near
Empty result
No logs