From b7d3fbff6c36471f502cbc96ebc76658d1c6ae1f Mon Sep 17 00:00:00 2001 From: aeolian <94939382+aeolianeth@users.noreply.github.com> Date: Tue, 11 Jun 2024 07:44:35 +1000 Subject: [PATCH] refactor: nuke project updates (#4354) --- codegen.yml | 2 +- scripts/graphql-codegen-override-pv.js | 2 +- .../ProjectDashboard/ProjectDashboard.tsx | 5 +- .../AddProjectUpdateModal.tsx | 267 ------------------ .../components/ProjectTabs/ProjectTabs.tsx | 14 - .../ProjectUpdatesProvider.tsx | 42 --- .../hooks/useFactoredProjectId.ts | 12 - .../hooks/useProjectUpdates.ts | 44 --- .../utils/projectUpdatesReducer.ts | 40 --- src/locales/messages.pot | 18 -- .../[projectId]/updates/[projectUpdateId].ts | 74 ----- .../api/projects/[projectId]/updates/index.ts | 86 ------ .../authenticateProjectUpdateOperation.ts | 45 --- 13 files changed, 3 insertions(+), 648 deletions(-) delete mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/AddProjectUpdateModal/AddProjectUpdateModal.tsx delete mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/ProjectUpdatesProvider.tsx delete mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useFactoredProjectId.ts delete mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useProjectUpdates.ts delete mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/utils/projectUpdatesReducer.ts delete mode 100644 src/pages/api/projects/[projectId]/updates/[projectUpdateId].ts delete mode 100644 src/pages/api/projects/[projectId]/updates/index.ts delete mode 100644 src/utils/authenticateProjectUpdateOperation.ts diff --git a/codegen.yml b/codegen.yml index 826d04611f..ab9d9b7d1b 100644 --- a/codegen.yml +++ b/codegen.yml @@ -2,7 +2,7 @@ overwrite: true schema: ${SUBGRAPH_URL} documents: 'src/graphql/**/*.graphql' generates: - src/generated/graphql.tsx: + src/generated/graphql.ts: plugins: - 'typescript' - 'typescript-operations' diff --git a/scripts/graphql-codegen-override-pv.js b/scripts/graphql-codegen-override-pv.js index ec4d23770f..1e48043632 100644 --- a/scripts/graphql-codegen-override-pv.js +++ b/scripts/graphql-codegen-override-pv.js @@ -1,6 +1,6 @@ const fs = require('fs') -const path = __dirname + '/../src/generated/graphql.tsx' +const path = __dirname + '/../src/generated/graphql.ts' try { // Load generated file diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/ProjectDashboard.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/ProjectDashboard.tsx index a8244f2362..c03f0e3ec7 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/ProjectDashboard.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/ProjectDashboard.tsx @@ -10,7 +10,6 @@ import { PayRedeemCard } from './components/PayRedeemCard' import { ProjectHeader } from './components/ProjectHeader/ProjectHeader' import { ProjectHeaderCountdown } from './components/ProjectHeaderCountdown' import { ProjectTabs } from './components/ProjectTabs/ProjectTabs' -import { ProjectUpdatesProvider } from './components/ProjectUpdatesProvider/ProjectUpdatesProvider' import { ReduxProjectCartProvider } from './components/ReduxProjectCartProvider' import { SuccessPayView } from './components/SuccessPayView/SuccessPayView' import { useProjectPageQueries } from './hooks/useProjectPageQueries' @@ -72,9 +71,7 @@ const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { - - {children} - + {children} diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/AddProjectUpdateModal/AddProjectUpdateModal.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/AddProjectUpdateModal/AddProjectUpdateModal.tsx deleted file mode 100644 index 4efa225318..0000000000 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/AddProjectUpdateModal/AddProjectUpdateModal.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import { PhotoIcon, XCircleIcon } from '@heroicons/react/24/outline' -import { Trans, t } from '@lingui/macro' -import { Button } from 'antd' -import axios from 'axios' -import ExternalLink from 'components/ExternalLink' -import { JuiceVideoThumbnailOrImage } from 'components/JuiceVideo/JuiceVideoThumbnailOrImage' -import Loading from 'components/Loading' -import { JuiceModal } from 'components/modals/JuiceModal' -import { PV_V2 } from 'constants/pv' -import { useProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' -import { Formik } from 'formik' -import { useIpfsFilePicker } from 'hooks/useIpfsFilePicker/useIpfsFilePicker' -import { useContext } from 'react' -import { twMerge } from 'tailwind-merge' -import { getSubgraphIdForProject } from 'utils/graph' -import * as Yup from 'yup' -import { ProjectUpdatesContext } from '../ProjectUpdatesProvider/ProjectUpdatesProvider' - -const ValidationSchema = Yup.object().shape({ - title: Yup.string().required('Title is required').max(100, 'Too long'), - message: Yup.string().required('Message is required').max(3000, 'Too long'), - imageUrl: Yup.string().url('Invalid URL'), -}) -type AddProjectUpdateFormValues = Yup.InferType - -export const AddProjectUpdateModal = ({ - open, - setOpen, -}: { - open: boolean - setOpen: React.Dispatch> -}) => { - const { loadProjectUpdates } = useContext(ProjectUpdatesContext) - const { projectId } = useProjectMetadataContext() - const project = projectId - ? getSubgraphIdForProject(PV_V2, projectId) - : undefined - - if (!project) return - - return ( - - validationSchema={ValidationSchema} - initialValues={{ - title: '', - message: '', - }} - onSubmit={async (values, helper) => { - const { title, message, imageUrl } = values - await axios.post(`/api/projects/${project}/updates`, { - title, - message, - imageUrl, - }) - loadProjectUpdates() - setOpen(false) - setTimeout(() => { - helper.setSubmitting(false) - helper.resetForm() - }, 300) - }} - > - {props => ( - - { - setOpen(false) - }} - > - - - Add an update that will be posted to your project page. - - - - - - Title - - - - props.setFieldValue('title', e.currentTarget.value) - } - /> - - - {props.errors.title && props.touched.title && ( - - {props.errors.title} - - )} - - - - - - Message - - - - {props.values.message.length}/3000 - - - props.setFieldValue('message', e.currentTarget.value) - } - rows={4} - /> - - - {props.errors.message && props.touched.message && ( - - {props.errors.message} - - )} - - - - - props.setFieldValue('imageUrl', url)} - /> - {!props.values.imageUrl && ( - - - A horizontal banner image sized 288 x 556 is recommended. - - - )} - - {props.errors.imageUrl && props.touched.imageUrl && ( - - {props.errors.imageUrl} - - )} - - - - - - )} - - ) -} - -const UploadImage = ({ - onChange, -}: { - onChange: (value: string | undefined) => void -}) => { - const acceptedFileTypes = - 'image/jpeg,image/png,image/gif,video/mp4,video/quicktime,video/x-m4v,video/webm' - const { - uploadedUrl, - isUploading, - selectedFile, - uploadProgress, - FileInput, - openFilePicker, - cancelUpload, - removeFile, - } = useIpfsFilePicker({ - accept: acceptedFileTypes, - onFileUrlChange: url => onChange(url), - }) - - return ( - <> - {FileInput} - {isUploading ? ( - - - - - - - - - - - - - ) : uploadedUrl && selectedFile ? ( - - - - - - - - Uploaded to: - - - - ) : ( - } - onClick={openFilePicker} - > - Add banner image - - )} - > - ) -} diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectTabs/ProjectTabs.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectTabs/ProjectTabs.tsx index 2be343ed89..39a2b6812b 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectTabs/ProjectTabs.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectTabs/ProjectTabs.tsx @@ -77,20 +77,6 @@ export const ProjectTabs = ({ className }: { className?: string }) => { panel: , }, { id: 'tokens', name: t`Tokens`, panel: }, - // disabled for now - // { - // id: 'updates', - // name: ( - // - // Updates - // {!!projectUpdates.length && ( - // - // )} - // - // ), - // panel: , - // hideTab: !isProjectOwner && projectUpdates.length === 0, - // }, ], [showNftRewards], ) diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/ProjectUpdatesProvider.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/ProjectUpdatesProvider.tsx deleted file mode 100644 index ec092dc4fe..0000000000 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/ProjectUpdatesProvider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { createContext, useEffect } from 'react' -import { useProjectUpdates } from './hooks/useProjectUpdates' - -export type ProjectUpdate = { - id: string - title: string - message: string - imageUrl: string | undefined - createdAt: Date - posterWallet: string -} - -type ProjectUpdatesContextType = ReturnType - -export const ProjectUpdatesContext = createContext({ - loading: false, - projectUpdates: [], - error: undefined, - loadProjectUpdates: async () => { - console.error('loadProjectUpdates was called before it was initialized') - }, -}) - -export const ProjectUpdatesProvider = ({ - children, -}: { - children: React.ReactNode -}) => { - const { loadProjectUpdates, ...projectUpdates } = useProjectUpdates() - - useEffect(() => { - loadProjectUpdates() - }, [loadProjectUpdates]) - - return ( - - {children} - - ) -} diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useFactoredProjectId.ts b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useFactoredProjectId.ts deleted file mode 100644 index 8fc9f0e53e..0000000000 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useFactoredProjectId.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PV_V2 } from 'constants/pv' -import { useProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' -import { getSubgraphIdForProject } from 'utils/graph' - -export const useFactoredProjectId = () => { - const { projectId } = useProjectMetadataContext() - const project = projectId - ? getSubgraphIdForProject(PV_V2, projectId) - : undefined - - return project -} diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useProjectUpdates.ts b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useProjectUpdates.ts deleted file mode 100644 index 80f3d9b2e0..0000000000 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/hooks/useProjectUpdates.ts +++ /dev/null @@ -1,44 +0,0 @@ -import axios from 'axios' -import { useCallback, useReducer } from 'react' -import { ProjectUpdate } from '../ProjectUpdatesProvider' -import { projectUpdatesReducer } from '../utils/projectUpdatesReducer' -import { useFactoredProjectId } from './useFactoredProjectId' - -export const useProjectUpdates = () => { - const projectId = useFactoredProjectId() - - const [state, dispatch] = useReducer(projectUpdatesReducer, { - projectUpdates: [], - loading: false, - error: undefined, - }) - - const loadProjectUpdates = useCallback(async () => { - if (!projectId) return - dispatch({ type: 'loading' }) - try { - const result = await axios.get<(ProjectUpdate & { createdAt: string })[]>( - `/api/projects/${projectId}/updates`, - ) - const projectUpdates = result.data - ? result.data.map(update => ({ - ...update, - createdAt: new Date(update.createdAt), - })) - : [] - dispatch({ - type: 'success', - projectUpdates, - }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - console.error(e) - dispatch({ type: 'error', error: e.message }) - } - }, [projectId]) - - return { - ...state, - loadProjectUpdates, - } -} diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/utils/projectUpdatesReducer.ts b/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/utils/projectUpdatesReducer.ts deleted file mode 100644 index 2837abb272..0000000000 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/ProjectUpdatesProvider/utils/projectUpdatesReducer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ProjectUpdate } from '../ProjectUpdatesProvider' - -type ProjectUpdatesReducerState = { - projectUpdates: ProjectUpdate[] - loading: boolean - error: string | undefined -} - -type ProjectUpdatesReducerAction = - | { type: 'loading' } - | { type: 'error'; error: string } - | { type: 'success'; projectUpdates: ProjectUpdate[] } - -export const projectUpdatesReducer = ( - state: ProjectUpdatesReducerState, - action: ProjectUpdatesReducerAction, -): ProjectUpdatesReducerState => { - switch (action.type) { - case 'loading': - return { - ...state, - loading: true, - error: undefined, - } - case 'error': - return { - ...state, - loading: false, - error: action.error, - } - case 'success': - return { - ...state, - loading: false, - projectUpdates: action.projectUpdates, - } - default: - return state - } -} diff --git a/src/locales/messages.pot b/src/locales/messages.pot index 1c75efba5d..fd20093453 100644 --- a/src/locales/messages.pot +++ b/src/locales/messages.pot @@ -665,9 +665,6 @@ msgstr "" msgid "No deadline" msgstr "" -msgid "Add an update that will be posted to your project page." -msgstr "" - msgid "Change networks to deploy" msgstr "" @@ -1544,9 +1541,6 @@ msgstr "" msgid "Your NFTs & Rewards" msgstr "" -msgid "Add update" -msgstr "" - msgid "Send a portion of your project's reserved tokens to other Ethereum wallets or Juicebox projects." msgstr "" @@ -1646,9 +1640,6 @@ msgstr "" msgid "Migrate tokens" msgstr "" -msgid "Title" -msgstr "" - msgid "Link minters of this NFT to your project's website, Discord, etc." msgstr "" @@ -1670,9 +1661,6 @@ msgstr "" msgid "Not set" msgstr "" -msgid "Add project update" -msgstr "" - msgid "Recipient address" msgstr "" @@ -1958,9 +1946,6 @@ msgstr "" msgid "Current balance" msgstr "" -msgid "Add banner image" -msgstr "" - msgid "ERC-20 tokens can only be issued once an ERC-20 token has been created for this project." msgstr "" @@ -4463,9 +4448,6 @@ msgstr "" msgid "Project contracts directory" msgstr "" -msgid "A horizontal banner image sized 288 x 556 is recommended." -msgstr "" - msgid "Save profile details" msgstr "" diff --git a/src/pages/api/projects/[projectId]/updates/[projectUpdateId].ts b/src/pages/api/projects/[projectId]/updates/[projectUpdateId].ts deleted file mode 100644 index e592d50fea..0000000000 --- a/src/pages/api/projects/[projectId]/updates/[projectUpdateId].ts +++ /dev/null @@ -1,74 +0,0 @@ -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { sudoPublicDbClient } from 'lib/api/supabase/clients' -import { NextApiRequest, NextApiResponse } from 'next' -import { Database } from 'types/database.types' -import { authenticateProjectUpdateOperation } from 'utils/authenticateProjectUpdateOperation' - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const session = await sudoPublicDbClient.auth.getSession() - if (!session) return res.status(401).json({ message: 'Unauthorized.' }) - - try { - switch (req.method) { - case 'GET': - return await GET(req, res) - case 'DELETE': - return await DELETE(req, res) - default: { - return res.status(405).json({ message: 'Method not allowed.' }) - } - } - } catch (e) { - console.error('Error occurred', e) - return res.status(500).json({ message: 'Internal server error occurred.' }) - } -} - -const GET = async (req: NextApiRequest, res: NextApiResponse) => { - const supabase = createServerSupabaseClient({ req, res }) - const result = await supabase - .from('project_updates') - .select('*,users (wallet)') - .eq('project', req.query.projectId) - .eq('id', req.query.projectUpdateId) - .maybeSingle() - if (result.error) { - console.error('Error occurred', result.error) - if (result.error.code === '42P01') { - return res.status(404).json({ message: 'Project not found.' }) - } - return res.status(500).json({ message: 'Internal server error occurred.' }) - } - if (!result.data) { - return res.status(404).json({ message: 'Project update not found.' }) - } - return res.status(200).json({ - createdAt: result.data.created_at, - id: result.data.id, - imageUrl: result.data.image_url, - message: result.data.message, - posterWallet: (result.data.users as { wallet: string }).wallet, - project: result.data.project, - title: result.data.title, - }) -} - -const DELETE = async (req: NextApiRequest, res: NextApiResponse) => { - const session = await authenticateProjectUpdateOperation(req, res) - if (!session) return - - const { error } = await sudoPublicDbClient - .from('project_updates') - .delete() - .eq('id', req.query.projectUpdateId) - .eq('project', req.query.projectId) - - if (error) { - console.error('Error occurred', error) - return res.status(500).json({ message: 'Internal server error occurred.' }) - } - - return res.status(200).json({ message: 'Success.' }) -} - -export default handler diff --git a/src/pages/api/projects/[projectId]/updates/index.ts b/src/pages/api/projects/[projectId]/updates/index.ts deleted file mode 100644 index ce8680bb9d..0000000000 --- a/src/pages/api/projects/[projectId]/updates/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { sudoPublicDbClient } from 'lib/api/supabase/clients' -import { NextApiRequest, NextApiResponse } from 'next' -import { Database } from 'types/database.types' -import { authenticateProjectUpdateOperation } from 'utils/authenticateProjectUpdateOperation' -import * as Yup from 'yup' - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const session = await sudoPublicDbClient.auth.getSession() - if (!session) return res.status(401).json({ message: 'Unauthorized.' }) - - try { - switch (req.method) { - case 'POST': - return await POST(req, res) - case 'GET': - return await GET(req, res) - default: { - return res.status(405).json({ message: 'Method not allowed.' }) - } - } - } catch (e) { - console.error('Error occurred', e) - return res.status(500).json({ message: 'Internal server error occurred.' }) - } -} - -const GET = async (req: NextApiRequest, res: NextApiResponse) => { - const supabase = createServerSupabaseClient({ req, res }) - const result = await supabase - .from('project_updates') - .select('*,user_profiles (wallet)') - .eq('project', req.query.projectId) - .order('created_at', { ascending: false }) - if (result.error) { - console.error('Error occurred', result.error) - if (result.error.code === '42P01') { - return res.status(404).json({ message: 'Project not found.' }) - } - return res.status(500).json({ message: 'Internal server error occurred.' }) - } - return res.status(200).json( - result.data.map(d => ({ - createdAt: d.created_at, - id: d.id, - imageUrl: d.image_url, - message: d.message, - posterWallet: (d.user_profiles as { wallet: string }).wallet, - project: d.project, - title: d.title, - })), - ) -} - -const POSTSchema = Yup.object().shape({ - title: Yup.string().required('Title is required').max(100, 'Too long'), - message: Yup.string().required('Message is required').max(3000, 'Too long'), - imageUrl: Yup.string().url('Invalid URL'), -}) - -const POST = async (req: NextApiRequest, res: NextApiResponse) => { - const session = await authenticateProjectUpdateOperation(req, res) - if (!session) return - - const result = await POSTSchema.validate(req.body) - const { title, message, imageUrl } = result - const { projectId } = req.query as { projectId: string } - if (!projectId) - return res.status(400).json({ message: 'Project ID is required' }) - - const { error } = await sudoPublicDbClient.from('project_updates').insert({ - title, - message, - image_url: imageUrl, - project: projectId, - poster_user_id: session.user.id, - }) - if (error) { - console.error('Error occurred', error) - return res.status(500).json({ message: 'Internal server error occurred.' }) - } - - return res.status(200).json('Success') -} - -export default handler diff --git a/src/utils/authenticateProjectUpdateOperation.ts b/src/utils/authenticateProjectUpdateOperation.ts deleted file mode 100644 index da81556470..0000000000 --- a/src/utils/authenticateProjectUpdateOperation.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { sudoPublicDbClient } from 'lib/api/supabase/clients' -import { NextApiRequest, NextApiResponse } from 'next' -import { authenticateUserApiCall } from 'server/auth' -import { isEqualAddress } from 'utils/address' - -/** - * Authenticate project update operation. - * Not be confused with updating a project - this is explicitly for CUD operations on project updates. - * - * Checks if the user is authenticated, and if the user is the owner of the project. - */ -export const authenticateProjectUpdateOperation = async ( - req: NextApiRequest, - res: NextApiResponse, -) => { - const session = await authenticateUserApiCall(req, res) - if (!session) return false - - const projectResult = await sudoPublicDbClient - .from('projects') - .select() - .eq('id', req.query.projectId) - .single() - - if (projectResult.error) { - console.error('Error occurred', projectResult.error) - return res.status(500).json({ message: 'Internal server error occurred.' }) - } - - const userResult = await sudoPublicDbClient - .from('users') - .select() - .eq('id', session.user.id) - .single() - if (userResult.error) { - console.error('Error occurred', userResult.error) - return res.status(500).json({ message: 'Internal server error occurred.' }) - } - - if (!isEqualAddress(userResult.data.wallet, projectResult.data.owner)) { - return res.status(401).json({ message: 'Unauthorized.' }) - } - - return session -}