Search
Search

Transaction: GbyQrCn...FaNa

Signed by
Receiver
Status
Succeeded
Transaction Fee
0.00188 
Deposit Value
0 
Gas Used
19 Tgas
Attached Gas
300 Tgas
Created
February 15, 2024 at 3:09:01pm
Hash
GbyQrCnzyj6Cx4CTveWVa9W1mp7gCpnNqNciBe1oFaNa

Actions

Called method: 'set' in contract: social.near
Arguments:
{ "data": { "potlock.near": { "widget": { "Project.ModalDonation": { "": "const {\n ownerId,\n registeredProjects,\n recipientId,\n referrerId,\n potId,\n potDetail,\n onClose,\n DONATION_CONTRACT_ID,\n} = props;\nconst projects = registeredProjects || [];\n\nconst projectIds = useMemo(\n // TODO: get projects for pot if potId\n () => projects.filter((project) => project.status === \"Approved\").map((project) => project.id),\n [projects]\n);\n\nconst IPFS_BASE_URL = \"https://nftstorage.link/ipfs/\";\nconst CLOSE_ICON_URL =\n IPFS_BASE_URL + \"bafkreifyg2vvmdjpbhkylnhye5es3vgpsivhigkjvtv2o4pzsae2z4vi5i\";\nconst EDIT_ICON_URL = IPFS_BASE_URL + \"bafkreigc2laqrwu6g4ihm5n2qfxwl3g5phujtrwybone2ouxaz5ittjzee\";\n\nconst MAX_NAME_LENGTH = 60;\nconst MAX_DESCRIPTION_LENGTH = 77;\n\nconst profile = Social.getr(`${recipientId}/profile`);\n\nconst Row = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n padding: 20px;\n gap: 24px;\n width: 100%;\n`;\n\nconst Column = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n`;\n\nconst ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n background: #f6f5f3;\n padding: 10px 20px;\n border-top-left-radius: 6px;\n border-top-right-radius: 6px;\n`;\n\nconst ModalHeaderText = styled.div`\n font-size: 16px;\n font-weight: 600;\n color: #292929;\n line-height: 24px;\n word-wrap: break-word;\n margin-left: 8px;\n`;\n\nconst PointerIcon = styled.img`\n width: 24px;\n height: 24px;\n\n &:hover {\n cursor: pointer;\n }\n`;\n\nconst Icon = styled.img`\n width: 20px;\n height: 20px;\n`;\n\nconst HintText = styled.div`\n font-size: 11px;\n color: #7b7b7b;\n font-weight: 400;\n line-height: 16px;\n word-wrap: break-word;\n`;\n\nconst ModalBody = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 16px 20px 32px 20px;\n gap: 24px;\n`;\n\nconst ModalFooter = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding: 12px 24px 24px 24px;\n border-bottom-left-radius: 6px;\n border-bottom-right-radius: 6px;\n gap: 24px;\n width: 100%;\n`;\n\nconst Name = styled.div`\n font-size: 14px;\n color: #292929;\n font-weight: 600;\n line-height: 24px;\n word-break: break-word;\n`;\n\nconst Description = styled.div`\n font-size: 14px;\n color: #7b7b7b;\n font-weight: 400;\n line-height: 24px;\n word-break: break-word;\n`;\n\nconst AddNote = styled.div`\n font-size: 14px;\n color: #292929;\n font-weight: 500;\n line-height: 20px;\n word-wrap: break-word;\n`;\n\nconst Label = styled.label`\n font-size: 12px;\n line-height: 16px;\n word-wrap: break-word;\n color: #2e2e2e;\n`;\n\nconst DENOMINATION_OPTIONS = [\n { text: \"NEAR\", value: \"NEAR\" },\n { text: \"USD\", value: \"USD\" },\n];\n\nconst DEFAULT_DONATION_AMOUNT = \"1\";\n\nconst MAX_NOTE_LENGTH = 60;\n\nState.init({\n amount: DEFAULT_DONATION_AMOUNT,\n denomination: DENOMINATION_OPTIONS[0].value,\n showBreakdown: false,\n bypassProtocolFee: false,\n addNote: false,\n donationNote: \"\",\n donationNoteError: \"\",\n});\n\nconst resetState = () => {\n State.update({\n amount: DEFAULT_DONATION_AMOUNT,\n denomination: DENOMINATION_OPTIONS[0].value,\n showBreakdown: false,\n bypassProtocolFee: false,\n addNote: false,\n donationNote: \"\",\n donationNoteError: \"\",\n });\n};\n\nconst profileName = profile?.name || \"No name\";\n\nconst handleAddToCart = () => {\n props.addProjectsToCart([\n {\n id: recipientId,\n amount: state.amount,\n ft: \"NEAR\",\n referrerId: referrerId,\n potId: potId,\n potDetail: potDetail,\n },\n ]);\n};\n\nconst handleDonate = () => {\n const amountIndivisible = props.SUPPORTED_FTS.NEAR.toIndivisible(parseFloat(state.amount));\n // TODO: get projectId for random donation\n let projectId = recipientId;\n if (!projectId) {\n // get random project\n const randomIndex = Math.floor(Math.random() * projects.length);\n console.log(\"randomIndex: \", randomIndex);\n console.log(\"projects.length: \", projects.length);\n projectId = projects[randomIndex].id;\n }\n console.log(\"projectId: \", projectId);\n const args = {\n referrer_id: referrerId,\n bypass_protocol_fee: state.bypassProtocolFee,\n message: state.donationNote,\n };\n if (potId) {\n args.project_id = projectId;\n } else {\n args.recipient_id = projectId;\n }\n\n const transactions = [\n {\n contractName: potId ?? DONATION_CONTRACT_ID,\n methodName: \"donate\",\n args,\n deposit: amountIndivisible.toString(),\n gas: \"300000000000000\",\n },\n ];\n console.log(\"transactions: \", transactions);\n\n const now = Date.now();\n Near.call(transactions);\n // NB: we won't get here if user used a web wallet, as it will redirect to the wallet\n // <-------- EXTENSION WALLET HANDLING -------->\n // poll for updates\n // TODO: update this to also poll Pot contract\n const pollIntervalMs = 1000;\n // const totalPollTimeMs = 60000; // consider adding in to make sure interval doesn't run indefinitely\n const pollId = setInterval(() => {\n Near.asyncView(DONATION_CONTRACT_ID, \"get_donations_for_donor\", {\n donor_id: context.accountId,\n // TODO: implement pagination (should be OK without until there are 500+ donations from this user)\n }).then((donations) => {\n for (const donation of donations) {\n const { recipient_id, donated_at_ms } = donation;\n if (recipient_id === projectId && donated_at_ms > now) {\n // display success message & clear cart\n clearInterval(pollId);\n props.openDonationSuccessModal(donation);\n }\n }\n });\n }, pollIntervalMs);\n};\n\nreturn (\n <Widget\n src={`${ownerId}/widget/Components.Modal`}\n props={{\n ...props,\n contentStyle: {\n padding: \"0px\",\n },\n children: (\n <>\n <ModalHeader>\n <div></div>\n <ModalHeaderText>Donate {recipientId ? \"to project\" : \"Randomly\"}</ModalHeaderText>\n <PointerIcon src={CLOSE_ICON_URL} onClick={onClose} />\n </ModalHeader>\n <ModalBody>\n {recipientId ? (\n profile === null ? (\n <Widget src={`${ownerId}/widget/Components.Loading`} />\n ) : (\n <Row>\n <Column>\n <Widget\n src={`${ownerId}/widget/Project.ProfileImage`}\n props={{\n ...props,\n accountId: recipientId,\n profile,\n style: {\n height: \"24px\",\n width: \"24px\",\n },\n }}\n />\n </Column>\n <Column>\n <Name>\n {profileName.length > MAX_NAME_LENGTH\n ? profileName.slice(0, MAX_NAME_LENGTH) + \"...\"\n : profileName}\n </Name>\n <Description>\n {profile?.description?.length > MAX_DESCRIPTION_LENGTH\n ? profile?.description?.slice(0, MAX_DESCRIPTION_LENGTH) + \"...\"\n : profile?.description}\n </Description>\n </Column>\n </Row>\n )\n ) : (\n <Description>\n Randomly donate to an approved project on our public good registry and discover who\n you supported afterwards!\n </Description>\n )}\n <Column style={{ width: \"100%\" }}>\n <Widget\n src={`${ownerId}/widget/Inputs.Text`}\n props={{\n label: \"Amount\",\n placeholder: \"0\",\n value: state.amount,\n onChange: (amount) => {\n amount = amount.replace(/[^\\d.]/g, \"\"); // remove all non-numeric characters except for decimal\n if (amount === \".\") amount = \"0.\";\n State.update({ amount });\n },\n inputStyles: {\n textAlign: \"right\",\n borderRadius: \"0px 4px 4px 0px\",\n },\n preInputChildren: (\n <Widget\n src={`${ownerId}/widget/Inputs.Select`}\n props={{\n noLabel: true,\n placeholder: \"\",\n options: DENOMINATION_OPTIONS,\n value: { text: state.denomination, value: state.denomination },\n onChange: ({ text, value }) => {\n State.update({ denomination: value });\n },\n containerStyles: {\n width: \"auto\",\n },\n inputStyles: {\n border: \"none\",\n borderRight: \"1px #F0F0F0 solid\",\n boxShadow: \"none\",\n borderRadius: \"4px 0px 0px 4px\",\n width: \"auto\",\n padding: \"12px 16px\",\n boxShadow: \"0px -2px 0px rgba(93, 93, 93, 0.24) inset\",\n },\n iconLeft:\n state.denomination == \"NEAR\" ? (\n <Icon src={props.SUPPORTED_FTS.NEAR.iconUrl} />\n ) : (\n \"$\"\n ),\n }}\n />\n ),\n }}\n />\n <Row style={{ justifyContent: \"space-between\", width: \"100%\", padding: \"0px\" }}>\n <HintText>1 NEAR = ~${props.nearToUsd * 1} USD</HintText>\n <div style={{ display: \"flex\" }}>\n <HintText style={{ marginRight: \"6px\" }}>Account balance: </HintText>\n <Icon\n style={{ width: \"14px\", height: \"14px\", marginRight: \"2px\" }}\n src={props.SUPPORTED_FTS.NEAR.iconUrl}\n />\n <HintText>-- Max</HintText>\n </div>\n </Row>\n </Column>\n <Row style={{ padding: \"0px\", gap: \"0px\" }}>\n <Widget\n src={`${ownerId}/widget/Inputs.Checkbox`}\n props={{\n id: \"bypassFeeSelector\",\n checked: state.bypassProtocolFee,\n onClick: (e) => {\n State.update({ bypassProtocolFee: e.target.checked });\n },\n }}\n />\n <Label htmlFor=\"bypassFeeSelector\">Bypass protocol fee</Label>\n </Row>\n <Widget\n src={`${ownerId}/widget/Cart.BreakdownSummary`}\n props={{\n ...props,\n referrerId,\n amountNear: state.amount,\n bypassProtocolFee: state.bypassProtocolFee,\n }}\n />\n {state.addNote ? (\n <Widget\n src={`${ownerId}/widget/Inputs.TextArea`}\n props={{\n label: \"Note\",\n inputRows: 2,\n inputStyle: {\n background: \"#FAFAFA\",\n },\n placeholder: `Add an optional note for the project (max ${MAX_NOTE_LENGTH} characters)`,\n value: state.donationNote,\n onChange: (donationNote) => State.update({ donationNote }),\n validate: () => {\n if (state.donationNote.length > MAX_NOTE_LENGTH) {\n State.update({\n donationNoteError: `Note must be less than ${MAX_NOTE_LENGTH} characters`,\n });\n return;\n }\n State.update({ donationNoteError: \"\" });\n },\n error: state.donationNoteError,\n }}\n />\n ) : (\n <Row style={{ padding: \"0px\", gap: \"0px\", cursor: \"pointer\" }}>\n <Icon\n src={EDIT_ICON_URL}\n style={{ width: \"18px\", height: \"18px\", marginRight: \"8px\" }}\n />\n <AddNote onClick={() => State.update({ addNote: true })}>Add Note</AddNote>\n </Row>\n )}\n </ModalBody>\n <ModalFooter>\n {recipientId && (\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"tertiary\",\n text: \"Add to cart\",\n onClick: handleAddToCart,\n }}\n />\n )}\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"primary\",\n text: \"Donate\",\n // disabled: !state.reviewMessage || !!state.reviewMessageError,\n onClick: handleDonate,\n }}\n />\n </ModalFooter>\n </>\n ),\n }}\n />\n);\n" }, "Project.ListPage": { "": "const { ownerId, userIsRegistryAdmin } = props;\n\nconst IPFS_BASE_URL = \"https://nftstorage.link/ipfs/\";\nconst HERO_BACKGROUND_IMAGE_URL =\n IPFS_BASE_URL + \"bafkreiewg5afxbkvo6jbn6jgv7zm4mtoys22jut65fldqtt7wagar4wbga\";\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n`;\n\nconst HeroOuter = styled.div`\n padding: 136px 64px;\n`;\n\nconst HeroInner = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n`;\n\nconst SectionHeader = styled.div`\n display: flex;\n flex-direction: row;\n width: 100%;\n align-items: center;\n margin-bottom: 24px;\n padding: 24px 64px 24px 64px;\n\n @media screen and (max-width: 768px) {\n padding: 16px 24px;\n }\n`;\n\nconst SectionTitle = styled.div`\n font-size: 24px;\n font-weight: 600;\n color: #2e2e2e;\n font-family: Mona-Sans;\n`;\n\nconst ProjectsCount = styled.div`\n color: #7b7b7b;\n font-size: 24px;\n font-weight: 400;\n margin-left: 32px;\n\n @media screen and (max-width: 768px) {\n margin-left: 16px;\n }\n`;\n\nconst ProjectsContainer = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n // padding: 0px 64px 96px 64px;\n // background: #fafafa;\n\n @media screen and (max-width: 768px) {\n margin-top: 200px;\n }\n`;\n\nconst HeroContainer = styled.div`\n width: 100%;\n min-height: 700px;\n position: relative;\n`;\n\nconst Hero = styled.img`\n width: 100%;\n height: 100%;\n display: block;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst InfoCardsContainer = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n margin: 40px 0;\n gap: 40px;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n gap: 24px;\n // justify-content: center;\n }\n`;\n\nconst projects = useMemo(\n () =>\n userIsRegistryAdmin\n ? props.registeredProjects\n : props.registeredProjects.filter((project) => project.status === \"Approved\"),\n [props.registeredProjects, userIsRegistryAdmin]\n);\n\nconst [totalDonations, totalDonors] = useMemo(() => {\n if (!props.donations) {\n return [\"\", \"\", \"\"];\n }\n let totalDonations = new Big(\"0\");\n let donors = {};\n props.donations.forEach((donation) => {\n const totalAmount = new Big(donation.total_amount);\n const referralAmount = new Big(donation.referrer_fee || \"0\");\n const protocolAmount = new Big(donation.protocol_fee || \"0\");\n totalDonations = totalDonations.plus(totalAmount.minus(referralAmount).minus(protocolAmount));\n donors[donation.donor_id] = true;\n });\n return [totalDonations.div(1e24).toNumber().toFixed(2), Object.keys(donors).length];\n}, [props.donations]);\n\nconst handleDonateRandomly = (e) => {\n e.preventDefault();\n props.openDonateToProjectModal();\n};\n\nreturn (\n <>\n <HeroContainer>\n <Hero src={HERO_BACKGROUND_IMAGE_URL} alt=\"hero\" />\n <Widget\n src={`${ownerId}/widget/Components.Header`}\n props={{\n title1: \"Transforming\",\n title2: \"Funding for Public Goods\",\n description:\n \"Discover impact projects, donate directly, or get automatic referral fees for raising donations\",\n centered: true,\n containerStyle: {\n position: \"absolute\",\n height: \"100%\",\n top: 0,\n left: 0,\n marginBottom: \"24px\",\n background:\n \"radial-gradient(80% 80% at 40.82% 50%, white 25%, rgba(255, 255, 255, 0) 100%)\",\n },\n buttonPrimary: (\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"primary\",\n text: \"Donate Randomly\",\n disabled: false,\n style: { padding: \"16px 24px\" },\n onClick: handleDonateRandomly,\n }}\n />\n ),\n buttonSecondary: (\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"secondary\",\n text: \"Register Your Project\",\n disabled: false,\n href: props.hrefWithEnv(`?tab=createproject`),\n style: { padding: \"16px 24px\" },\n }}\n />\n ),\n // TODO: refactor this\n children: totalDonations && (\n <InfoCardsContainer>\n <Widget\n src={`${ownerId}/widget/Components.InfoCard`}\n props={{\n infoTextPrimary: props.nearToUsd\n ? `$${(totalDonations * props.nearToUsd).toFixed(2)}`\n : `${totalDonations} N`,\n infoTextSecondary: \"Total Contributed\",\n }}\n />\n <Widget\n src={`${ownerId}/widget/Components.InfoCard`}\n props={{\n infoTextPrimary: totalDonors,\n infoTextSecondary: \"Unique Donors\",\n }}\n />\n <Widget\n src={`${ownerId}/widget/Components.InfoCard`}\n props={{\n infoTextPrimary: props.donations ? props.donations.length : \"-\",\n infoTextSecondary: \"Donations\",\n }}\n />\n </InfoCardsContainer>\n ),\n }}\n />\n </HeroContainer>\n <ProjectsContainer>\n <Widget\n src={`${ownerId}/widget/Components.ListSection`}\n props={{\n ...props,\n items: projects,\n }}\n />\n </ProjectsContainer>\n </>\n);\n" }, "Project.ModalDonationSuccess": { "": "const {\n ownerId,\n onClose,\n IPFS_BASE_URL,\n SUPPORTED_FTS: { NEAR },\n} = props;\n\nconst loraCss = fetch(\"https://fonts.googleapis.com/css2?family=Lora&display=swap\").body;\n\nconst HEADER_ICON_URL =\n IPFS_BASE_URL + \"bafkreiholfe7utobo5y2znjdr6ou26qmlcgf5teoxtyjo2undgfpl5kcwe\";\nconst TWITTER_ICON_URL =\n IPFS_BASE_URL + \"bafkreibkeyodxxrf76cr5q3in4tsmhuhzmkl5cdr56rfl57x4aji47gsby\";\n\nconst DEFAULT_GATEWAY = \"https://bos.potlock.org/\";\nconst POTLOCK_TWITTER_ACCOUNT_ID = \"PotLock_\";\n\nconst DEFAULT_SHARE_HASHTAGS = [\"BOS\", \"PublicGoods\", \"Donations\"];\n\nconst Column = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n`;\n\nconst Row = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n`;\n\nconst ModalMain = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n gap: 24px;\n padding: 80px 36px;\n`;\n\nconst ModalFooter = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n background: #f6f5f3;\n gap: 12px;\n padding: 28px 36px;\n`;\n\nconst HeaderIcon = styled.img`\n width: 64px;\n height: 64px;\n`;\n\nconst AmountNear = styled.div`\n color: #292929;\n font-size: 32px;\n font-weight: 600;\n line-height: 40px;\n font-family: \"Lora\";\n ${loraCss}\n`;\n\nconst AmountUsd = styled.div`\n color: #7b7b7b;\n font-size: 14px;\n font-weight: 400;\n line-height: 24px;\n word-wrap: break-word;\n`;\n\nconst NearIcon = styled.img`\n width: 28px;\n height: 28px;\n`;\n\nconst TextBold = styled.div`\n color: #292929;\n font-size: 14px;\n font-weight: 600;\n line-height: 24px;\n word-wrap: break-word;\n`;\n\nconst UserChip = styled.div`\n display: flex;\n flex-direction: row;\n padding: 2px 12px;\n gap: 4px;\n border-radius: 32px;\n background: #ebebeb;\n`;\n\nconst UserChipLink = styled.a`\n display: flex;\n flex-direction: row;\n padding: 2px 12px;\n gap: 4px;\n border-radius: 32px;\n background: #ebebeb;\n\n &:hover {\n text-decoration: none;\n }\n`;\n\nconst ShareText = styled.div`\n color: #7b7b7b;\n font-size: 14px;\n font-weight: 600;\n line-height: 24px;\n word-wrap: break-word;\n`;\n\nconst SocialIcon = styled.img`\n width: 24px;\n height: 24px;\n cursor: pointer;\n`;\n\nState.init({\n showBreakdown: false,\n successfulDonation: null,\n});\n\nif (props.isModalOpen && !state.successfulDonation) {\n let successfulDonation = props.successfulDonation;\n if (!successfulDonation && props.transactionHashes) {\n const body = JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n });\n const res = fetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body,\n });\n // console.log(\"tx res: \", res);\n if (res.ok) {\n const successVal = res.body.result.status?.SuccessValue;\n let decoded = Buffer.from(successVal, \"base64\").toString(\"utf-8\"); // atob not working\n successfulDonation = JSON.parse(decoded);\n }\n }\n const { donor_id, recipient_id } = successfulDonation;\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${recipient_id}/profile/**`],\n }).then((recipientData) => {\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${donor_id}/profile/**`],\n }).then((donorData) => {\n State.update({\n successfulDonation,\n recipientProfile: recipientData[recipient_id]?.profile || {},\n donorProfile: donorData[donor_id]?.profile || {},\n });\n });\n });\n}\n\nconst twitterIntent = useMemo(() => {\n if (!state.recipientProfile) return;\n const twitterIntentBase = \"https://twitter.com/intent/tweet?text=\";\n let url =\n DEFAULT_GATEWAY +\n `${ownerId}/widget/Index?tab=project&projectId=${state.successfulDonation.recipient_id}&referrerId=${context.accountId}`;\n let text = `I just donated to ${\n state.recipientProfile\n ? state.recipientProfile.linktree?.twitter\n ? `@${state.recipientProfile.linktree.twitter}`\n : state.recipientProfile.name\n : state.successfulDonation.recipient_id\n } on @${POTLOCK_TWITTER_ACCOUNT_ID}! Support public goods at `;\n text = encodeURIComponent(text);\n url = encodeURIComponent(url);\n return twitterIntentBase + text + `&url=${url}` + `&hashtags=${DEFAULT_SHARE_HASHTAGS.join(\",\")}`;\n}, [state.successfulDonation, state.recipientProfile]);\n\nreturn (\n <Widget\n src={`${ownerId}/widget/Components.Modal`}\n props={{\n ...props,\n contentStyle: {\n padding: \"0px\",\n },\n children: (\n <>\n <ModalMain>\n <HeaderIcon src={HEADER_ICON_URL} />\n <Column>\n <Row style={{ gap: \"9px\" }}>\n <AmountNear>\n {state.successfulDonation?.total_amount\n ? parseFloat(\n NEAR.fromIndivisible(state.successfulDonation.total_amount).toString()\n )\n : \"-\"}\n </AmountNear>\n <NearIcon src={NEAR.iconUrl} />\n </Row>\n <AmountUsd>\n {(state.successfulDonation?.total_amount\n ? props.yoctosToUsd(state.successfulDonation.total_amount)\n : \"$-\") + \" USD\"}\n </AmountUsd>\n </Column>\n <Row style={{ gap: \"8px\" }}>\n <TextBold>Has been donated to</TextBold>\n <UserChipLink\n href={props.hrefWithEnv(\n `?tab=project&projectId=${state.successfulDonation.recipient_id}`\n )}\n target=\"_blank\"\n >\n {state.successfulDonation && (\n <Widget\n src={`${ownerId}/widget/Project.ProfileImage`}\n props={{\n ...props,\n accountId: state.successfulDonation.recipient_id,\n style: {\n height: \"17px\",\n width: \"17px\",\n },\n }}\n />\n )}\n <TextBold>{state.recipientProfile?.name || \"-\"}</TextBold>\n </UserChipLink>\n </Row>\n <Widget\n src={`${ownerId}/widget/Cart.BreakdownSummary`}\n props={{\n ...props,\n referrerId: state.successfulDonation?.referrer_id,\n amountNear: NEAR.fromIndivisible(\n state.successfulDonation?.total_amount || \"0\"\n ).toString(),\n bypassProtocolFee:\n !state.successfulDonation?.protocol_fee ||\n state.successfulDonation?.protocol_fee === \"0\", // TODO: allow user to choose\n headerStyle: { justifyContent: \"center\" },\n }}\n />\n <Row style={{ width: \"100%\", justifyContent: \"center\", gap: \"24px\" }}>\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"secondary\",\n text: \"Do it again!\",\n onClick: () => {\n onClose();\n props.openDonateToProjectModal();\n },\n style: { width: \"100%\" },\n }}\n />\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"secondary\",\n text: \"Explore projects\",\n onClick: () => {\n onClose();\n },\n style: { width: \"100%\" },\n }}\n />\n </Row>\n </ModalMain>\n <ModalFooter>\n <Row style={{ gap: \"6px\", justifyContent: \"center\" }}>\n <TextBold>From</TextBold>\n <UserChip>\n {state.donorProfile && (\n <Widget\n src={`${ownerId}/widget/Project.ProfileImage`}\n props={{\n ...props,\n accountId: state.successfulDonation?.donor_id,\n style: {\n height: \"17px\",\n width: \"17px\",\n },\n }}\n />\n )}\n <TextBold>{state.donorProfile?.name || \"-\"}</TextBold>\n </UserChip>\n </Row>\n <Row style={{ gap: \"8px\", justifyContent: \"center\" }}>\n <ShareText>Share to</ShareText>\n <a href={twitterIntent} target=\"_blank\">\n <SocialIcon src={TWITTER_ICON_URL} />\n </a>\n </Row>\n </ModalFooter>\n </>\n ),\n }}\n />\n);\n" }, "Index": { "": "const ownerId = \"potlock.near\";\nconst registryContractId =\n props.env === \"staging\" ? \"registry.staging.potlock.near\" : \"registry.potlock.near\";\nconst donationContractId = \"donate.potlock.near\";\n\nconst CREATE_PROJECT_TAB = \"createproject\";\nconst EDIT_PROJECT_TAB = \"editproject\";\nconst PROJECTS_LIST_TAB = \"projects\";\nconst PROJECT_DETAIL_TAB = \"project\";\nconst CART_TAB = \"cart\";\nconst FEED_TAB = \"feed\";\nconst POTS_TAB = \"pots\";\nconst DEPLOY_POT_TAB = \"deploypot\";\nconst POT_DETAIL_TAB = \"pot\";\nconst DONORS_TAB = \"donors\";\n\nconst Theme = styled.div`\n position: relative;\n * {\n font-family: \"Mona-Sans\";\n font-style: normal;\n font-weight: 400;\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 400;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-Regular.woff) format(\"woff\");\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 500;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-Medium.woff) format(\"woff\");\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 600;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-SemiBold.woff) format(\"woff\");\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 700;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-Bold.woff) format(\"woff\");\n }\n`;\n\nconst NEAR_ACCOUNT_ID_REGEX = /^(?=.{2,64}$)(?!.*\\.\\.)(?!.*-$)(?!.*_$)[a-z\\d._-]+$/i;\n\nState.init({\n cart: null,\n checkoutSuccess: false,\n checkoutSuccessTxHash: null,\n donations: null,\n // previousCart: null,\n nearToUsd: null,\n isCartModalOpen: false,\n isNavMenuOpen: false,\n registryConfig: null,\n userIsRegistryAdmin: null,\n registeredProjects: null,\n donnorProjectId: null,\n amount: null,\n note: null,\n referrerId: null,\n currency: null,\n // isSybilModalOpen: false,\n donateToProjectModal: {\n isOpen: false,\n recipientId: null,\n referrerId: null,\n potId: null,\n potDetail: null,\n },\n donationSuccessModal: {\n isOpen: (!props.tab || props.tab === PROJECTS_LIST_TAB) && props.transactionHashes,\n successfulDonation: null,\n },\n});\n\nconst NEAR_USD_CACHE_KEY = \"NEAR_USD\";\nconst nearUsdCache = Storage.get(NEAR_USD_CACHE_KEY);\nconst EXCHANGE_RATE_VALIDITY_MS = 1000 * 60 * 60; // 1 hour\n\nif (!state.nearToUsd) {\n if (\n nearUsdCache === undefined ||\n (nearUsdCache && nearUsdCache.ts < Date.now() - EXCHANGE_RATE_VALIDITY_MS)\n ) {\n // undefined means it's not in the cache\n // this case handles the first time fetching the rate, and also if the rate is expired\n console.log(\"fetching near to usd rate\");\n asyncFetch(\"https://api.coingecko.com/api/v3/simple/price?ids=near&vs_currencies=usd\").then(\n (res) => {\n if (res.ok) {\n State.update({ nearToUsd: res.body.near.usd });\n Storage.set(NEAR_USD_CACHE_KEY, { rate: res.body.near.usd, ts: Date.now() });\n }\n }\n );\n } else if (nearUsdCache) {\n // valid cache value\n console.log(\"using cached near to usd rate\");\n State.update({ nearToUsd: nearUsdCache.rate });\n }\n}\n\n// console.log(\"state in Index: \", state);\n\nif (!state.registeredProjects) {\n State.update({ registeredProjects: Near.view(registryContractId, \"get_projects\", {}) });\n}\n\nif (!state.registeredProjects) return \"\";\n\nif (!state.donations) {\n State.update({\n donations: Near.view(donationContractId, \"get_donations\", {}), // TODO: ADD PAGINATION\n });\n}\n\nconst IPFS_BASE_URL = \"https://nftstorage.link/ipfs/\";\n\nconst getImageUrlFromSocialImage = (image) => {\n if (image.url) {\n return image.url;\n } else if (image.ipfs_cid) {\n return IPFS_BASE_URL + image.ipfs_cid;\n }\n};\n\nif (!state.registeredProjects) {\n Near.asyncView(registryContractId, \"get_projects\", {})\n .then((projects) => {\n // get social data for each project\n Near.asyncView(\"social.near\", \"get\", {\n keys: projects.map((project) => `${project.id}/profile/**`),\n }).then((socialData) => {\n const formattedProjects = projects.map((project) => {\n const profileData = socialData[project.id]?.profile;\n let profileImageUrl = DEFAULT_PROFILE_IMAGE_URL;\n if (profileData.image) {\n const imageUrl = getImageUrlFromSocialImage(profileData.image);\n if (imageUrl) profileImageUrl = imageUrl;\n }\n // get banner image URL\n let bannerImageUrl = DEFAULT_BANNER_IMAGE_URL;\n if (profileData.backgroundImage) {\n const imageUrl = getImageUrlFromSocialImage(profileData.backgroundImage);\n if (imageUrl) bannerImageUrl = imageUrl;\n }\n const formatted = {\n id: project.id,\n name: profileData.name ?? \"\",\n description: profileData.description ?? \"\",\n bannerImageUrl,\n profileImageUrl,\n status: project.status,\n tags: [profileData.category.text ?? CATEGORY_MAPPINGS[profileData.category] ?? \"\"],\n };\n return formatted;\n });\n State.update({\n registeredProjects: formattedProjects,\n });\n });\n })\n .catch((e) => {\n console.log(\"error getting projects: \", e);\n State.update({ getRegisteredProjectsError: e });\n });\n}\n\nif (state.registryConfig === null) {\n const registryConfig = Near.view(registryContractId, \"get_config\", {});\n if (registryConfig) {\n State.update({\n registryConfig,\n userIsRegistryAdmin: registryConfig.admins.includes(context.accountId),\n });\n }\n}\n\nconst tabContentWidget = {\n [CREATE_PROJECT_TAB]: \"Project.Create\",\n [EDIT_PROJECT_TAB]: \"Project.Create\",\n [PROJECTS_LIST_TAB]: \"Project.ListPage\",\n [PROJECT_DETAIL_TAB]: \"Project.Detail\",\n [CART_TAB]: \"Cart.Checkout\",\n [FEED_TAB]: \"Components.Feed\",\n [POTS_TAB]: \"Pots.Home\",\n [DEPLOY_POT_TAB]: \"Pots.Deploy\",\n [POT_DETAIL_TAB]: \"Pots.Detail\",\n [DONORS_TAB]: \"Components.Donors\",\n};\n\nconst getTabWidget = (tab) => {\n const defaultTabWidget = tabContentWidget[PROJECTS_LIST_TAB];\n if (tab in tabContentWidget) {\n return tabContentWidget[props.tab];\n }\n return defaultTabWidget;\n};\n\nconst CART_KEY = \"cart\";\n\n// const PREVIOUS_CART_KEY = \"previousCart\";\nconst storageCart = Storage.get(CART_KEY);\nconst StorageCurrency = Storage.get(\"currency\");\nconst StorageNote = Storage.get(\"note\");\nconst StorageAmount = Storage.get(\"amount\");\nconst StorageProjectId = Storage.get(\"projectId\");\nconst StorageReferrerId = Storage.get(\"referrerId\");\n// const storagePreviousCart = Storage.get(PREVIOUS_CART_KEY);\nconst DEFAULT_CART = {};\n\nconst props = {\n ...props,\n ...state,\n ownerId: \"potlock.near\",\n referrerId: props.referrerId,\n setCurrency: (cur) => {\n const currency = state.currency ?? cur;\n State.update({ currency: currency });\n Storage.set(\"currency\", currency);\n },\n setNote: (n) => {\n const note = state.note ?? n;\n State.update({ note: note });\n Storage.set(\"note\", note);\n },\n setAmount: (value) => {\n const amount = state.amount ?? value;\n State.update({ amount: amount });\n Storage.set(\"amount\", amount);\n },\n setProjectId: (id) => {\n const donnorProjectId = state.donnorProjectId ?? id;\n State.update({ donnorProjectId: donnorProjectId });\n Storage.set(\"projectId\", donnorProjectId);\n },\n setReferrerId: (ref) => {\n const referrerId = state.referrerId ?? ref;\n State.update({ referrerId: referrerId });\n Storage.set(\"referrerId\", referrerId);\n },\n addProjectsToCart: (projects) => {\n const cart = state.cart ?? {};\n projects.forEach((item) => {\n if (!item.ft) item.ft = \"NEAR\"; // default to NEAR\n cart[item.id] = item; // default to NEAR\n });\n State.update({ cart });\n Storage.set(CART_KEY, JSON.stringify(cart));\n },\n removeProjectsFromCart: (projectIds) => {\n const cart = state.cart ?? {};\n projectIds.forEach((projectId) => {\n delete cart[projectId];\n });\n State.update({ cart });\n Storage.set(CART_KEY, JSON.stringify(cart));\n },\n updateCartItem: ({ projectId, amount, ft, price, referrerId, potId, potDetail, note }) => {\n const cart = state.cart ?? {};\n const updated = {};\n // if (amount === \"\") updated.amount = \"0\";\n if (amount || amount === \"\") updated.amount = amount;\n if (ft) updated.ft = ft;\n if (price) updated.price = price;\n if (referrerId) updated.referrerId = referrerId;\n if (potId) updated.potId = potId;\n if (potDetail) updated.potDetail = potDetail;\n if (note) updated.note = note;\n cart[projectId] = updated;\n State.update({ cart });\n Storage.set(CART_KEY, JSON.stringify(cart));\n },\n clearCart: () => {\n State.update({ cart: {} });\n Storage.set(CART_KEY, JSON.stringify(DEFAULT_CART));\n },\n setCheckoutSuccess: (checkoutSuccess) => {\n State.update({ checkoutSuccess });\n },\n setIsCartModalOpen: (isOpen) => {\n State.update({ isCartModalOpen: isOpen });\n },\n setIsNavMenuOpen: (isOpen) => {\n State.update({ isNavMenuOpen: isOpen });\n },\n validateNearAddress: (address) => {\n let isValid = NEAR_ACCOUNT_ID_REGEX.test(address);\n // Additional \".near\" check for IDs less than 64 characters\n if (address.length < 64 && !address.endsWith(\".near\")) {\n isValid = false;\n }\n return isValid;\n },\n validateEVMAddress: (address) => {\n // Check if the address is defined and the length is correct (42 characters, including '0x')\n if (!address || address.length !== 42) {\n return false;\n }\n // Check if the address starts with '0x' and contains only valid hexadecimal characters after '0x'\n const re = /^0x[a-fA-F0-9]{40}$/;\n return re.test(address);\n },\n validateGithubRepoUrl: (url) => {\n // Regular expression to match the GitHub repository URL pattern\n // This regex checks for optional \"www.\", a required \"github.com/\", and then captures the username and repo name segments\n const githubRepoUrlPattern =\n /^(https?:\\/\\/)?(www\\.)?github\\.com\\/[a-zA-Z0-9-]+\\/[a-zA-Z0-9_.-]+\\/?$/;\n return githubRepoUrlPattern.test(url);\n },\n PROJECT_STATUSES: [\"Pending\", \"Approved\", \"Rejected\", \"Graylisted\", \"Blacklisted\"],\n SUPPORTED_FTS: {\n // TODO: move this to state to handle selected FT once we support multiple FTs\n NEAR: {\n iconUrl: IPFS_BASE_URL + \"bafkreidnqlap4cp5o334lzbhgbabwr6yzkj6albia62l6ipjsasokjm6mi\",\n toIndivisible: (amount) => new Big(amount).mul(new Big(10).pow(24)),\n fromIndivisible: (amount, decimals) =>\n Big(amount)\n .div(Big(10).pow(24))\n .toFixed(decimals || 2),\n },\n USD: {\n iconUrl: \"$\",\n toIndivisible: (amount) => new Big(amount).mul(new Big(10).pow(24)),\n fromIndivisible: (amount, decimals) =>\n Big(amount)\n .div(Big(10).pow(24))\n .toFixed(decimals || 2),\n },\n },\n DONATION_CONTRACT_ID: donationContractId,\n REGISTRY_CONTRACT_ID: registryContractId,\n POT_FACTORY_CONTRACT_ID:\n props.env === \"staging\" ? \"potfactory.staging.potlock.near\" : \"v1.potfactory.potlock.near\",\n NADABOT_CONTRACT_ID: props.env === \"staging\" ? \"v1.staging.nadabot.near\" : \"v1.nadabot.near\",\n ToDo: styled.div`\n position: relative;\n\n &::before {\n content: \"TODO: \";\n position: absolute;\n left: 0;\n top: 0;\n transform: translate(-110%, 0);\n background-color: yellow;\n }\n `,\n ONE_TGAS: Big(1_000_000_000_000),\n MAX_DONATION_MESSAGE_LENGTH: 100,\n hrefWithEnv: (href) => {\n // add env=staging to params\n if (props.env === \"staging\") {\n return `${href}${href.includes(\"?\") ? \"&\" : \"?\"}env=staging`;\n }\n return href;\n },\n nearToUsdWithFallback: (amountNear) => {\n return state.nearToUsd\n ? \"~$\" + (amountNear * state.nearToUsd).toFixed(2)\n : amountNear + \" NEAR\";\n },\n yoctosToUsdWithFallback: (amountYoctos) => {\n return state.nearToUsd\n ? \"~$\" + new Big(amountYoctos).mul(state.nearToUsd).div(1e24).toNumber().toFixed(2)\n : new Big(amountYoctos).div(1e24).toNumber().toFixed(2) + \" NEAR\";\n },\n yoctosToUsd: (amountYoctos) => {\n return state.nearToUsd\n ? \"~$\" + new Big(amountYoctos).mul(state.nearToUsd).div(1e24).toNumber().toFixed(2)\n : null;\n },\n yoctosToNear: (amountYoctos, abbreviate) => {\n return new Big(amountYoctos).div(1e24).toNumber().toFixed(2) + (abbreviate ? \" N\" : \"NEAR\");\n },\n formatDate: (timestamp) => {\n const months = [\n \"Jan\",\n \"Feb\",\n \"Mar\",\n \"Apr\",\n \"May\",\n \"Jun\",\n \"Jul\",\n \"Aug\",\n \"Sep\",\n \"Oct\",\n \"Nov\",\n \"Dec\",\n ];\n const date = new Date(timestamp);\n\n const year = date.getFullYear();\n const month = months[date.getMonth()];\n const day = date.getDate();\n let hour = date.getHours();\n const minute = date.getMinutes();\n const ampm = hour >= 12 ? \"pm\" : \"am\";\n\n // Convert hour to 12-hour format\n hour = hour % 12;\n hour = hour ? hour : 12; // the hour '0' should be '12'\n\n // Minutes should be two digits\n const minuteFormatted = minute < 10 ? \"0\" + minute : minute;\n\n return `${month} ${day}, ${year} ${hour}:${minuteFormatted}${ampm}`;\n },\n daysAgo: (timestamp) => {\n const now = new Date();\n const pastDate = new Date(timestamp);\n const differenceInTime = now - pastDate;\n\n // Convert time difference from milliseconds to days\n const differenceInDays = Math.floor(differenceInTime / (1000 * 3600 * 24));\n return differenceInDays === 0\n ? \"< 1 day ago\"\n : `${differenceInDays} ${differenceInDays === 1 ? \"day\" : \"days\"} ago`;\n },\n daysUntil: (timestamp) => {\n const now = new Date();\n const futureDate = new Date(timestamp);\n const differenceInTime = futureDate - now;\n\n // Convert time difference from milliseconds to days\n const differenceInDays = Math.ceil(differenceInTime / (1000 * 3600 * 24));\n\n return `${differenceInDays} ${differenceInDays === 1 ? \"day\" : \"days\"}`;\n },\n NADA_BOT_URL: \"https://app.nada.bot\",\n // openSybilModal: () => {\n // State.update({ isSybilModalOpen: true });\n // },\n getTagsFromSocialProfileData: (profileData) => {\n // first try to get tags from plCategories, then category (deprecated/old format), then default to empty array\n if (!profileData) return [];\n const DEPRECATED_CATEGORY_MAPPINGS = {\n \"social-impact\": \"Social Impact\",\n \"non-profit\": \"NonProfit\",\n climate: \"Climate\",\n \"public-good\": \"Public Good\",\n \"de-sci\": \"DeSci\",\n \"open-source\": \"Open Source\",\n community: \"Community\",\n education: \"Education\",\n };\n const tags = profileData.plCategories\n ? JSON.parse(profileData.plCategories)\n : profileData.category\n ? [profileData.category.text ?? DEPRECATED_CATEGORY_MAPPINGS[profileData.category] ?? \"\"]\n : [];\n return tags;\n },\n doesUserHaveDaoFunctionCallProposalPermissions: (policy) => {\n // TODO: break this out (NB: duplicated in Project.CreateForm)\n const userRoles = policy.roles.filter((role) => {\n if (role.kind === \"Everyone\") return true;\n return role.kind.Group && role.kind.Group.includes(context.accountId);\n });\n const kind = \"call\";\n const action = \"AddProposal\";\n // Check if the user is allowed to perform the action\n const allowed = userRoles.some(({ permissions }) => {\n return (\n permissions.includes(`${kind}:${action}`) ||\n permissions.includes(`${kind}:*`) ||\n permissions.includes(`*:${action}`) ||\n permissions.includes(\"*:*\")\n );\n });\n return allowed;\n },\n openDonateToProjectModal: (recipientId, referrerId, potId, potDetail) => {\n State.update({\n donateToProjectModal: { isOpen: true, recipientId, referrerId, potId, potDetail },\n });\n },\n openDonationSuccessModal: (successfulDonation) => {\n console.log(\"opening success modal with donation data: \", successfulDonation);\n State.update({\n donationSuccessModal: {\n isOpen: true,\n successfulDonation,\n },\n });\n },\n basisPointsToPercent: (basisPoints) => {\n return basisPoints / 100;\n },\n IPFS_BASE_URL: \"https://nftstorage.link/ipfs/\",\n};\n\nif (props.transactionHashes && props.tab === CART_TAB) {\n // if transaction hashes are in URL but haven't been added to props, override state:\n props.checkoutSuccessTxHash = props.transactionHashes;\n props.checkoutSuccess = true;\n}\n\nif (props.transactionHashes && props.tab === DEPLOY_POT_TAB) {\n // if transaction hashes are in URL but haven't been added to props, override state:\n props.deploymentSuccessTxHash = props.transactionHashes;\n props.deploymentSuccess = true;\n}\n\nif (state.cart === null && storageCart !== null) {\n // cart hasn't been set on state yet, and storageCart has been fetched\n // if storageCart isn't undefined, set it on state\n // otherwise, set default cart on state\n let cart = DEFAULT_CART;\n if (storageCart) {\n cart = JSON.parse(storageCart);\n }\n State.update({ cart });\n}\n\nif (\n state.currency === null &&\n state.donnorProjectId === null &&\n state.amount === null &&\n StorageCurrency !== null &&\n StorageAmount !== null &&\n StorageProjectId !== null\n) {\n State.update({ currency: StorageCurrency });\n State.update({ amount: StorageAmount });\n State.update({ donnorProjectId: StorageProjectId });\n State.update({ note: StorageNote });\n State.update({ referrerId: StorageReferrerId });\n}\n\n// if (state.previousCart === null && storagePreviousCart !== null) {\n// // previousCart hasn't been set on state yet, and storagePreviousCart has been fetched\n// // if storagePreviousCart isn't undefined, set it on state\n// if (storagePreviousCart && Object.keys(JSON.parse(storagePreviousCart)).length > 0) {\n// console.log(\"updating previous cart\");\n// State.update({ previousCart: JSON.parse(storagePreviousCart) });\n// }\n// }\n\n// console.log(\"state in Index: \", state);\n\nif (state.checkoutSuccessTxHash && state.cart && Object.keys(state.cart).length > 0) {\n // if checkout was successful after wallet redirect, clear cart\n // store previous cart in local storage to show success message\n // console.log(\"previous cart: \", state.cart);\n props.clearCart();\n}\n\nif (props.tab === EDIT_PROJECT_TAB) {\n props.edit = true;\n}\n\nconst tabContent = <Widget src={`${ownerId}/widget/${getTabWidget(props.tab)}`} props={props} />;\n\nconst Content = styled.div`\n width: 100%;\n height: 100%;\n background: #ffffff;\n // padding: 3em;\n border-radius: 0rem 0rem 1.5rem 1.5rem;\n border-top: 1px solid var(--ui-elements-light, #eceef0);\n background: var(--base-white, #fff);\n\n &.form {\n border: none;\n background: #fafafa;\n }\n`;\n\nconst isForm = [CREATE_PROJECT_TAB].includes(props.tab);\n\nif (!state.cart || !state.registeredProjects) {\n return \"\";\n}\n\nreturn (\n <Theme>\n <Widget src={`${ownerId}/widget/Components.Nav`} props={props} />\n <Content className={isForm ? \"form\" : \"\"}>{tabContent}</Content>\n <Widget\n src={`${ownerId}/widget/Project.ModalDonation`}\n props={{\n ...props,\n isModalOpen: state.donateToProjectModal.isOpen,\n onClose: () =>\n State.update({\n donateToProjectModal: {\n isOpen: false,\n recipientId: null,\n referrerId: null,\n potId: null,\n potDetail: null,\n },\n }),\n recipientId: state.donateToProjectModal.recipientId,\n referrerId: state.donateToProjectModal.referrerId,\n potId: state.donateToProjectModal.potId,\n }}\n />\n <Widget\n src={`${ownerId}/widget/Project.ModalDonationSuccess`}\n props={{\n ...props,\n successfulDonation: state.donationSuccessModal.successfulDonation,\n isModalOpen: state.donationSuccessModal.isOpen,\n onClose: () =>\n State.update({\n donationSuccessModal: {\n isOpen: false,\n successfulDonation: null,\n },\n }),\n }}\n />\n </Theme>\n);\n" } } } } }

