diff --git a/.example.env b/.example.env index e0553bc825..aa4a6aaff2 100644 --- a/.example.env +++ b/.example.env @@ -62,6 +62,3 @@ DB_PROJECTS_WEBHOOK_URL= # Discord Webhook URL for /contact page. CONTACT_WEBHOOK_URL= -# Github access token (for project archival requests) -GITHUB_ACCESS_TOKEN= - diff --git a/src/components/Project/ArchiveProject.tsx b/src/components/Project/ArchiveProject.tsx index aa1964dd5e..7c279b6104 100644 --- a/src/components/Project/ArchiveProject.tsx +++ b/src/components/Project/ArchiveProject.tsx @@ -1,6 +1,5 @@ import { t, Trans } from '@lingui/macro' import { Button, Statistic } from 'antd' -import axios from 'axios' import { Callout } from 'components/Callout/Callout' import { PV_V1, PV_V2 } from 'constants/pv' import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' @@ -65,17 +64,6 @@ export function ArchiveProject({ return emitErrorNotification(t`Failed to update project metadata`) } - // Create github issue when archive is requested - // https://docs.github.com/en/rest/reference/issues#create-an-issue - // Do this first, in case the user closes the page before the on-chain tx completes - axios.post(`/api/github/archive-project`, { - archived, - projectId, - projectMetadata, - handle, - pv, - }) - const txSuccessful = await storeCidTx( { cid: uploadedMetadata.Hash }, { @@ -87,8 +75,9 @@ export function ArchiveProject({ }, ) if (!txSuccessful) { - emitErrorNotification(t`Transaction unsuccessful`) + emitErrorNotification(t`Failed to update project metadata`) setIsLoadingArchive(false) + return } } diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/ProjectHeader.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/ProjectHeader.tsx index eaebd7d238..10eba86717 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/ProjectHeader.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/ProjectHeader.tsx @@ -1,6 +1,7 @@ import { Cog6ToothIcon } from '@heroicons/react/24/outline' import { Trans } from '@lingui/macro' import { Button, Divider } from 'antd' +import { Badge } from 'components/Badge' import { DomainBadge } from 'components/DomainBadge' import EthereumAddress from 'components/EthereumAddress' import { GnosisSafeBadge } from 'components/Project/ProjectHeader/GnosisSafeBadge' @@ -23,8 +24,16 @@ import { Subtitle } from './components/Subtitle' import ToolsDrawerButton from './components/ToolsDrawerButton' export const ProjectHeader = ({ className }: { className?: string }) => { - const { title, subtitle, domain, projectId, handle, owner, gnosisSafe } = - useProjectHeader() + const { + title, + subtitle, + domain, + projectId, + handle, + owner, + gnosisSafe, + archived, + } = useProjectHeader() const isMobile = useMobile() const canReconfigure = useV2V3WalletHasPermission( V2V3OperatorPermission.RECONFIGURE, @@ -72,7 +81,9 @@ export const ProjectHeader = ({ className }: { className?: string }) => { - +
+ {archived ? Archived : null} +
{subtitle && diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/components/ProjectHeaderStats.test.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/components/ProjectHeaderStats.test.tsx index 074b1e1635..a50d263526 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/components/ProjectHeaderStats.test.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectHeader/components/ProjectHeaderStats.test.tsx @@ -36,6 +36,7 @@ const MOCK_PROJECT_HEADER_DATA: ProjectHeaderData = { totalVolume: BigNumber.from(420), last7DaysPercent: 69, gnosisSafe: undefined, + archived: false, } describe('ProjectHeaderStats', () => { diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectHeader.ts b/src/components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectHeader.ts index 4faa19d2d9..e60b2e8452 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectHeader.ts +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectHeader.ts @@ -18,6 +18,7 @@ export interface ProjectHeaderData { totalVolume: BigNumber | undefined last7DaysPercent: number gnosisSafe: GnosisSafe | undefined | null + archived: boolean | undefined } export const useProjectHeader = (): ProjectHeaderData => { @@ -48,5 +49,6 @@ export const useProjectHeader = (): ProjectHeaderData => { totalVolume, last7DaysPercent, gnosisSafe, + archived: projectMetadata?.archived, } } diff --git a/src/hooks/v2v3/usePayProjectDisabled.ts b/src/hooks/v2v3/usePayProjectDisabled.ts index 7e208af91d..7a7fe1fe6d 100644 --- a/src/hooks/v2v3/usePayProjectDisabled.ts +++ b/src/hooks/v2v3/usePayProjectDisabled.ts @@ -1,6 +1,7 @@ import { t } from '@lingui/macro' import { useProjectContext } from 'components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectContext' import { useProjectIsOFACListed } from 'components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectIsOFACListed' +import { useProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' import { useV2V3BlockedProject } from './useBlockedProject' export enum PayDisabledReason { @@ -23,6 +24,7 @@ export function usePayProjectDisabled(): { reason: PayDisabledReason | undefined message: string | undefined } { + const { projectMetadata } = useProjectMetadataContext() const { fundingCycleMetadata, loading } = useProjectContext() const isBlockedProject = useV2V3BlockedProject() const { isAddressListedInOFAC, isLoading: isOFACLoading } = @@ -61,6 +63,14 @@ export function usePayProjectDisabled(): { } } + if (projectMetadata?.archived) { + return { + ...disabled, + reason: PayDisabledReason.BLOCKED, + message: t`This project has been archived and can't be paid.`, + } + } + return { payDisabled: false, loading: false, diff --git a/src/lib/api/supabase/projects/logger.ts b/src/lib/api/supabase/projects/logger.ts index 0e3f6e3f38..7dcd9ca293 100644 --- a/src/lib/api/supabase/projects/logger.ts +++ b/src/lib/api/supabase/projects/logger.ts @@ -35,7 +35,7 @@ export async function dbpLog( // log the error to the console if (type === 'alert') { - logger.error({ data: { type, message: DBP_ALERTS[opts.alert] } }) + logger.error({ data: { type, message: DBP_ALERTS[opts.alert], body } }) } else { logger.info({ data: { type, message: DBP_NOTIFS[opts.notif] } }) } diff --git a/src/locales/messages.pot b/src/locales/messages.pot index d341278835..bf6cbd5707 100644 --- a/src/locales/messages.pot +++ b/src/locales/messages.pot @@ -200,6 +200,9 @@ msgstr "" msgid "Juicebox loading animation" msgstr "" +msgid "This project has been archived and can't be paid." +msgstr "" + msgid "Payments to this project paused" msgstr "" @@ -4691,9 +4694,6 @@ msgstr "" msgid "Potential risks" msgstr "" -msgid "Transaction unsuccessful" -msgstr "" - msgid "All-in-one crowdfunding with powerful treasury management and redemptions." msgstr "" diff --git a/src/pages/api/github/archive-project.ts b/src/pages/api/github/archive-project.ts deleted file mode 100644 index 1baddf4fe6..0000000000 --- a/src/pages/api/github/archive-project.ts +++ /dev/null @@ -1,82 +0,0 @@ -import axios from 'axios' -import { AnyProjectMetadata } from 'models/projectMetadata' -import { NextApiRequest, NextApiResponse } from 'next' - -import { readNetwork } from 'constants/networks' -import { PV_V2 } from 'constants/pv' -import { PV } from 'models/pv' - -interface ArchiveProjectNextApiRequest extends NextApiRequest { - body: { - archived: boolean - projectId: number | undefined - metadata: AnyProjectMetadata | undefined - handle: string | undefined - pv: PV - } -} - -const handler = async ( - req: ArchiveProjectNextApiRequest, - res: NextApiResponse, -) => { - const githubToken = process.env.GITHUB_ACCESS_TOKEN - if (!githubToken) { - res.status(500).json({ - error: 'GITHUB_ACCESS_TOKEN is not set', - }) - return - } - - try { - const { archived, projectId, metadata, handle, pv } = req.body - - if (!projectId || !metadata || !handle) { - throw new Error() - } - - const headers = { - Authorization: `Bearer ${githubToken}`, - } - - const title = `[${archived ? 'ARCHIVE' : 'UNARCHIVE'}] Project: "${ - metadata.name - }"` - - const body = ` - Chain: ${readNetwork.name} - Project ID: ${projectId} - ${handle ? `Handle: ${handle}` : ''} - ` - - const labels = [ - 'archive request', - pv === PV_V2 ? 'V2' : 'V1', - 'bot', - readNetwork.name === 'goerli' ? 'site:goerli' : undefined, - readNetwork.name === 'mainnet' ? 'site:mainnet' : undefined, - ].filter(Boolean) - - const data = { - title, - body, - labels, - } - - const response = await axios.post( - 'https://api.github.com/repos/jbx-protocol/juice-interface/issues', - data, - { - headers, - }, - ) - - res.status(200).json({ - response, - }) - } catch (error) { - return res.status(500) - } -} - -export default handler diff --git a/src/pages/api/ipfs/[cid].ts b/src/pages/api/ipfs/[cid].ts index 928c7bebcf..1f54baf3b0 100644 --- a/src/pages/api/ipfs/[cid].ts +++ b/src/pages/api/ipfs/[cid].ts @@ -8,6 +8,9 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { + if (!req) { + res.status(500).end() + } if (req.method !== 'GET') { return res.status(405).end() } diff --git a/src/pages/api/juicebox/pv/[pv]/project/[projectId]/refreshMetadata.ts b/src/pages/api/juicebox/pv/[pv]/project/[projectId]/refreshMetadata.ts new file mode 100644 index 0000000000..76cd15edef --- /dev/null +++ b/src/pages/api/juicebox/pv/[pv]/project/[projectId]/refreshMetadata.ts @@ -0,0 +1,33 @@ +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' +import { Database } from 'types/database.types' +import { getProjectMetadata } from 'utils/server/metadata' + +/** + * Force refresh a given project's metadata in the database. + */ +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method !== 'PUT') { + return res.status(405).end() + } + + try { + const { projectId, pv } = req.query + if (!projectId || !pv) { + return res.status(400).json({ error: 'projectId is required' }) + } + + const supabase = createServerSupabaseClient({ req, res }) + + const metadata = await getProjectMetadata(projectId as string) + await supabase + .from('projects') + .update({ archived: metadata?.archived }) // TODO add more + .eq('id', projectId) + + return res.status(204).end() + } catch (error) { + return res.status(500).end() + } +} +export default handler diff --git a/src/pages/api/nextjs/revalidate-project.ts b/src/pages/api/nextjs/revalidate-project.ts index 46781989d6..c496c65dc2 100644 --- a/src/pages/api/nextjs/revalidate-project.ts +++ b/src/pages/api/nextjs/revalidate-project.ts @@ -43,7 +43,7 @@ export default async function handler( // Update database projects whenever a project needs revalidating. However, database will only get updated if the new project data is already available in the subgraph, which can sometimes take a couple minutes await axios - .get('/api/projects/update') + .get(`${process.env.NEXT_PUBLIC_BASE_URL}api/projects/update`) // can throw error when env isnt set correctly .catch(err => console.error('Database projects update failed', err)) diff --git a/src/pages/api/projects/update-retry-ipfs.ts b/src/pages/api/projects/update-retry-ipfs.ts deleted file mode 100644 index 995833c541..0000000000 --- a/src/pages/api/projects/update-retry-ipfs.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { updateDBProjects } from 'lib/api/supabase/projects' -import { NextApiHandler } from 'next' - -// Synchronizes the Sepana engine with the latest Juicebox Subgraph/IPFS data -const handler: NextApiHandler = async (_, res) => { - await updateDBProjects(res, true) -} - -export default handler