Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Final Report Submission #293

Merged
merged 45 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d139a6e
Started working on final report editor and submission page
namedotget Dec 11, 2024
5384148
Merged
namedotget Dec 18, 2024
5111851
Added final report submission
namedotget Dec 19, 2024
5d1cfef
Merge branch 'main' of https://github.com/Official-MoonDao/MoonDAO in…
namedotget Dec 23, 2024
797fff6
Updated frontend to support updated projects contract
namedotget Dec 27, 2024
1b26e31
Merge branch 'main' of https://github.com/Official-MoonDao/MoonDAO in…
namedotget Dec 30, 2024
41585a3
Adjusted styling
namedotget Dec 31, 2024
8c0b0e4
Refactor
namedotget Dec 31, 2024
c5b888b
Added note w/ hat tree links
namedotget Dec 31, 2024
5b31d2f
Started adding project pages, wip
namedotget Dec 31, 2024
47ea56c
wip
namedotget Jan 3, 2025
0b5a32f
Refactor section card
namedotget Jan 3, 2025
ba77419
Started implementing new design for /rewards
namedotget Jan 3, 2025
d625553
wip
namedotget Jan 6, 2025
11624f2
Merged ng-retro
namedotget Jan 6, 2025
63778bb
Refactor
namedotget Jan 6, 2025
2369db2
Added and updated abis
namedotget Jan 6, 2025
145a159
Added project data to useProjectData
namedotget Jan 6, 2025
1af7f25
Added assets'
namedotget Jan 6, 2025
7125345
Adjusted project profile and projects page, added project data
namedotget Jan 6, 2025
5790697
Fixed input unfocus bug
namedotget Jan 7, 2025
5fe73ac
Added project addresses for sepolia
namedotget Jan 7, 2025
d13264b
Refactored to pull project data from tableland entry rather than nft
namedotget Jan 7, 2025
cf97900
Removed unused import
namedotget Jan 7, 2025
cf9fdf0
Retroactive Rewards
namedotget Jan 7, 2025
312f731
Removed outline on focus for Search
namedotget Jan 7, 2025
c6d5913
Added isActive check for projects
namedotget Jan 7, 2025
0c785ce
Added project hat tree id for sepolia
namedotget Jan 7, 2025
4b15204
Added isActive check for project data
namedotget Jan 7, 2025
2bf76ac
Reconfigured project pages to use a tableland entry instead of an NFT
namedotget Jan 7, 2025
f98695c
Merged main
namedotget Jan 7, 2025
dcdb7d8
Merged main
namedotget Jan 8, 2025
5173533
Refactor
namedotget Jan 8, 2025
32cd35a
Final Report Editor
namedotget Jan 8, 2025
1fdd5d8
Added isActive check
namedotget Jan 8, 2025
d1e8b98
Added redirect for /projects => /project
namedotget Jan 8, 2025
dbab4fc
Changed 'Inactive' tab label to 'Past'
namedotget Jan 8, 2025
ea58042
Merge branch 'ng-projects' into ng-final-report-submission
namedotget Jan 8, 2025
fff515b
Merge branch 'main' of https://github.com/Official-MoonDao/MoonDAO in…
namedotget Jan 8, 2025
ded8d38
Removed report page and added redirect
namedotget Jan 8, 2025
ccd649d
Removed log
namedotget Jan 8, 2025
59b48ab
Cleanup
namedotget Jan 8, 2025
f8095d8
Updated final report template
namedotget Jan 8, 2025
f89055d
Added project addresses for arb and arb projects hat tree id
namedotget Jan 8, 2025
5ab4a5c
Merged main
namedotget Jan 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 241 additions & 0 deletions ui/components/nance/FinalReportEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
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 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, 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 { 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'
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 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

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: () => <LoadingSpinner />,
}
)

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<SignStatus>('idle')

const { contract: projectsTableContact } = useContract(
PROJECT_TABLE_ADDRESSES[selectedChain.slug],
ProjectTableABI
)

