/* INCLUDE: "common.jsx" */ const nearDevGovGigsContractAccountId = props.nearDevGovGigsContractAccountId || (context.widgetSrc ?? "devgovgigs.near").split("/", 1)[0]; const nearDevGovGigsWidgetsAccountId = props.nearDevGovGigsWidgetsAccountId || (context.widgetSrc ?? "devgovgigs.near").split("/", 1)[0]; function widget(widgetName, widgetProps, key) { widgetProps = { ...widgetProps, nearDevGovGigsContractAccountId: props.nearDevGovGigsContractAccountId, nearDevGovGigsWidgetsAccountId: props.nearDevGovGigsWidgetsAccountId, referral: props.referral, }; return ( <Widget src={`${nearDevGovGigsWidgetsAccountId}/widget/gigs-board.${widgetName}`} props={widgetProps} key={key} /> ); } function href(widgetName, linkProps) { linkProps = { ...linkProps }; if (props.nearDevGovGigsContractAccountId) { linkProps.nearDevGovGigsContractAccountId = props.nearDevGovGigsContractAccountId; } if (props.nearDevGovGigsWidgetsAccountId) { linkProps.nearDevGovGigsWidgetsAccountId = props.nearDevGovGigsWidgetsAccountId; } if (props.referral) { linkProps.referral = props.referral; } const linkPropsQuery = Object.entries(linkProps) .map(([key, value]) => `${key}=${value}`) .join("&"); return `#/${nearDevGovGigsWidgetsAccountId}/widget/gigs-board.pages.${widgetName}${ linkPropsQuery ? "?" : "" }${linkPropsQuery}`; } /* END_INCLUDE: "common.jsx" */ const postType = props.postType ?? "Sponsorship"; const parentId = props.parentId ?? null; const postId = props.postId ?? null; const mode = props.mode ?? "Create"; const referralLabels = props.referral ? [`referral:${props.referral}`] : []; const labelStrings = (props.labels ?? []).concat(referralLabels); const labels = labelStrings.map((s) => { return { name: s }; }); initState({ author_id: context.accountId, // Should be a list of objects with field "name". labels, // Should be a list of labels as strings. // Both of the label structures should be modified together. labelStrings, postType, name: props.name ?? "", description: props.description ?? "", amount: props.amount ?? "0", token: props.token ?? "Near", supervisor: props.supervisor ?? "", githubLink: props.githubLink ?? "", }); let fields = { Comment: ["description"], Idea: ["name", "description"], Submission: ["name", "description"], Attestation: ["name", "description"], Sponsorship: [ "name", "description", "amount", "sponsorship_token", "supervisor", ], Github: ["githubLink", "name", "description"], }[postType]; // This must be outside onClick, because Near.view returns null at first, and when the view call finished, it returns true/false. // If checking this inside onClick, it will give `null` and we cannot tell the result is true or false. let grantNotify = Near.view("social.near", "is_write_permission_granted", { predecessor_id: nearDevGovGigsContractAccountId, key: context.accountId + "/index/notify", }); if (grantNotify === null) { return; } const onClick = () => { let labels = state.labelStrings; var body = { Comment: { description: state.description, comment_version: "V2" }, Idea: { name: state.name, description: state.description, idea_version: "V1", }, Submission: { name: state.name, description: state.description, submission_version: "V1", }, Attestation: { name: state.name, description: state.description, attestation_version: "V1", }, Sponsorship: { name: state.name, description: state.description, amount: state.amount, sponsorship_token: state.token, supervisor: state.supervisor, sponsorship_version: "V1", }, Github: { name: state.name, description: state.description, github_version: "V0", github_link: state.githubLink, }, }[postType]; body["post_type"] = postType; if (!context.accountId) { return; } let txn = []; if (mode == "Create") { txn.push({ contractName: nearDevGovGigsContractAccountId, methodName: "add_post", args: { parent_id: parentId, labels, body, }, deposit: Big(10).pow(21).mul(2), gas: Big(10).pow(12).mul(100), }); } else if (mode == "Edit") { txn.push({ contractName: nearDevGovGigsContractAccountId, methodName: "edit_post", args: { id: postId, labels, body, }, deposit: Big(10).pow(21).mul(2), gas: Big(10).pow(12).mul(100), }); } if (mode == "Create" || mode == "Edit") { if (grantNotify === false) { txn.unshift({ contractName: "social.near", methodName: "grant_write_permission", args: { predecessor_id: nearDevGovGigsContractAccountId, keys: [context.accountId + "/index/notify"], }, deposit: Big(10).pow(23), gas: Big(10).pow(12).mul(30), }); } Near.call(txn); } }; const normalizeLabel = (label) => label .replaceAll(/[- \.]/g, "_") .replaceAll(/[^\w]+/g, "") .replaceAll(/_+/g, "-") .replace(/^-+/, "") .replace(/-+$/, "") .toLowerCase() .trim("-"); const setLabels = (labels) => { labels = labels.map((o) => { o.name = normalizeLabel(o.name); return o; }); let labelStrings = labels.map((o) => { return o.name; }); State.update({ labels, labelStrings }); }; const existingLabelStrings = Near.view(nearDevGovGigsContractAccountId, "get_all_labels") ?? []; const existingLabels = existingLabelStrings.map((s) => { return { name: s }; }); const labelEditor = ( <div className="col-lg-12 mb-2"> Labels: <Typeahead multiple labelKey="name" onChange={setLabels} options={existingLabels} placeholder="near.social, widget, NEP, standard, protocol, tool" selected={state.labels} positionFixed allowNew /> </div> ); const githubLinkDiv = fields.includes("githubLink") ? ( <div className="col-lg-12 mb-2"> Github Issue URL: <input type="text" value={state.githubLink} onChange={(event) => State.update({ githubLink: event.target.value })} /> </div> ) : null; const nameDiv = fields.includes("name") ? ( <div className="col-lg-6 mb-2"> Title: <input type="text" value={state.name} onChange={(event) => State.update({ name: event.target.value })} /> </div> ) : null; const descriptionDiv = fields.includes("description") ? ( <div className="col-lg-12 mb-2"> Description: <br /> <textarea value={state.description} type="text" rows={6} className="form-control" onChange={(event) => State.update({ description: event.target.value })} /> </div> ) : null; const amountDiv = fields.includes("amount") ? ( <div className="col-lg-6 mb-2"> Amount: <input type="text" value={state.amount} onChange={(event) => State.update({ amount: event.target.value })} /> </div> ) : null; const tokenDiv = fields.includes("sponsorship_token") ? ( <div className="col-lg-6 mb-2"> Tokens: <input type="text" value={state.token} onChange={(event) => State.update({ token: event.target.value })} /> </div> ) : null; const supervisorDiv = fields.includes("supervisor") ? ( <div className="col-lg-6 mb-2"> Supervisor: <input type="text" value={state.supervisor} onChange={(event) => State.update({ supervisor: event.target.value })} /> </div> ) : null; const disclaimer = ( <p> <i> * Note, all projects that were granted sponsorships are required to pass KYC to receive the funding. </i> </p> ); const renamedPostType = postType == "Submission" ? "Solution" : postType; // Below there is a weird code with fields.includes("githubLink") ternary operator. // This is to hack around rendering bug of near.social. return ( <div className="card"> <div className="card-header"> {mode} {renamedPostType} </div> <div class="card-body"> {fields.includes("githubLink") ? ( <div className="row"> {githubLinkDiv} {labelEditor} {nameDiv} {descriptionDiv} </div> ) : ( <div className="row"> {labelEditor} {nameDiv} {amountDiv} {tokenDiv} {supervisorDiv} {descriptionDiv} </div> )} <a className="btn btn-outline-primary mb-2" onClick={onClick}> Submit </a> {disclaimer} </div> <div class="card-footer"> Preview: {widget("components.posts.Post", { isPreview: true, id: 0, // irrelevant post: { author_id: state.author_id, likes: [], snapshot: { editor_id: state.editor_id, labels: state.labelStrings, post_type: postType, name: state.name, description: state.description, amount: state.amount, sponsorship_token: state.token, supervisor: state.supervisor, github_link: state.githubLink, }, }, })} </div> </div> );