From d139a6e0eeaea55c789f9f62cddbb118e10af104 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 10 Dec 2024 21:07:27 -0600 Subject: [PATCH 01/35] Started working on final report editor and submission page --- .../nance/ActiveProjectsDropdown.tsx | 106 +++++++++ ui/components/nance/FinalReportEditor.tsx | 212 ++++++++++++++++++ ui/pages/report.tsx | 46 ++++ 3 files changed, 364 insertions(+) create mode 100644 ui/components/nance/ActiveProjectsDropdown.tsx create mode 100644 ui/components/nance/FinalReportEditor.tsx create mode 100644 ui/pages/report.tsx diff --git a/ui/components/nance/ActiveProjectsDropdown.tsx b/ui/components/nance/ActiveProjectsDropdown.tsx new file mode 100644 index 00000000..362cb75c --- /dev/null +++ b/ui/components/nance/ActiveProjectsDropdown.tsx @@ -0,0 +1,106 @@ +import { + Menu, + MenuButton, + MenuItem, + MenuItems, + Transition, +} from '@headlessui/react' +import { + ShareIcon, + EllipsisVerticalIcon, + PencilIcon, + ChevronDownIcon, + TrashIcon, + ArchiveBoxArrowDownIcon, +} from '@heroicons/react/24/outline' +import { Chain } from '@thirdweb-dev/chains' +import { useContract } from '@thirdweb-dev/react' +import ProjectABI from 'const/abis/Project.json' +import { PROJECT_TABLE_ADDRESSES, TABLELAND_ENDPOINT } from 'const/config' +import { Fragment, useEffect, useMemo, useState } from 'react' +import toast from 'react-hot-toast' +import useNewestProposals from '@/lib/nance/useNewestProposals' + +type ActiveProjectsDropdownProps = { + selectedChain: Chain + setProposalId: (id: string) => void +} + +export default function ActiveProjectsDropdown({ + selectedChain, + setProposalId, +}: ActiveProjectsDropdownProps) { + const [activeProjects, setActiveProjects] = useState([]) + const [selectedProject, setSelectedProject] = useState() + const { contract: projectsTableContract } = useContract( + PROJECT_TABLE_ADDRESSES[selectedChain.slug], + ProjectABI + ) + + useEffect(() => { + async function getActiveProjects() { + const projectsTableName = await projectsTableContract?.call( + 'getTableName' + ) + const statement = `SELECT * FROM ${projectsTableName}` + const projectsRes = await fetch( + `${TABLELAND_ENDPOINT}?statement=${statement}` + ) + const projects = await projectsRes.json() + setActiveProjects(projects) + } + if (projectsTableContract) { + getActiveProjects() + } + }, [projectsTableContract]) + return ( + <> + + +
+
+ +
+ {selectedProject?.title || 'Select Project'} +
+
+ + +
+ + {({ focus }) => ( +
+ {activeProjects.map((aP: any) => ( + + ))} +
+ )} +
+
+
+
+
+ + ) +} diff --git a/ui/components/nance/FinalReportEditor.tsx b/ui/components/nance/FinalReportEditor.tsx new file mode 100644 index 00000000..77b10c3a --- /dev/null +++ b/ui/components/nance/FinalReportEditor.tsx @@ -0,0 +1,212 @@ +import { GetMarkdown, SetMarkdown } from '@nance/nance-editor' +import { useProposal, useProposalUpload } from '@nance/nance-hooks' +import { RequestBudget } from '@nance/nance-sdk' +import { getUnixTime } from 'date-fns' +import { StringParam, useQueryParams } from 'next-query-params' +import dynamic from 'next/dynamic' +import { useRouter } from 'next/router' +import { useContext, useEffect, useState } from 'react' +import { SubmitHandler, useForm } from 'react-hook-form' +import toast from 'react-hot-toast' +import { useLocalStorage } from 'react-use' +import { NANCE_SPACE_NAME } from '../../lib/nance/constants' +import { pinBlobOrFile } from '@/lib/ipfs/pinBlobOrFile' +import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' +import { TEMPLATE } from '@/lib/nance' +import useAccount from '@/lib/nance/useAccountAddress' +import { useSignProposal } from '@/lib/nance/useSignProposal' +import ChainContext from '@/lib/thirdweb/chain-context' +import { classNames } from '@/lib/utils/tailwind' +import '@nance/nance-editor/lib/css/dark.css' +import '@nance/nance-editor/lib/css/editor.css' +import Head from '@/components/layout/Head' +import { LoadingSpinner } from '@/components/layout/LoadingSpinner' +import ProposalTitleInput from '@/components/nance/ProposalTitleInput' +import ActiveProjectsDropdown from './ActiveProjectsDropdown' + +type SignStatus = 'idle' | 'loading' | 'success' | 'error' + +const ProposalLocalCache = dynamic( + import('@/components/nance/ProposalLocalCache'), + { ssr: false } +) + +let getMarkdown: GetMarkdown +let setMarkdown: SetMarkdown + +const NanceEditor = dynamic( + async () => { + getMarkdown = (await import('@nance/nance-editor')).getMarkdown + setMarkdown = (await import('@nance/nance-editor')).setMarkdown + return import('@nance/nance-editor').then((mod) => mod.NanceEditor) + }, + { + ssr: false, + loading: () => , + } +) +export type FinalReportCache = { + title?: string + body?: string + timestamp: number +} + +export default function FinalReportEditor() { + const router = useRouter() + const { selectedChain } = useContext(ChainContext) + + const [signingStatus, setSigningStatus] = useState('idle') + + const [{ proposalId }, setQuery] = useQueryParams({ proposalId: StringParam }) + const shouldFetch = !!proposalId + const { data } = useProposal( + { space: NANCE_SPACE_NAME, uuid: proposalId! }, + shouldFetch + ) + const loadedProposal = data?.data + + const [reportCache, setReportCache, clearReportCache] = + useLocalStorage( + `NanceFinalReportCacheV1-${loadedProposal?.uuid.substring(0, 5)}` + ) + const [reportTitle, setReportTitle] = useState(loadedProposal?.title) + const methods = useForm({ + mode: 'onBlur', + }) + const { handleSubmit, reset, getValues, watch } = methods + + function restoreFromTitleAndBody(t: string, b: string) { + setReportTitle(t) + setMarkdown?.(b) + } + + useEffect(() => { + if (loadedProposal) { + console.log(reportCache) + restoreFromTitleAndBody( + loadedProposal.title, + reportCache?.body || TEMPLATE + ) + } + }, [loadedProposal, proposalId]) + + const onSubmit: SubmitHandler = async (formData) => { + console.debug('formData', formData) + } + + const { wallet } = useAccount() + const { signProposalAsync } = useSignProposal(wallet) + const { trigger } = useProposalUpload(NANCE_SPACE_NAME, loadedProposal?.uuid) + const buttonsDisabled = !wallet?.linked || signingStatus === 'loading' + + const saveProposalBodyCache = function () { + let body = getMarkdown() + + setReportCache({ + timestamp: getUnixTime(new Date()), + title: reportTitle || reportCache?.title || loadedProposal?.title, + body: body || undefined, + }) + } + + const setProposalId = function (id: string) { + setQuery({ proposalId: id }) + } + + useEffect(() => { + const subscription = watch((value, { name, type }) => { + if (type === 'change') { + saveProposalBodyCache() + } + }) + + return () => subscription.unsubscribe() + }, [watch]) + + const [reportBody, setReportBody] = useState(reportCache?.body) + + useEffect(() => { + setReportBody(reportCache?.body) + }, [reportBody, reportCache]) + + useEffect(() => { + console.log(reportBody) + }, [reportBody]) + + return ( +
+ + +
+
+
+ +
+
+ { + console.debug('setReportTitle', s) + const cache = reportCache || { + body: TEMPLATE, + } + setReportCache({ + ...cache, + title: s, + timestamp: getUnixTime(new Date()), + }) + }} + /> + +
+
+ { + const res = await pinBlobOrFile(val) + return res.url + }} + darkMode={true} + onEditorChange={(m) => { + saveProposalBodyCache() + }} + /> +
+ +
+ +
+ {/* Submit buttons */} +
+ {/* SUBMIT */} + +
+
+
+
+
+ ) +} diff --git a/ui/pages/report.tsx b/ui/pages/report.tsx new file mode 100644 index 00000000..5e0b906e --- /dev/null +++ b/ui/pages/report.tsx @@ -0,0 +1,46 @@ +import { NanceProvider } from '@nance/nance-hooks' +import { useRouter } from 'next/router' +import React from 'react' +import { NANCE_API_URL } from '../lib/nance/constants' +import Container from '../components/layout/Container' +import ContentLayout from '../components/layout/ContentLayout' +import WebsiteHead from '../components/layout/Head' +// Add this import +import { NoticeFooter } from '../components/layout/NoticeFooter' +import FinalReportEditor from '@/components/nance/FinalReportEditor' + +export default function FinalReport() { + const router = useRouter() // Initialize the router + const { proposalId } = router.query // Get the proposalId from the URL + + // Determine the header text based on the presence of proposalId + const headerText = proposalId ? 'Edit Final Report' : 'New Final Report' + + const title = 'New Proposal' + const description = ( + +

Submit a final report for your project.

