Search
Search

Transaction: FmEMqzK...tJCc

Signed by
Receiver
Status
Succeeded
Transaction Fee
0.00229 
Deposit Value
0 
Gas Used
23 Tgas
Attached Gas
300 Tgas
Created
March 06, 2024 at 1:41:28am
Hash
FmEMqzK6nKb23r1Mq3MUbPe7n73RHbm93fW73migtJCc

Actions

Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devhub.near": { "widget": { "devhub.entity.addon.blog.Page": { "": "const { getAccountCommunityPermissions } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n) || {\n getAccountCommunityPermissions: () => {},\n};\nconst imagelink =\n \"https://ipfs.near.social/ipfs/bafkreiajzvmy7574k7mp3if6u53mdukfr3hoc2kjkhjadt6x56vqhd5swy\";\n\nfunction Page({ data, onEdit, labels, accountId }) {\n const { category, title, description, subtitle, date, content } = data;\n const handle = labels?.[1]; // community-handle\n const permissions = getAccountCommunityPermissions({\n account_id: accountId,\n community_handle: handle,\n });\n const isAllowedToEdit = permissions?.can_configure ?? false;\n const Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n margin-bottom: 2rem;\n position: relative;\n ${category &&\n `\n span.category {\n color: ${\n category.toLowerCase() === \"news\"\n ? \"#F40303\"\n : category.toLowerCase() === \"guide\"\n ? \"#004BE1\"\n : category.toLowerCase() === \"reference\" && \"#FF7A00\"\n };\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n text-transform: uppercase;\n }\n `}\n\n span.date {\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin: 1.5rem 0;\n }\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1.5rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n .edit-icon {\n position: absolute;\n top: 20px;\n right: 20px;\n cursor: pointer;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n `;\n\n const BackgroundImage = styled.img`\n width: 100%;\n height: auto;\n object-fit: cover;\n margin-bottom: 1rem;\n `;\n\n const options = { year: \"numeric\", month: \"short\", day: \"numeric\" };\n const formattedDate = new Date(date).toLocaleString(\"en-US\", options);\n\n return (\n <>\n <BackgroundImage src={imagelink} />\n <Container>\n {isAllowedToEdit && (\n <div className=\"edit-icon\" onClick={onEdit}>\n <div class=\"bi bi-pencil-square\" style={{ fontSize: \"30px\" }}></div>\n </div>\n )}\n {category && <span className=\"category\">{category}</span>}\n <h1>{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <span className=\"date\">{formattedDate}</span>\n <p>{description}</p>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{ text: content }}\n />\n </Container>\n </>\n );\n}\n\nreturn { Page };\n" }, "devhub.entity.proposal.Proposal": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\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 .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: 205px;\n background-color: lightgrey;\n }\n .vertical-line-sm {\n width: 2px;\n height: 80px;\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={\"devhub.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 editorAccountId = snapshot.editor_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 = `devhub.near/widget/devhub.entity.proposal.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 {isVerified ? (\n <img\n src=\"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\"\n height={40}\n />\n ) : (\n \"Need icon\"\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 <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.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 ))}\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={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 = isAllowedToEditProposal && proposal.author_id !== accountId;\n\nconst editProposalStatus = ({ timeline }) => {\n Near.call({\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\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([\"\"]);\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 extractNotifyAccountId = (item) => {\n if (!item || item.type !== \"social\" || !item.path) {\n return undefined;\n }\n const accountId = item.path.split(\"/\")[0];\n return `${accountId}/post/main` === item.path ? accountId : undefined;\n};\n\nreturn (\n <Container className=\"d-flex flex-column gap-2 w-100 mt-4\">\n <Widget\n src={\"devhub.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={\"devhub.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 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={\"devhub.near/widget/devhub.entity.proposal.History\"}\n props={{\n id: proposal.id,\n timestamp: snapshot.timestamp,\n }}\n />\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\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"create-proposal\",\n id: proposal.id,\n timestamp: timestamp,\n },\n })}\n style={{ textDecoration: \"none\" }}\n >\n <Widget\n src={\"devhub.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 gap-2 align-items-center text-sm pb-3\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n size: \"sm\",\n }}\n />\n <div>\n <b>{proposal.author_id} </b> created on{\" \"}\n {readableDate(snapshot.timestamp / 1000000)}\n </div>\n </div>\n <div className=\"card card-body rounded-0 p-4\">\n {snapshot.timeline.status === TIMELINE_STATUS.DRAFT &&\n isAllowedToEditProposal && (\n <div className=\"draft-info-container p-4 d-flex justify-content-between align-items-center gap-2 rounded-2\">\n <div>\n <b>\n This proposal is in draft mode and open for community 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={\"devhub.near/widget/devhub.components.molecule.Button\"}\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-4 d-flex justify-content-between align-items-center gap-2 rounded-2\">\n <div>\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 to\n sponsors that it’s no longer active or relevant.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\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 gap-6\">\n <div className=\"flex-3\">\n <div\n className=\"d-flex gap-2 flex-1\"\n style={{ zIndex: 99, background: \"white\", position: \"relative\" }}\n >\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: editorAccountId,\n }}\n />\n <ProposalContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n {snapshot.editor_id} ・{\" \"}\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: editorAccountId,\n blockHeight: blockHeight,\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"d-flex flex-column gap-1 p-2 px-3\">\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n PROPOSAL CATEGORY\n </div>\n <div>\n <Widget\n src={\n \"devhub.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=\"devhub.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=\"near/widget/v1.LikeButton\"\n props={{\n item,\n }}\n />\n <Widget\n src={\n \"devhub.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 <Widget\n src=\"near/widget/ShareButton\"\n props={{\n postType: \"post\",\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={\"devhub.near/widget/devhub.entity.proposal.Comments\"}\n props={{\n item: item,\n snapshotHistory: [...proposal.snapshot_history, snapshot],\n }}\n />\n </div>\n <div className=\"mt-4\">\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n item: item,\n notifyAccountId: extractNotifyAccountId(item),\n id: proposal.id,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column gap-4 flex-1\">\n <SidePanelItem title=\"Author\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: editorAccountId,\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=\"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=\"devhub.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: proposalStatusOptions,\n selectedValue: updatedProposalStatus,\n onUpdate: (v) => {\n setUpdatedProposalStatus({\n ...v,\n 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 request\n 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.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((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 </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 {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=\"devhub.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 \"devhub.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 \"devhub.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 \"devhub.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 \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n if (\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n ) {\n editProposalStatus({\n timeline: {\n ...updatedProposalStatus.value,\n payouts: !paymentHashes[0]\n ? []\n : paymentHashes,\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 </Container>\n);\n" }, "devhub.entity.addon.kanban.Configurator": { "": "const Struct = VM.require(\"devhub.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\nconst { useQuery } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n);\nconst { uuid, withUUIDIndex } = VM.require(\n \"devhub.near/widget/core.lib.uuid\"\n);\n\nuuid || (uuid = () => {});\nwithUUIDIndex || (withUUIDIndex = () => {});\nuseQuery || (useQuery = () => {});\n\nconst AttractableDiv = styled.div`\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n transition: box-shadow 0.6s;\n\n &:hover {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n }\n`;\n\nconst settings = {\n maxColumnsNumber: 10,\n};\n\nconst KanbanPostBoardBasicInfoSchema = {\n title: { label: \"Title\", order: 1, placeholder: \"Enter board title\" },\n description: {\n label: \"Description\",\n order: 2,\n placeholder: \"Enter board description\",\n },\n};\n\nconst KanbanPostBoardTicketFeaturesSchema = {\n author: { label: \"Author\" },\n like_count: { label: \"Likes\" },\n approved_sponsorship_value: { label: \"Funding amount\" },\n sponsorship_supervisor: { label: \"Supervisor/Sponser\" },\n tags: { label: \"Tags\" },\n type: { label: \"Post type\" },\n};\n\nconst KanbanPostBoardDefaults = {\n metadata: {\n id: uuid(),\n type: \"kanban.post_board\",\n title: \"\",\n description: \"\",\n ticket: {\n type: \"kanban.post_ticket\",\n features: {\n author: true,\n like_count: true,\n approved_sponsorship_value: true,\n sponsorship_supervisor: true,\n tags: true,\n type: true,\n },\n sortBy: \"\",\n },\n },\n payload: {\n columns: {},\n },\n};\n\nconst toMigrated = ({ config, metadata, payload }) => ({\n metadata: {\n ...KanbanPostBoardDefaults.metadata,\n ...metadata,\n ticket: {\n ...KanbanPostBoardDefaults.metadata.ticket,\n ...metadata.ticket,\n features: {\n ...KanbanPostBoardDefaults.metadata.ticket.features,\n ...metadata.ticket.features,\n },\n },\n },\n payload: {\n ...KanbanPostBoardDefaults.payload,\n ...payload,\n ...config,\n },\n});\n\nconst sortByOptions = [\n { label: \"None\", value: \"none\" },\n { label: \"Amount: High to Low\", value: \"descending-amount\" },\n { label: \"Amount: Low to High\", value: \"ascending-amount\" },\n { label: \"Date: Newest to Oldest\", value: \"descending-date\" },\n { label: \"Date: Oldest to Newest\", value: \"ascending-date\" },\n { label: \"Author: A-Z\", value: \"ascending-author\" },\n { label: \"Author: Z-A\", value: \"descending-author\" },\n { label: \"Sponsor/Supervisor: A-Z\", value: \"ascending-sponsor\" },\n { label: \"Sponsor/Supervisor: Z-A\", value: \"descending-sponsor\" },\n { label: \"Most Likes\", value: \"descending-likes\" },\n { label: \"Fewest Likes\", value: \"ascending-likes\" },\n];\n\nconst KanbanViewConfigurator = ({ handle, data, permissions, onSubmit }) => {\n const tags = useCache(\n () =>\n Near.asyncView(\"devhub.near\", \"get_all_labels\").then(\n (res) => res\n ),\n handle,\n {\n subscribe: false,\n }\n );\n\n if (!data) {\n return (\n <div class=\"alert alert-danger\" role=\"alert\">\n Loading...\n </div>\n );\n }\n const initialFormState = Struct.pick(\n data.metadata === undefined ? {} : toMigrated(data),\n [\"metadata\", \"payload\"]\n );\n\n const [formState, setForm] = useState(initialFormState);\n const [showPreview, setPreview] = useState(false);\n\n const formUpdate =\n ({ path, via: customFieldUpdate, ...params }) =>\n (fieldInput) => {\n const transformFn = (node) => {\n if (typeof customFieldUpdate === \"function\") {\n return customFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n } else {\n return Struct.defaultFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n }\n };\n const updatedValues = Struct.deepFieldUpdate(\n formState ?? {},\n path,\n (node) => transformFn(node)\n );\n setForm((prevFormState) => ({\n ...prevFormState,\n ...updatedValues,\n }));\n };\n\n const formReset = () => {\n setForm(initialFormState);\n };\n\n const newViewInit = () => {\n setForm(KanbanPostBoardDefaults);\n };\n\n const columnsCreateNew = ({ lastKnownValue }) =>\n Object.keys(lastKnownValue).length < settings.maxColumnsNumber\n ? {\n ...(lastKnownValue ?? {}),\n ...withUUIDIndex({ tag: \"\", title: \"New column\", description: \"\" }),\n }\n : lastKnownValue;\n\n const columnsDeleteById =\n (id) =>\n ({ lastKnownValue }) =>\n Object.fromEntries(\n Object.entries(lastKnownValue).filter(([columnId]) => columnId !== id)\n );\n\n const onCancel = () => {\n formReset();\n };\n\n const onSave = () => onSubmit(formState);\n\n const formElement = (\n <div className=\"card p-2\">\n <div className=\"card-body d-flex flex-column gap-3\">\n <div className=\"d-flex flex-column flex-lg-row align-items-stretch w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Basic information\",\n externalState: formState.metadata,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({ path: [\"metadata\"] }),\n schema: KanbanPostBoardBasicInfoSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Fields to display\n </span>\n <div>\n <Widget\n src={`devhub.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Card fields\",\n classNames: { root: \"w-auto h-auto\" },\n externalState: formState.metadata.ticket.features,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"metadata\", \"ticket\", \"features\"],\n }),\n schema: KanbanPostBoardTicketFeaturesSchema,\n style: { minWidth: \"36%\" },\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Sort by\n </span>\n <div>\n <div className=\"input-group\">\n <select\n className=\"form-select border border-1\"\n value={formState.metadata.ticket.sortBy}\n onChange={formUpdate({\n path: [\"metadata\", \"ticket\", \"sortBy\"],\n })}\n aria-label={label}\n >\n <option value=\"none\" disabled hidden>\n None\n </option>\n {sortByOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n <div className=\"d-flex align-items-center justify-content-between w-100 mb-1\">\n <span className=\"d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-list-task\" />\n <span>{`Columns ( max. ${settings.maxColumnsNumber} )`}</span>\n </span>\n </div>\n\n <div className=\"d-flex flex-column align-items-center gap-4 w-100\">\n {Object.values(formState.payload.columns ?? {}).map(\n ({ id, description, tag, title }) => (\n <AttractableDiv\n className=\"d-flex gap-3 rounded-4 border p-3 w-100\"\n key={`column-${id}-configurator`}\n >\n <div className=\"d-flex flex-column gap-1 w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-title`,\n label: \"Title\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"title\"],\n }),\n placeholder: \"Enter column title\",\n value: title,\n }}\n />\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"description\"],\n }),\n placeholder: \"Enter a brief description for the column\",\n value: description,\n }}\n />\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span className=\"d-flex justify-content-between align-items-center gap-3 w-100\">\n Enter a tag to filter posts in this column\n </span>\n <div className=\"w-100\">\n <Widget\n src=\"devhub.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tag: tag,\n onTagSearch: formUpdate({\n path: [\"payload\", \"columns\", id, \"tag\"],\n }),\n }}\n />\n </div>\n </div>\n </div>\n\n <div\n className=\"d-flex flex-column gap-3 border-start p-3 pe-0\"\n style={{ marginTop: -16, marginBottom: -16 }}\n >\n <button\n className=\"btn btn-outline-danger\"\n onClick={formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsDeleteById(id),\n })}\n title=\"Delete column\"\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </div>\n </AttractableDiv>\n )\n )}\n </div>\n <div className=\"d-flex gap-3 justify-content-between w-100 mt-2 flex-wrap flex-sm-nowrap\">\n <div style={{ flex: \"none\" }}>\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-sm btn-outline-secondary\",\n },\n label: \"New column\",\n disabled:\n Object.keys(formState.payload.columns).length >=\n settings.maxColumnsNumber,\n icon: { type: \"bootstrap_icon\", variant: \"bi-plus-lg\" },\n onClick: formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsCreateNew,\n }),\n }}\n />\n </div>\n <div className=\"d-flex gap-3 justify-content-end w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex btn btn-outline-danger shadow-none border-0\",\n },\n isHidden: typeof onCancel !== \"function\",\n label: \"Cancel\",\n onClick: onCancel,\n }}\n />\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"btn btn-success\" },\n disabled: form.isSynced,\n icon: {\n type: \"svg_icon\",\n variant: \"floppy_drive\",\n width: 14,\n height: 14,\n },\n isHidden: typeof onSave !== \"function\",\n label: \"Save\",\n onClick: onSave,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n );\n\n return (\n <div\n className=\"d-flex flex-column gap-4 w-100\"\n style={{ maxWidth: \"100%\" }}\n >\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${!showPreview ? \"active\" : \"\"}`}\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n onClick={() => setPreview(false)}\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${showPreview ? \"active\" : \"\"}`}\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n onClick={() => setPreview(true)}\n >\n Preview\n </button>\n </li>\n </ul>\n {showPreview ? (\n <div>\n <Widget\n src={`devhub.near/widget/devhub.entity.addon.kanban.Viewer`}\n props={{\n data: formState,\n }}\n />\n </div>\n ) : (\n <div className={[\"d-flex flex-column gap-4 w-100\"].join(\" \")}>\n <div className=\"d-flex align-items-center justify-content-between gap-3 w-100\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-gear-wide-connected\" />\n <span>Kanban board configuration</span>\n </h5>\n </div>\n {Object.keys(formState.metadata ?? {}).length > 0 && formElement}\n </div>\n )}\n {!Object.keys(formState.metadata ?? {}).length && (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n This community doesn't have a kanban board\n </h5>\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n icon: { type: \"bootstrap_icon\", variant: \"bi-kanban-fill\" },\n isHidden: !permissions.can_configure,\n label: \"Create kanban board\",\n onClick: newViewInit,\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\nreturn KanbanViewConfigurator(props);\n" }, "devhub.entity.post.List": { "": "// This component implementation was forked from [IndexFeed], but it does not fully implement lazy loading.\n// While this component uses InfiniteScroll, it still loads the whole list of Post IDs in one view call.\n// The contract will need to be extended with pagination support, yet, even in the current state the page loads much faster.\n// [IndexFeed]: https://near.social/#/mob.near/widget/WidgetSource?src=mob.near/widget/IndexFeed\n\nconst { href } = VM.require(\"devhub.near/widget/core.lib.url\");\n\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.near/widget/devhub.entity.post.draft\"\n);\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v36_posts_with_latest_snapshot`;\nconst totalQueryName =\n props.totalQueryName ??\n \"bo_near_devhub_v36_posts_with_latest_snapshot_aggregate\";\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {ts: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst totalQuery = `query DevhubTotalPostsQuery($where: ${queryName}_bool_exp = {}) {\n ${totalQueryName}(\n where: $where\n ) {\n aggregate {\n count\n }\n }\n }\n`;\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nfunction searchConditionChanged() {\n return (\n props.author != state.author ||\n props.term != state.term ||\n props.tag != state.tag ||\n props.recency != state.recency\n );\n}\n\nfunction updateSearchCondition() {\n State.update({\n author: props.author,\n term: props.term,\n tag: props.tag,\n recency: props.recency,\n loading: true,\n });\n}\n\nconst initialRenderLimit = props.initialRenderLimit ?? 3;\nconst addDisplayCount = props.nextLimit ?? initialRenderLimit;\n\nState.init({\n period: \"week\",\n totalItems: 0,\n displayCount: initialRenderLimit,\n});\n\nfunction getPostIds(tag, offset) {\n if (searchConditionChanged()) {\n updateSearchCondition();\n }\n let where = {};\n let authorId = props.author;\n let label = tag || props.tag;\n if (authorId) {\n where = { author_id: { _eq: authorId }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (label) {\n if (typeof label === \"string\") {\n // Handle a single label\n where = { labels: { _contains: label }, ...where };\n } else if (Array.isArray(label)) {\n // Handle an array of labels\n where = {\n labels: {\n _containsAny: label,\n },\n ...where,\n };\n }\n }\n if (!props.recency) {\n // show only top level posts\n where = { parent_id: { _is_null: true }, ...where };\n }\n\n // Don't show blog and devhub-test posts\n where = {\n _and: [\n {\n _not: {\n labels: { _contains: \"blog\" },\n parent_id: { _is_null: true },\n post_type: { _eq: \"Comment\" },\n },\n },\n {\n _not: {\n labels: { _contains: \"devhub-test\" },\n },\n },\n ],\n ...where,\n };\n\n if (!offset) {\n fetchGraphQL(totalQuery, \"DevhubTotalPostsQuery\", {\n where,\n }).then((result) => {\n const data = result.body.data[totalQueryName];\n State.update({\n totalItems: data.aggregate.count,\n });\n });\n }\n\n fetchGraphQL(query, \"DevhubPostsQuery\", {\n limit: 50,\n offset: offset ?? 0,\n where,\n }).then((result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[queryName];\n if (offset) {\n State.update({\n postIds: state.postIds.concat(data.map((p) => p.post_id)),\n loading: false,\n });\n } else {\n State.update({\n postIds: data.map((p) => p.post_id),\n loading: false,\n });\n }\n }\n } else {\n State.update({ loading: false });\n }\n });\n}\n\nif (!state.items || searchConditionChanged()) {\n getPostIds();\n}\n\nfunction defaultRenderItem(postId, additionalProps) {\n if (!additionalProps) {\n additionalProps = {};\n }\n // It is important to have a non-zero-height element as otherwise InfiniteScroll loads too many items on initial load\n return (\n <div className=\"py-2\" style={{ minHeight: \"150px\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.Post\"}\n props={{\n id: postId,\n expandable: true,\n defaultExpanded: false,\n isInList: true,\n draftState,\n isPreview: false,\n onDraftStateChange,\n ...additionalProps,\n referral: postId,\n updateTagInParent: (tag) => {\n if (typeof props.updateTagInput === \"function\") {\n props.updateTagInput(tag);\n }\n getPostIds(tag);\n },\n transactionHashes: props.transactionHashes,\n }}\n />\n </div>\n );\n}\n\nconst renderItem = props.renderItem ?? defaultRenderItem;\n\nconst cachedRenderItem = (item, i) => {\n if (props.term) {\n return renderItem(item, {\n searchKeywords: [props.term],\n });\n }\n\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item);\n State.update();\n }\n return state.cachedItems[key];\n};\n\nconst ONE_DAY = 60 * 60 * 24 * 1000;\nconst ONE_WEEK = 60 * 60 * 24 * 1000 * 7;\nconst ONE_MONTH = 60 * 60 * 24 * 1000 * 30;\n\nfunction getHotnessScore(post) {\n //post.id - shows the age of the post, should grow exponentially, since newer posts are more important\n //post.likes.length - linear value\n const age = Math.pow(post.id, 5);\n const comments = post.comments;\n const commentAge = comments.reduce((sum, age) => sum + Math.pow(age, 5), 0);\n const totalAge = age + commentAge;\n //use log functions to make likes score and exponentially big age score close to each other\n return Math.log10(post.likes.length) + Math.log(Math.log10(totalAge));\n}\n\nconst getPeriodText = (period) => {\n let text = \"Last 24 hours\";\n if (period === \"week\") {\n text = \"Last week\";\n }\n if (period === \"month\") {\n text = \"Last month\";\n }\n return text;\n};\n\nlet postIds = state.postIds ?? null;\n\nconst loader = (\n <div className=\"loader\" key={\"loader\"}>\n <span\n className=\"spinner-grow spinner-grow-sm me-1\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n Loading ...\n </div>\n);\n\nif (postIds === null) {\n return loader;\n}\nconst initialItems = postIds;\n\nconst jInitialItems = JSON.stringify(initialItems);\nif (state.jInitialItems !== jInitialItems) {\n // const jIndex = JSON.stringify(index);\n // if (jIndex !== state.jIndex) {\n State.update({\n jIndex,\n jInitialItems,\n items: initialItems,\n cachedItems: {},\n });\n}\n\nconst makeMoreItems = () => {\n State.update({\n displayCount: state.displayCount + addDisplayCount,\n });\n if (\n state.items.length - state.displayCount < addDisplayCount * 5 &&\n !state.loading\n ) {\n State.update({ loading: true });\n getPostIds(null, state.items.length);\n }\n};\n\nconst items = state.items ? state.items.slice(0, state.displayCount) : [];\nconst renderedItems = items.map(cachedRenderItem);\n\nconst Head =\n props.recency == \"hot\" ? (\n <div class=\"row\">\n <div class=\"fs-5 col-6 align-self-center\">\n <i class=\"bi-fire\"></i>\n <span>Hottest Posts</span>\n </div>\n <div class=\"col-6 dropdown d-flex justify-content-end\">\n <a\n class=\"btn btn-secondary dropdown-toggle\"\n href=\"#\"\n role=\"button\"\n id=\"dropdownMenuLink\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n {getPeriodText(state.period)}\n </a>\n\n <ul class=\"dropdown-menu\" aria-labelledby=\"dropdownMenuLink\">\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"day\" });\n }}\n >\n {getPeriodText(\"day\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"week\" });\n }}\n >\n {getPeriodText(\"week\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"month\" });\n }}\n >\n {getPeriodText(\"month\")}\n </button>\n </li>\n </ul>\n </div>\n </div>\n ) : (\n <></>\n );\n\nreturn (\n <>\n {Head}\n {state.loading ? loader : null}\n {is_edit_or_add_post_transaction ? (\n <p class=\"text-secondary mt-4\">\n Post {transaction_method_name == \"edit_post\" ? \"edited\" : \"added\"}{\" \"}\n successfully. Back to{\" \"}\n <Link\n style={{\n color: \"#3252A6\",\n }}\n className=\"fw-bold\"\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"feed\" },\n })}\n >\n feed\n </Link>\n </p>\n ) : state.items.length > 0 ? (\n <div style={{ overflow: \"auto\", height: \"60vh\" }}>\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n hasMore={state.totalItems > state.items.length}\n loader={loader}\n useWindow={false}\n >\n {renderedItems}\n </InfiniteScroll>\n </div>\n ) : (\n <p class=\"text-secondary\">\n No posts{\" \"}\n {props.term || props.tag || props.author ? \"matches search\" : \"\"}\n {props.recency === \"hot\"\n ? \" in \" + getPeriodText(state.period).toLowerCase()\n : \"\"}\n </p>\n )}\n </>\n);\n" } } } } }