const [{ proposalId }, setQuery] = useQueryParams({ proposalId: StringParam })
const shouldFetch = !!proposalId
const { data } = useProposal(
{ space: NANCE_SPACE_NAME, uuid: proposalId! },
shouldFetch
)
const [loadedProposal, setLoadedProposal] = useState<any>(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<Project | undefined>()
const { isManager } = useProjectData(
projectContract,
hatsContract,
selectedProject
)

useEffect(() => {
if (projectsWithoutReport) {
setSelectedProject(
projectsWithoutReport.find((p) => p.MDP === loadedProposal?.proposalId)
)
}
}, [projectsWithoutReport, loadedProposal])

const methods = useForm<RequestBudget>({
mode: 'onBlur',
})
const { handleSubmit } = methods

const onSubmit: SubmitHandler<RequestBudget> = async (formData) => {
console.debug('formData', formData)

if (!reportTitle || !loadedProposal) {
return toast.error('Please select a project that you are a manager of.', {
style: toastStyle,
})
}

try {
const markdown = getMarkdown()
if (!markdown) {
throw new Error('No markdown found')
}
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(file)

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()
const project = projectData[0]

await projectsTableContact?.call('updateFinalReportIPFS', [
project.id,
'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' || !isManager || !selectedProject

const setProposalId = function (id: string) {
setQuery({ proposalId: id })
}

return (
<div className="flex flex-col justify-center items-start animate-fadeIn w-[90vw] md:w-full">
<Head title="Final Report Editor" />

<div className="pt-2 w-full md:max-w-[1200px]">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="w-full py-0 rounded-[20px] flex justify-end">
<ProposalTitleInput
value={reportTitle}
onChange={(s) => {
console.debug('setReportTitle', s)
}}
/>
<div className="flex flex-col gap-2">
<ProjectsDropdown
projects={projectsWithoutReport}
setProposalId={setProposalId}
selectedProject={selectedProject}
setSelectedProject={setSelectedProject}
/>
<EditorMarkdownUpload setMarkdown={setMarkdown} />
</div>
</div>
<div className="pt-0 rounded-t-[20px] rounded-b-[0px] bg-dark-cool">
<NanceEditor
initialValue={FINAL_REPORT_TEMPLATE}
fileUploadExternal={async (val) => {
const res = await pinBlobOrFile(val)
return res.url
}}
darkMode={true}
onEditorChange={(m) => {}}
/>
</div>

<div className="p-5 rounded-b-[20px] rounded-t-[0px] bg-dark-cool"></div>

<div className="mt-3 flex justify-end">
{/* Submit buttons */}
<div className="flex justify-end space-x-5">
{/* SUBMIT */}
<button
type="submit"
className={classNames(
buttonsDisabled && 'tooltip',
'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 || !projectsTableContact}
data-tip={
!selectedProject
? 'Please select a project.'
: !isManager
? 'You are not a manager.'
: signingStatus === 'loading'
? 'Signing...'
: 'You need to connect wallet first.'
}
>
{signingStatus === 'loading' ? 'Signing...' : 'Submit'}
</button>
</div>
</div>
</form>
</div>
</div>
)
}
7 changes: 4 additions & 3 deletions ui/components/nance/ProposalEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
getActionsFromBody,
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'
Expand Down Expand Up @@ -75,6 +75,7 @@ export type ProposalCache = {

export default function ProposalEditor() {
const router = useRouter()
const address = useAddress()

const [signingStatus, setSigningStatus] = useState<SignStatus>('idle')
const [attachBudget, setAttachBudget] = useState<boolean>(false)
Expand Down Expand Up @@ -141,7 +142,7 @@ export default function ProposalEditor() {
let proposal = buildProposal(proposalStatus)

if (attachBudget) {
const uuid = uuidGen();
const uuid = uuidGen()
const action: Action = {
type: 'Request Budget',
payload: formData,
Expand All @@ -166,7 +167,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 {
Expand Down
76 changes: 76 additions & 0 deletions ui/components/project/ProjectsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
Menu,
MenuButton,
MenuItem,
MenuItems,
Transition,
} from '@headlessui/react'
import { EllipsisVerticalIcon } from '@heroicons/react/24/outline'
import { Fragment } from 'react'
import { Project } from '@/lib/project/useProjectData'

type ProjectsDropdownProps = {
projects: Project[] | undefined
selectedProject: Project | undefined
setSelectedProject: (project: Project) => void
setProposalId?: (id: string) => void
}

export default function ProjectsDropdown({
projects,
selectedProject,
setSelectedProject,
setProposalId,
}: ProjectsDropdownProps) {
return (
<>
<Menu as="div" className="relative inline-block">
<MenuButton className="w-full">
<div className="inline-flex w-full justify-end rounded-md sm:hidden">
<EllipsisVerticalIcon
className="h-7 w-7 text-indigo-600"
aria-hidden="true"
/>
</div>

<div className="hidden w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-1 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:inline-flex">
{selectedProject?.name || 'Select Project'}
</div>
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems className="absolute z-20 right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black/5 focus:outline-none">
<div className="px-1 py-1">
<MenuItem>
{({ focus }) => (
<div className="flex flex-col gap-2 overflow-y-scroll">
{projects?.map((aP: any) => (
<button
key={aP.id}
className="text-sm text-black"
type="button"
onClick={() => {
setSelectedProject(aP)
setProposalId && setProposalId(aP.MDP)
}}
>
{aP.name}
</button>
))}
</div>
)}
</MenuItem>
</div>
</MenuItems>
</Transition>
</Menu>
</>
)
}
Loading
Loading