+
+ ) + + return ( + <> + + + + + + + + + + + ) +} From 51118516f0c4bff3fa702d5e51420e8a035883f2 Mon Sep 17 00:00:00 2001 From: namedotget Date: Thu, 19 Dec 2024 15:30:30 -0600 Subject: [PATCH 02/35] Added final report submission --- ui/components/nance/FinalReportEditor.tsx | 161 +++++++++------------- ui/components/nance/ProposalEditor.tsx | 4 +- ui/lib/navigation/useNavigation.tsx | 4 + ui/pages/submission.tsx | 152 +++++++++++++------- 4 files changed, 179 insertions(+), 142 deletions(-) diff --git a/ui/components/nance/FinalReportEditor.tsx b/ui/components/nance/FinalReportEditor.tsx index 77b10c3a..f014ec1d 100644 --- a/ui/components/nance/FinalReportEditor.tsx +++ b/ui/components/nance/FinalReportEditor.tsx @@ -1,16 +1,18 @@ import { GetMarkdown, SetMarkdown } from '@nance/nance-editor' import { useProposal, useProposalUpload } from '@nance/nance-hooks' import { RequestBudget } from '@nance/nance-sdk' -import { getUnixTime } from 'date-fns' +import { getAccessToken } from '@privy-io/react-auth' +import { useAddress, useContract } from '@thirdweb-dev/react' +import ProjectsABI from 'const/abis/Project.json' +import { PROJECT_TABLE_ADDRESSES, TABLELAND_ENDPOINT } from 'const/config' import { StringParam, useQueryParams } from 'next-query-params' import dynamic from 'next/dynamic' -import { useRouter } from 'next/router' -import { useContext, useEffect, useState } from 'react' +import { useContext, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import toast from 'react-hot-toast' -import { useLocalStorage } from 'react-use' import { NANCE_SPACE_NAME } from '../../lib/nance/constants' import { pinBlobOrFile } from '@/lib/ipfs/pinBlobOrFile' +import { createSession, destroySession } from '@/lib/iron-session/iron-session' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { TEMPLATE } from '@/lib/nance' import useAccount from '@/lib/nance/useAccountAddress' @@ -23,14 +25,10 @@ import Head from '@/components/layout/Head' import { LoadingSpinner } from '@/components/layout/LoadingSpinner' import ProposalTitleInput from '@/components/nance/ProposalTitleInput' import ActiveProjectsDropdown from './ActiveProjectsDropdown' +import EditorMarkdownUpload from './EditorMarkdownUpload' type SignStatus = 'idle' | 'loading' | 'success' | 'error' -const ProposalLocalCache = dynamic( - import('@/components/nance/ProposalLocalCache'), - { ssr: false } -) - let getMarkdown: GetMarkdown let setMarkdown: SetMarkdown @@ -45,18 +43,18 @@ const NanceEditor = dynamic( loading: () => , } ) -export type FinalReportCache = { - title?: string - body?: string - timestamp: number -} export default function FinalReportEditor() { - const router = useRouter() const { selectedChain } = useContext(ChainContext) + const address = useAddress() const [signingStatus, setSigningStatus] = useState('idle') + const { contract: projectsTableContact } = useContract( + PROJECT_TABLE_ADDRESSES[selectedChain.slug], + ProjectsABI + ) + const [{ proposalId }, setQuery] = useQueryParams({ proposalId: StringParam }) const shouldFetch = !!proposalId const { data } = useProposal( @@ -64,120 +62,95 @@ export default function FinalReportEditor() { shouldFetch ) const loadedProposal = data?.data + const reportTitle = loadedProposal + ? loadedProposal?.title + ' Final Report' + : '' - const [reportCache, setReportCache, clearReportCache] = - useLocalStorage( - `NanceFinalReportCacheV1-${loadedProposal?.uuid.substring(0, 5)}` - ) - const [reportTitle, setReportTitle] = useState(loadedProposal?.title) const methods = useForm({ mode: 'onBlur', }) const { handleSubmit, reset, getValues, watch } = methods - function restoreFromTitleAndBody(t: string, b: string) { - setReportTitle(t) - setMarkdown?.(b) - } - - useEffect(() => { - if (loadedProposal) { - console.log(reportCache) - restoreFromTitleAndBody( - loadedProposal.title, - reportCache?.body || TEMPLATE + const onSubmit: SubmitHandler = async (formData) => { + console.debug('formData', formData) + //check if connected wallet is a rocketeer of the proposal + const teamMembers = loadedProposal?.actions?.[0]?.payload?.projectTeam + if ( + !teamMembers.some( + (m: any) => + (m.payoutAddress === address || m.votingAddress === address) && + m.isRocketeer === true ) + ) { + return toast.error('You are not a rocketeer of this proposal.', { + style: toastStyle, + }) } - }, [loadedProposal, proposalId]) - const onSubmit: SubmitHandler = async (formData) => { - console.debug('formData', formData) + const accessToken = await getAccessToken() + await createSession(accessToken) + try { + const markdown = getMarkdown() + if (!markdown) { + throw new Error('No markdown found') + } + const blob = new Blob([markdown], { type: 'text/markdown' }) + // const { cid: markdownIpfsHash } = await pinBlobOrFile(blob) + const projectsTableName = await projectsTableContact?.call('getTableName') + const statement = `SELECT * FROM ${projectsTableName} WHERE MDP = ${loadedProposal?.proposalId}` + const projectRes = await fetch( + `${TABLELAND_ENDPOINT}?statement=${statement}` + ) + const projectData = await projectRes.json() + console.log(projectData) + // await projectsTableContact?.call('updateTable', []) + } catch (err) { + toast.error('Unable to upload final report, please contact support.', { + style: toastStyle, + }) + } + await destroySession(accessToken) } const { wallet } = useAccount() const { signProposalAsync } = useSignProposal(wallet) const { trigger } = useProposalUpload(NANCE_SPACE_NAME, loadedProposal?.uuid) - const buttonsDisabled = !wallet?.linked || signingStatus === 'loading' - - const saveProposalBodyCache = function () { - let body = getMarkdown() - - setReportCache({ - timestamp: getUnixTime(new Date()), - title: reportTitle || reportCache?.title || loadedProposal?.title, - body: body || undefined, - }) - } + const buttonsDisabled = !address || signingStatus === 'loading' const setProposalId = function (id: string) { setQuery({ proposalId: id }) } - useEffect(() => { - const subscription = watch((value, { name, type }) => { - if (type === 'change') { - saveProposalBodyCache() - } - }) - - return () => subscription.unsubscribe() - }, [watch]) - - const [reportBody, setReportBody] = useState(reportCache?.body) - - useEffect(() => { - setReportBody(reportCache?.body) - }, [reportBody, reportCache]) - - useEffect(() => { - console.log(reportBody) - }, [reportBody]) - return (
-
+
-
- -
-
+
{ console.debug('setReportTitle', s) - const cache = reportCache || { - body: TEMPLATE, - } - setReportCache({ - ...cache, - title: s, - timestamp: getUnixTime(new Date()), - }) }} /> - +
+ + +
-
+
{ const res = await pinBlobOrFile(val) return res.url }} darkMode={true} - onEditorChange={(m) => { - saveProposalBodyCache() - }} + onEditorChange={(m) => {}} />
@@ -194,7 +167,7 @@ export default function FinalReportEditor() { 'px-5 py-3 gradient-2 border border-transparent font-RobotoMono rounded-[20px] rounded-tl-[10px] duration-300 disabled:cursor-not-allowed disabled:hover:rounded-sm disabled:opacity-40' )} onClick={() => {}} - disabled={buttonsDisabled} + disabled={buttonsDisabled || !projectsTableContact} data-tip={ signingStatus === 'loading' ? 'Signing...' diff --git a/ui/components/nance/ProposalEditor.tsx b/ui/components/nance/ProposalEditor.tsx index cfb0669e..6f881cfc 100644 --- a/ui/components/nance/ProposalEditor.tsx +++ b/ui/components/nance/ProposalEditor.tsx @@ -15,6 +15,7 @@ import { trimActionsFromBody, } from '@nance/nance-sdk' import { usePrivy } from '@privy-io/react-auth' +import { useAddress } from '@thirdweb-dev/react' import { add, differenceInDays, getUnixTime } from 'date-fns' import { StringParam, useQueryParams } from 'next-query-params' import dynamic from 'next/dynamic' @@ -100,6 +101,7 @@ export type ProposalCache = { export default function ProposalEditor() { const router = useRouter() const { getAccessToken } = usePrivy() + const address = useAddress() const [signingStatus, setSigningStatus] = useState('idle') const [attachBudget, setAttachBudget] = useState(false) @@ -188,7 +190,7 @@ export default function ProposalEditor() { const { wallet } = useAccount() const { signProposalAsync } = useSignProposal(wallet) const { trigger } = useProposalUpload(NANCE_SPACE_NAME, loadedProposal?.uuid) - const buttonsDisabled = !wallet?.linked || signingStatus === 'loading' + const buttonsDisabled = !address || signingStatus === 'loading' const buildProposal = (status: ProposalStatus) => { return { diff --git a/ui/lib/navigation/useNavigation.tsx b/ui/lib/navigation/useNavigation.tsx index 2c2910ab..a09322e0 100644 --- a/ui/lib/navigation/useNavigation.tsx +++ b/ui/lib/navigation/useNavigation.tsx @@ -57,6 +57,10 @@ export default function useNavigation(citizen: any) { name: 'Submit Contribution', href: '/submission?tag=contribution', }, + { + name: 'Submit Final Report', + href: '/submission?tag=report', + }, { name: 'Get $MOONEY', href: '/get-mooney', diff --git a/ui/pages/submission.tsx b/ui/pages/submission.tsx index 2c31ea2d..0e81d0fe 100644 --- a/ui/pages/submission.tsx +++ b/ui/pages/submission.tsx @@ -1,33 +1,51 @@ -import React, { useEffect, useState } from 'react'; -import { Tab } from '@headlessui/react'; -import { NanceProvider } from "@nance/nance-hooks"; -import { NANCE_API_URL } from "../lib/nance/constants"; -import Container from '../components/layout/Container'; -import ContentLayout from '../components/layout/ContentLayout'; -import ProposalEditor from '../components/nance/ProposalEditor'; -import ContributionEditor from '../components/contribution/ContributionEditor'; -import WebsiteHead from '../components/layout/Head'; -import { NoticeFooter } from '../components/layout/NoticeFooter'; -import { useRouter } from 'next/router'; +import { Tab } from '@headlessui/react' +import { NanceProvider } from '@nance/nance-hooks' +import { useRouter } from 'next/router' +import React, { useEffect, useState } from 'react' +import { NANCE_API_URL } from '../lib/nance/constants' +import ContributionEditor from '../components/contribution/ContributionEditor' +import Container from '../components/layout/Container' +import ContentLayout from '../components/layout/ContentLayout' +import WebsiteHead from '../components/layout/Head' +import { NoticeFooter } from '../components/layout/NoticeFooter' +import ProposalEditor from '../components/nance/ProposalEditor' +import FinalReportEditor from '@/components/nance/FinalReportEditor' const SubmissionPage: React.FC = () => { - const router = useRouter(); - const { tag } = router.query; - const [selectedIndex, setSelectedIndex] = useState(0); - const title = 'Collaborate with MoonDAO'; + const router = useRouter() + const { tag } = router.query + const [selectedIndex, setSelectedIndex] = useState(0) + const title = 'Collaborate with MoonDAO' useEffect(() => { - if (tag === 'contribution') { - setSelectedIndex(1); + if (tag === 'report') { + setSelectedIndex(2) + } else if (tag === 'contribution') { + setSelectedIndex(1) } else { - setSelectedIndex(0); + setSelectedIndex(0) } - }, [tag]); + }, [tag]) - const headerContent = ( -
-
- ); + useEffect(() => { + if (selectedIndex === 2) { + router.push({ + pathname: '/submission', + query: { tag: 'report' }, + }) + } else if (selectedIndex === 1) { + router.push({ + pathname: '/submission', + query: { tag: 'contribution' }, + }) + } else { + router.push({ + pathname: '/submission', + }) + } + }, [selectedIndex]) + + const headerContent =
return ( <> @@ -43,34 +61,61 @@ const SubmissionPage: React.FC = () => { isProfile={true} >
- + - - `rounded-lg py-2.5 px-5 font-GoodTimes leading-5 focus:outline-none - ${selected - ? 'bg-gradient-to-r from-[#5757ec] to-[#6b3d79] text-white shadow' - : 'text-white/70 hover:text-white'}` - }> + + `rounded-lg py-2.5 px-5 font-GoodTimes leading-5 focus:outline-none + ${ + selected + ? 'bg-gradient-to-r from-[#5757ec] to-[#6b3d79] text-white shadow' + : 'text-white/70 hover:text-white' + }` + } + > Submit Proposal - - `rounded-lg py-2.5 font-GoodTimes leading-5 px-5 focus:outline-none - ${selected - ? 'bg-gradient-to-r from-[#5757ec] to-[#6b3d79] text-white shadow' - : 'text-white/70 hover:text-white'}` - }> + + `rounded-lg py-2.5 font-GoodTimes leading-5 px-5 focus:outline-none + ${ + selected + ? 'bg-gradient-to-r from-[#5757ec] to-[#6b3d79] text-white shadow' + : 'text-white/70 hover:text-white' + }` + } + > Submit Contribution + + `rounded-lg py-2.5 font-GoodTimes leading-5 px-5 focus:outline-none + ${ + selected + ? 'bg-gradient-to-r from-[#5757ec] to-[#6b3d79] text-white shadow' + : 'text-white/70 hover:text-white' + }` + } + > + Submit Report +

- Submit a proposal to receive financing or special permissions from the MoonDAO community. Please refer to {' '} - + Submit a proposal to receive financing or special + permissions from the MoonDAO community. Please refer to{' '} + our documentation - - {' '}for more details before getting started. + {' '} + for more details before getting started.

@@ -80,15 +125,28 @@ const SubmissionPage: React.FC = () => {

- What have you done to accelerate the impact of MoonDAO's mission? Submit your results for senate review and potential rewards. Please refer to {' '} - + What have you done to accelerate the impact of MoonDAO's + mission? Submit your results for senate review and + potential rewards. Please refer to{' '} + our documentation - - {' '}for more details. + {' '} + for more details.

+ +
+

+ Submit a final report for your project. +

+
+ +
@@ -97,7 +155,7 @@ const SubmissionPage: React.FC = () => { - ); -}; + ) +} -export default SubmissionPage; +export default SubmissionPage From 797fff642da715365a41b268a080bbb3dbfc3e17 Mon Sep 17 00:00:00 2001 From: namedotget Date: Fri, 27 Dec 2024 13:57:40 -0600 Subject: [PATCH 03/35] Updated frontend to support updated projects contract --- .../nance/ActiveProjectsDropdown.tsx | 1 + ui/components/nance/FinalReportEditor.tsx | 39 ++-- ui/const/abis/Project.json | 184 +++++++++++++++++- 3 files changed, 207 insertions(+), 17 deletions(-) diff --git a/ui/components/nance/ActiveProjectsDropdown.tsx b/ui/components/nance/ActiveProjectsDropdown.tsx index 362cb75c..dcdce553 100644 --- a/ui/components/nance/ActiveProjectsDropdown.tsx +++ b/ui/components/nance/ActiveProjectsDropdown.tsx @@ -86,6 +86,7 @@ export default function ActiveProjectsDropdown({ +
+
+ + ) +} diff --git a/ui/const/whitelist.ts b/ui/const/whitelist.ts index 4252206d..fd8cee25 100644 --- a/ui/const/whitelist.ts +++ b/ui/const/whitelist.ts @@ -1,6 +1,7 @@ //https://docs.google.com/spreadsheets/d/1LsYxkI_1alFUD_NxM2a5RngeSRC5e5ElUpP6aR30DiM/edit?gid=0#gid=0 export const blockedTeams: any = [] export const blockedCitizens: any = [48, 72] +export const blockedProjects: any = [] export const featuredTeams: any = [ '6', diff --git a/ui/lib/navigation/useNavigation.tsx b/ui/lib/navigation/useNavigation.tsx index 2a20b598..5f974fd5 100644 --- a/ui/lib/navigation/useNavigation.tsx +++ b/ui/lib/navigation/useNavigation.tsx @@ -1,6 +1,7 @@ import { BuildingLibraryIcon, ClipboardDocumentListIcon, + DocumentIcon, FolderIcon, PlusIcon, RocketLaunchIcon, @@ -25,6 +26,11 @@ export default function useNavigation(citizen: any) { href: '/network', icon: IconOrg, }, + { + name: 'Projects', + href: '/project', + icon: DocumentIcon, + }, { name: 'Info', icon: FolderIcon, diff --git a/ui/lib/project/useProjectData.tsx b/ui/lib/project/useProjectData.tsx new file mode 100644 index 00000000..38d3b2d6 --- /dev/null +++ b/ui/lib/project/useProjectData.tsx @@ -0,0 +1,39 @@ +import { useAddress } from '@thirdweb-dev/react' +import { useState } from 'react' +import { useHandleRead } from '@/lib/thirdweb/hooks' +import { getAttribute } from '@/lib//utils/nft' + +export default function useProjectData( + projectContract: any, + hatsContract: any, + nft: any +) { + const address = useAddress() + + const [isLoading, setIsLoading] = useState(false) + const [isActive, setIsActive] = useState(false) + const [isManager, setIsManager] = useState(false) + const [subIsValid, setSubIsValid] = useState(true) + const [hatTreeId, setHatTreeId] = useState() + + const { data: adminHatId } = useHandleRead( + projectContract, + 'projectAdminHat', + [nft?.metadata?.id || ''] + ) + + const { data: managerHatId } = useHandleRead( + projectContract, + 'projectManagerHat', + [nft?.metadata?.id || ''] + ) + + return { + isLoading, + isActive, + isManager, + subIsValid, + adminHatId, + managerHatId, + } +} diff --git a/ui/pages/project/[tokenId].tsx b/ui/pages/project/[tokenId].tsx new file mode 100644 index 00000000..d4fbfe53 --- /dev/null +++ b/ui/pages/project/[tokenId].tsx @@ -0,0 +1,475 @@ +import { PencilIcon } from '@heroicons/react/24/outline' +import { + ThirdwebNftMedia, + useAddress, + useContract, + useContractRead, + useSDK, +} from '@thirdweb-dev/react' +import TeamABI from 'const/abis/Team.json' +import { + CITIZEN_ADDRESSES, + TEAM_ADDRESSES, + HATS_ADDRESS, + MOONEY_ADDRESSES, + ZERO_ADDRESS, +} from 'const/config' +import { GetServerSideProps } from 'next' +import Image from 'next/image' +import { useContext, useEffect, useState } from 'react' +import toast from 'react-hot-toast' +import { useSubHats } from '@/lib/hats/useSubHats' +import useProjectData from '@/lib/project/useProjectData' +import ChainContext from '@/lib/thirdweb/chain-context' +import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' +import { useMOONEYBalance } from '@/lib/tokens/mooney-token' +import Address from '@/components/layout/Address' +import Container from '@/components/layout/Container' +import ContentLayout from '@/components/layout/ContentLayout' +import Frame from '@/components/layout/Frame' +import Head from '@/components/layout/Head' +import { NoticeFooter } from '@/components/layout/NoticeFooter' +import SlidingCardMenu from '@/components/layout/SlidingCardMenu' +import GeneralActions from '@/components/subscription/GeneralActions' +import { SubscriptionModal } from '@/components/subscription/SubscriptionModal' +import TeamDonation from '@/components/subscription/TeamDonation' +import TeamManageMembers from '@/components/subscription/TeamManageMembers' +import TeamMembers from '@/components/subscription/TeamMembers' +import TeamMetadataModal from '@/components/subscription/TeamMetadataModal' +import TeamTreasury from '@/components/subscription/TeamTreasury' +import ProjectActions from '@/components/subscription/project/ProjectActions' + +export default function ProjectProfile({ tokenId, nft, imageIpfsLink }: any) { + const sdk = useSDK() + const address = useAddress() + + const { selectedChain } = useContext(ChainContext) + + //Modal states + const [teamMetadataModalEnabled, setTeamMetadataModalEnabled] = + useState(false) + const [teamSubscriptionModalEnabled, setTeamSubscriptionModalEnabled] = + useState(false) + + //Contracts + const { contract: hatsContract } = useContract(HATS_ADDRESS) + const { contract: projectContract } = useContract( + TEAM_ADDRESSES[selectedChain.slug], + TeamABI + ) + const { contract: citizenConract } = useContract( + CITIZEN_ADDRESSES[selectedChain.slug] + ) + const { contract: mooneyContract } = useContract( + MOONEY_ADDRESSES[selectedChain.slug] + ) + const { data: MOONEYBalance } = useMOONEYBalance(mooneyContract, nft?.owner) + + const { + adminHatId, + managerHatId, + isManager, + subIsValid, + isActive, + isLoading: isLoadingProjectData, + } = useProjectData(projectContract, hatsContract, nft) + //Hats + const hats = useSubHats(selectedChain, adminHatId) + + const [nativeBalance, setNativeBalance] = useState(0) + + //Subscription Data + const { data: expiresAt } = useContractRead(projectContract, 'expiresAt', [ + nft?.metadata?.id, + ]) + + // get native balance for multisigj + useEffect(() => { + async function getNativeBalance() { + const provider = sdk?.getProvider() + const balance: any = await provider?.getBalance(nft?.owner as string) + setNativeBalance(+(balance.toString() / 10 ** 18).toFixed(5)) + } + + if (sdk && nft?.owner) { + getNativeBalance() + } + }, [sdk, nft]) + + useChainDefault() + + //Profile Header Section + const ProfileHeader = ( +
+ +
+
+
+
+ {nft?.metadata.image ? ( +
+ +
+ +
+
+ ) : ( + <> + )} +
+
+
+ {subIsValid && isManager && ( + + )} + {nft ? ( +

+ {nft.metadata.name} +

+ ) : ( + <> + )} +
+
+
+ {nft?.metadata.description ? ( +

+ {nft?.metadata.description || ''} +

+ ) : ( + <> + )} + +
+ {isManager || address === nft.owner ? ( + '' + ) : ( +
+ {!isActive && subIsValid && ( + + )} +
+ )} + + {/*Subscription Extension Container*/} + {/* {isManager || address === nft.owner ? ( +
+ {expiresAt && ( +
+
+
+ +
+
+
+ )} +
+ ) : ( + <> + )} */} +
+ {/* {isManager || address === nft.owner ? ( +

+ {'Exp: '} + {new Date(expiresAt?.toString() * 1000).toLocaleString()} +

+ ) : ( + <> + )} */} +
+
+
+
+
+
+
+
+ +
+ ) + + const teamIcon = '/./assets/icon-team.svg' + + return ( + + + {teamSubscriptionModalEnabled && ( + + )} + {teamMetadataModalEnabled && ( + + )} + } + > +
+
+
+ +
+
+ + {/* Header and socials */} + {subIsValid ? ( +
+ {/* Team Actions */} + {/* Team */} + +
+
+
+ Job icon +

Meet the Team

+
+ {isManager && ( +
+ {/* { + window.open( + `https://app.hatsprotocol.xyz/trees/${selectedChain.chainId}/${hatTreeId}` + ) + }} + > + Manage Members + */} + +
+ )} +
+ + +
+ {hats?.[0].id && ( + + )} +
+
+
+ + {/* Mooney and Voting Power */} + {isManager && ( + + )} + {/* General Actions */} + {isManager && } +
+ ) : ( + // Subscription Expired + + {isManager && ( + + )} + + )} +
+
+
+ ) +} + +export const getServerSideProps: GetServerSideProps = async ({ params }) => { + // const tokenId: any = params?.tokenId + + // const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + // const sdk = initSDK(chain) + + // if (tokenId === undefined) { + // return { + // notFound: true, + // } + // } + + // const teamContract = await sdk.getContract( + // TEAM_ADDRESSES[chain.slug], + // TeamABI + // ) + // const nft = await teamContract.erc721.get(tokenId) + + // if ( + // !nft || + // !nft.metadata.uri || + // blockedTeams.includes(Number(nft.metadata.id)) + // ) { + // return { + // notFound: true, + // } + // } + + // const rawMetadataRes = await fetch(nft.metadata.uri) + // const rawMetadata = await rawMetadataRes.json() + // const imageIpfsLink = rawMetadata.image + + const tokenId = '0' + const imageIpfsLink = + 'https://ipfs.io/ipfs/QmQrX8HmgQAgVsWJdcqM4D5X85yo25hSt3jvXWv5Ytf5gG' + + const nft = { + owner: ZERO_ADDRESS, + metadata: { + name: 'Project #1', + description: 'Project #1 Description', + image: + 'https://ipfs.io/ipfs/QmQrX8HmgQAgVsWJdcqM4D5X85yo25hSt3jvXWv5Ytf5gG', + id: '0', + + attributes: [ + { + trait_type: 'website', + value: 'Project Website', + }, + ], + }, + } + + return { + props: { + nft, + tokenId, + imageIpfsLink, + }, + } +} diff --git a/ui/pages/project/index.tsx b/ui/pages/project/index.tsx new file mode 100644 index 00000000..a2d869e1 --- /dev/null +++ b/ui/pages/project/index.tsx @@ -0,0 +1,309 @@ +import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' +import { NFT } from '@thirdweb-dev/react' +import TeamABI from 'const/abis/Team.json' +import { TEAM_ADDRESSES } from 'const/config' +import { blockedProjects } from 'const/whitelist' +import Image from 'next/image' +import Link from 'next/link' +import { useRouter } from 'next/router' +import React, { useState, useEffect, useCallback } from 'react' +import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' +import { initSDK } from '@/lib/thirdweb/thirdweb' +import { useShallowQueryRoute } from '@/lib/utils/hooks' +import { getAttribute } from '@/lib/utils/nft' +import Card from '@/components/layout/Card' +import CardGridContainer from '@/components/layout/CardGridContainer' +import CardSkeleton from '@/components/layout/CardSkeleton' +import Container from '@/components/layout/Container' +import ContentLayout from '@/components/layout/ContentLayout' +import Frame from '@/components/layout/Frame' +import Head from '@/components/layout/Head' +import { NoticeFooter } from '@/components/layout/NoticeFooter' +import Search from '@/components/layout/Search' +import StandardButton from '@/components/layout/StandardButton' +import Tab from '@/components/layout/Tab' + +type NetworkProps = { + activeProjects: NFT[] + inactiveProjects: NFT[] +} + +export default function Projects({ + activeProjects, + inactiveProjects, +}: NetworkProps) { + const router = useRouter() + const shallowQueryRoute = useShallowQueryRoute() + + const [input, setInput] = useState('') + function filterBySearch(nfts: NFT[]) { + return nfts.filter((nft) => { + return nft.metadata.name + ?.toString() + .toLowerCase() + .includes(input.toLowerCase()) + }) + } + + const [tab, setTab] = useState('active') + function loadByTab(tab: string) { + if (tab === 'active') { + setCachedNFTs( + input != '' ? filterBySearch(activeProjects) : activeProjects + ) + } else if (tab === 'inactive') { + setCachedNFTs( + input != '' ? filterBySearch(inactiveProjects) : inactiveProjects + ) + } else { + const nfts = + activeProjects?.[0] && inactiveProjects?.[0] + ? [...activeProjects, ...inactiveProjects] + : activeProjects?.[0] + ? activeProjects + : inactiveProjects?.[0] + ? inactiveProjects + : [] + setCachedNFTs(input != '' ? filterBySearch(nfts) : nfts) + } + } + + const handleTabChange = useCallback( + (newTab: string) => { + setTab(newTab) + setPageIdx(1) + shallowQueryRoute({ tab: newTab, page: '1' }) + }, + [shallowQueryRoute] + ) + + const handlePageChange = useCallback( + (newPage: number) => { + setPageIdx(newPage) + shallowQueryRoute({ tab, page: newPage.toString() }) + }, + [shallowQueryRoute, tab] + ) + + const [maxPage, setMaxPage] = useState(1) + + useEffect(() => { + const totalActiveProjects = + input != '' + ? filterBySearch(activeProjects).length + : activeProjects.length + const totalInactiveProjects = + input != '' + ? filterBySearch(inactiveProjects).length + : inactiveProjects.length + + if (tab === 'active') setMaxPage(Math.ceil(totalActiveProjects / 9)) + if (tab === 'inactive') setMaxPage(Math.ceil(totalInactiveProjects / 9)) + }, [tab, input, activeProjects, inactiveProjects]) + + const [cachedNFTs, setCachedNFTs] = useState([]) + + const [pageIdx, setPageIdx] = useState(1) + + useEffect(() => { + const { tab: urlTab, page: urlPage } = router.query + if (urlTab && (urlTab === 'active' || urlTab === 'inactive')) { + setTab(urlTab as string) + } + if (urlPage && !isNaN(Number(urlPage))) { + setPageIdx(Number(urlPage)) + } + }, [router.query]) + + useEffect(() => { + loadByTab(tab) + }, [tab, input, activeProjects, inactiveProjects, router.query]) + + useChainDefault() + + const descriptionSection = ( +
+
MoonDAO Projects.
+ + + +
+
+ +
+ + Active + + + Inactive + +
+ +
+
+
+ ) + + return ( +
+ + + } + mainPadding + mode="compact" + popOverEffect={false} + isProfile + > + <> + + {cachedNFTs?.[0] ? ( + cachedNFTs + ?.slice((pageIdx - 1) * 9, pageIdx * 9) + .map((nft: any, I: number) => { + if (nft.metadata.name !== 'Failed to load NFT metadata') { + const type = nft.metadata.attributes.find( + (attr: any) => attr.trait_type === 'communications' + ) + ? 'team' + : 'citizen' + return ( +
+ +
+ ) + } + }) + ) : ( + <> + {Array.from({ length: 9 }).map((_, i) => ( + + ))} + + )} +
+ +
+ +

+ Page {pageIdx} of {maxPage} +

+ +
+ + +
+
+
+ ) +} + +export async function getStaticProps() { + const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + const sdk = initSDK(chain) + const now = Math.floor(Date.now() / 1000) + + const projectContract = await sdk.getContract( + TEAM_ADDRESSES[chain.slug], + TeamABI + ) + const totalProjects = await projectContract.call('totalSupply') + + const activeProjects = [] + const inactiveProjects = [] + for (let i = 0; i < totalProjects; i++) { + if (!blockedProjects.includes(i)) { + const project = await projectContract.erc721.get(i) + const expiresAt = await projectContract.call('expiresAt', [ + project?.metadata?.id, + ]) + if (expiresAt.toNumber() > now) { + const active = getAttribute( + project.metadata.attributes as any[], + 'active' + )?.value + if (active === '0') { + inactiveProjects.push(project) + } else if (active === '1' || !active) { + activeProjects.push(project) + } + } + } + } + + return { + props: { + activeProjects: activeProjects.reverse(), + inactiveProjects: inactiveProjects.reverse(), + }, + revalidate: 60, + } +} From 47ea56c3342b2196dd1aba3f52a1e3159a36a62f Mon Sep 17 00:00:00 2001 From: namedotget Date: Fri, 3 Jan 2025 11:17:25 -0600 Subject: [PATCH 08/35] wip --- ui/const/config.ts | 16 +++++++-- ui/pages/project/[tokenId].tsx | 59 +++++++++++++++++----------------- ui/pages/project/index.tsx | 31 ++++++++---------- 3 files changed, 56 insertions(+), 50 deletions(-) diff --git a/ui/const/config.ts b/ui/const/config.ts index 1252d069..c44a9da8 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -117,11 +117,21 @@ export const CITIZEN_TABLE_NAMES: Index = { sepolia: 'CITIZENTABLE_11155111_1671', } +export const PROJECT_ADDRESSES: Index = { + arbitrum: '', + sepolia: '0x9016Ac821055d1E32a5E18d43493faf601b9E756', +} + +export const PROJECT_CREATOR_ADDRESSES: Index = { + arbitrum: '', + sepolia: '0xb1089317f7338860B29fC6d324E460E8FA2333da', +} + export const PROJECT_TABLE_ADDRESSES: Index = { - arbitrum: '0x2E8e4B7DAf62868d3184E691f3Cd5Bd9c069cAe1', - 'arbitrum-sepolia': '0x7876d5a6050fE861B8b0A96FF37B34116C9D0636', - sepolia: '0x4eC886128E970eEef0230a775cA1fD46f9383C27', + arbitrum: '', + sepolia: '0xC391008458004e33bED39EF2c2539857006c0c74', } + export const COMPETITOR_TABLE_ADDRESSES: Index = { sepolia: '0x9057Fff69e8b016a214C4f894430F71dad50b42c', } diff --git a/ui/pages/project/[tokenId].tsx b/ui/pages/project/[tokenId].tsx index d4fbfe53..9a3c3725 100644 --- a/ui/pages/project/[tokenId].tsx +++ b/ui/pages/project/[tokenId].tsx @@ -30,6 +30,7 @@ import Frame from '@/components/layout/Frame' import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' import SlidingCardMenu from '@/components/layout/SlidingCardMenu' +import Button from '@/components/subscription/Button' import GeneralActions from '@/components/subscription/GeneralActions' import { SubscriptionModal } from '@/components/subscription/SubscriptionModal' import TeamDonation from '@/components/subscription/TeamDonation' @@ -209,42 +210,42 @@ export default function ProjectProfile({ tokenId, nft, imageIpfsLink }: any) { )} {/*Subscription Extension Container*/} - {/* {isManager || address === nft.owner ? ( -
- {expiresAt && ( -
+ {isManager || address === nft.owner ? ( +
+ {expiresAt && ( +
+
-
{ + setTeamSubscriptionModalEnabled(true) + }} > - -
+ {'Extend Plan'} +
- )} -
- ) : ( - <> - )} */} -
- {/* {isManager || address === nft.owner ? ( -

- {'Exp: '} - {new Date(expiresAt?.toString() * 1000).toLocaleString()} -

+
+ )} +
) : ( <> - )} */} + )} +
+ {isManager || address === nft.owner ? ( +

+ {'Exp: '} + {new Date(expiresAt?.toString() * 1000).toLocaleString()} +

+ ) : ( + <> + )}
diff --git a/ui/pages/project/index.tsx b/ui/pages/project/index.tsx index a2d869e1..b866d759 100644 --- a/ui/pages/project/index.tsx +++ b/ui/pages/project/index.tsx @@ -1,10 +1,9 @@ import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import { NFT } from '@thirdweb-dev/react' import TeamABI from 'const/abis/Team.json' -import { TEAM_ADDRESSES } from 'const/config' +import { PROJECT_ADDRESSES, TEAM_ADDRESSES } from 'const/config' import { blockedProjects } from 'const/whitelist' import Image from 'next/image' -import Link from 'next/link' import { useRouter } from 'next/router' import React, { useState, useEffect, useCallback } from 'react' import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' @@ -20,7 +19,6 @@ import Frame from '@/components/layout/Frame' import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' import Search from '@/components/layout/Search' -import StandardButton from '@/components/layout/StandardButton' import Tab from '@/components/layout/Tab' type NetworkProps = { @@ -269,32 +267,29 @@ export default function Projects({ export async function getStaticProps() { const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const sdk = initSDK(chain) - const now = Math.floor(Date.now() / 1000) const projectContract = await sdk.getContract( - TEAM_ADDRESSES[chain.slug], + PROJECT_ADDRESSES[chain.slug], TeamABI ) const totalProjects = await projectContract.call('totalSupply') + console.log(totalProjects.toString()) + const activeProjects = [] const inactiveProjects = [] for (let i = 0; i < totalProjects; i++) { if (!blockedProjects.includes(i)) { const project = await projectContract.erc721.get(i) - const expiresAt = await projectContract.call('expiresAt', [ - project?.metadata?.id, - ]) - if (expiresAt.toNumber() > now) { - const active = getAttribute( - project.metadata.attributes as any[], - 'active' - )?.value - if (active === '0') { - inactiveProjects.push(project) - } else if (active === '1' || !active) { - activeProjects.push(project) - } + + const active = getAttribute( + project.metadata.attributes as any[], + 'active' + )?.value + if (active === '0') { + inactiveProjects.push(project) + } else if (active === '1' || active) { + activeProjects.push(project) } } } From 0b5a32f03e0651b5c5829e78034c3dce2a3d428e Mon Sep 17 00:00:00 2001 From: namedotget Date: Fri, 3 Jan 2025 14:33:52 -0600 Subject: [PATCH 09/35] Refactor section card --- .../dashboard/analytics/AnalyticsPage.tsx | 20 +++++-------------- ui/components/layout/SectionCard.tsx | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 ui/components/layout/SectionCard.tsx diff --git a/ui/components/dashboard/analytics/AnalyticsPage.tsx b/ui/components/dashboard/analytics/AnalyticsPage.tsx index 486cbddf..80aad722 100644 --- a/ui/components/dashboard/analytics/AnalyticsPage.tsx +++ b/ui/components/dashboard/analytics/AnalyticsPage.tsx @@ -1,22 +1,12 @@ import useTranslation from 'next-translate/useTranslation' import React, { useMemo, useState } from 'react' import { useAssets } from '../../../lib/dashboard/hooks' +import SectionCard from '@/components/layout/SectionCard' import AnalyticsChainSelector from './AnalyticsChainSelector' import { AnalyticsProgress } from './AnalyticsProgress' import AnalyticsSkeleton from './AnalyticsSkeleton' import BarChart from './BarChart' -function Frame(props: any) { - return ( -
- {props.children} -
- ) -} - function Data({ text, value }: any) { return (
@@ -51,15 +41,15 @@ export default function AnalyticsPage({ vMooneyData }: any) { return ( <> - +

{'Governance Power Over Time'}

- - +
+

{'Voting Power Key Figures'} @@ -97,7 +87,7 @@ export default function AnalyticsPage({ vMooneyData }: any) {

- + ) } diff --git a/ui/components/layout/SectionCard.tsx b/ui/components/layout/SectionCard.tsx new file mode 100644 index 00000000..d01c430d --- /dev/null +++ b/ui/components/layout/SectionCard.tsx @@ -0,0 +1,20 @@ +type SectionCardProps = { + id?: string + className?: string + children: React.ReactNode +} + +export default function SectionCard({ + id, + className = '', + children, +}: SectionCardProps) { + return ( +
+ {children} +
+ ) +} From ba77419ea8684e8c62334489c750a7a8e5d03459 Mon Sep 17 00:00:00 2001 From: namedotget Date: Fri, 3 Jan 2025 14:34:28 -0600 Subject: [PATCH 10/35] Started implementing new design for /rewards --- ui/components/nance/RetroactiveRewards.tsx | 126 ++++++++++++++++++++- ui/public/assets/plus-icon.png | Bin 0 -> 453 bytes 2 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 ui/public/assets/plus-icon.png diff --git a/ui/components/nance/RetroactiveRewards.tsx b/ui/components/nance/RetroactiveRewards.tsx index 12de480f..9c560245 100644 --- a/ui/components/nance/RetroactiveRewards.tsx +++ b/ui/components/nance/RetroactiveRewards.tsx @@ -1,18 +1,24 @@ -import { Arbitrum, ArbitrumSepolia } from '@thirdweb-dev/chains' +import { PlusCircleIcon } from '@heroicons/react/24/outline' +import { Arbitrum, ArbitrumSepolia, Ethereum } from '@thirdweb-dev/chains' import { useAddress, useContract } from '@thirdweb-dev/react' +import { nativeOnChain } from '@uniswap/smart-order-router' import { DISTRIBUTION_TABLE_ADDRESSES, SNAPSHOT_RETROACTIVE_REWARDS_ID, } from 'const/config' import _ from 'lodash' +import Image from 'next/image' import { useState, useEffect } from 'react' import toast from 'react-hot-toast' import { useCitizens } from '@/lib/citizen/useCitizen' +import { assetImageExtension } from '@/lib/dashboard/dashboard-utils.ts/asset-config' import { useAssets } from '@/lib/dashboard/hooks' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' import { useVotingPowers } from '@/lib/snapshot' import useWindowSize from '@/lib/team/use-window-size' +import { useUniswapTokens } from '@/lib/uniswap/hooks/useUniswapTokens' +import { pregenSwapRoute } from '@/lib/uniswap/pregenSwapRoute' import { getBudget, getPayouts } from '@/lib/utils/rewards' import { computeRewardPercentages } from '@/lib/utils/voting' import Asset from '@/components/dashboard/treasury/balance/Asset' @@ -20,7 +26,8 @@ import Container from '@/components/layout/Container' import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' -import StandardButton from '../layout/StandardButton' +import SectionCard from '@/components/layout/SectionCard' +import StandardButton from '@/components/layout/StandardButton' export type Project = { id: string @@ -36,12 +43,53 @@ export type Distribution = { distribution: { [key: string]: number } } +type RewardAssetProps = { + name: string + value: string + usdValue: string + approximateUSD?: boolean +} + export type RetroactiveRewardsProps = { projects: Project[] distributions: Distribution[] refreshRewards: () => void } +function RewardAsset({ + name, + value, + usdValue, + approximateUSD, +}: RewardAssetProps) { + const image = assetImageExtension[name] + ? `/coins/${name}.${assetImageExtension[name]}` + : '/coins/DEFAULT.png' + + return ( +
+ {name} +
+
+

{name}

+

{value}

+
+ {Number(usdValue) > 0 && ( +

{`(${ + approximateUSD ? '~' : '' + }${usdValue})`}

+ )} +
+
+ ) +} + export function RetroactiveRewards({ projects, distributions, @@ -53,7 +101,7 @@ export function RetroactiveRewards({ const userAddress = useAddress() const year = new Date().getFullYear() - const quarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 + const quarter = Math.ceil((new Date().getMonth() + 1) / 3) const [edit, setEdit] = useState(false) const [distribution, setDistribution] = useState<{ [key: string]: number }>( @@ -131,6 +179,22 @@ export function RetroactiveRewards({ year, quarter ) + const [mooneyBudgetUSD, setMooneyBudgetUSD] = useState(0) + const { MOONEY, DAI } = useUniswapTokens(Ethereum) + + useEffect(() => { + async function getMooneyBudgetUSD() { + const route = await pregenSwapRoute(Ethereum, mooneyBudget, MOONEY, DAI) + + const usd = route?.route[0].rawQuote.toString() / 1e18 + setMooneyBudgetUSD(usd) + } + + if (mooneyBudget) { + getMooneyBudgetUSD() + } + }, [mooneyBudget]) + const { projectIdToETHPayout, projectIdToMooneyPayout, @@ -214,7 +278,7 @@ export function RetroactiveRewards({ /> } @@ -223,6 +287,56 @@ export function RetroactiveRewards({ popOverEffect={false} isProfile > + +

{`Q${quarter}: ${year} Rewards`}

+
+ +
+ + + +
+ Create Project + {'Create Project'} +
+
+
+
+ +
+

+ Active Projects +

+ +
+ {projects.map((project, i) => ( +
+
{project.title}
+
+ ))} +
+
+
diff --git a/ui/public/assets/plus-icon.png b/ui/public/assets/plus-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..faaab8c5320988b45a41f11517c10aad4c082424 GIT binary patch literal 453 zcmV;$0XqJPP)>^$6_*b|+{yz}>(byh?>1B&8VQn0$Bd@Ew+) zkwKQC3;=9-XjxCJ9ZLt(C)UXNU=2#Ck(R(_hSj5tHbww@1D+gWs1>j%Di9u=hJ0cOVB>fl!d{BE8SBaetjDM)-BaR zGP9MH8mdbxrq5Itv!V2kmAV?+p;Ispk$E{DJBss2$k?T&a9o>uXhU~kGczwmJC@#8 z{)J6ECnq?*Yv8Ba9_>%OVs4m}P`bl+p7t@aFGBmOwD1O$~ppza-$rW7n z?+bzlKcz v|4_N+ literal 0 HcmV?d00001 From 63778bb8eb93d3eb6b0bb66a8518eeb6885366b0 Mon Sep 17 00:00:00 2001 From: namedotget Date: Mon, 6 Jan 2025 16:33:57 -0600 Subject: [PATCH 11/35] Refactor --- ui/components/layout/NumberStepper.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/components/layout/NumberStepper.tsx b/ui/components/layout/NumberStepper.tsx index 34e3a276..a676efce 100644 --- a/ui/components/layout/NumberStepper.tsx +++ b/ui/components/layout/NumberStepper.tsx @@ -15,14 +15,14 @@ export default function NumberStepper({ max, min, }: NumberStepperProps) { - function handleIncrease() { + function increase() { if (max && number + step > max) { return } setNumber(number + step) } - function handleDecrease() { + function decrease() { if (min !== undefined && number - step < min) { return } @@ -36,13 +36,12 @@ export default function NumberStepper({ type="number" value={number} onChange={(e) => setNumber(Number(e.target.value))} - step={0} />
- - - )} - {nft ? ( -

- {nft.metadata.name} -

- ) : ( - <> - )} -
-
{nft?.metadata.description ? (

)} - -

- {isManager || address === nft.owner ? ( - '' - ) : ( -
- {!isActive && subIsValid && ( - - )} -
- )} - - {/*Subscription Extension Container*/} - {isManager || address === nft.owner ? ( -
- {expiresAt && ( -
-
-
- -
-
-
- )} -
- ) : ( - <> - )} -
- {isManager || address === nft.owner ? ( -

- {'Exp: '} - {new Date(expiresAt?.toString() * 1000).toLocaleString()} -

- ) : ( - <> - )} -
-
-
+
+

{`Awarded: ${totalBudget} ETH`}

+ ETH +
) - const teamIcon = '/./assets/icon-team.svg' - return ( - - {teamSubscriptionModalEnabled && ( - - )} - {teamMetadataModalEnabled && ( - - )} + } > @@ -297,115 +149,128 @@ export default function ProjectProfile({ tokenId, nft, imageIpfsLink }: any) { id="page-container" className="animate-fadeIn flex flex-col gap-5 w-full max-w-[1080px]" > -
+ {/* Project Overview */} +
- +
+ + +
+ Attach Final Report + {'Attach Final Report'} +
+
+ + +
+ Star Icon +

Project Overview

+
+
+ +

+ {proposalJSON?.abstract} +

+ +
+ + Report Icon +

{'Review Original Proposal'}

+ +
-
+ - {/* Header and socials */} - {subIsValid ? ( -
- {/* Team Actions */} - {/* Team */} - + +
-
-
- Job icon + Job icon +

Meet the Team

+
+ {isManager && ( +
+ -

Meet the Team

- {isManager && ( -
- {/* { - window.open( - `https://app.hatsprotocol.xyz/trees/${selectedChain.chainId}/${hatTreeId}` - ) - }} - > - Manage Members - */} - -
+ )} +
+ + +
+ {hats?.[0].id && ( + )}
- - -
- {hats?.[0].id && ( - - )} -
-
-
- - {/* Mooney and Voting Power */} - {isManager && ( - - )} - {/* General Actions */} - {isManager && } -
- ) : ( - // Subscription Expired - - {isManager && ( - - )} + +
- )} + {/* Mooney and Voting Power */} + {isManager && ( + + )} +
@@ -413,64 +278,57 @@ export default function ProjectProfile({ tokenId, nft, imageIpfsLink }: any) { } export const getServerSideProps: GetServerSideProps = async ({ params }) => { - // const tokenId: any = params?.tokenId + const tokenId: any = params?.tokenId - // const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia - // const sdk = initSDK(chain) + const chain = Sepolia + const sdk = initSDK(chain) - // if (tokenId === undefined) { - // return { - // notFound: true, - // } - // } - - // const teamContract = await sdk.getContract( - // TEAM_ADDRESSES[chain.slug], - // TeamABI - // ) - // const nft = await teamContract.erc721.get(tokenId) - - // if ( - // !nft || - // !nft.metadata.uri || - // blockedTeams.includes(Number(nft.metadata.id)) - // ) { - // return { - // notFound: true, - // } - // } + if (tokenId === undefined) { + return { + notFound: true, + } + } - // const rawMetadataRes = await fetch(nft.metadata.uri) - // const rawMetadata = await rawMetadataRes.json() - // const imageIpfsLink = rawMetadata.image + const projectContract = await sdk.getContract( + PROJECT_ADDRESSES[chain.slug], + ProjectABI + ) - const tokenId = '0' - const imageIpfsLink = - 'https://ipfs.io/ipfs/QmQrX8HmgQAgVsWJdcqM4D5X85yo25hSt3jvXWv5Ytf5gG' + // const nft = await projectContract.erc721.get(tokenId) const nft = { - owner: ZERO_ADDRESS, metadata: { - name: 'Project #1', - description: 'Project #1 Description', - image: - 'https://ipfs.io/ipfs/QmQrX8HmgQAgVsWJdcqM4D5X85yo25hSt3jvXWv5Ytf5gG', - id: '0', - + name: 'Deprize Development', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', attributes: [ { - trait_type: 'website', - value: 'Project Website', + trait_type: 'active', + value: 'true', + }, + { + trait_type: 'proposalIPFS', + value: + 'ipfs://bafkreifsaljrpcjycsd5fzmpwvo5k2ye7geaeqqcjym4ornafjqwahjmoe', + }, + { + trait_type: 'MDP', + value: '159', }, ], }, } + if (!nft || blockedProjects.includes(Number(tokenId))) { + return { + notFound: true, + } + } + return { props: { nft, tokenId, - imageIpfsLink, }, } } diff --git a/ui/pages/project/index.tsx b/ui/pages/project/index.tsx index b866d759..1cec9b1c 100644 --- a/ui/pages/project/index.tsx +++ b/ui/pages/project/index.tsx @@ -1,11 +1,14 @@ import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' -import { NFT } from '@thirdweb-dev/react' +import { NFT, useContract } from '@thirdweb-dev/react' +import HatsABI from 'const/abis/Hats.json' +import ProjectABI from 'const/abis/Project.json' import TeamABI from 'const/abis/Team.json' -import { PROJECT_ADDRESSES, TEAM_ADDRESSES } from 'const/config' +import { HATS_ADDRESS, PROJECT_ADDRESSES, TEAM_ADDRESSES } from 'const/config' import { blockedProjects } from 'const/whitelist' import Image from 'next/image' import { useRouter } from 'next/router' -import React, { useState, useEffect, useCallback } from 'react' +import React, { useState, useEffect, useCallback, useContext } from 'react' +import ChainContext from '@/lib/thirdweb/chain-context' import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' import { initSDK } from '@/lib/thirdweb/thirdweb' import { useShallowQueryRoute } from '@/lib/utils/hooks' @@ -20,6 +23,7 @@ import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' import Search from '@/components/layout/Search' import Tab from '@/components/layout/Tab' +import ProjectCard from '@/components/projects/ProjectCard' type NetworkProps = { activeProjects: NFT[] @@ -30,9 +34,17 @@ export default function Projects({ activeProjects, inactiveProjects, }: NetworkProps) { + const { selectedChain } = useContext(ChainContext) const router = useRouter() const shallowQueryRoute = useShallowQueryRoute() + //Contracts + const { contract: projectContract } = useContract( + PROJECT_ADDRESSES[selectedChain.slug], + ProjectABI + ) + const { contract: hatsContract } = useContract(HATS_ADDRESS, HatsABI) + const [input, setInput] = useState('') function filterBySearch(nfts: NFT[]) { return nfts.filter((nft) => { @@ -179,27 +191,14 @@ export default function Projects({ cachedNFTs ?.slice((pageIdx - 1) * 9, pageIdx * 9) .map((nft: any, I: number) => { - if (nft.metadata.name !== 'Failed to load NFT metadata') { - const type = nft.metadata.attributes.find( - (attr: any) => attr.trait_type === 'communications' - ) - ? 'team' - : 'citizen' - return ( -
- -
- ) - } + return ( + + ) }) ) : ( <> @@ -274,8 +273,6 @@ export async function getStaticProps() { ) const totalProjects = await projectContract.call('totalSupply') - console.log(totalProjects.toString()) - const activeProjects = [] const inactiveProjects = [] for (let i = 0; i < totalProjects; i++) { @@ -294,6 +291,33 @@ export async function getStaticProps() { } } + const nft = { + metadata: { + name: 'Deprize Development', + tokenId: 0, + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + attributes: [ + { + trait_type: 'active', + value: 'true', + }, + { + trait_type: 'proposalIPFS', + value: + 'ipfs://bafkreifsaljrpcjycsd5fzmpwvo5k2ye7geaeqqcjym4ornafjqwahjmoe', + }, + { + trait_type: 'MDP', + value: '159', + }, + ], + }, + } + + activeProjects.push(nft) + inactiveProjects.push(nft) + return { props: { activeProjects: activeProjects.reverse(), From 57906978b30d46fe0177baf1b2b998598c5d3cc8 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 13:45:16 -0600 Subject: [PATCH 16/35] Fixed input unfocus bug --- ui/components/layout/NumberStepper.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/components/layout/NumberStepper.tsx b/ui/components/layout/NumberStepper.tsx index a676efce..2ebb8aac 100644 --- a/ui/components/layout/NumberStepper.tsx +++ b/ui/components/layout/NumberStepper.tsx @@ -1,4 +1,5 @@ import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline' +import { useRef } from 'react' type NumberStepperProps = { number: number @@ -15,11 +16,14 @@ export default function NumberStepper({ max, min, }: NumberStepperProps) { + const inputRef = useRef(null) + function increase() { if (max && number + step > max) { return } setNumber(number + step) + inputRef.current?.focus() } function decrease() { @@ -27,15 +31,19 @@ export default function NumberStepper({ return } setNumber(number - step) + inputRef.current?.focus() } return (
setNumber(Number(e.target.value))} + step={0} />
))}
From 32cd35a9dae0b9d2c5ec5a6f42091131322a86c0 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 20:34:09 -0600 Subject: [PATCH 27/35] Final Report Editor --- ui/components/nance/FinalReportEditor.tsx | 119 +++++--- ui/lib/nance/index.ts | 313 +++++++++++++++++++++- 2 files changed, 395 insertions(+), 37 deletions(-) diff --git a/ui/components/nance/FinalReportEditor.tsx b/ui/components/nance/FinalReportEditor.tsx index 57147366..bc9c7702 100644 --- a/ui/components/nance/FinalReportEditor.tsx +++ b/ui/components/nance/FinalReportEditor.tsx @@ -2,17 +2,25 @@ import { GetMarkdown, SetMarkdown } from '@nance/nance-editor' import { useProposal } from '@nance/nance-hooks' import { RequestBudget } from '@nance/nance-sdk' import { useAddress, useContract } from '@thirdweb-dev/react' +import HatsABI from 'const/abis/Hats.json' import ProjectsABI from 'const/abis/Project.json' -import { PROJECT_TABLE_ADDRESSES, TABLELAND_ENDPOINT } from 'const/config' +import ProjectTableABI from 'const/abis/ProjectTable.json' +import { + HATS_ADDRESS, + PROJECT_ADDRESSES, + PROJECT_TABLE_ADDRESSES, + TABLELAND_ENDPOINT, +} from 'const/config' import { StringParam, useQueryParams } from 'next-query-params' import dynamic from 'next/dynamic' -import { useContext, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import toast from 'react-hot-toast' import { NANCE_SPACE_NAME } from '../../lib/nance/constants' import { pinBlobOrFile } from '@/lib/ipfs/pinBlobOrFile' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' -import { TEMPLATE } from '@/lib/nance' +import { FINAL_REPORT_TEMPLATE } from '@/lib/nance' +import useProjectData, { Project } from '@/lib/project/useProjectData' import ChainContext from '@/lib/thirdweb/chain-context' import { classNames } from '@/lib/utils/tailwind' import '@nance/nance-editor/lib/css/dark.css' @@ -20,11 +28,15 @@ import '@nance/nance-editor/lib/css/editor.css' import Head from '@/components/layout/Head' import { LoadingSpinner } from '@/components/layout/LoadingSpinner' import ProposalTitleInput from '@/components/nance/ProposalTitleInput' -import ActiveProjectsDropdown from './ActiveProjectsDropdown' +import ProjectsDropdown from '@/components/project/ProjectsDropdown' import EditorMarkdownUpload from './EditorMarkdownUpload' type SignStatus = 'idle' | 'loading' | 'success' | 'error' +type FinalReportEditorProps = { + projectsWithoutReport: Project[] | undefined +} + let getMarkdown: GetMarkdown let setMarkdown: SetMarkdown @@ -40,15 +52,24 @@ const NanceEditor = dynamic( } ) -export default function FinalReportEditor() { +export default function FinalReportEditor({ + projectsWithoutReport, +}: FinalReportEditorProps) { const { selectedChain } = useContext(ChainContext) const address = useAddress() + //Contracts + const { contract: projectContract } = useContract( + PROJECT_ADDRESSES[selectedChain.slug], + ProjectsABI + ) + const { contract: hatsContract } = useContract(HATS_ADDRESS, HatsABI) + const [signingStatus, setSigningStatus] = useState('idle') const { contract: projectsTableContact } = useContract( PROJECT_TABLE_ADDRESSES[selectedChain.slug], - ProjectsABI + ProjectTableABI ) const [{ proposalId }, setQuery] = useQueryParams({ proposalId: StringParam }) @@ -57,11 +78,37 @@ export default function FinalReportEditor() { { space: NANCE_SPACE_NAME, uuid: proposalId! }, shouldFetch ) - const loadedProposal = data?.data - const reportTitle = loadedProposal + const [loadedProposal, setLoadedProposal] = useState(undefined) + + useEffect(() => { + if (projectsWithoutReport) { + setLoadedProposal( + projectsWithoutReport.find((p: any) => p.MDP === Number(proposalId)) + ? data?.data + : undefined + ) + } + }, [projectsWithoutReport, data, proposalId]) + + const reportTitle = loadedProposal?.title ? loadedProposal?.title + ' Final Report' : '' + const [selectedProject, setSelectedProject] = useState() + const { isManager } = useProjectData( + projectContract, + hatsContract, + selectedProject + ) + + useEffect(() => { + if (projectsWithoutReport) { + setSelectedProject( + projectsWithoutReport.find((p) => p.MDP === loadedProposal?.proposalId) + ) + } + }, [projectsWithoutReport, loadedProposal]) + const methods = useForm({ mode: 'onBlur', }) @@ -71,24 +118,7 @@ export default function FinalReportEditor() { console.debug('formData', formData) if (!reportTitle || !loadedProposal) { - return toast.error( - 'Please select an active project that you are a rocketeer of.', - { - style: toastStyle, - } - ) - } - //check if connected wallet is a rocketeer of the proposal - const payload: any = loadedProposal?.actions?.[0]?.payload - const teamMembers = payload?.projectTeam - if ( - !teamMembers.some( - (m: any) => - (m.payoutAddress === address || m.votingAddress === address) && - m.isRocketeer === true - ) - ) { - return toast.error('You are not a rocketeer of this proposal.', { + return toast.error('Please select a project that you are a manager of.', { style: toastStyle, }) } @@ -98,9 +128,13 @@ export default function FinalReportEditor() { if (!markdown) { throw new Error('No markdown found') } - const blob = new Blob([markdown], { type: 'text/markdown' }) + const header = `# ${reportTitle}\n\n` + const fileName = `${reportTitle.replace(/\s+/g, '-')}.md` + const file = new File([header + markdown], fileName, { + type: 'text/markdown', + }) - const { cid: markdownIpfsHash } = await pinBlobOrFile(blob) + const { cid: markdownIpfsHash } = await pinBlobOrFile(file) const projectsTableName = await projectsTableContact?.call('getTableName') const statement = `SELECT * FROM ${projectsTableName} WHERE MDP = ${loadedProposal?.proposalId}` @@ -110,19 +144,28 @@ export default function FinalReportEditor() { const projectData = await projectRes.json() const project = projectData[0] - await projectsTableContact?.call('updateTableCol', [ + await projectsTableContact?.call('updateFinalReportIPFS', [ project.id, - 'finalReportIPFS', 'ipfs://' + markdownIpfsHash, ]) + setSelectedProject(undefined) + setMarkdown(FINAL_REPORT_TEMPLATE) + setLoadedProposal(undefined) + setQuery({ proposalId: undefined }) + + toast.success('Final report uploaded successfully.', { + style: toastStyle, + }) } catch (err) { + console.log(err) toast.error('Unable to upload final report, please contact support.', { style: toastStyle, }) } } - const buttonsDisabled = !address || signingStatus === 'loading' + const buttonsDisabled = + !address || signingStatus === 'loading' || !isManager || !selectedProject const setProposalId = function (id: string) { setQuery({ proposalId: id }) @@ -142,16 +185,18 @@ export default function FinalReportEditor() { }} />
-
{ const res = await pinBlobOrFile(val) return res.url @@ -176,7 +221,11 @@ export default function FinalReportEditor() { onClick={() => {}} disabled={buttonsDisabled || !projectsTableContact} data-tip={ - signingStatus === 'loading' + !selectedProject + ? 'Please select a project.' + : !isManager + ? 'You are not a manager.' + : signingStatus === 'loading' ? 'Signing...' : 'You need to connect wallet first.' } diff --git a/ui/lib/nance/index.ts b/ui/lib/nance/index.ts index 88ea3023..266c3090 100644 --- a/ui/lib/nance/index.ts +++ b/ui/lib/nance/index.ts @@ -1,7 +1,7 @@ -import { v4 } from "uuid"; +import { v4 } from 'uuid' export function uuidGen(): string { - return v4().replaceAll('-', ''); + return v4().replaceAll('-', '') } export function formatNumberUSStyle( @@ -91,3 +91,312 @@ export const TEMPLATE = `\n*Note: Please remove the italicized instructions befo | :---- | :---- | :---- | :---- | | *Send* | *0* | *ETH* | *TBD* | ` + +export const FINAL_REPORT_TEMPLATE = ` +*The title will be included at the top of the final report* + +# **Results** + +1. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +2. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +3. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +4. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +5. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +6. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +7. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 2. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 3. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 4. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + + 5. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +8. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +9. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +10. **Objective:** + 1. **Key Result**: + 1. **Results**: + 2. **Learnings:** + 3. **Maintenance**: + 4. **Team’s Self-Reported Score:** + 5. **Team Contributions** + +# **Coordinape Results** + +[Link to the Coordinape](https://coordinape.com/): Make the Astronauts the admin. + +| Member Name | % of total rewards | Upfront Payment | +| :---- | :---- | :---- | +| @TeamMemberName | 21% | 3,000 DAI, 50,000 MOONEY | +| | | | + +# **Treasury Transparency** + +Link to Treasury with unused funds returned to the main DAO Treasury. + +| Txn Title | Reason | Amount | Recipient | Etherscan Link or Gnosis Link | Deliverable | +| :---- | :---- | :---- | :---- | :---- | :---- | +| Legal Retainer | Retainer for contract lawyer | | Lawyers Inc. | **** | **** or if nothing to show please include a description of what was delivered. | +| | | | | | | + +**Project Wrap Up Checklist** + +- [ ] Added Project Final Report onto the Website Dashboard +- [ ] Upload Final report to the Google Drive +- [ ] Returned excess funds to the Treasury +- [ ] Presented Final Report to Senate +- [ ] Update Discord Roles +- [ ] (if needed) Create documentation for users of the work, or people who will need to maintain the project once it is completed. +` From 1fdd5d89a9e6c719b0187564cfe724433107c5c2 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 20:36:20 -0600 Subject: [PATCH 28/35] Added isActive check --- ui/lib/project/useProjectData.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/lib/project/useProjectData.tsx b/ui/lib/project/useProjectData.tsx index b9453151..f1fb5c05 100644 --- a/ui/lib/project/useProjectData.tsx +++ b/ui/lib/project/useProjectData.tsx @@ -54,6 +54,10 @@ export default function useProjectData( [project?.id || ''] ) + const isActive = useMemo(() => { + return project?.active === 1 + }, [project]) + const totalBudget = useMemo(() => { let budget = 0 if (nanceProposal?.actions && nanceProposal.actions.length > 0) { @@ -99,6 +103,7 @@ export default function useProjectData( return { ...project, isManager, + isActive, hatTreeId, adminHatId, managerHatId, From d1e8b98878988fd48f76678c142c0a0658219745 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 20:42:15 -0600 Subject: [PATCH 29/35] Added redirect for /projects => /project --- ui/next.config.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/next.config.js b/ui/next.config.js index c0cfd194..b5d99af3 100644 --- a/ui/next.config.js +++ b/ui/next.config.js @@ -212,12 +212,17 @@ module.exports = nextTranslate({ source: '/propose', destination: '/submit?tag=proposal', permanent: true, - }, + }, { source: '/teams', destination: '/network?tab=teams', permanent: true, }, + { + source: '/projects', + destination: '/project', + permanent: true, + }, ] }, webpack: (config, { isServer }) => { From dbab4fc846f21e799f5279941f48dc17e4730e0e Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 20:42:56 -0600 Subject: [PATCH 30/35] Changed 'Inactive' tab label to 'Past' --- ui/pages/project/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/project/index.tsx b/ui/pages/project/index.tsx index e139960b..1f377bd9 100644 --- a/ui/pages/project/index.tsx +++ b/ui/pages/project/index.tsx @@ -165,7 +165,7 @@ export default function Projects({ setTab={handleTabChange} icon="/../.././assets/icon-passport.svg" > - Inactive + Past
From ded8d38737b23ff79c0ef1f7ac339325e5af9a5f Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 20:51:15 -0600 Subject: [PATCH 31/35] Removed report page and added redirect --- ui/next.config.js | 5 +++++ ui/pages/report.tsx | 46 --------------------------------------------- 2 files changed, 5 insertions(+), 46 deletions(-) delete mode 100644 ui/pages/report.tsx diff --git a/ui/next.config.js b/ui/next.config.js index b5d99af3..f38e45ba 100644 --- a/ui/next.config.js +++ b/ui/next.config.js @@ -223,6 +223,11 @@ module.exports = nextTranslate({ destination: '/project', permanent: true, }, + { + source: '/report', + destination: '/submit?tag=report', + permanent: true, + }, ] }, webpack: (config, { isServer }) => { diff --git a/ui/pages/report.tsx b/ui/pages/report.tsx deleted file mode 100644 index 5e0b906e..00000000 --- a/ui/pages/report.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { NanceProvider } from '@nance/nance-hooks' -import { useRouter } from 'next/router' -import React from 'react' -import { NANCE_API_URL } from '../lib/nance/constants' -import Container from '../components/layout/Container' -import ContentLayout from '../components/layout/ContentLayout' -import WebsiteHead from '../components/layout/Head' -// Add this import -import { NoticeFooter } from '../components/layout/NoticeFooter' -import FinalReportEditor from '@/components/nance/FinalReportEditor' - -export default function FinalReport() { - const router = useRouter() // Initialize the router - const { proposalId } = router.query // Get the proposalId from the URL - - // Determine the header text based on the presence of proposalId - const headerText = proposalId ? 'Edit Final Report' : 'New Final Report' - - const title = 'New Proposal' - const description = ( - -

Submit a final report for your project.

-
- ) - - return ( - <> - - - - - - - - - - - ) -} From ccd649dd98b866bae7518131a963948163d4a212 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 21:01:12 -0600 Subject: [PATCH 32/35] Removed log --- ui/pages/submit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/submit.tsx b/ui/pages/submit.tsx index 46b6104c..38f07ad4 100644 --- a/ui/pages/submit.tsx +++ b/ui/pages/submit.tsx @@ -240,7 +240,7 @@ export async function getStaticProps() { `${TABLELAND_ENDPOINT}?statement=${statement}` ) const projects = await projectsRes.json() - console.log(projects) + return { props: { projectsWithoutReport: projects, From 59b48ab5cdfa07d960f1e44c4c9456867dbfe564 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 21:02:11 -0600 Subject: [PATCH 33/35] Cleanup --- ui/pages/submit.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ui/pages/submit.tsx b/ui/pages/submit.tsx index 38f07ad4..3c4a0d8e 100644 --- a/ui/pages/submit.tsx +++ b/ui/pages/submit.tsx @@ -6,7 +6,6 @@ import { PROJECT_TABLE_ADDRESSES, TABLELAND_ENDPOINT } from 'const/config' import { StringParam, useQueryParams } from 'next-query-params' import Image from 'next/image' import Link from 'next/link' -import { useRouter } from 'next/router' import React, { useEffect, useState } from 'react' import { NANCE_API_URL } from '../lib/nance/constants' import { Project } from '@/lib/project/useProjectData' @@ -19,12 +18,6 @@ import { NoticeFooter } from '../components/layout/NoticeFooter' import ProposalEditor from '../components/nance/ProposalEditor' import FinalReportEditor from '@/components/nance/FinalReportEditor' -const tagIndicies: Record = { - report: 2, - contribution: 1, - proposal: 0, -} - export default function SubmissionPage({ projectsWithoutReport, }: { @@ -32,7 +25,6 @@ export default function SubmissionPage({ }) { const [{ tag }, setQuery] = useQueryParams({ tag: StringParam }) - // Default to 0 to avoid hydration mismatch const [selectedIndex, setSelectedIndex] = useState(0) const title = 'Collaborate with MoonDAO' @@ -47,7 +39,6 @@ export default function SubmissionPage({ }, [tag]) useEffect(() => { - // Update the query parameter only if it differs from the current tag if (selectedIndex === 2 && tag !== 'report') { setQuery({ tag: 'report' }, 'replaceIn') } else if (selectedIndex === 1 && tag !== 'contribution') { @@ -57,8 +48,6 @@ export default function SubmissionPage({ } }, [selectedIndex]) - const headerContent =
- return ( <> From f8095d8fc0b1f8e509824d3dea6d31254e028e0b Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 21:07:54 -0600 Subject: [PATCH 34/35] Updated final report template --- ui/lib/nance/index.ts | 367 ++++++++---------------------------------- 1 file changed, 64 insertions(+), 303 deletions(-) diff --git a/ui/lib/nance/index.ts b/ui/lib/nance/index.ts index 266c3090..f714094c 100644 --- a/ui/lib/nance/index.ts +++ b/ui/lib/nance/index.ts @@ -93,310 +93,71 @@ export const TEMPLATE = `\n*Note: Please remove the italicized instructions befo ` export const FINAL_REPORT_TEMPLATE = ` -*The title will be included at the top of the final report* - -# **Results** - -1. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -2. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -3. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -4. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -5. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -6. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -7. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 2. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 3. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 4. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - - 5. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -8. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -9. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -10. **Objective:** - 1. **Key Result**: - 1. **Results**: - 2. **Learnings:** - 3. **Maintenance**: - 4. **Team’s Self-Reported Score:** - 5. **Team Contributions** - -# **Coordinape Results** - -[Link to the Coordinape](https://coordinape.com/): Make the Astronauts the admin. - -| Member Name | % of total rewards | Upfront Payment | -| :---- | :---- | :---- | -| @TeamMemberName | 21% | 3,000 DAI, 50,000 MOONEY | -| | | | - -# **Treasury Transparency** - -Link to Treasury with unused funds returned to the main DAO Treasury. +*The title of the project will be included at the top of the file." -| Txn Title | Reason | Amount | Recipient | Etherscan Link or Gnosis Link | Deliverable | -| :---- | :---- | :---- | :---- | :---- | :---- | -| Legal Retainer | Retainer for contract lawyer | | Lawyers Inc. | **** | **** or if nothing to show please include a description of what was delivered. | -| | | | | | | +*\*Please read [Projects System v6: Completion](https://docs.moondao.com/Projects/Project-System#completion) before submitting to understand the process of submitting a project final report. When ready, download this doc as a markdown file (File \> Download \> Markdown (.md)) and then upload and submit it at [https://moondao.com/report](https://moondao.com/report)* + +## Original Proposal + +*To be filled out by the Project Lead.* + +**Link to Original Proposal:** *Link to the original proposal* +**Original Abstract:** *Please include the original abstract from the project proposal.* + +## Results + +*To be filled out by the Project Lead.* + +*For each OKR please copy the exact objective and result as appeared on your original proposal. Make a new outline for each objective and key result and keep it in this format.* + +1. **Objective:** *Original objective as it appears in your initial proposal.* + + **Summary:** *Overall discussion of the objective and how it went.* + **Learnings**: *What went well? What went wrong? How could it be improved?* + **Maintenance**: *Create documentation if there is long-term operation of the work that you created so that our operations team can continue supporting the work.* + **Results:** *Please provide the actual results for each key objective.* + 1. **Key Result**: *Original key result as it appears in your initial proposal.* + **Results**: *The actual quantitative result or a link to the work completed.* + 2. **Key Result**: *Original key result as it appears in your initial proposal.* + **Results**: *The actual quantitative result or a link to the work completed.* + + **Grade (Do not fill out \- Exec Leads will review):** *Overall Grade For The Objective* + *Superb \= This is reserved for if the project went incredibly well without any flaws and vastly surpassed all original metrics. Very few projects will meet this grade.* + + *Exceeds Expectations \= Did better than expected. The team surpassed expectations and went above and beyond the original scope of the project.* + + *Meets Expectations \= The project met all its original goals and sufficiently hit all the criteria.* + + *Does Not Meet Expectations \= The project did not achieve its original goal.* -**Project Wrap Up Checklist** +## Member Contributions -- [ ] Added Project Final Report onto the Website Dashboard -- [ ] Upload Final report to the Google Drive -- [ ] Returned excess funds to the Treasury -- [ ] Presented Final Report to Senate -- [ ] Update Discord Roles -- [ ] (if needed) Create documentation for users of the work, or people who will need to maintain the project once it is completed. +*To be filled out by each Project Contributor.* + +**@TeamMemberName1:** *A paragraph or two about the work that the member did on the team and a link to the work they did or contributions.* + +**@TeamMemberName2:** *A paragraph or two about the work that the member did on the team and a link to the work they did or contributions.* + +## Reward Distribution (Table A) + +*To be filled out by the Project Lead* + +[*Link to the Coordinape*](https://coordinape.com/)*: Make the Astronauts the admin of the coordinape circle.* + +| Member Name | % of total rewards | Upfront Payment Received | Wallet to receive ETH | +| :---- | :---- | :---- | :---- | +| *@TeamMemberName* | *21%* | *3,000 DAI, 50,000 MOONEY* | | + +## Treasury Transparency (Table B) + +*To be filled out by the Project Lead* + +*Link to Treasury with **unused funds returned to the [MoonDAO Treasury](https://app.safe.global/home?safe=eth:0xce4a1E86a5c47CD677338f53DA22A91d85cab2c9).*** + +*Arbitrum Address: arb1:0xAF26a002d716508b7e375f1f620338442F5470c0* +*Ethereum Address: eth:0xce4a1E86a5c47CD677338f53DA22A91d85cab2c9* + +| Txn Title | Reason | Amount | Recipient | Etherscan Link or Gnosis Link | Deliverable | +| :---- | :---- | :---- | :---- | :---- | :---- | +| Legal Retainer | Retainer for contract lawyer | 3,000 DAI | Lawyers Inc. | **\** | **\** or if nothing to show please include a description of what was delivered. | ` From f89055d1d1fdadbec665c0a910a277b4225bdc47 Mon Sep 17 00:00:00 2001 From: namedotget Date: Tue, 7 Jan 2025 21:19:10 -0600 Subject: [PATCH 35/35] Added project addresses for arb and arb projects hat tree id --- ui/const/config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/const/config.ts b/ui/const/config.ts index 11255958..853da3f9 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -118,17 +118,17 @@ export const CITIZEN_TABLE_NAMES: Index = { } export const PROJECT_ADDRESSES: Index = { - arbitrum: '', + arbitrum: '0xB42017d8Beb6758F99eCbEDcB83EC59F8Fa526EA', sepolia: '0x19124F594c3BbCb82078b157e526B278C8E9EfFc', } export const PROJECT_CREATOR_ADDRESSES: Index = { - arbitrum: '', + arbitrum: '0x15F84Ee204a93FE2B80B2B2d30dAEfA154f4429d', sepolia: '0xd1EfE13758b73F2Db9Ed19921eB756fbe4C26E2D', } export const PROJECT_TABLE_ADDRESSES: Index = { - arbitrum: '', + arbitrum: '0xC6620Aa6010c0FFcDd74bf7D3e569Cc4b2e5B5Db', sepolia: '0x17729AFF287d9873F5610c029A5Db814e428e97a', } @@ -204,7 +204,7 @@ export const MOONDAO_HAT_TREE_IDS: Index = { //Projects Sepolia Hat Tree : https://app.hatsprotocol.xyz/trees/11155111/729 //ProjectsArbitrum Hat Tree : export const PROJECT_HAT_TREE_IDS: Index = { - arbitrum: '', + arbitrum: '0x00000045', sepolia: '0x000002d9', }