Transaction Execution Plan

Convert Transaction To Receipt
Gas Burned:
2 Tgas
Tokens Burned:
0.00025 
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
16 Tgas
Tokens Burned:
0.00163 
Called method: 'set' in contract: social.near
Arguments:
{ "data": { "potlock.near": { "widget": { "Project.ModalDonation": { "": "const {\n ownerId,\n registeredProjects,\n recipientId,\n referrerId,\n potId,\n potDetail,\n onClose,\n DONATION_CONTRACT_ID,\n} = props;\nconst projects = registeredProjects || [];\n\nconst projectIds = useMemo(\n // TODO: get projects for pot if potId\n () => projects.filter((project) => project.status === \"Approved\").map((project) => project.id),\n [projects]\n);\n\nconst IPFS_BASE_URL = \"https://nftstorage.link/ipfs/\";\nconst CLOSE_ICON_URL =\n IPFS_BASE_URL + \"bafkreifyg2vvmdjpbhkylnhye5es3vgpsivhigkjvtv2o4pzsae2z4vi5i\";\nconst EDIT_ICON_URL = IPFS_BASE_URL + \"bafkreigc2laqrwu6g4ihm5n2qfxwl3g5phujtrwybone2ouxaz5ittjzee\";\n\nconst MAX_NAME_LENGTH = 60;\nconst MAX_DESCRIPTION_LENGTH = 77;\n\nconst profile = Social.getr(`${recipientId}/profile`);\n\nconst Row = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n padding: 20px;\n gap: 24px;\n width: 100%;\n`;\n\nconst Column = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n`;\n\nconst ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n background: #f6f5f3;\n padding: 10px 20px;\n border-top-left-radius: 6px;\n border-top-right-radius: 6px;\n`;\n\nconst ModalHeaderText = styled.div`\n font-size: 16px;\n font-weight: 600;\n color: #292929;\n line-height: 24px;\n word-wrap: break-word;\n margin-left: 8px;\n`;\n\nconst PointerIcon = styled.img`\n width: 24px;\n height: 24px;\n\n &:hover {\n cursor: pointer;\n }\n`;\n\nconst Icon = styled.img`\n width: 20px;\n height: 20px;\n`;\n\nconst HintText = styled.div`\n font-size: 11px;\n color: #7b7b7b;\n font-weight: 400;\n line-height: 16px;\n word-wrap: break-word;\n`;\n\nconst ModalBody = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 16px 20px 32px 20px;\n gap: 24px;\n`;\n\nconst ModalFooter = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-end;\n padding: 12px 24px 24px 24px;\n border-bottom-left-radius: 6px;\n border-bottom-right-radius: 6px;\n gap: 24px;\n width: 100%;\n`;\n\nconst Name = styled.div`\n font-size: 14px;\n color: #292929;\n font-weight: 600;\n line-height: 24px;\n word-break: break-word;\n`;\n\nconst Description = styled.div`\n font-size: 14px;\n color: #7b7b7b;\n font-weight: 400;\n line-height: 24px;\n word-break: break-word;\n`;\n\nconst AddNote = styled.div`\n font-size: 14px;\n color: #292929;\n font-weight: 500;\n line-height: 20px;\n word-wrap: break-word;\n`;\n\nconst Label = styled.label`\n font-size: 12px;\n line-height: 16px;\n word-wrap: break-word;\n color: #2e2e2e;\n`;\n\nconst DENOMINATION_OPTIONS = [\n { text: \"NEAR\", value: \"NEAR\" },\n { text: \"USD\", value: \"USD\" },\n];\n\nconst DEFAULT_DONATION_AMOUNT = \"1\";\n\nconst MAX_NOTE_LENGTH = 60;\n\nState.init({\n amount: DEFAULT_DONATION_AMOUNT,\n denomination: DENOMINATION_OPTIONS[0].value,\n showBreakdown: false,\n bypassProtocolFee: false,\n addNote: false,\n donationNote: \"\",\n donationNoteError: \"\",\n});\n\nconst resetState = () => {\n State.update({\n amount: DEFAULT_DONATION_AMOUNT,\n denomination: DENOMINATION_OPTIONS[0].value,\n showBreakdown: false,\n bypassProtocolFee: false,\n addNote: false,\n donationNote: \"\",\n donationNoteError: \"\",\n });\n};\n\nconst profileName = profile?.name || \"No name\";\n\nconst handleAddToCart = () => {\n props.addProjectsToCart([\n {\n id: recipientId,\n amount: state.amount,\n ft: \"NEAR\",\n referrerId: referrerId,\n potId: potId,\n potDetail: potDetail,\n },\n ]);\n};\n\nconst handleDonate = () => {\n const amountIndivisible = props.SUPPORTED_FTS.NEAR.toIndivisible(parseFloat(state.amount));\n // TODO: get projectId for random donation\n let projectId = recipientId;\n if (!projectId) {\n // get random project\n const randomIndex = Math.floor(Math.random() * projects.length);\n console.log(\"randomIndex: \", randomIndex);\n console.log(\"projects.length: \", projects.length);\n projectId = projects[randomIndex].id;\n }\n console.log(\"projectId: \", projectId);\n const args = {\n referrer_id: referrerId,\n bypass_protocol_fee: state.bypassProtocolFee,\n message: state.donationNote,\n };\n if (potId) {\n args.project_id = projectId;\n } else {\n args.recipient_id = projectId;\n }\n\n const transactions = [\n {\n contractName: potId ?? DONATION_CONTRACT_ID,\n methodName: \"donate\",\n args,\n deposit: amountIndivisible.toString(),\n gas: \"300000000000000\",\n },\n ];\n console.log(\"transactions: \", transactions);\n\n const now = Date.now();\n Near.call(transactions);\n // NB: we won't get here if user used a web wallet, as it will redirect to the wallet\n // <-------- EXTENSION WALLET HANDLING -------->\n // poll for updates\n // TODO: update this to also poll Pot contract\n const pollIntervalMs = 1000;\n // const totalPollTimeMs = 60000; // consider adding in to make sure interval doesn't run indefinitely\n const pollId = setInterval(() => {\n Near.asyncView(DONATION_CONTRACT_ID, \"get_donations_for_donor\", {\n donor_id: context.accountId,\n // TODO: implement pagination (should be OK without until there are 500+ donations from this user)\n }).then((donations) => {\n for (const donation of donations) {\n const { recipient_id, donated_at_ms } = donation;\n if (recipient_id === projectId && donated_at_ms > now) {\n // display success message & clear cart\n clearInterval(pollId);\n props.openDonationSuccessModal(donation);\n }\n }\n });\n }, pollIntervalMs);\n};\n\nreturn (\n <Widget\n src={`${ownerId}/widget/Components.Modal`}\n props={{\n ...props,\n contentStyle: {\n padding: \"0px\",\n },\n children: (\n <>\n <ModalHeader>\n <div></div>\n <ModalHeaderText>Donate {recipientId ? \"to project\" : \"Randomly\"}</ModalHeaderText>\n <PointerIcon src={CLOSE_ICON_URL} onClick={onClose} />\n </ModalHeader>\n <ModalBody>\n {recipientId ? (\n profile === null ? (\n <Widget src={`${ownerId}/widget/Components.Loading`} />\n ) : (\n <Row>\n <Column>\n <Widget\n src={`${ownerId}/widget/Project.ProfileImage`}\n props={{\n ...props,\n accountId: recipientId,\n profile,\n style: {\n height: \"24px\",\n width: \"24px\",\n },\n }}\n />\n </Column>\n <Column>\n <Name>\n {profileName.length > MAX_NAME_LENGTH\n ? profileName.slice(0, MAX_NAME_LENGTH) + \"...\"\n : profileName}\n </Name>\n <Description>\n {profile?.description?.length > MAX_DESCRIPTION_LENGTH\n ? profile?.description?.slice(0, MAX_DESCRIPTION_LENGTH) + \"...\"\n : profile?.description}\n </Description>\n </Column>\n </Row>\n )\n ) : (\n <Description>\n Randomly donate to an approved project on our public good registry and discover who\n you supported afterwards!\n </Description>\n )}\n <Column style={{ width: \"100%\" }}>\n <Widget\n src={`${ownerId}/widget/Inputs.Text`}\n props={{\n label: \"Amount\",\n placeholder: \"0\",\n value: state.amount,\n onChange: (amount) => {\n amount = amount.replace(/[^\\d.]/g, \"\"); // remove all non-numeric characters except for decimal\n if (amount === \".\") amount = \"0.\";\n State.update({ amount });\n },\n inputStyles: {\n textAlign: \"right\",\n borderRadius: \"0px 4px 4px 0px\",\n },\n preInputChildren: (\n <Widget\n src={`${ownerId}/widget/Inputs.Select`}\n props={{\n noLabel: true,\n placeholder: \"\",\n options: DENOMINATION_OPTIONS,\n value: { text: state.denomination, value: state.denomination },\n onChange: ({ text, value }) => {\n State.update({ denomination: value });\n },\n containerStyles: {\n width: \"auto\",\n },\n inputStyles: {\n border: \"none\",\n borderRight: \"1px #F0F0F0 solid\",\n boxShadow: \"none\",\n borderRadius: \"4px 0px 0px 4px\",\n width: \"auto\",\n padding: \"12px 16px\",\n boxShadow: \"0px -2px 0px rgba(93, 93, 93, 0.24) inset\",\n },\n iconLeft:\n state.denomination == \"NEAR\" ? (\n <Icon src={props.SUPPORTED_FTS.NEAR.iconUrl} />\n ) : (\n \"$\"\n ),\n }}\n />\n ),\n }}\n />\n <Row style={{ justifyContent: \"space-between\", width: \"100%\", padding: \"0px\" }}>\n <HintText>1 NEAR = ~${props.nearToUsd * 1} USD</HintText>\n <div style={{ display: \"flex\" }}>\n <HintText style={{ marginRight: \"6px\" }}>Account balance: </HintText>\n <Icon\n style={{ width: \"14px\", height: \"14px\", marginRight: \"2px\" }}\n src={props.SUPPORTED_FTS.NEAR.iconUrl}\n />\n <HintText>-- Max</HintText>\n </div>\n </Row>\n </Column>\n <Row style={{ padding: \"0px\", gap: \"0px\" }}>\n <Widget\n src={`${ownerId}/widget/Inputs.Checkbox`}\n props={{\n id: \"bypassFeeSelector\",\n checked: state.bypassProtocolFee,\n onClick: (e) => {\n State.update({ bypassProtocolFee: e.target.checked });\n },\n }}\n />\n <Label htmlFor=\"bypassFeeSelector\">Bypass protocol fee</Label>\n </Row>\n <Widget\n src={`${ownerId}/widget/Cart.BreakdownSummary`}\n props={{\n ...props,\n referrerId,\n amountNear: state.amount,\n bypassProtocolFee: state.bypassProtocolFee,\n }}\n />\n {state.addNote ? (\n <Widget\n src={`${ownerId}/widget/Inputs.TextArea`}\n props={{\n label: \"Note\",\n inputRows: 2,\n inputStyle: {\n background: \"#FAFAFA\",\n },\n placeholder: `Add an optional note for the project (max ${MAX_NOTE_LENGTH} characters)`,\n value: state.donationNote,\n onChange: (donationNote) => State.update({ donationNote }),\n validate: () => {\n if (state.donationNote.length > MAX_NOTE_LENGTH) {\n State.update({\n donationNoteError: `Note must be less than ${MAX_NOTE_LENGTH} characters`,\n });\n return;\n }\n State.update({ donationNoteError: \"\" });\n },\n error: state.donationNoteError,\n }}\n />\n ) : (\n <Row style={{ padding: \"0px\", gap: \"0px\", cursor: \"pointer\" }}>\n <Icon\n src={EDIT_ICON_URL}\n style={{ width: \"18px\", height: \"18px\", marginRight: \"8px\" }}\n />\n <AddNote onClick={() => State.update({ addNote: true })}>Add Note</AddNote>\n </Row>\n )}\n </ModalBody>\n <ModalFooter>\n {recipientId && (\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"tertiary\",\n text: \"Add to cart\",\n onClick: handleAddToCart,\n }}\n />\n )}\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"primary\",\n text: \"Donate\",\n // disabled: !state.reviewMessage || !!state.reviewMessageError,\n onClick: handleDonate,\n }}\n />\n </ModalFooter>\n </>\n ),\n }}\n />\n);\n" }, "Project.ListPage": { "": "const { ownerId, userIsRegistryAdmin } = props;\n\nconst IPFS_BASE_URL = \"https://nftstorage.link/ipfs/\";\nconst HERO_BACKGROUND_IMAGE_URL =\n IPFS_BASE_URL + \"bafkreiewg5afxbkvo6jbn6jgv7zm4mtoys22jut65fldqtt7wagar4wbga\";\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n`;\n\nconst HeroOuter = styled.div`\n padding: 136px 64px;\n`;\n\nconst HeroInner = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n`;\n\nconst SectionHeader = styled.div`\n display: flex;\n flex-direction: row;\n width: 100%;\n align-items: center;\n margin-bottom: 24px;\n padding: 24px 64px 24px 64px;\n\n @media screen and (max-width: 768px) {\n padding: 16px 24px;\n }\n`;\n\nconst SectionTitle = styled.div`\n font-size: 24px;\n font-weight: 600;\n color: #2e2e2e;\n font-family: Mona-Sans;\n`;\n\nconst ProjectsCount = styled.div`\n color: #7b7b7b;\n font-size: 24px;\n font-weight: 400;\n margin-left: 32px;\n\n @media screen and (max-width: 768px) {\n margin-left: 16px;\n }\n`;\n\nconst ProjectsContainer = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n // padding: 0px 64px 96px 64px;\n // background: #fafafa;\n\n @media screen and (max-width: 768px) {\n margin-top: 200px;\n }\n`;\n\nconst HeroContainer = styled.div`\n width: 100%;\n min-height: 700px;\n position: relative;\n`;\n\nconst Hero = styled.img`\n width: 100%;\n height: 100%;\n display: block;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst InfoCardsContainer = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n margin: 40px 0;\n gap: 40px;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n gap: 24px;\n // justify-content: center;\n }\n`;\n\nconst projects = useMemo(\n () =>\n userIsRegistryAdmin\n ? props.registeredProjects\n : props.registeredProjects.filter((project) => project.status === \"Approved\"),\n [props.registeredProjects, userIsRegistryAdmin]\n);\n\nconst [totalDonations, totalDonors] = useMemo(() => {\n if (!props.donations) {\n return [\"\", \"\", \"\"];\n }\n let totalDonations = new Big(\"0\");\n let donors = {};\n props.donations.forEach((donation) => {\n const totalAmount = new Big(donation.total_amount);\n const referralAmount = new Big(donation.referrer_fee || \"0\");\n const protocolAmount = new Big(donation.protocol_fee || \"0\");\n totalDonations = totalDonations.plus(totalAmount.minus(referralAmount).minus(protocolAmount));\n donors[donation.donor_id] = true;\n });\n return [totalDonations.div(1e24).toNumber().toFixed(2), Object.keys(donors).length];\n}, [props.donations]);\n\nconst handleDonateRandomly = (e) => {\n e.preventDefault();\n props.openDonateToProjectModal();\n};\n\nreturn (\n <>\n <HeroContainer>\n <Hero src={HERO_BACKGROUND_IMAGE_URL} alt=\"hero\" />\n <Widget\n src={`${ownerId}/widget/Components.Header`}\n props={{\n title1: \"Transforming\",\n title2: \"Funding for Public Goods\",\n description:\n \"Discover impact projects, donate directly, or get automatic referral fees for raising donations\",\n centered: true,\n containerStyle: {\n position: \"absolute\",\n height: \"100%\",\n top: 0,\n left: 0,\n marginBottom: \"24px\",\n background:\n \"radial-gradient(80% 80% at 40.82% 50%, white 25%, rgba(255, 255, 255, 0) 100%)\",\n },\n buttonPrimary: (\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"primary\",\n text: \"Donate Randomly\",\n disabled: false,\n style: { padding: \"16px 24px\" },\n onClick: handleDonateRandomly,\n }}\n />\n ),\n buttonSecondary: (\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"secondary\",\n text: \"Register Your Project\",\n disabled: false,\n href: props.hrefWithEnv(`?tab=createproject`),\n style: { padding: \"16px 24px\" },\n }}\n />\n ),\n // TODO: refactor this\n children: totalDonations && (\n <InfoCardsContainer>\n <Widget\n src={`${ownerId}/widget/Components.InfoCard`}\n props={{\n infoTextPrimary: props.nearToUsd\n ? `$${(totalDonations * props.nearToUsd).toFixed(2)}`\n : `${totalDonations} N`,\n infoTextSecondary: \"Total Contributed\",\n }}\n />\n <Widget\n src={`${ownerId}/widget/Components.InfoCard`}\n props={{\n infoTextPrimary: totalDonors,\n infoTextSecondary: \"Unique Donors\",\n }}\n />\n <Widget\n src={`${ownerId}/widget/Components.InfoCard`}\n props={{\n infoTextPrimary: props.donations ? props.donations.length : \"-\",\n infoTextSecondary: \"Donations\",\n }}\n />\n </InfoCardsContainer>\n ),\n }}\n />\n </HeroContainer>\n <ProjectsContainer>\n <Widget\n src={`${ownerId}/widget/Components.ListSection`}\n props={{\n ...props,\n items: projects,\n }}\n />\n </ProjectsContainer>\n </>\n);\n" }, "Project.ModalDonationSuccess": { "": "const {\n ownerId,\n onClose,\n IPFS_BASE_URL,\n SUPPORTED_FTS: { NEAR },\n} = props;\n\nconst loraCss = fetch(\"https://fonts.googleapis.com/css2?family=Lora&display=swap\").body;\n\nconst HEADER_ICON_URL =\n IPFS_BASE_URL + \"bafkreiholfe7utobo5y2znjdr6ou26qmlcgf5teoxtyjo2undgfpl5kcwe\";\nconst TWITTER_ICON_URL =\n IPFS_BASE_URL + \"bafkreibkeyodxxrf76cr5q3in4tsmhuhzmkl5cdr56rfl57x4aji47gsby\";\n\nconst DEFAULT_GATEWAY = \"https://bos.potlock.org/\";\nconst POTLOCK_TWITTER_ACCOUNT_ID = \"PotLock_\";\n\nconst DEFAULT_SHARE_HASHTAGS = [\"BOS\", \"PublicGoods\", \"Donations\"];\n\nconst Column = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n`;\n\nconst Row = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n`;\n\nconst ModalMain = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n gap: 24px;\n padding: 80px 36px;\n`;\n\nconst ModalFooter = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n background: #f6f5f3;\n gap: 12px;\n padding: 28px 36px;\n`;\n\nconst HeaderIcon = styled.img`\n width: 64px;\n height: 64px;\n`;\n\nconst AmountNear = styled.div`\n color: #292929;\n font-size: 32px;\n font-weight: 600;\n line-height: 40px;\n font-family: \"Lora\";\n ${loraCss}\n`;\n\nconst AmountUsd = styled.div`\n color: #7b7b7b;\n font-size: 14px;\n font-weight: 400;\n line-height: 24px;\n word-wrap: break-word;\n`;\n\nconst NearIcon = styled.img`\n width: 28px;\n height: 28px;\n`;\n\nconst TextBold = styled.div`\n color: #292929;\n font-size: 14px;\n font-weight: 600;\n line-height: 24px;\n word-wrap: break-word;\n`;\n\nconst UserChip = styled.div`\n display: flex;\n flex-direction: row;\n padding: 2px 12px;\n gap: 4px;\n border-radius: 32px;\n background: #ebebeb;\n`;\n\nconst UserChipLink = styled.a`\n display: flex;\n flex-direction: row;\n padding: 2px 12px;\n gap: 4px;\n border-radius: 32px;\n background: #ebebeb;\n\n &:hover {\n text-decoration: none;\n }\n`;\n\nconst ShareText = styled.div`\n color: #7b7b7b;\n font-size: 14px;\n font-weight: 600;\n line-height: 24px;\n word-wrap: break-word;\n`;\n\nconst SocialIcon = styled.img`\n width: 24px;\n height: 24px;\n cursor: pointer;\n`;\n\nState.init({\n showBreakdown: false,\n successfulDonation: null,\n});\n\nif (props.isModalOpen && !state.successfulDonation) {\n let successfulDonation = props.successfulDonation;\n if (!successfulDonation && props.transactionHashes) {\n const body = JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n });\n const res = fetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body,\n });\n // console.log(\"tx res: \", res);\n if (res.ok) {\n const successVal = res.body.result.status?.SuccessValue;\n let decoded = Buffer.from(successVal, \"base64\").toString(\"utf-8\"); // atob not working\n successfulDonation = JSON.parse(decoded);\n }\n }\n const { donor_id, recipient_id } = successfulDonation;\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${recipient_id}/profile/**`],\n }).then((recipientData) => {\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${donor_id}/profile/**`],\n }).then((donorData) => {\n State.update({\n successfulDonation,\n recipientProfile: recipientData[recipient_id]?.profile || {},\n donorProfile: donorData[donor_id]?.profile || {},\n });\n });\n });\n}\n\nconst twitterIntent = useMemo(() => {\n if (!state.recipientProfile) return;\n const twitterIntentBase = \"https://twitter.com/intent/tweet?text=\";\n let url =\n DEFAULT_GATEWAY +\n `${ownerId}/widget/Index?tab=project&projectId=${state.successfulDonation.recipient_id}&referrerId=${context.accountId}`;\n let text = `I just donated to ${\n state.recipientProfile\n ? state.recipientProfile.linktree?.twitter\n ? `@${state.recipientProfile.linktree.twitter}`\n : state.recipientProfile.name\n : state.successfulDonation.recipient_id\n } on @${POTLOCK_TWITTER_ACCOUNT_ID}! Support public goods at `;\n text = encodeURIComponent(text);\n url = encodeURIComponent(url);\n return twitterIntentBase + text + `&url=${url}` + `&hashtags=${DEFAULT_SHARE_HASHTAGS.join(\",\")}`;\n}, [state.successfulDonation, state.recipientProfile]);\n\nreturn (\n <Widget\n src={`${ownerId}/widget/Components.Modal`}\n props={{\n ...props,\n contentStyle: {\n padding: \"0px\",\n },\n children: (\n <>\n <ModalMain>\n <HeaderIcon src={HEADER_ICON_URL} />\n <Column>\n <Row style={{ gap: \"9px\" }}>\n <AmountNear>\n {state.successfulDonation?.total_amount\n ? parseFloat(\n NEAR.fromIndivisible(state.successfulDonation.total_amount).toString()\n )\n : \"-\"}\n </AmountNear>\n <NearIcon src={NEAR.iconUrl} />\n </Row>\n <AmountUsd>\n {(state.successfulDonation?.total_amount\n ? props.yoctosToUsd(state.successfulDonation.total_amount)\n : \"$-\") + \" USD\"}\n </AmountUsd>\n </Column>\n <Row style={{ gap: \"8px\" }}>\n <TextBold>Has been donated to</TextBold>\n <UserChipLink\n href={props.hrefWithEnv(\n `?tab=project&projectId=${state.successfulDonation.recipient_id}`\n )}\n target=\"_blank\"\n >\n {state.successfulDonation && (\n <Widget\n src={`${ownerId}/widget/Project.ProfileImage`}\n props={{\n ...props,\n accountId: state.successfulDonation.recipient_id,\n style: {\n height: \"17px\",\n width: \"17px\",\n },\n }}\n />\n )}\n <TextBold>{state.recipientProfile?.name || \"-\"}</TextBold>\n </UserChipLink>\n </Row>\n <Widget\n src={`${ownerId}/widget/Cart.BreakdownSummary`}\n props={{\n ...props,\n referrerId: state.successfulDonation?.referrer_id,\n amountNear: NEAR.fromIndivisible(\n state.successfulDonation?.total_amount || \"0\"\n ).toString(),\n bypassProtocolFee:\n !state.successfulDonation?.protocol_fee ||\n state.successfulDonation?.protocol_fee === \"0\", // TODO: allow user to choose\n headerStyle: { justifyContent: \"center\" },\n }}\n />\n <Row style={{ width: \"100%\", justifyContent: \"center\", gap: \"24px\" }}>\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"secondary\",\n text: \"Do it again!\",\n onClick: () => {\n onClose();\n props.openDonateToProjectModal();\n },\n style: { width: \"100%\" },\n }}\n />\n <Widget\n src={`${ownerId}/widget/Components.Button`}\n props={{\n type: \"secondary\",\n text: \"Explore projects\",\n onClick: () => {\n onClose();\n },\n style: { width: \"100%\" },\n }}\n />\n </Row>\n </ModalMain>\n <ModalFooter>\n <Row style={{ gap: \"6px\", justifyContent: \"center\" }}>\n <TextBold>From</TextBold>\n <UserChip>\n {state.donorProfile && (\n <Widget\n src={`${ownerId}/widget/Project.ProfileImage`}\n props={{\n ...props,\n accountId: state.successfulDonation?.donor_id,\n style: {\n height: \"17px\",\n width: \"17px\",\n },\n }}\n />\n )}\n <TextBold>{state.donorProfile?.name || \"-\"}</TextBold>\n </UserChip>\n </Row>\n <Row style={{ gap: \"8px\", justifyContent: \"center\" }}>\n <ShareText>Share to</ShareText>\n <a href={twitterIntent} target=\"_blank\">\n <SocialIcon src={TWITTER_ICON_URL} />\n </a>\n </Row>\n </ModalFooter>\n </>\n ),\n }}\n />\n);\n" }, "Index": { "": "const ownerId = \"potlock.near\";\nconst registryContractId =\n props.env === \"staging\" ? \"registry.staging.potlock.near\" : \"registry.potlock.near\";\nconst donationContractId = \"donate.potlock.near\";\n\nconst CREATE_PROJECT_TAB = \"createproject\";\nconst EDIT_PROJECT_TAB = \"editproject\";\nconst PROJECTS_LIST_TAB = \"projects\";\nconst PROJECT_DETAIL_TAB = \"project\";\nconst CART_TAB = \"cart\";\nconst FEED_TAB = \"feed\";\nconst POTS_TAB = \"pots\";\nconst DEPLOY_POT_TAB = \"deploypot\";\nconst POT_DETAIL_TAB = \"pot\";\nconst DONORS_TAB = \"donors\";\n\nconst Theme = styled.div`\n position: relative;\n * {\n font-family: \"Mona-Sans\";\n font-style: normal;\n font-weight: 400;\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 400;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-Regular.woff) format(\"woff\");\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 500;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-Medium.woff) format(\"woff\");\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 600;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-SemiBold.woff) format(\"woff\");\n }\n @font-face {\n font-family: mona-sans;\n font-style: normal;\n font-weight: 700;\n src: local(\"Mona-Sans\"),\n url(https://fonts.cdnfonts.com/s/91271/Mona-Sans-Bold.woff) format(\"woff\");\n }\n`;\n\nconst NEAR_ACCOUNT_ID_REGEX = /^(?=.{2,64}$)(?!.*\\.\\.)(?!.*-$)(?!.*_$)[a-z\\d._-]+$/i;\n\nState.init({\n cart: null,\n checkoutSuccess: false,\n checkoutSuccessTxHash: null,\n donations: null,\n // previousCart: null,\n nearToUsd: null,\n isCartModalOpen: false,\n isNavMenuOpen: false,\n registryConfig: null,\n userIsRegistryAdmin: null,\n registeredProjects: null,\n donnorProjectId: null,\n amount: null,\n note: null,\n referrerId: null,\n currency: null,\n // isSybilModalOpen: false,\n donateToProjectModal: {\n isOpen: false,\n recipientId: null,\n referrerId: null,\n potId: null,\n potDetail: null,\n },\n donationSuccessModal: {\n isOpen: (!props.tab || props.tab === PROJECTS_LIST_TAB) && props.transactionHashes,\n successfulDonation: null,\n },\n});\n\nconst NEAR_USD_CACHE_KEY = \"NEAR_USD\";\nconst nearUsdCache = Storage.get(NEAR_USD_CACHE_KEY);\nconst EXCHANGE_RATE_VALIDITY_MS = 1000 * 60 * 60; // 1 hour\n\nif (!state.nearToUsd) {\n if (\n nearUsdCache === undefined ||\n (nearUsdCache && nearUsdCache.ts < Date.now() - EXCHANGE_RATE_VALIDITY_MS)\n ) {\n // undefined means it's not in the cache\n // this case handles the first time fetching the rate, and also if the rate is expired\n console.log(\"fetching near to usd rate\");\n asyncFetch(\"https://api.coingecko.com/api/v3/simple/price?ids=near&vs_currencies=usd\").then(\n (res) => {\n if (res.ok) {\n State.update({ nearToUsd: res.body.near.usd });\n Storage.set(NEAR_USD_CACHE_KEY, { rate: res.body.near.usd, ts: Date.now() });\n }\n }\n );\n } else if (nearUsdCache) {\n // valid cache value\n console.log(\"using cached near to usd rate\");\n State.update({ nearToUsd: nearUsdCache.rate });\n }\n}\n\n// console.log(\"state in Index: \", state);\n\nif (!state.registeredProjects) {\n State.update({ registeredProjects: Near.view(registryContractId, \"get_projects\", {}) });\n}\n\nif (!state.registeredProjects) return \"\";\n\nif (!state.donations) {\n State.update({\n donations: Near.view(donationContractId, \"get_donations\", {}), // TODO: ADD PAGINATION\n });\n}\n\nconst IPFS_BASE_URL = \"https://nftstorage.link/ipfs/\";\n\nconst getImageUrlFromSocialImage = (image) => {\n if (image.url) {\n return image.url;\n } else if (image.ipfs_cid) {\n return IPFS_BASE_URL + image.ipfs_cid;\n }\n};\n\nif (!state.registeredProjects) {\n Near.asyncView(registryContractId, \"get_projects\", {})\n .then((projects) => {\n // get social data for each project\n Near.asyncView(\"social.near\", \"get\", {\n keys: projects.map((project) => `${project.id}/profile/**`),\n }).then((socialData) => {\n const formattedProjects = projects.map((project) => {\n const profileData = socialData[project.id]?.profile;\n let profileImageUrl = DEFAULT_PROFILE_IMAGE_URL;\n if (profileData.image) {\n const imageUrl = getImageUrlFromSocialImage(profileData.image);\n if (imageUrl) profileImageUrl = imageUrl;\n }\n // get banner image URL\n let bannerImageUrl = DEFAULT_BANNER_IMAGE_URL;\n if (profileData.backgroundImage) {\n const imageUrl = getImageUrlFromSocialImage(profileData.backgroundImage);\n if (imageUrl) bannerImageUrl = imageUrl;\n }\n const formatted = {\n id: project.id,\n name: profileData.name ?? \"\",\n description: profileData.description ?? \"\",\n bannerImageUrl,\n profileImageUrl,\n status: project.status,\n tags: [profileData.category.text ?? CATEGORY_MAPPINGS[profileData.category] ?? \"\"],\n };\n return formatted;\n });\n State.update({\n registeredProjects: formattedProjects,\n });\n });\n })\n .catch((e) => {\n console.log(\"error getting projects: \", e);\n State.update({ getRegisteredProjectsError: e });\n });\n}\n\nif (state.registryConfig === null) {\n const registryConfig = Near.view(registryContractId, \"get_config\", {});\n if (registryConfig) {\n State.update({\n registryConfig,\n userIsRegistryAdmin: registryConfig.admins.includes(context.accountId),\n });\n }\n}\n\nconst tabContentWidget = {\n [CREATE_PROJECT_TAB]: \"Project.Create\",\n [EDIT_PROJECT_TAB]: \"Project.Create\",\n [PROJECTS_LIST_TAB]: \"Project.ListPage\",\n [PROJECT_DETAIL_TAB]: \"Project.Detail\",\n [CART_TAB]: \"Cart.Checkout\",\n [FEED_TAB]: \"Components.Feed\",\n [POTS_TAB]: \"Pots.Home\",\n [DEPLOY_POT_TAB]: \"Pots.Deploy\",\n [POT_DETAIL_TAB]: \"Pots.Detail\",\n [DONORS_TAB]: \"Components.Donors\",\n};\n\nconst getTabWidget = (tab) => {\n const defaultTabWidget = tabContentWidget[PROJECTS_LIST_TAB];\n if (tab in tabContentWidget) {\n return tabContentWidget[props.tab];\n }\n return defaultTabWidget;\n};\n\nconst CART_KEY = \"cart\";\n\n// const PREVIOUS_CART_KEY = \"previousCart\";\nconst storageCart = Storage.get(CART_KEY);\nconst StorageCurrency = Storage.get(\"currency\");\nconst StorageNote = Storage.get(\"note\");\nconst StorageAmount = Storage.get(\"amount\");\nconst StorageProjectId = Storage.get(\"projectId\");\nconst StorageReferrerId = Storage.get(\"referrerId\");\n// const storagePreviousCart = Storage.get(PREVIOUS_CART_KEY);\nconst DEFAULT_CART = {};\n\nconst props = {\n ...props,\n ...state,\n ownerId: \"potlock.near\",\n referrerId: props.referrerId,\n setCurrency: (cur) => {\n const currency = state.currency ?? cur;\n State.update({ currency: currency });\n Storage.set(\"currency\", currency);\n },\n setNote: (n) => {\n const note = state.note ?? n;\n State.update({ note: note });\n Storage.set(\"note\", note);\n },\n setAmount: (value) => {\n const amount = state.amount ?? value;\n State.update({ amount: amount });\n Storage.set(\"amount\", amount);\n },\n setProjectId: (id) => {\n const donnorProjectId = state.donnorProjectId ?? id;\n State.update({ donnorProjectId: donnorProjectId });\n Storage.set(\"projectId\", donnorProjectId);\n },\n setReferrerId: (ref) => {\n const referrerId = state.referrerId ?? ref;\n State.update({ referrerId: referrerId });\n Storage.set(\"referrerId\", referrerId);\n },\n addProjectsToCart: (projects) => {\n const cart = state.cart ?? {};\n projects.forEach((item) => {\n if (!item.ft) item.ft = \"NEAR\"; // default to NEAR\n cart[item.id] = item; // default to NEAR\n });\n State.update({ cart });\n Storage.set(CART_KEY, JSON.stringify(cart));\n },\n removeProjectsFromCart: (projectIds) => {\n const cart = state.cart ?? {};\n projectIds.forEach((projectId) => {\n delete cart[projectId];\n });\n State.update({ cart });\n Storage.set(CART_KEY, JSON.stringify(cart));\n },\n updateCartItem: ({ projectId, amount, ft, price, referrerId, potId, potDetail, note }) => {\n const cart = state.cart ?? {};\n const updated = {};\n // if (amount === \"\") updated.amount = \"0\";\n if (amount || amount === \"\") updated.amount = amount;\n if (ft) updated.ft = ft;\n if (price) updated.price = price;\n if (referrerId) updated.referrerId = referrerId;\n if (potId) updated.potId = potId;\n if (potDetail) updated.potDetail = potDetail;\n if (note) updated.note = note;\n cart[projectId] = updated;\n State.update({ cart });\n Storage.set(CART_KEY, JSON.stringify(cart));\n },\n clearCart: () => {\n State.update({ cart: {} });\n Storage.set(CART_KEY, JSON.stringify(DEFAULT_CART));\n },\n setCheckoutSuccess: (checkoutSuccess) => {\n State.update({ checkoutSuccess });\n },\n setIsCartModalOpen: (isOpen) => {\n State.update({ isCartModalOpen: isOpen });\n },\n setIsNavMenuOpen: (isOpen) => {\n State.update({ isNavMenuOpen: isOpen });\n },\n validateNearAddress: (address) => {\n let isValid = NEAR_ACCOUNT_ID_REGEX.test(address);\n // Additional \".near\" check for IDs less than 64 characters\n if (address.length < 64 && !address.endsWith(\".near\")) {\n isValid = false;\n }\n return isValid;\n },\n validateEVMAddress: (address) => {\n // Check if the address is defined and the length is correct (42 characters, including '0x')\n if (!address || address.length !== 42) {\n return false;\n }\n // Check if the address starts with '0x' and contains only valid hexadecimal characters after '0x'\n const re = /^0x[a-fA-F0-9]{40}$/;\n return re.test(address);\n },\n validateGithubRepoUrl: (url) => {\n // Regular expression to match the GitHub repository URL pattern\n // This regex checks for optional \"www.\", a required \"github.com/\", and then captures the username and repo name segments\n const githubRepoUrlPattern =\n /^(https?:\\/\\/)?(www\\.)?github\\.com\\/[a-zA-Z0-9-]+\\/[a-zA-Z0-9_.-]+\\/?$/;\n return githubRepoUrlPattern.test(url);\n },\n PROJECT_STATUSES: [\"Pending\", \"Approved\", \"Rejected\", \"Graylisted\", \"Blacklisted\"],\n SUPPORTED_FTS: {\n // TODO: move this to state to handle selected FT once we support multiple FTs\n NEAR: {\n iconUrl: IPFS_BASE_URL + \"bafkreidnqlap4cp5o334lzbhgbabwr6yzkj6albia62l6ipjsasokjm6mi\",\n toIndivisible: (amount) => new Big(amount).mul(new Big(10).pow(24)),\n fromIndivisible: (amount, decimals) =>\n Big(amount)\n .div(Big(10).pow(24))\n .toFixed(decimals || 2),\n },\n USD: {\n iconUrl: \"$\",\n toIndivisible: (amount) => new Big(amount).mul(new Big(10).pow(24)),\n fromIndivisible: (amount, decimals) =>\n Big(amount)\n .div(Big(10).pow(24))\n .toFixed(decimals || 2),\n },\n },\n DONATION_CONTRACT_ID: donationContractId,\n REGISTRY_CONTRACT_ID: registryContractId,\n POT_FACTORY_CONTRACT_ID:\n props.env === \"staging\" ? \"potfactory.staging.potlock.near\" : \"v1.potfactory.potlock.near\",\n NADABOT_CONTRACT_ID: props.env === \"staging\" ? \"v1.staging.nadabot.near\" : \"v1.nadabot.near\",\n ToDo: styled.div`\n position: relative;\n\n &::before {\n content: \"TODO: \";\n position: absolute;\n left: 0;\n top: 0;\n transform: translate(-110%, 0);\n background-color: yellow;\n }\n `,\n ONE_TGAS: Big(1_000_000_000_000),\n MAX_DONATION_MESSAGE_LENGTH: 100,\n hrefWithEnv: (href) => {\n // add env=staging to params\n if (props.env === \"staging\") {\n return `${href}${href.includes(\"?\") ? \"&\" : \"?\"}env=staging`;\n }\n return href;\n },\n nearToUsdWithFallback: (amountNear) => {\n return state.nearToUsd\n ? \"~$\" + (amountNear * state.nearToUsd).toFixed(2)\n : amountNear + \" NEAR\";\n },\n yoctosToUsdWithFallback: (amountYoctos) => {\n return state.nearToUsd\n ? \"~$\" + new Big(amountYoctos).mul(state.nearToUsd).div(1e24).toNumber().toFixed(2)\n : new Big(amountYoctos).div(1e24).toNumber().toFixed(2) + \" NEAR\";\n },\n yoctosToUsd: (amountYoctos) => {\n return state.nearToUsd\n ? \"~$\" + new Big(amountYoctos).mul(state.nearToUsd).div(1e24).toNumber().toFixed(2)\n : null;\n },\n yoctosToNear: (amountYoctos, abbreviate) => {\n return new Big(amountYoctos).div(1e24).toNumber().toFixed(2) + (abbreviate ? \" N\" : \"NEAR\");\n },\n formatDate: (timestamp) => {\n const months = [\n \"Jan\",\n \"Feb\",\n \"Mar\",\n \"Apr\",\n \"May\",\n \"Jun\",\n \"Jul\",\n \"Aug\",\n \"Sep\",\n \"Oct\",\n \"Nov\",\n \"Dec\",\n ];\n const date = new Date(timestamp);\n\n const year = date.getFullYear();\n const month = months[date.getMonth()];\n const day = date.getDate();\n let hour = date.getHours();\n const minute = date.getMinutes();\n const ampm = hour >= 12 ? \"pm\" : \"am\";\n\n // Convert hour to 12-hour format\n hour = hour % 12;\n hour = hour ? hour : 12; // the hour '0' should be '12'\n\n // Minutes should be two digits\n const minuteFormatted = minute < 10 ? \"0\" + minute : minute;\n\n return `${month} ${day}, ${year} ${hour}:${minuteFormatted}${ampm}`;\n },\n daysAgo: (timestamp) => {\n const now = new Date();\n const pastDate = new Date(timestamp);\n const differenceInTime = now - pastDate;\n\n // Convert time difference from milliseconds to days\n const differenceInDays = Math.floor(differenceInTime / (1000 * 3600 * 24));\n return differenceInDays === 0\n ? \"< 1 day ago\"\n : `${differenceInDays} ${differenceInDays === 1 ? \"day\" : \"days\"} ago`;\n },\n daysUntil: (timestamp) => {\n const now = new Date();\n const futureDate = new Date(timestamp);\n const differenceInTime = futureDate - now;\n\n // Convert time difference from milliseconds to days\n const differenceInDays = Math.ceil(differenceInTime / (1000 * 3600 * 24));\n\n return `${differenceInDays} ${differenceInDays === 1 ? \"day\" : \"days\"}`;\n },\n NADA_BOT_URL: \"https://app.nada.bot\",\n // openSybilModal: () => {\n // State.update({ isSybilModalOpen: true });\n // },\n getTagsFromSocialProfileData: (profileData) => {\n // first try to get tags from plCategories, then category (deprecated/old format), then default to empty array\n if (!profileData) return [];\n const DEPRECATED_CATEGORY_MAPPINGS = {\n \"social-impact\": \"Social Impact\",\n \"non-profit\": \"NonProfit\",\n climate: \"Climate\",\n \"public-good\": \"Public Good\",\n \"de-sci\": \"DeSci\",\n \"open-source\": \"Open Source\",\n community: \"Community\",\n education: \"Education\",\n };\n const tags = profileData.plCategories\n ? JSON.parse(profileData.plCategories)\n : profileData.category\n ? [profileData.category.text ?? DEPRECATED_CATEGORY_MAPPINGS[profileData.category] ?? \"\"]\n : [];\n return tags;\n },\n doesUserHaveDaoFunctionCallProposalPermissions: (policy) => {\n // TODO: break this out (NB: duplicated in Project.CreateForm)\n const userRoles = policy.roles.filter((role) => {\n if (role.kind === \"Everyone\") return true;\n return role.kind.Group && role.kind.Group.includes(context.accountId);\n });\n const kind = \"call\";\n const action = \"AddProposal\";\n // Check if the user is allowed to perform the action\n const allowed = userRoles.some(({ permissions }) => {\n return (\n permissions.includes(`${kind}:${action}`) ||\n permissions.includes(`${kind}:*`) ||\n permissions.includes(`*:${action}`) ||\n permissions.includes(\"*:*\")\n );\n });\n return allowed;\n },\n openDonateToProjectModal: (recipientId, referrerId, potId, potDetail) => {\n State.update({\n donateToProjectModal: { isOpen: true, recipientId, referrerId, potId, potDetail },\n });\n },\n openDonationSuccessModal: (successfulDonation) => {\n console.log(\"opening success modal with donation data: \", successfulDonation);\n State.update({\n donationSuccessModal: {\n isOpen: true,\n successfulDonation,\n },\n });\n },\n basisPointsToPercent: (basisPoints) => {\n return basisPoints / 100;\n },\n IPFS_BASE_URL: \"https://nftstorage.link/ipfs/\",\n};\n\nif (props.transactionHashes && props.tab === CART_TAB) {\n // if transaction hashes are in URL but haven't been added to props, override state:\n props.checkoutSuccessTxHash = props.transactionHashes;\n props.checkoutSuccess = true;\n}\n\nif (props.transactionHashes && props.tab === DEPLOY_POT_TAB) {\n // if transaction hashes are in URL but haven't been added to props, override state:\n props.deploymentSuccessTxHash = props.transactionHashes;\n props.deploymentSuccess = true;\n}\n\nif (state.cart === null && storageCart !== null) {\n // cart hasn't been set on state yet, and storageCart has been fetched\n // if storageCart isn't undefined, set it on state\n // otherwise, set default cart on state\n let cart = DEFAULT_CART;\n if (storageCart) {\n cart = JSON.parse(storageCart);\n }\n State.update({ cart });\n}\n\nif (\n state.currency === null &&\n state.donnorProjectId === null &&\n state.amount === null &&\n StorageCurrency !== null &&\n StorageAmount !== null &&\n StorageProjectId !== null\n) {\n State.update({ currency: StorageCurrency });\n State.update({ amount: StorageAmount });\n State.update({ donnorProjectId: StorageProjectId });\n State.update({ note: StorageNote });\n State.update({ referrerId: StorageReferrerId });\n}\n\n// if (state.previousCart === null && storagePreviousCart !== null) {\n// // previousCart hasn't been set on state yet, and storagePreviousCart has been fetched\n// // if storagePreviousCart isn't undefined, set it on state\n// if (storagePreviousCart && Object.keys(JSON.parse(storagePreviousCart)).length > 0) {\n// console.log(\"updating previous cart\");\n// State.update({ previousCart: JSON.parse(storagePreviousCart) });\n// }\n// }\n\n// console.log(\"state in Index: \", state);\n\nif (state.checkoutSuccessTxHash && state.cart && Object.keys(state.cart).length > 0) {\n // if checkout was successful after wallet redirect, clear cart\n // store previous cart in local storage to show success message\n // console.log(\"previous cart: \", state.cart);\n props.clearCart();\n}\n\nif (props.tab === EDIT_PROJECT_TAB) {\n props.edit = true;\n}\n\nconst tabContent = <Widget src={`${ownerId}/widget/${getTabWidget(props.tab)}`} props={props} />;\n\nconst Content = styled.div`\n width: 100%;\n height: 100%;\n background: #ffffff;\n // padding: 3em;\n border-radius: 0rem 0rem 1.5rem 1.5rem;\n border-top: 1px solid var(--ui-elements-light, #eceef0);\n background: var(--base-white, #fff);\n\n &.form {\n border: none;\n background: #fafafa;\n }\n`;\n\nconst isForm = [CREATE_PROJECT_TAB].includes(props.tab);\n\nif (!state.cart || !state.registeredProjects) {\n return \"\";\n}\n\nreturn (\n <Theme>\n <Widget src={`${ownerId}/widget/Components.Nav`} props={props} />\n <Content className={isForm ? \"form\" : \"\"}>{tabContent}</Content>\n <Widget\n src={`${ownerId}/widget/Project.ModalDonation`}\n props={{\n ...props,\n isModalOpen: state.donateToProjectModal.isOpen,\n onClose: () =>\n State.update({\n donateToProjectModal: {\n isOpen: false,\n recipientId: null,\n referrerId: null,\n potId: null,\n potDetail: null,\n },\n }),\n recipientId: state.donateToProjectModal.recipientId,\n referrerId: state.donateToProjectModal.referrerId,\n potId: state.donateToProjectModal.potId,\n }}\n />\n <Widget\n src={`${ownerId}/widget/Project.ModalDonationSuccess`}\n props={{\n ...props,\n successfulDonation: state.donationSuccessModal.successfulDonation,\n isModalOpen: state.donationSuccessModal.isOpen,\n onClose: () =>\n State.update({\n donationSuccessModal: {\n isOpen: false,\n successfulDonation: null,\n },\n }),\n }}\n />\n </Theme>\n);\n" } } } } }
Result:
{ "block_height": "112888340" }
No logs
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 0.18747  to potlock.near
Empty result
No logs