diff --git a/pages/projects/[slug].tsx b/pages/projects/[slug].tsx index 0188c50efd..e502ae6c9a 100644 --- a/pages/projects/[slug].tsx +++ b/pages/projects/[slug].tsx @@ -1,20 +1,16 @@ -import { GetServerSideProps } from 'next/types'; +import { GetStaticProps } from 'next/types'; import { IMainCategory, IProject, IQFRound } from '@/apollo/types/types'; -import { transformGraphQLErrorsToStatusCode } from '@/helpers/requests'; import { initializeApollo } from '@/apollo/apolloClient'; -import { OPTIONS_HOME_PROJECTS } from '@/apollo/gql/gqlOptions'; import { FETCH_ALL_PROJECTS, FETCH_MAIN_CATEGORIES, } from '@/apollo/gql/gqlProjects'; import { GeneralMetatags } from '@/components/Metatag'; import ProjectsIndex from '@/components/views/projects/ProjectsIndex'; -import { useReferral } from '@/hooks/useReferral'; import { projectsMetatags } from '@/content/metatags'; import { ProjectsProvider } from '@/context/projects.context'; import { FETCH_QF_ROUNDS } from '@/apollo/gql/gqlQF'; -import { getMainCategorySlug } from '@/helpers/projects'; -import { EProjectsSortBy } from '@/apollo/types/gqlEnums'; +import { OPTIONS_HOME_PROJECTS } from '@/apollo/gql/gqlOptions'; export interface IProjectsRouteProps { projects: IProject[]; @@ -45,8 +41,6 @@ const ProjectsCategoriesRoute = (props: IProjectsCategoriesRouteProps) => { qfRounds, } = props; - useReferral(); - return ( { ); }; -export const getServerSideProps: GetServerSideProps = async context => { +// This function gets called at build time +export async function getStaticPaths() { + const apolloClient = initializeApollo(); + + // Call an external API endpoint to get posts + const { data } = await apolloClient.query({ + query: FETCH_MAIN_CATEGORIES, + fetchPolicy: 'no-cache', // Adjust based on your caching policy needs + }); + + const mainCategories = data.mainCategories as IMainCategory[]; + + // Get the paths we want to pre-render based on posts + const _paths = mainCategories.map(category => ({ + params: { slug: category.slug }, + })); + + const paths = [{ params: { slug: 'all' } }, ..._paths]; + + // We'll pre-render only these paths at build time. + // { fallback: false } means other routes should 404. + return { paths, fallback: false }; +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { const apolloClient = initializeApollo(); - const { variables, notifyOnNetworkStatusChange } = OPTIONS_HOME_PROJECTS; + const { variables } = OPTIONS_HOME_PROJECTS; + try { - const { query } = context; - const slug = query.slug; - - const { - data: { mainCategories }, - }: { - data: { mainCategories: IMainCategory[] }; - } = await apolloClient.query({ + // Fetch main categories + const { data: mainCategoriesData } = await apolloClient.query({ query: FETCH_MAIN_CATEGORIES, - fetchPolicy: 'network-only', + fetchPolicy: 'no-cache', // Adjust based on your caching policy needs + }); + + // Fetch projects with a predefined sorting + const { data: projectsData } = await apolloClient.query({ + query: FETCH_ALL_PROJECTS, + variables: { + ...variables, + mainCategory: params?.slug === 'all' ? null : params?.slug, + }, + fetchPolicy: 'no-cache', }); - const updatedMainCategory = [allCategoriesItem, ...mainCategories]; + // Fetch QF rounds + const { data: qfRoundsData } = await apolloClient.query({ + query: FETCH_QF_ROUNDS, + fetchPolicy: 'no-cache', + }); + + const updatedMainCategory = [ + allCategoriesItem, + ...mainCategoriesData.mainCategories, + ]; + const selectedMainCategory = updatedMainCategory.find(mainCategory => { - return mainCategory.slug === slug; + return mainCategory.slug === params?.slug; }); - if (selectedMainCategory) { - const updatedSelectedMainCategory = { - ...selectedMainCategory, - selected: true, - }; - const apolloClient = initializeApollo(); - const { data } = await apolloClient.query({ - query: FETCH_ALL_PROJECTS, - variables: { - ...variables, - sortingBy: query.sort || EProjectsSortBy.INSTANT_BOOSTING, - searchTerm: query.searchTerm, - filters: query.filter - ? Array.isArray(query.filter) - ? query.filter - : [query.filter] - : null, - campaignSlug: query.campaignSlug, - category: query.category, - mainCategory: getMainCategorySlug( - updatedSelectedMainCategory, - ), - notifyOnNetworkStatusChange, - }, - fetchPolicy: 'network-only', - }); - const { projects, totalCount } = data.allProjects; - const { - data: { qfRounds }, - } = await apolloClient.query({ - query: FETCH_QF_ROUNDS, - fetchPolicy: 'network-only', - }); - return { - props: { - projects, - mainCategories: updatedMainCategory, - selectedMainCategory: updatedSelectedMainCategory, - totalCount, - qfRounds, - }, - }; - } - return { - notFound: true, - }; - } catch (error: any) { - const statusCode = transformGraphQLErrorsToStatusCode( - error?.graphQLErrors, - ); return { props: { - errorStatus: statusCode, + projects: projectsData.allProjects.projects, + totalCount: projectsData.allProjects.totalCount, + mainCategories: updatedMainCategory, + qfRounds: qfRoundsData.qfRounds, + selectedMainCategory, }, + revalidate: 300, // Optionally, revalidate at most once per hour }; + } catch (error) { + console.error('Failed to fetch API:', error); + return { props: { hasError: true } }; } }; diff --git a/pages/qf/[slug].tsx b/pages/qf/[slug].tsx index 9221a38b10..91e6aa886f 100644 --- a/pages/qf/[slug].tsx +++ b/pages/qf/[slug].tsx @@ -1,8 +1,6 @@ -import { GetServerSideProps } from 'next/types'; +import { GetStaticProps } from 'next/types'; import { EProjectsFilter, IMainCategory } from '@/apollo/types/types'; -import { transformGraphQLErrorsToStatusCode } from '@/helpers/requests'; import { initializeApollo } from '@/apollo/apolloClient'; -import { OPTIONS_HOME_PROJECTS } from '@/apollo/gql/gqlOptions'; import { FETCH_ALL_PROJECTS, FETCH_MAIN_CATEGORIES, @@ -14,8 +12,7 @@ import { ProjectsProvider } from '@/context/projects.context'; import { FETCH_QF_ROUNDS } from '@/apollo/gql/gqlQF'; import { useReferral } from '@/hooks/useReferral'; import { IProjectsRouteProps, allCategoriesItem } from 'pages/projects/[slug]'; -import { getMainCategorySlug } from '@/helpers/projects'; -import { EProjectsSortBy } from '@/apollo/types/gqlEnums'; +import { OPTIONS_HOME_PROJECTS } from '@/apollo/gql/gqlOptions'; interface IProjectsCategoriesRouteProps extends IProjectsRouteProps { selectedMainCategory: IMainCategory; @@ -45,89 +42,79 @@ const QFProjectsCategoriesRoute = (props: IProjectsCategoriesRouteProps) => { ); }; -export const getServerSideProps: GetServerSideProps = async context => { +// This function gets called at build time +export async function getStaticPaths() { + const apolloClient = initializeApollo(); + + // Call an external API endpoint to get posts + const { data } = await apolloClient.query({ + query: FETCH_MAIN_CATEGORIES, + fetchPolicy: 'no-cache', // Adjust based on your caching policy needs + }); + + const mainCategories = data.mainCategories as IMainCategory[]; + + // Get the paths we want to pre-render based on posts + const _paths = mainCategories.map(category => ({ + params: { slug: category.slug }, + })); + + const paths = [{ params: { slug: 'all' } }, ..._paths]; + + // We'll pre-render only these paths at build time. + // { fallback: false } means other routes should 404. + return { paths, fallback: false }; +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { const apolloClient = initializeApollo(); - const { variables, notifyOnNetworkStatusChange } = OPTIONS_HOME_PROJECTS; + const { variables } = OPTIONS_HOME_PROJECTS; try { - const { query } = context; - const slug = query.slug; - - const { - data: { mainCategories }, - }: { - data: { mainCategories: IMainCategory[] }; - } = await apolloClient.query({ + // Fetch main categories + const { data: mainCategoriesData } = await apolloClient.query({ query: FETCH_MAIN_CATEGORIES, - fetchPolicy: 'network-only', + fetchPolicy: 'no-cache', // Adjust based on your caching policy needs }); - const updatedMainCategory = [allCategoriesItem, ...mainCategories]; + // Fetch projects with a predefined sorting + const { data: projectsData } = await apolloClient.query({ + query: FETCH_ALL_PROJECTS, + variables: { + ...variables, + mainCategory: params?.slug === 'all' ? null : params?.slug, + filters: [EProjectsFilter.ACTIVE_QF_ROUND], + }, + fetchPolicy: 'no-cache', + }); + + // Fetch QF rounds + const { data: qfRoundsData } = await apolloClient.query({ + query: FETCH_QF_ROUNDS, + fetchPolicy: 'no-cache', + }); + + const updatedMainCategory = [ + allCategoriesItem, + ...mainCategoriesData.mainCategories, + ]; + const selectedMainCategory = updatedMainCategory.find(mainCategory => { - return mainCategory.slug === slug; + return mainCategory.slug === params?.slug; }); - if (selectedMainCategory) { - const updatedSelectedMainCategory = { - ...selectedMainCategory, - selected: true, - }; - const apolloClient = initializeApollo(); - - let _filters = query.filter - ? Array.isArray(query.filter) - ? query.filter - : [query.filter] - : undefined; - - _filters - ? _filters.push(EProjectsFilter.ACTIVE_QF_ROUND) - : (_filters = [EProjectsFilter.ACTIVE_QF_ROUND]); - - const { data } = await apolloClient.query({ - query: FETCH_ALL_PROJECTS, - variables: { - ...variables, - sortingBy: query.sort || EProjectsSortBy.INSTANT_BOOSTING, - searchTerm: query.searchTerm, - filters: _filters, - campaignSlug: query.campaignSlug, - category: query.category, - mainCategory: getMainCategorySlug( - updatedSelectedMainCategory, - ), - notifyOnNetworkStatusChange, - }, - fetchPolicy: 'network-only', - }); - const { projects, totalCount } = data.allProjects; - const { - data: { qfRounds }, - } = await apolloClient.query({ - query: FETCH_QF_ROUNDS, - fetchPolicy: 'network-only', - }); - return { - props: { - projects, - mainCategories: updatedMainCategory, - selectedMainCategory: updatedSelectedMainCategory, - totalCount, - qfRounds, - }, - }; - } - return { - notFound: true, - }; - } catch (error: any) { - const statusCode = transformGraphQLErrorsToStatusCode( - error?.graphQLErrors, - ); return { props: { - errorStatus: statusCode, + projects: projectsData.allProjects.projects, + totalCount: projectsData.allProjects.totalCount, + mainCategories: updatedMainCategory, + qfRounds: qfRoundsData.qfRounds, + selectedMainCategory, }, + revalidate: 300, // Optionally, revalidate at most once per hour }; + } catch (error) { + console.error('Failed to fetch API:', error); + return { props: { hasError: true } }; } }; diff --git a/src/apollo/apolloClient.ts b/src/apollo/apolloClient.ts index d89253265c..244301e0ff 100644 --- a/src/apollo/apolloClient.ts +++ b/src/apollo/apolloClient.ts @@ -151,7 +151,7 @@ function createApolloClient() { return new ApolloClient({ ssrMode, - link: errorLink.concat(authLink.concat(httpLink.concat(retryLink))), + link: ApolloLink.from([errorLink, authLink, retryLink, httpLink]), cache: new InMemoryCache({ addTypename: false, }), diff --git a/src/components/cards/MintCard.tsx b/src/components/cards/MintCard.tsx index 910432237a..f7d5da3941 100644 --- a/src/components/cards/MintCard.tsx +++ b/src/components/cards/MintCard.tsx @@ -18,12 +18,15 @@ import { readContracts, readContract } from '@wagmi/core'; import { MintModal } from '../modals/Mint/MintModal'; import { formatWeiHelper } from '@/helpers/number'; import config from '@/configuration'; -import { abi as PFP_ABI } from '@/artifacts/pfpGiver.json'; +import pfpJson from '@/artifacts/pfpGiver.json'; import { InsufficientFundModal } from '../modals/InsufficientFund'; import { usePFPMintData } from '@/context/pfpmint.context'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { wagmiConfig } from '@/wagmiConfigs'; import { getReadContractResult } from '@/lib/contracts'; + +const { abi: PFP_ABI } = pfpJson; + const MIN_NFT_QTY = 1; interface IpfpContractData { diff --git a/src/components/modals/Mint/MintModal.tsx b/src/components/modals/Mint/MintModal.tsx index d6ea0d3e65..713bf897b7 100644 --- a/src/components/modals/Mint/MintModal.tsx +++ b/src/components/modals/Mint/MintModal.tsx @@ -18,7 +18,7 @@ import { formatWeiHelper } from '@/helpers/number'; import { waitForTransaction } from '@/lib/transaction'; import { approveERC20tokenTransfer } from '@/lib/stakingPool'; import config from '@/configuration'; -import { abi as PFP_ABI } from '@/artifacts/pfpGiver.json'; +import pfpJson from '@/artifacts/pfpGiver.json'; import { EPFPMinSteps, usePFPMintData } from '@/context/pfpmint.context'; import { MintSteps } from './MintSteps'; import { wagmiConfig } from '@/wagmiConfigs'; @@ -29,6 +29,8 @@ export enum MintStep { MINTING, } +const { abi: PFP_ABI } = pfpJson; + interface IMintModalProps extends IModal { qty: number; nftPrice?: bigint; diff --git a/src/components/views/projects/ProjectsBanner.tsx b/src/components/views/projects/ProjectsBanner.tsx index 3fe016f021..c8b12cd496 100644 --- a/src/components/views/projects/ProjectsBanner.tsx +++ b/src/components/views/projects/ProjectsBanner.tsx @@ -36,6 +36,7 @@ export const ProjectsBanner: FC = ({ mainCategory }) => { } fill alt={_mainCategory.title} + priority /> {formatMessage({ id: `projects_${_mainCategory.slug}` })} diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index d1583ea89b..9b96ba5357 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -80,12 +80,13 @@ const ProjectsIndex = (props: IProjectsView) => { const fetchProjects = useCallback( (isLoadMore?: boolean, loadNum?: number, userIdChanged = false) => { + console.log('Fetching-filteredProjects', filteredProjects.length); const variables: IQueries = { limit: userIdChanged ? filteredProjects.length > 50 ? BACKEND_QUERY_LIMIT - : filteredProjects.length - : projects.length, + : filteredProjects.length || BACKEND_QUERY_LIMIT + : projects.length || BACKEND_QUERY_LIMIT, skip: userIdChanged ? 0 : projects.length * (loadNum || 0), }; @@ -95,7 +96,8 @@ const ProjectsIndex = (props: IProjectsView) => { setIsLoading(true); if ( - contextVariables.mainCategory !== router.query?.slug?.toString() + contextVariables?.mainCategory !== + router.query?.slug?.toString() ) return; @@ -136,9 +138,9 @@ const ProjectsIndex = (props: IProjectsView) => { }, [ contextVariables, - filteredProjects.length, + filteredProjects?.length, isArchivedQF, - projects.length, + projects?.length, router.query.slug, selectedMainCategory, user?.id, @@ -146,14 +148,52 @@ const ProjectsIndex = (props: IProjectsView) => { ); useEffect(() => { + console.log('Fetching-User?.id', user?.id); pageNum.current = 0; fetchProjects(false, 0, true); }, [user?.id]); useEffect(() => { + if (!contextVariables) return; + console.log('Fetching-campaignSlug', contextVariables.campaignSlug); pageNum.current = 0; - fetchProjects(false, 0); - }, [contextVariables]); + fetchProjects(false, 0, true); + }, [contextVariables?.campaignSlug]); + + useEffect(() => { + if (!contextVariables) return; + console.log('Fetching-mainCategory', contextVariables.mainCategory); + pageNum.current = 0; + fetchProjects(false, 0, true); + }, [contextVariables?.mainCategory]); + + useEffect(() => { + if (!contextVariables) return; + console.log('Fetching-category', contextVariables.category); + pageNum.current = 0; + fetchProjects(false, 0, true); + }, [contextVariables?.category]); + + useEffect(() => { + if (!contextVariables) return; + console.log('Fetching-filters', contextVariables.filters); + pageNum.current = 0; + fetchProjects(false, 0, true); + }, [contextVariables?.filters]); + + useEffect(() => { + if (!contextVariables) return; + console.log('Fetching-searchTerm', contextVariables.searchTerm); + pageNum.current = 0; + fetchProjects(false, 0, true); + }, [contextVariables?.searchTerm]); + + useEffect(() => { + if (!contextVariables) return; + console.log('Fetching-sortingBy', contextVariables.sortingBy); + pageNum.current = 0; + fetchProjects(false, 0, true); + }, [contextVariables?.sortingBy]); const loadMore = useCallback(() => { if (isLoading) return; diff --git a/src/components/views/projects/ProjectsSearchDesktop.tsx b/src/components/views/projects/ProjectsSearchDesktop.tsx index 5977282f8a..02644a2856 100644 --- a/src/components/views/projects/ProjectsSearchDesktop.tsx +++ b/src/components/views/projects/ProjectsSearchDesktop.tsx @@ -15,7 +15,7 @@ import useFocus from '@/hooks/useFocus'; const ProjectsSearchDesktop = () => { const { variables } = useProjectsContext(); - const [searchValue, setSearchValue] = useState(variables.searchTerm); + const [searchValue, setSearchValue] = useState(variables?.searchTerm); const router = useRouter(); const { formatMessage } = useIntl(); @@ -46,8 +46,8 @@ const ProjectsSearchDesktop = () => { }; useEffect(() => { - setSearchValue(variables.searchTerm); - }, [variables.searchTerm]); + setSearchValue(variables?.searchTerm); + }, [variables?.searchTerm]); return ( <SearchContainer className='fadeIn'> @@ -67,7 +67,7 @@ const ProjectsSearchDesktop = () => { ref={inputRef} /> </form> - {variables.searchTerm ? ( + {variables?.searchTerm ? ( <ClearSearch onClick={removeSearch}> {formatMessage({ id: 'label.clear' })} </ClearSearch> diff --git a/src/components/views/projects/ProjectsSearchTablet.tsx b/src/components/views/projects/ProjectsSearchTablet.tsx index 38f02d9d67..c881371cf2 100644 --- a/src/components/views/projects/ProjectsSearchTablet.tsx +++ b/src/components/views/projects/ProjectsSearchTablet.tsx @@ -7,7 +7,7 @@ import { useProjectsContext } from '@/context/projects.context'; const ProjectsSearchTablet = () => { const { variables } = useProjectsContext(); - const [searchValue, setSearchValue] = useState(variables.searchTerm); + const [searchValue, setSearchValue] = useState(variables?.searchTerm); const router = useRouter(); const handleSearch = (searchTerm?: string) => { @@ -34,8 +34,8 @@ const ProjectsSearchTablet = () => { }; useEffect(() => { - setSearchValue(variables.searchTerm); - }, [variables.searchTerm]); + setSearchValue(variables?.searchTerm); + }, [variables?.searchTerm]); return ( <SearchContainer className='fadeIn'> @@ -51,7 +51,7 @@ const ProjectsSearchTablet = () => { onChange={e => setSearchValue(e.target.value)} /> </form> - {variables.searchTerm ? ( + {variables?.searchTerm ? ( <SearchHint onClick={removeSearch}> <IconX /> </SearchHint> diff --git a/src/context/projects.context.tsx b/src/context/projects.context.tsx index 33ca932a15..0ce35e57d6 100644 --- a/src/context/projects.context.tsx +++ b/src/context/projects.context.tsx @@ -5,6 +5,7 @@ import { SetStateAction, useContext, useState, + useEffect, } from 'react'; import { useRouter } from 'next/router'; import { EProjectsFilter, IMainCategory, IQFRound } from '@/apollo/types/types'; @@ -21,7 +22,7 @@ interface IVariables { } interface IProjectsContext { - variables: IVariables; + variables?: IVariables; mainCategories: IMainCategory[]; selectedMainCategory?: IMainCategory; qfRounds: IQFRound[]; @@ -30,13 +31,8 @@ interface IProjectsContext { setIsQF: Dispatch<SetStateAction<boolean>>; } -const variablesDefaultValue = { - sortingBy: EProjectsSortBy.INSTANT_BOOSTING, - filters: undefined, -}; - const ProjectsContext = createContext<IProjectsContext>({ - variables: variablesDefaultValue, + variables: undefined, mainCategories: [], qfRounds: [], isQF: false, @@ -63,42 +59,55 @@ export const ProjectsProvider = (props: { qfRounds, } = props; - const [_isQF, setIsQF] = useState(isQF); const router = useRouter(); + const [_isQF, setIsQF] = useState(props.isQF); + const [variables, setVariables] = useState<IVariables>(); - let sort = EProjectsSortBy.INSTANT_BOOSTING; - const sortValue = router.query.sort as string; - if (sortValue) sort = sortMap[sortValue.toLowerCase()]; + useEffect(() => { + if (!router.isReady) return; + let sort = EProjectsSortBy.INSTANT_BOOSTING; + const sortValue = router.query.sort as string; + if (sortValue && sortMap[sortValue.toLowerCase()]) { + sort = sortMap[sortValue.toLowerCase()]; + } - let filters: EProjectsFilter[] | undefined; - if (router.query.filter) { - filters = ( - Array.isArray(router.query.filter) - ? router.query.filter - : [router.query.filter] - ) as EProjectsFilter[]; - } - if (_isQF) { - filters - ? filters.push(EProjectsFilter.ACTIVE_QF_ROUND) - : (filters = [EProjectsFilter.ACTIVE_QF_ROUND]); - } + let filters: EProjectsFilter[] | undefined; + if (router.query.filter) { + filters = ( + Array.isArray(router.query.filter) + ? router.query.filter + : [router.query.filter] + ) as EProjectsFilter[]; + } + if (_isQF) { + filters + ? filters.push(EProjectsFilter.ACTIVE_QF_ROUND) + : (filters = [EProjectsFilter.ACTIVE_QF_ROUND]); + } + + let searchTerm = router.query.searchTerm as string; + let campaignSlug = router.query.campaign as string; + let category = router.query.category as string; + + // After setting initial params based on URL + setVariables({ + sortingBy: sort, + searchTerm, + filters, + campaignSlug, + mainCategory: router.query?.slug?.toString(), + category, + }); - let searchTerm = router.query.searchTerm as string; - let campaignSlug = router.query.campaign as string; - let category = router.query.category as string; + return () => { + setVariables(undefined); // Reset on component unmount if needed + }; + }, [_isQF, router.query, router.isReady]); return ( <ProjectsContext.Provider value={{ - variables: { - sortingBy: sort, - searchTerm, - filters, - campaignSlug, - mainCategory: router.query?.slug?.toString(), - category, - }, + variables, mainCategories, selectedMainCategory, qfRounds: qfRounds || [],