Transaction Execution Plan

Convert Transaction To Receipt
Gas Burned:
2 Tgas
Tokens Burned:
0.00026 
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
20 Tgas
Tokens Burned:
0.00203 
Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devhub.near": { "widget": { "devhub.entity.addon.blog.Page": { "": "const { getAccountCommunityPermissions } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n) || {\n getAccountCommunityPermissions: () => {},\n};\nconst imagelink =\n \"https://ipfs.near.social/ipfs/bafkreiajzvmy7574k7mp3if6u53mdukfr3hoc2kjkhjadt6x56vqhd5swy\";\n\nfunction Page({ data, onEdit, labels, accountId }) {\n const { category, title, description, subtitle, date, content } = data;\n const handle = labels?.[1]; // community-handle\n const permissions = getAccountCommunityPermissions({\n account_id: accountId,\n community_handle: handle,\n });\n const isAllowedToEdit = permissions?.can_configure ?? false;\n const Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n margin-bottom: 2rem;\n position: relative;\n ${category &&\n `\n span.category {\n color: ${\n category.toLowerCase() === \"news\"\n ? \"#F40303\"\n : category.toLowerCase() === \"guide\"\n ? \"#004BE1\"\n : category.toLowerCase() === \"reference\" && \"#FF7A00\"\n };\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n text-transform: uppercase;\n }\n `}\n\n span.date {\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin: 1.5rem 0;\n }\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1.5rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n .edit-icon {\n position: absolute;\n top: 20px;\n right: 20px;\n cursor: pointer;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n `;\n\n const BackgroundImage = styled.img`\n width: 100%;\n height: auto;\n object-fit: cover;\n margin-bottom: 1rem;\n `;\n\n const options = { year: \"numeric\", month: \"short\", day: \"numeric\" };\n const formattedDate = new Date(date).toLocaleString(\"en-US\", options);\n\n return (\n <>\n <BackgroundImage src={imagelink} />\n <Container>\n {isAllowedToEdit && (\n <div className=\"edit-icon\" onClick={onEdit}>\n <div class=\"bi bi-pencil-square\" style={{ fontSize: \"30px\" }}></div>\n </div>\n )}\n {category && <span className=\"category\">{category}</span>}\n <h1>{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <span className=\"date\">{formattedDate}</span>\n <p>{description}</p>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{ text: content }}\n />\n </Container>\n </>\n );\n}\n\nreturn { Page };\n" }, "devhub.entity.proposal.Proposal": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\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 .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: 205px;\n background-color: lightgrey;\n }\n .vertical-line-sm {\n width: 2px;\n height: 80px;\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={\"devhub.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 editorAccountId = snapshot.editor_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 = `devhub.near/widget/devhub.entity.proposal.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 {isVerified ? (\n <img\n src=\"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\"\n height={40}\n />\n ) : (\n \"Need icon\"\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 <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.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 ))}\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={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 = isAllowedToEditProposal && proposal.author_id !== accountId;\n\nconst editProposalStatus = ({ timeline }) => {\n Near.call({\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\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([\"\"]);\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 extractNotifyAccountId = (item) => {\n if (!item || item.type !== \"social\" || !item.path) {\n return undefined;\n }\n const accountId = item.path.split(\"/\")[0];\n return `${accountId}/post/main` === item.path ? accountId : undefined;\n};\n\nreturn (\n <Container className=\"d-flex flex-column gap-2 w-100 mt-4\">\n <Widget\n src={\"devhub.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={\"devhub.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 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={\"devhub.near/widget/devhub.entity.proposal.History\"}\n props={{\n id: proposal.id,\n timestamp: snapshot.timestamp,\n }}\n />\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\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"create-proposal\",\n id: proposal.id,\n timestamp: timestamp,\n },\n })}\n style={{ textDecoration: \"none\" }}\n >\n <Widget\n src={\"devhub.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 gap-2 align-items-center text-sm pb-3\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n size: \"sm\",\n }}\n />\n <div>\n <b>{proposal.author_id} </b> created on{\" \"}\n {readableDate(snapshot.timestamp / 1000000)}\n </div>\n </div>\n <div className=\"card card-body rounded-0 p-4\">\n {snapshot.timeline.status === TIMELINE_STATUS.DRAFT &&\n isAllowedToEditProposal && (\n <div className=\"draft-info-container p-4 d-flex justify-content-between align-items-center gap-2 rounded-2\">\n <div>\n <b>\n This proposal is in draft mode and open for community 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={\"devhub.near/widget/devhub.components.molecule.Button\"}\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-4 d-flex justify-content-between align-items-center gap-2 rounded-2\">\n <div>\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 to\n sponsors that it’s no longer active or relevant.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\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 gap-6\">\n <div className=\"flex-3\">\n <div\n className=\"d-flex gap-2 flex-1\"\n style={{ zIndex: 99, background: \"white\", position: \"relative\" }}\n >\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: editorAccountId,\n }}\n />\n <ProposalContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n {snapshot.editor_id} ・{\" \"}\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: editorAccountId,\n blockHeight: blockHeight,\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"d-flex flex-column gap-1 p-2 px-3\">\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n PROPOSAL CATEGORY\n </div>\n <div>\n <Widget\n src={\n \"devhub.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=\"devhub.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=\"near/widget/v1.LikeButton\"\n props={{\n item,\n }}\n />\n <Widget\n src={\n \"devhub.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 <Widget\n src=\"near/widget/ShareButton\"\n props={{\n postType: \"post\",\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={\"devhub.near/widget/devhub.entity.proposal.Comments\"}\n props={{\n item: item,\n snapshotHistory: [...proposal.snapshot_history, snapshot],\n }}\n />\n </div>\n <div className=\"mt-4\">\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n item: item,\n notifyAccountId: extractNotifyAccountId(item),\n id: proposal.id,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column gap-4 flex-1\">\n <SidePanelItem title=\"Author\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: editorAccountId,\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=\"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=\"devhub.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: proposalStatusOptions,\n selectedValue: updatedProposalStatus,\n onUpdate: (v) => {\n setUpdatedProposalStatus({\n ...v,\n 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 request\n 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.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((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 </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 {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=\"devhub.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 \"devhub.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 \"devhub.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 \"devhub.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 \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n if (\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n ) {\n editProposalStatus({\n timeline: {\n ...updatedProposalStatus.value,\n payouts: !paymentHashes[0]\n ? []\n : paymentHashes,\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 </Container>\n);\n" }, "devhub.entity.addon.kanban.Configurator": { "": "const Struct = VM.require(\"devhub.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\nconst { useQuery } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n);\nconst { uuid, withUUIDIndex } = VM.require(\n \"devhub.near/widget/core.lib.uuid\"\n);\n\nuuid || (uuid = () => {});\nwithUUIDIndex || (withUUIDIndex = () => {});\nuseQuery || (useQuery = () => {});\n\nconst AttractableDiv = styled.div`\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n transition: box-shadow 0.6s;\n\n &:hover {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n }\n`;\n\nconst settings = {\n maxColumnsNumber: 10,\n};\n\nconst KanbanPostBoardBasicInfoSchema = {\n title: { label: \"Title\", order: 1, placeholder: \"Enter board title\" },\n description: {\n label: \"Description\",\n order: 2,\n placeholder: \"Enter board description\",\n },\n};\n\nconst KanbanPostBoardTicketFeaturesSchema = {\n author: { label: \"Author\" },\n like_count: { label: \"Likes\" },\n approved_sponsorship_value: { label: \"Funding amount\" },\n sponsorship_supervisor: { label: \"Supervisor/Sponser\" },\n tags: { label: \"Tags\" },\n type: { label: \"Post type\" },\n};\n\nconst KanbanPostBoardDefaults = {\n metadata: {\n id: uuid(),\n type: \"kanban.post_board\",\n title: \"\",\n description: \"\",\n ticket: {\n type: \"kanban.post_ticket\",\n features: {\n author: true,\n like_count: true,\n approved_sponsorship_value: true,\n sponsorship_supervisor: true,\n tags: true,\n type: true,\n },\n sortBy: \"\",\n },\n },\n payload: {\n columns: {},\n },\n};\n\nconst toMigrated = ({ config, metadata, payload }) => ({\n metadata: {\n ...KanbanPostBoardDefaults.metadata,\n ...metadata,\n ticket: {\n ...KanbanPostBoardDefaults.metadata.ticket,\n ...metadata.ticket,\n features: {\n ...KanbanPostBoardDefaults.metadata.ticket.features,\n ...metadata.ticket.features,\n },\n },\n },\n payload: {\n ...KanbanPostBoardDefaults.payload,\n ...payload,\n ...config,\n },\n});\n\nconst sortByOptions = [\n { label: \"None\", value: \"none\" },\n { label: \"Amount: High to Low\", value: \"descending-amount\" },\n { label: \"Amount: Low to High\", value: \"ascending-amount\" },\n { label: \"Date: Newest to Oldest\", value: \"descending-date\" },\n { label: \"Date: Oldest to Newest\", value: \"ascending-date\" },\n { label: \"Author: A-Z\", value: \"ascending-author\" },\n { label: \"Author: Z-A\", value: \"descending-author\" },\n { label: \"Sponsor/Supervisor: A-Z\", value: \"ascending-sponsor\" },\n { label: \"Sponsor/Supervisor: Z-A\", value: \"descending-sponsor\" },\n { label: \"Most Likes\", value: \"descending-likes\" },\n { label: \"Fewest Likes\", value: \"ascending-likes\" },\n];\n\nconst KanbanViewConfigurator = ({ handle, data, permissions, onSubmit }) => {\n const tags = useCache(\n () =>\n Near.asyncView(\"devhub.near\", \"get_all_labels\").then(\n (res) => res\n ),\n handle,\n {\n subscribe: false,\n }\n );\n\n if (!data) {\n return (\n <div class=\"alert alert-danger\" role=\"alert\">\n Loading...\n </div>\n );\n }\n const initialFormState = Struct.pick(\n data.metadata === undefined ? {} : toMigrated(data),\n [\"metadata\", \"payload\"]\n );\n\n const [formState, setForm] = useState(initialFormState);\n const [showPreview, setPreview] = useState(false);\n\n const formUpdate =\n ({ path, via: customFieldUpdate, ...params }) =>\n (fieldInput) => {\n const transformFn = (node) => {\n if (typeof customFieldUpdate === \"function\") {\n return customFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n } else {\n return Struct.defaultFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n }\n };\n const updatedValues = Struct.deepFieldUpdate(\n formState ?? {},\n path,\n (node) => transformFn(node)\n );\n setForm((prevFormState) => ({\n ...prevFormState,\n ...updatedValues,\n }));\n };\n\n const formReset = () => {\n setForm(initialFormState);\n };\n\n const newViewInit = () => {\n setForm(KanbanPostBoardDefaults);\n };\n\n const columnsCreateNew = ({ lastKnownValue }) =>\n Object.keys(lastKnownValue).length < settings.maxColumnsNumber\n ? {\n ...(lastKnownValue ?? {}),\n ...withUUIDIndex({ tag: \"\", title: \"New column\", description: \"\" }),\n }\n : lastKnownValue;\n\n const columnsDeleteById =\n (id) =>\n ({ lastKnownValue }) =>\n Object.fromEntries(\n Object.entries(lastKnownValue).filter(([columnId]) => columnId !== id)\n );\n\n const onCancel = () => {\n formReset();\n };\n\n const onSave = () => onSubmit(formState);\n\n const formElement = (\n <div className=\"card p-2\">\n <div className=\"card-body d-flex flex-column gap-3\">\n <div className=\"d-flex flex-column flex-lg-row align-items-stretch w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Basic information\",\n externalState: formState.metadata,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({ path: [\"metadata\"] }),\n schema: KanbanPostBoardBasicInfoSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Fields to display\n </span>\n <div>\n <Widget\n src={`devhub.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Card fields\",\n classNames: { root: \"w-auto h-auto\" },\n externalState: formState.metadata.ticket.features,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"metadata\", \"ticket\", \"features\"],\n }),\n schema: KanbanPostBoardTicketFeaturesSchema,\n style: { minWidth: \"36%\" },\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Sort by\n </span>\n <div>\n <div className=\"input-group\">\n <select\n className=\"form-select border border-1\"\n value={formState.metadata.ticket.sortBy}\n onChange={formUpdate({\n path: [\"metadata\", \"ticket\", \"sortBy\"],\n })}\n aria-label={label}\n >\n <option value=\"none\" disabled hidden>\n None\n </option>\n {sortByOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n <div className=\"d-flex align-items-center justify-content-between w-100 mb-1\">\n <span className=\"d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-list-task\" />\n <span>{`Columns ( max. ${settings.maxColumnsNumber} )`}</span>\n </span>\n </div>\n\n <div className=\"d-flex flex-column align-items-center gap-4 w-100\">\n {Object.values(formState.payload.columns ?? {}).map(\n ({ id, description, tag, title }) => (\n <AttractableDiv\n className=\"d-flex gap-3 rounded-4 border p-3 w-100\"\n key={`column-${id}-configurator`}\n >\n <div className=\"d-flex flex-column gap-1 w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-title`,\n label: \"Title\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"title\"],\n }),\n placeholder: \"Enter column title\",\n value: title,\n }}\n />\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"description\"],\n }),\n placeholder: \"Enter a brief description for the column\",\n value: description,\n }}\n />\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span className=\"d-flex justify-content-between align-items-center gap-3 w-100\">\n Enter a tag to filter posts in this column\n </span>\n <div className=\"w-100\">\n <Widget\n src=\"devhub.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tag: tag,\n onTagSearch: formUpdate({\n path: [\"payload\", \"columns\", id, \"tag\"],\n }),\n }}\n />\n </div>\n </div>\n </div>\n\n <div\n className=\"d-flex flex-column gap-3 border-start p-3 pe-0\"\n style={{ marginTop: -16, marginBottom: -16 }}\n >\n <button\n className=\"btn btn-outline-danger\"\n onClick={formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsDeleteById(id),\n })}\n title=\"Delete column\"\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </div>\n </AttractableDiv>\n )\n )}\n </div>\n <div className=\"d-flex gap-3 justify-content-between w-100 mt-2 flex-wrap flex-sm-nowrap\">\n <div style={{ flex: \"none\" }}>\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-sm btn-outline-secondary\",\n },\n label: \"New column\",\n disabled:\n Object.keys(formState.payload.columns).length >=\n settings.maxColumnsNumber,\n icon: { type: \"bootstrap_icon\", variant: \"bi-plus-lg\" },\n onClick: formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsCreateNew,\n }),\n }}\n />\n </div>\n <div className=\"d-flex gap-3 justify-content-end w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex btn btn-outline-danger shadow-none border-0\",\n },\n isHidden: typeof onCancel !== \"function\",\n label: \"Cancel\",\n onClick: onCancel,\n }}\n />\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"btn btn-success\" },\n disabled: form.isSynced,\n icon: {\n type: \"svg_icon\",\n variant: \"floppy_drive\",\n width: 14,\n height: 14,\n },\n isHidden: typeof onSave !== \"function\",\n label: \"Save\",\n onClick: onSave,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n );\n\n return (\n <div\n className=\"d-flex flex-column gap-4 w-100\"\n style={{ maxWidth: \"100%\" }}\n >\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${!showPreview ? \"active\" : \"\"}`}\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n onClick={() => setPreview(false)}\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${showPreview ? \"active\" : \"\"}`}\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n onClick={() => setPreview(true)}\n >\n Preview\n </button>\n </li>\n </ul>\n {showPreview ? (\n <div>\n <Widget\n src={`devhub.near/widget/devhub.entity.addon.kanban.Viewer`}\n props={{\n data: formState,\n }}\n />\n </div>\n ) : (\n <div className={[\"d-flex flex-column gap-4 w-100\"].join(\" \")}>\n <div className=\"d-flex align-items-center justify-content-between gap-3 w-100\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-gear-wide-connected\" />\n <span>Kanban board configuration</span>\n </h5>\n </div>\n {Object.keys(formState.metadata ?? {}).length > 0 && formElement}\n </div>\n )}\n {!Object.keys(formState.metadata ?? {}).length && (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n This community doesn't have a kanban board\n </h5>\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n icon: { type: \"bootstrap_icon\", variant: \"bi-kanban-fill\" },\n isHidden: !permissions.can_configure,\n label: \"Create kanban board\",\n onClick: newViewInit,\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\nreturn KanbanViewConfigurator(props);\n" }, "devhub.entity.post.List": { "": "// This component implementation was forked from [IndexFeed], but it does not fully implement lazy loading.\n// While this component uses InfiniteScroll, it still loads the whole list of Post IDs in one view call.\n// The contract will need to be extended with pagination support, yet, even in the current state the page loads much faster.\n// [IndexFeed]: https://near.social/#/mob.near/widget/WidgetSource?src=mob.near/widget/IndexFeed\n\nconst { href } = VM.require(\"devhub.near/widget/core.lib.url\");\n\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.near/widget/devhub.entity.post.draft\"\n);\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v36_posts_with_latest_snapshot`;\nconst totalQueryName =\n props.totalQueryName ??\n \"bo_near_devhub_v36_posts_with_latest_snapshot_aggregate\";\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {ts: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst totalQuery = `query DevhubTotalPostsQuery($where: ${queryName}_bool_exp = {}) {\n ${totalQueryName}(\n where: $where\n ) {\n aggregate {\n count\n }\n }\n }\n`;\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nfunction searchConditionChanged() {\n return (\n props.author != state.author ||\n props.term != state.term ||\n props.tag != state.tag ||\n props.recency != state.recency\n );\n}\n\nfunction updateSearchCondition() {\n State.update({\n author: props.author,\n term: props.term,\n tag: props.tag,\n recency: props.recency,\n loading: true,\n });\n}\n\nconst initialRenderLimit = props.initialRenderLimit ?? 3;\nconst addDisplayCount = props.nextLimit ?? initialRenderLimit;\n\nState.init({\n period: \"week\",\n totalItems: 0,\n displayCount: initialRenderLimit,\n});\n\nfunction getPostIds(tag, offset) {\n if (searchConditionChanged()) {\n updateSearchCondition();\n }\n let where = {};\n let authorId = props.author;\n let label = tag || props.tag;\n if (authorId) {\n where = { author_id: { _eq: authorId }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (label) {\n if (typeof label === \"string\") {\n // Handle a single label\n where = { labels: { _contains: label }, ...where };\n } else if (Array.isArray(label)) {\n // Handle an array of labels\n where = {\n labels: {\n _containsAny: label,\n },\n ...where,\n };\n }\n }\n if (!props.recency) {\n // show only top level posts\n where = { parent_id: { _is_null: true }, ...where };\n }\n\n // Don't show blog and devhub-test posts\n where = {\n _and: [\n {\n _not: {\n labels: { _contains: \"blog\" },\n parent_id: { _is_null: true },\n post_type: { _eq: \"Comment\" },\n },\n },\n {\n _not: {\n labels: { _contains: \"devhub-test\" },\n },\n },\n ],\n ...where,\n };\n\n if (!offset) {\n fetchGraphQL(totalQuery, \"DevhubTotalPostsQuery\", {\n where,\n }).then((result) => {\n const data = result.body.data[totalQueryName];\n State.update({\n totalItems: data.aggregate.count,\n });\n });\n }\n\n fetchGraphQL(query, \"DevhubPostsQuery\", {\n limit: 50,\n offset: offset ?? 0,\n where,\n }).then((result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[queryName];\n if (offset) {\n State.update({\n postIds: state.postIds.concat(data.map((p) => p.post_id)),\n loading: false,\n });\n } else {\n State.update({\n postIds: data.map((p) => p.post_id),\n loading: false,\n });\n }\n }\n } else {\n State.update({ loading: false });\n }\n });\n}\n\nif (!state.items || searchConditionChanged()) {\n getPostIds();\n}\n\nfunction defaultRenderItem(postId, additionalProps) {\n if (!additionalProps) {\n additionalProps = {};\n }\n // It is important to have a non-zero-height element as otherwise InfiniteScroll loads too many items on initial load\n return (\n <div className=\"py-2\" style={{ minHeight: \"150px\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.Post\"}\n props={{\n id: postId,\n expandable: true,\n defaultExpanded: false,\n isInList: true,\n draftState,\n isPreview: false,\n onDraftStateChange,\n ...additionalProps,\n referral: postId,\n updateTagInParent: (tag) => {\n if (typeof props.updateTagInput === \"function\") {\n props.updateTagInput(tag);\n }\n getPostIds(tag);\n },\n transactionHashes: props.transactionHashes,\n }}\n />\n </div>\n );\n}\n\nconst renderItem = props.renderItem ?? defaultRenderItem;\n\nconst cachedRenderItem = (item, i) => {\n if (props.term) {\n return renderItem(item, {\n searchKeywords: [props.term],\n });\n }\n\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item);\n State.update();\n }\n return state.cachedItems[key];\n};\n\nconst ONE_DAY = 60 * 60 * 24 * 1000;\nconst ONE_WEEK = 60 * 60 * 24 * 1000 * 7;\nconst ONE_MONTH = 60 * 60 * 24 * 1000 * 30;\n\nfunction getHotnessScore(post) {\n //post.id - shows the age of the post, should grow exponentially, since newer posts are more important\n //post.likes.length - linear value\n const age = Math.pow(post.id, 5);\n const comments = post.comments;\n const commentAge = comments.reduce((sum, age) => sum + Math.pow(age, 5), 0);\n const totalAge = age + commentAge;\n //use log functions to make likes score and exponentially big age score close to each other\n return Math.log10(post.likes.length) + Math.log(Math.log10(totalAge));\n}\n\nconst getPeriodText = (period) => {\n let text = \"Last 24 hours\";\n if (period === \"week\") {\n text = \"Last week\";\n }\n if (period === \"month\") {\n text = \"Last month\";\n }\n return text;\n};\n\nlet postIds = state.postIds ?? null;\n\nconst loader = (\n <div className=\"loader\" key={\"loader\"}>\n <span\n className=\"spinner-grow spinner-grow-sm me-1\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n Loading ...\n </div>\n);\n\nif (postIds === null) {\n return loader;\n}\nconst initialItems = postIds;\n\nconst jInitialItems = JSON.stringify(initialItems);\nif (state.jInitialItems !== jInitialItems) {\n // const jIndex = JSON.stringify(index);\n // if (jIndex !== state.jIndex) {\n State.update({\n jIndex,\n jInitialItems,\n items: initialItems,\n cachedItems: {},\n });\n}\n\nconst makeMoreItems = () => {\n State.update({\n displayCount: state.displayCount + addDisplayCount,\n });\n if (\n state.items.length - state.displayCount < addDisplayCount * 5 &&\n !state.loading\n ) {\n State.update({ loading: true });\n getPostIds(null, state.items.length);\n }\n};\n\nconst items = state.items ? state.items.slice(0, state.displayCount) : [];\nconst renderedItems = items.map(cachedRenderItem);\n\nconst Head =\n props.recency == \"hot\" ? (\n <div class=\"row\">\n <div class=\"fs-5 col-6 align-self-center\">\n <i class=\"bi-fire\"></i>\n <span>Hottest Posts</span>\n </div>\n <div class=\"col-6 dropdown d-flex justify-content-end\">\n <a\n class=\"btn btn-secondary dropdown-toggle\"\n href=\"#\"\n role=\"button\"\n id=\"dropdownMenuLink\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n {getPeriodText(state.period)}\n </a>\n\n <ul class=\"dropdown-menu\" aria-labelledby=\"dropdownMenuLink\">\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"day\" });\n }}\n >\n {getPeriodText(\"day\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"week\" });\n }}\n >\n {getPeriodText(\"week\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"month\" });\n }}\n >\n {getPeriodText(\"month\")}\n </button>\n </li>\n </ul>\n </div>\n </div>\n ) : (\n <></>\n );\n\nreturn (\n <>\n {Head}\n {state.loading ? loader : null}\n {is_edit_or_add_post_transaction ? (\n <p class=\"text-secondary mt-4\">\n Post {transaction_method_name == \"edit_post\" ? \"edited\" : \"added\"}{\" \"}\n successfully. Back to{\" \"}\n <Link\n style={{\n color: \"#3252A6\",\n }}\n className=\"fw-bold\"\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"feed\" },\n })}\n >\n feed\n </Link>\n </p>\n ) : state.items.length > 0 ? (\n <div style={{ overflow: \"auto\", height: \"60vh\" }}>\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n hasMore={state.totalItems > state.items.length}\n loader={loader}\n useWindow={false}\n >\n {renderedItems}\n </InfiniteScroll>\n </div>\n ) : (\n <p class=\"text-secondary\">\n No posts{\" \"}\n {props.term || props.tag || props.author ? \"matches search\" : \"\"}\n {props.recency === \"hot\"\n ? \" in \" + getPeriodText(state.period).toLowerCase()\n : \"\"}\n </p>\n )}\n </>\n);\n" } } } } }
Result:
{ "block_height": "114175488" }
No logs
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 0.1871  to devhub.near
Empty result
No logs