From 547a477b1a95a39f60108b7a834038bb26d2887d Mon Sep 17 00:00:00 2001 From: kkatusic Date: Wed, 5 Jun 2024 00:06:46 +0200 Subject: [PATCH 01/87] started back improvement --- src/components/views/projects/ProjectsIndex.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 025a50b22f..b6d29a2486 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -157,10 +157,22 @@ const ProjectsIndex = (props: IProjectsView) => { fetchProjects(false, 0); }, [contextVariables]); + // Handle back button navigation + useEffect(() => { + console.log('projects', router); + const storedPage = localStorage.getItem('lastViewedPage'); + if (storedPage) { + for (let i = 0; i < Number(storedPage); i++) { + // fetchProjects(false, i); + } + } + }, [router, fetchProjects]); + const loadMore = useCallback(() => { if (isLoading) return; fetchProjects(true, pageNum.current + 1); pageNum.current = pageNum.current + 1; + localStorage.setItem('lastViewedPage', pageNum.current.toString()); }, [fetchProjects, isLoading]); const handleCreateButton = () => { From a69c84999e166d5669c8b7557c4def9d3234d886 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Thu, 6 Jun 2024 10:30:03 +0200 Subject: [PATCH 02/87] attempt to solve back option with useEffect and local storage --- src/components/project-card/ProjectCard.tsx | 1 + .../views/projects/ProjectsIndex.tsx | 41 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/components/project-card/ProjectCard.tsx b/src/components/project-card/ProjectCard.tsx index d2fbb29ac4..3662709839 100644 --- a/src/components/project-card/ProjectCard.tsx +++ b/src/components/project-card/ProjectCard.tsx @@ -122,6 +122,7 @@ const ProjectCard = (props: IProjectCard) => { { router?.events?.on('routeChangeStart', () => setIsLoading(true)); const fetchProjects = useCallback( - (isLoadMore?: boolean, loadNum?: number, userIdChanged = false) => { + ( + isLoadMore?: boolean, + loadNum?: number, + userIdChanged = false, + customLimit = 0, + ) => { + const queryLimit = customLimit || projects.length; + const variables: IQueries = { limit: userIdChanged ? filteredProjects.length > 50 ? BACKEND_QUERY_LIMIT : filteredProjects.length - : projects.length, + : queryLimit, skip: userIdChanged ? 0 : projects.length * (loadNum || 0), }; @@ -160,17 +167,6 @@ const ProjectsIndex = (props: IProjectsView) => { fetchProjects(false, 0); }, [contextVariables]); - // Handle back button navigation - useEffect(() => { - console.log('projects', router); - const storedPage = localStorage.getItem('lastViewedPage'); - if (storedPage) { - for (let i = 0; i < Number(storedPage); i++) { - // fetchProjects(false, i); - } - } - }, [router, fetchProjects]); - const loadMore = useCallback(() => { if (isLoading) return; fetchProjects(true, pageNum.current + 1); @@ -178,6 +174,25 @@ const ProjectsIndex = (props: IProjectsView) => { localStorage.setItem('lastViewedPage', pageNum.current.toString()); }, [fetchProjects, isLoading]); + // Handle back button navigation + + useEffect(() => { + const storedPage = localStorage.getItem('lastViewedPage'); + const lastVisitedProject = + localStorage.getItem('lastViewedProject') ?? null; + if ( + storedPage && + Number(storedPage) > 0 && + pageNum.current <= Number(storedPage) && + lastVisitedProject != null && + !isLoading + ) { + localStorage.removeItem('lastViewedProject'); + const limit = parseInt(storedPage) * 15; + fetchProjects(false, 0, false, limit); + } + }, [fetchProjects, isLoading]); + const handleCreateButton = () => { if (isUserRegistered(user)) { router.push(Routes.CreateProject); From e5eeddc783c6d9a106ed88307dcfc9f8bc74bfa0 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 5 Jul 2024 16:59:24 +0200 Subject: [PATCH 03/87] reverted changes --- .../views/projects/ProjectsIndex.tsx | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index a0732c7b24..78adcc5dbf 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -84,20 +84,13 @@ const ProjectsIndex = (props: IProjectsView) => { router?.events?.on('routeChangeStart', () => setIsLoading(true)); const fetchProjects = useCallback( - ( - isLoadMore?: boolean, - loadNum?: number, - userIdChanged = false, - customLimit = 0, - ) => { - const queryLimit = customLimit || projects.length; - + (isLoadMore?: boolean, loadNum?: number, userIdChanged = false) => { const variables: IQueries = { limit: userIdChanged ? filteredProjects.length > 50 ? BACKEND_QUERY_LIMIT : filteredProjects.length - : queryLimit, + : projects.length, skip: userIdChanged ? 0 : projects.length * (loadNum || 0), }; @@ -170,26 +163,6 @@ const ProjectsIndex = (props: IProjectsView) => { if (isLoading) return; fetchProjects(true, pageNum.current + 1); pageNum.current = pageNum.current + 1; - localStorage.setItem('lastViewedPage', pageNum.current.toString()); - }, [fetchProjects, isLoading]); - - // Handle back button navigation - - useEffect(() => { - const storedPage = localStorage.getItem('lastViewedPage'); - const lastVisitedProject = - localStorage.getItem('lastViewedProject') ?? null; - if ( - storedPage && - Number(storedPage) > 0 && - pageNum.current <= Number(storedPage) && - lastVisitedProject != null && - !isLoading - ) { - localStorage.removeItem('lastViewedProject'); - const limit = parseInt(storedPage) * 15; - fetchProjects(false, 0, false, limit); - } }, [fetchProjects, isLoading]); const handleCreateButton = () => { From b358443bb62483f1eadbed83328416442e4504a5 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 9 Jul 2024 00:53:44 +0200 Subject: [PATCH 04/87] started implementing useInfiniteQuery from React Query --- .../views/projects/ProjectsIndex.tsx | 83 ++++++++++++++++--- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 78adcc5dbf..a8bae33143 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -10,7 +10,7 @@ import { import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { captureException } from '@sentry/nextjs'; - +import { useInfiniteQuery } from '@tanstack/react-query'; import ProjectCard from '@/components/project-card/ProjectCard'; import Routes from '@/lib/constants/Routes'; import { isUserRegistered, showToastError } from '@/lib/helpers'; @@ -53,6 +53,30 @@ interface IQueries { connectedWalletUserId?: number; } +interface FetchProjectsParams { + pageParam?: number; + queryKey: [ + string, + { + isLoadMore: boolean; + loadNum: number; + userIdChanged: boolean; + }, + ]; +} + +interface FetchProjectsResponse { + projects: IProject[]; + totalCount: number; + nextPage: number | undefined; +} + +interface FetchProjectsResponse { + projects: IProject[]; + totalCount: number; + nextPage: number | undefined; +} + const ProjectsIndex = (props: IProjectsView) => { const { formatMessage } = useIntl(); const { projects, totalCount: _totalCount } = props; @@ -84,7 +108,20 @@ const ProjectsIndex = (props: IProjectsView) => { router?.events?.on('routeChangeStart', () => setIsLoading(true)); const fetchProjects = useCallback( - (isLoadMore?: boolean, loadNum?: number, userIdChanged = false) => { + async ({ + pageParam = 0, + queryKey, + }: FetchProjectsParams): Promise => { + const [_key, { isLoadMore, loadNum, userIdChanged }] = queryKey; + + console.log( + 'fetchProjects functions', + isLoadMore, + loadNum, + userIdChanged, + pageParam, + ); + const variables: IQueries = { limit: userIdChanged ? filteredProjects.length > 50 @@ -137,6 +174,7 @@ const ProjectsIndex = (props: IProjectsView) => { }, }); }); + return undefined; }, [ contextVariables, @@ -149,21 +187,44 @@ const ProjectsIndex = (props: IProjectsView) => { ], ); + const [isLoadMore, setIsLoadMore] = useState(false); + const [loadNum, setLoadNum] = useState(0); + const [userIdChanged, setUserIdChanged] = useState(false); + + const { + data, + error, + fetchNextPage, + hasNextPage, + isError, + isFetching, + isFetchingNextPage, + } = useInfiniteQuery({ + queryKey: ['projects', { isLoadMore, loadNum, userIdChanged }], + queryFn: fetchProjects, + getNextPageParam: lastPage => lastPage?.nextPage, + initialPageParam: 0, + }); + useEffect(() => { + console.log('fetchProjects functions call 1'); pageNum.current = 0; - fetchProjects(false, 0, true); + // fetchProjects(false, 0, true); }, [user?.id]); useEffect(() => { + console.log('fetchProjects functions call 2'); pageNum.current = 0; - fetchProjects(false, 0); + // fetchProjects(false, 0); + fetchNextPage(); }, [contextVariables]); const loadMore = useCallback(() => { - if (isLoading) return; - fetchProjects(true, pageNum.current + 1); + if (isFetching) return; + // fetchProjects(true, pageNum.current + 1); + fetchNextPage(); pageNum.current = pageNum.current + 1; - }, [fetchProjects, isLoading]); + }, [isFetching]); const handleCreateButton = () => { if (isUserRegistered(user)) { @@ -209,7 +270,7 @@ const ProjectsIndex = (props: IProjectsView) => { ) { setIsNotFound(true); } - }, [selectedMainCategory, mainCategories.length]); + }, [selectedMainCategory, mainCategories.length, isArchivedQF]); if (isNotFound) return ; @@ -251,7 +312,7 @@ const ProjectsIndex = (props: IProjectsView) => { )} - {isLoading && } + {isFetching && } {filteredProjects?.length > 0 ? ( @@ -283,14 +344,14 @@ const ProjectsIndex = (props: IProjectsView) => {
From 2845f1b3d60a7f8a9b550e4f7746d7c5098835a3 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 9 Jul 2024 13:07:00 +0200 Subject: [PATCH 05/87] reverted back again --- .../views/projects/ProjectsIndex.tsx | 139 ++++++++---------- 1 file changed, 63 insertions(+), 76 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index a8bae33143..2b186c9ed6 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -107,85 +107,72 @@ const ProjectsIndex = (props: IProjectsView) => { router?.events?.on('routeChangeStart', () => setIsLoading(true)); - const fetchProjects = useCallback( - async ({ - pageParam = 0, - queryKey, - }: FetchProjectsParams): Promise => { - const [_key, { isLoadMore, loadNum, userIdChanged }] = queryKey; - - console.log( - 'fetchProjects functions', - isLoadMore, - loadNum, - userIdChanged, - pageParam, - ); - - const variables: IQueries = { - limit: userIdChanged - ? filteredProjects.length > 50 - ? BACKEND_QUERY_LIMIT - : filteredProjects.length - : projects.length, - skip: userIdChanged ? 0 : projects.length * (loadNum || 0), - }; - - if (user?.id) { - variables.connectedWalletUserId = Number(user?.id); - } + const fetchProjects = async ({ + pageParam = 0, + queryKey, + }: FetchProjectsParams): Promise => { + const [_key, { isLoadMore, loadNum, userIdChanged }] = queryKey; + + console.log( + 'fetchProjects functions', + isLoadMore, + loadNum, + userIdChanged, + pageParam, + ); + + const variables: IQueries = { + limit: userIdChanged + ? filteredProjects.length > 50 + ? BACKEND_QUERY_LIMIT + : filteredProjects.length + : projects.length, + skip: userIdChanged ? 0 : projects.length * (loadNum || 0), + }; + + if (user?.id) { + variables.connectedWalletUserId = Number(user?.id); + } - setIsLoading(true); - if ( - contextVariables.mainCategory !== router.query?.slug?.toString() - ) - return; - - client - .query({ - query: FETCH_ALL_PROJECTS, - variables: { - ...variables, - ...contextVariables, - mainCategory: isArchivedQF - ? undefined - : getMainCategorySlug(selectedMainCategory), - qfRoundSlug: isArchivedQF ? router.query.slug : null, + setIsLoading(true); + if (contextVariables.mainCategory !== router.query?.slug?.toString()) + return; + + client + .query({ + query: FETCH_ALL_PROJECTS, + variables: { + ...variables, + ...contextVariables, + mainCategory: isArchivedQF + ? undefined + : getMainCategorySlug(selectedMainCategory), + qfRoundSlug: isArchivedQF ? router.query.slug : null, + }, + }) + .then((res: { data: { allProjects: IFetchAllProjects } }) => { + const data = res.data?.allProjects?.projects; + const count = res.data?.allProjects?.totalCount; + setTotalCount(count); + + setFilteredProjects(prevProjects => { + isInfiniteScrolling.current = + (data.length + prevProjects.length) % 45 !== 0; + return isLoadMore ? [...prevProjects, ...data] : data; + }); + setIsLoading(false); + }) + .catch((err: any) => { + setIsLoading(false); + showToastError(err); + captureException(err, { + tags: { + section: 'fetchAllProjects', }, - }) - .then((res: { data: { allProjects: IFetchAllProjects } }) => { - const data = res.data?.allProjects?.projects; - const count = res.data?.allProjects?.totalCount; - setTotalCount(count); - - setFilteredProjects(prevProjects => { - isInfiniteScrolling.current = - (data.length + prevProjects.length) % 45 !== 0; - return isLoadMore ? [...prevProjects, ...data] : data; - }); - setIsLoading(false); - }) - .catch((err: any) => { - setIsLoading(false); - showToastError(err); - captureException(err, { - tags: { - section: 'fetchAllProjects', - }, - }); }); - return undefined; - }, - [ - contextVariables, - filteredProjects.length, - isArchivedQF, - projects.length, - router.query.slug, - selectedMainCategory, - user?.id, - ], - ); + }); + return undefined; + }; const [isLoadMore, setIsLoadMore] = useState(false); const [loadNum, setLoadNum] = useState(0); From c56a54fbd9ffb40a84963f0f8865de06c87135bf Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 12 Jul 2024 11:22:38 +0200 Subject: [PATCH 06/87] reverted back again :( --- .../views/projects/ProjectsIndex.tsx | 192 +++++++----------- 1 file changed, 72 insertions(+), 120 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 2b186c9ed6..78adcc5dbf 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -10,7 +10,7 @@ import { import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { captureException } from '@sentry/nextjs'; -import { useInfiniteQuery } from '@tanstack/react-query'; + import ProjectCard from '@/components/project-card/ProjectCard'; import Routes from '@/lib/constants/Routes'; import { isUserRegistered, showToastError } from '@/lib/helpers'; @@ -53,30 +53,6 @@ interface IQueries { connectedWalletUserId?: number; } -interface FetchProjectsParams { - pageParam?: number; - queryKey: [ - string, - { - isLoadMore: boolean; - loadNum: number; - userIdChanged: boolean; - }, - ]; -} - -interface FetchProjectsResponse { - projects: IProject[]; - totalCount: number; - nextPage: number | undefined; -} - -interface FetchProjectsResponse { - projects: IProject[]; - totalCount: number; - nextPage: number | undefined; -} - const ProjectsIndex = (props: IProjectsView) => { const { formatMessage } = useIntl(); const { projects, totalCount: _totalCount } = props; @@ -107,111 +83,87 @@ const ProjectsIndex = (props: IProjectsView) => { router?.events?.on('routeChangeStart', () => setIsLoading(true)); - const fetchProjects = async ({ - pageParam = 0, - queryKey, - }: FetchProjectsParams): Promise => { - const [_key, { isLoadMore, loadNum, userIdChanged }] = queryKey; - - console.log( - 'fetchProjects functions', - isLoadMore, - loadNum, - userIdChanged, - pageParam, - ); - - const variables: IQueries = { - limit: userIdChanged - ? filteredProjects.length > 50 - ? BACKEND_QUERY_LIMIT - : filteredProjects.length - : projects.length, - skip: userIdChanged ? 0 : projects.length * (loadNum || 0), - }; - - if (user?.id) { - variables.connectedWalletUserId = Number(user?.id); - } + const fetchProjects = useCallback( + (isLoadMore?: boolean, loadNum?: number, userIdChanged = false) => { + const variables: IQueries = { + limit: userIdChanged + ? filteredProjects.length > 50 + ? BACKEND_QUERY_LIMIT + : filteredProjects.length + : projects.length, + skip: userIdChanged ? 0 : projects.length * (loadNum || 0), + }; + + if (user?.id) { + variables.connectedWalletUserId = Number(user?.id); + } - setIsLoading(true); - if (contextVariables.mainCategory !== router.query?.slug?.toString()) - return; - - client - .query({ - query: FETCH_ALL_PROJECTS, - variables: { - ...variables, - ...contextVariables, - mainCategory: isArchivedQF - ? undefined - : getMainCategorySlug(selectedMainCategory), - qfRoundSlug: isArchivedQF ? router.query.slug : null, - }, - }) - .then((res: { data: { allProjects: IFetchAllProjects } }) => { - const data = res.data?.allProjects?.projects; - const count = res.data?.allProjects?.totalCount; - setTotalCount(count); - - setFilteredProjects(prevProjects => { - isInfiniteScrolling.current = - (data.length + prevProjects.length) % 45 !== 0; - return isLoadMore ? [...prevProjects, ...data] : data; - }); - setIsLoading(false); - }) - .catch((err: any) => { - setIsLoading(false); - showToastError(err); - captureException(err, { - tags: { - section: 'fetchAllProjects', + setIsLoading(true); + if ( + contextVariables.mainCategory !== router.query?.slug?.toString() + ) + return; + + client + .query({ + query: FETCH_ALL_PROJECTS, + variables: { + ...variables, + ...contextVariables, + mainCategory: isArchivedQF + ? undefined + : getMainCategorySlug(selectedMainCategory), + qfRoundSlug: isArchivedQF ? router.query.slug : null, }, + }) + .then((res: { data: { allProjects: IFetchAllProjects } }) => { + const data = res.data?.allProjects?.projects; + const count = res.data?.allProjects?.totalCount; + setTotalCount(count); + + setFilteredProjects(prevProjects => { + isInfiniteScrolling.current = + (data.length + prevProjects.length) % 45 !== 0; + return isLoadMore ? [...prevProjects, ...data] : data; + }); + setIsLoading(false); + }) + .catch((err: any) => { + setIsLoading(false); + showToastError(err); + captureException(err, { + tags: { + section: 'fetchAllProjects', + }, + }); }); - }); - return undefined; - }; - - const [isLoadMore, setIsLoadMore] = useState(false); - const [loadNum, setLoadNum] = useState(0); - const [userIdChanged, setUserIdChanged] = useState(false); - - const { - data, - error, - fetchNextPage, - hasNextPage, - isError, - isFetching, - isFetchingNextPage, - } = useInfiniteQuery({ - queryKey: ['projects', { isLoadMore, loadNum, userIdChanged }], - queryFn: fetchProjects, - getNextPageParam: lastPage => lastPage?.nextPage, - initialPageParam: 0, - }); + }, + [ + contextVariables, + filteredProjects.length, + isArchivedQF, + projects.length, + router.query.slug, + selectedMainCategory, + user?.id, + ], + ); useEffect(() => { - console.log('fetchProjects functions call 1'); pageNum.current = 0; - // fetchProjects(false, 0, true); + fetchProjects(false, 0, true); }, [user?.id]); useEffect(() => { - console.log('fetchProjects functions call 2'); pageNum.current = 0; - // fetchProjects(false, 0); - fetchNextPage(); + fetchProjects(false, 0); }, [contextVariables]); const loadMore = useCallback(() => { - if (isFetching) return; - // fetchProjects(true, pageNum.current + 1); - fetchNextPage(); + if (isLoading) return; + fetchProjects(true, pageNum.current + 1); pageNum.current = pageNum.current + 1; - }, [isFetching]); + }, [fetchProjects, isLoading]); const handleCreateButton = () => { if (isUserRegistered(user)) { @@ -257,7 +209,7 @@ const ProjectsIndex = (props: IProjectsView) => { ) { setIsNotFound(true); } - }, [selectedMainCategory, mainCategories.length, isArchivedQF]); + }, [selectedMainCategory, mainCategories.length]); if (isNotFound) return ; @@ -299,7 +251,7 @@ const ProjectsIndex = (props: IProjectsView) => { )} - {isFetching && } + {isLoading && } {filteredProjects?.length > 0 ? ( @@ -331,14 +283,14 @@ const ProjectsIndex = (props: IProjectsView) => {
From 68aa294ae21526a9a2b217deebb2708e658ddbe3 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Sun, 14 Jul 2024 13:49:52 +0200 Subject: [PATCH 07/87] set up React Query useInfiniteQuery function --- .../views/projects/ProjectsIndex.tsx | 97 ++++++++++++++++--- 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 78adcc5dbf..99cb0af216 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -11,6 +11,7 @@ import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { captureException } from '@sentry/nextjs'; +import { QueryFunctionContext, useInfiniteQuery } from '@tanstack/react-query'; import ProjectCard from '@/components/project-card/ProjectCard'; import Routes from '@/lib/constants/Routes'; import { isUserRegistered, showToastError } from '@/lib/helpers'; @@ -53,6 +54,24 @@ interface IQueries { connectedWalletUserId?: number; } +interface FetchProjectsResponse { + projects: IProject[]; + totalCount: number; + nextPage: number; +} + +interface FetchProjectsParams { + queryKey: [ + string, + { + isLoadMore: boolean; + loadNum: number; + userIdChanged: boolean; + }, + ]; + pageParam?: number; +} + const ProjectsIndex = (props: IProjectsView) => { const { formatMessage } = useIntl(); const { projects, totalCount: _totalCount } = props; @@ -83,8 +102,15 @@ const ProjectsIndex = (props: IProjectsView) => { router?.events?.on('routeChangeStart', () => setIsLoading(true)); + // Default values for queryKey + const [isLoadMore, setIsLoadMore] = useState(false); + const [loadNum, setLoadNum] = useState(0); + const [userIdChanged, setUserIdChanged] = useState(false); + const fetchProjects = useCallback( - (isLoadMore?: boolean, loadNum?: number, userIdChanged = false) => { + async (pageParam: number | unknown): Promise => { + console.log('fetchProjects', pageParam); + const variables: IQueries = { limit: userIdChanged ? filteredProjects.length > 50 @@ -101,8 +127,9 @@ const ProjectsIndex = (props: IProjectsView) => { setIsLoading(true); if ( contextVariables.mainCategory !== router.query?.slug?.toString() - ) - return; + ) { + return { projects: [], totalCount: 0, nextPage: 1 }; + } client .query({ @@ -127,6 +154,17 @@ const ProjectsIndex = (props: IProjectsView) => { return isLoadMore ? [...prevProjects, ...data] : data; }); setIsLoading(false); + + const result = { + projects: isLoadMore + ? [...filteredProjects, ...data] + : data, + nextPage: pageParam ? pageParam + 1 : 1, + totalCount: count, + }; + + console.log('fetchProjects result', result); + return result; }) .catch((err: any) => { setIsLoading(false); @@ -137,6 +175,8 @@ const ProjectsIndex = (props: IProjectsView) => { }, }); }); + + return { projects: [], totalCount: 0, nextPage: 1 }; }, [ contextVariables, @@ -149,21 +189,48 @@ const ProjectsIndex = (props: IProjectsView) => { ], ); - useEffect(() => { - pageNum.current = 0; - fetchProjects(false, 0, true); - }, [user?.id]); + const { + data, + error, + fetchNextPage, + hasNextPage, + isError, + isFetching, + isFetchingNextPage, + } = useInfiniteQuery({ + queryKey: ['projects'], + queryFn: ({ pageParam = 0 }: QueryFunctionContext) => + fetchProjects(pageParam), + // queryFn: ({ pageParam }) => fetchProjects(pageParam), + // queryFn: ({ pageParam = 0 }: { pageParam: number }) => + // fetchProjects(pageParam), + + // getNextPageParam: lastPage => lastPage?.nextPage, + getNextPageParam: (lastPage, pages) => { + console.log('getNextPageParam called', lastPage, pages); + return lastPage?.nextPage ?? false; + }, + initialPageParam: 0, + }); - useEffect(() => { - pageNum.current = 0; - fetchProjects(false, 0); - }, [contextVariables]); + // useEffect(() => { + // pageNum.current = 0; + // fetchProjects(false, 0, true); + // }, [user?.id]); + + // useEffect(() => { + // pageNum.current = 0; + // fetchProjects(false, 0); + // }, [contextVariables]); const loadMore = useCallback(() => { - if (isLoading) return; - fetchProjects(true, pageNum.current + 1); - pageNum.current = pageNum.current + 1; - }, [fetchProjects, isLoading]); + // if (isLoading) return; + // fetchProjects(true, pageNum.current + 1); + // pageNum.current = pageNum.current + 1; + console.log('LOAD MORE'); + fetchNextPage(); + // }, [fetchProjects, isLoading]); + }, [fetchNextPage]); const handleCreateButton = () => { if (isUserRegistered(user)) { From 0429727ba8737b5e8d50536ef6a064a25c62a809 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 16 Jul 2024 13:54:33 +0200 Subject: [PATCH 08/87] updated last page variable --- .../views/projects/ProjectsIndex.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 99cb0af216..5e8b0f98c6 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -57,7 +57,7 @@ interface IQueries { interface FetchProjectsResponse { projects: IProject[]; totalCount: number; - nextPage: number; + lastPage: number; } interface FetchProjectsParams { @@ -128,7 +128,7 @@ const ProjectsIndex = (props: IProjectsView) => { if ( contextVariables.mainCategory !== router.query?.slug?.toString() ) { - return { projects: [], totalCount: 0, nextPage: 1 }; + return { projects: [], totalCount: 0, lastPage: 0 }; } client @@ -159,7 +159,7 @@ const ProjectsIndex = (props: IProjectsView) => { projects: isLoadMore ? [...filteredProjects, ...data] : data, - nextPage: pageParam ? pageParam + 1 : 1, + lastPage: pageParam ? pageParam : 1, totalCount: count, }; @@ -176,7 +176,7 @@ const ProjectsIndex = (props: IProjectsView) => { }); }); - return { projects: [], totalCount: 0, nextPage: 1 }; + return { projects: [], totalCount: 0, lastPage: 0 }; }, [ contextVariables, @@ -206,9 +206,14 @@ const ProjectsIndex = (props: IProjectsView) => { // fetchProjects(pageParam), // getNextPageParam: lastPage => lastPage?.nextPage, - getNextPageParam: (lastPage, pages) => { - console.log('getNextPageParam called', lastPage, pages); - return lastPage?.nextPage ?? false; + // getNextPageParam: (lastPage, pages: FetchProjectsResponse[]) => { + // console.log('getNextPageParam called', pages); + // // return lastPage?.nextPage ?? false; + // return lastPage.nextPage + 1; + // }, + getNextPageParam: (returnedData: FetchProjectsResponse) => { + console.log('getNextPageParam called', returnedData); + return returnedData.lastPage + 1; }, initialPageParam: 0, }); From ef3c043df5f47342d5bafb9eba2a213407083771 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 16 Jul 2024 18:26:55 +0200 Subject: [PATCH 09/87] continuing --- .../views/projects/ProjectsIndex.tsx | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 5e8b0f98c6..22041006f8 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -55,22 +55,22 @@ interface IQueries { } interface FetchProjectsResponse { - projects: IProject[]; + data: IProject[]; totalCount: number; lastPage: number; } -interface FetchProjectsParams { - queryKey: [ - string, - { - isLoadMore: boolean; - loadNum: number; - userIdChanged: boolean; - }, - ]; - pageParam?: number; -} +// interface FetchProjectsParams { +// queryKey: [ +// string, +// { +// isLoadMore: boolean; +// loadNum: number; +// userIdChanged: boolean; +// }, +// ]; +// pageParam?: number; +// } const ProjectsIndex = (props: IProjectsView) => { const { formatMessage } = useIntl(); @@ -109,7 +109,9 @@ const ProjectsIndex = (props: IProjectsView) => { const fetchProjects = useCallback( async (pageParam: number | unknown): Promise => { - console.log('fetchProjects', pageParam); + const currentPage = pageParam === undefined ? pageParam : 0; + + console.log({ currentPage }); const variables: IQueries = { limit: userIdChanged @@ -117,7 +119,7 @@ const ProjectsIndex = (props: IProjectsView) => { ? BACKEND_QUERY_LIMIT : filteredProjects.length : projects.length, - skip: userIdChanged ? 0 : projects.length * (loadNum || 0), + skip: userIdChanged ? 0 : projects.length * (currentPage || 0), }; if (user?.id) { @@ -128,7 +130,7 @@ const ProjectsIndex = (props: IProjectsView) => { if ( contextVariables.mainCategory !== router.query?.slug?.toString() ) { - return { projects: [], totalCount: 0, lastPage: 0 }; + return { data: [], totalCount: 0, lastPage: 0 }; } client @@ -156,10 +158,8 @@ const ProjectsIndex = (props: IProjectsView) => { setIsLoading(false); const result = { - projects: isLoadMore - ? [...filteredProjects, ...data] - : data, - lastPage: pageParam ? pageParam : 1, + data: data, + lastPage: currentPage, totalCount: count, }; @@ -176,7 +176,7 @@ const ProjectsIndex = (props: IProjectsView) => { }); }); - return { projects: [], totalCount: 0, lastPage: 0 }; + return { data: [], totalCount: 0, lastPage: 0 }; }, [ contextVariables, @@ -211,13 +211,29 @@ const ProjectsIndex = (props: IProjectsView) => { // // return lastPage?.nextPage ?? false; // return lastPage.nextPage + 1; // }, - getNextPageParam: (returnedData: FetchProjectsResponse) => { - console.log('getNextPageParam called', returnedData); - return returnedData.lastPage + 1; + // getNextPageParam: (returnedData: FetchProjectsResponse) => { + getNextPageParam: (lastPage, allPages, lastPageParam) => { + console.log('getNextPageParam called', lastPage); + console.log('getNextPageParam called', allPages); + console.log('getNextPageParam called', allPages); + console.log('getNextPageParam zadnja stranica', lastPage.lastPage); + return lastPage.lastPage + 1; }, initialPageParam: 0, }); + useEffect(() => { + if (data) { + console.log('Data from React Query:', data); + } + if (hasNextPage !== undefined) { + console.log('Has Next Page:', hasNextPage); + } + if (isFetchingNextPage !== undefined) { + console.log('Is Fetching Next Page:', isFetchingNextPage); + } + }, [data, hasNextPage, isFetchingNextPage]); + // useEffect(() => { // pageNum.current = 0; // fetchProjects(false, 0, true); From b231f2476bf630878fa50aed595761edd8217b13 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 19 Jul 2024 10:10:25 +0200 Subject: [PATCH 10/87] renamed variables --- .../views/projects/ProjectsIndex.tsx | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 22041006f8..a71b457d55 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -56,8 +56,8 @@ interface IQueries { interface FetchProjectsResponse { data: IProject[]; - totalCount: number; - lastPage: number; + previousCursor?: number; + nextCursor?: number; } // interface FetchProjectsParams { @@ -111,7 +111,7 @@ const ProjectsIndex = (props: IProjectsView) => { async (pageParam: number | unknown): Promise => { const currentPage = pageParam === undefined ? pageParam : 0; - console.log({ currentPage }); + console.log('currentPage', currentPage); const variables: IQueries = { limit: userIdChanged @@ -130,7 +130,7 @@ const ProjectsIndex = (props: IProjectsView) => { if ( contextVariables.mainCategory !== router.query?.slug?.toString() ) { - return { data: [], totalCount: 0, lastPage: 0 }; + return { data: [], previousCursor: 0, nextCursor: 0 }; } client @@ -147,6 +147,7 @@ const ProjectsIndex = (props: IProjectsView) => { }) .then((res: { data: { allProjects: IFetchAllProjects } }) => { const data = res.data?.allProjects?.projects; + console.log({ res }); const count = res.data?.allProjects?.totalCount; setTotalCount(count); @@ -159,8 +160,8 @@ const ProjectsIndex = (props: IProjectsView) => { const result = { data: data, - lastPage: currentPage, - totalCount: count, + previousCursor: projects.length * (currentPage || 0), + nextCursor: projects.length * (currentPage || 0) + 15, }; console.log('fetchProjects result', result); @@ -176,7 +177,7 @@ const ProjectsIndex = (props: IProjectsView) => { }); }); - return { data: [], totalCount: 0, lastPage: 0 }; + return { data: [], previousCursor: 0, nextCursor: 0 }; }, [ contextVariables, @@ -186,6 +187,8 @@ const ProjectsIndex = (props: IProjectsView) => { router.query.slug, selectedMainCategory, user?.id, + userIdChanged, + isLoadMore, ], ); @@ -201,23 +204,9 @@ const ProjectsIndex = (props: IProjectsView) => { queryKey: ['projects'], queryFn: ({ pageParam = 0 }: QueryFunctionContext) => fetchProjects(pageParam), - // queryFn: ({ pageParam }) => fetchProjects(pageParam), - // queryFn: ({ pageParam = 0 }: { pageParam: number }) => - // fetchProjects(pageParam), - - // getNextPageParam: lastPage => lastPage?.nextPage, - // getNextPageParam: (lastPage, pages: FetchProjectsResponse[]) => { - // console.log('getNextPageParam called', pages); - // // return lastPage?.nextPage ?? false; - // return lastPage.nextPage + 1; - // }, - // getNextPageParam: (returnedData: FetchProjectsResponse) => { - getNextPageParam: (lastPage, allPages, lastPageParam) => { - console.log('getNextPageParam called', lastPage); - console.log('getNextPageParam called', allPages); - console.log('getNextPageParam called', allPages); - console.log('getNextPageParam zadnja stranica', lastPage.lastPage); - return lastPage.lastPage + 1; + getNextPageParam: (lastPage, fetchedData) => { + console.log('getNextPageParam called', lastPage, fetchedData); + return lastPage.nextCursor; }, initialPageParam: 0, }); @@ -394,6 +383,12 @@ const ProjectsIndex = (props: IProjectsView) => { /> )} + ); From a77dd0f37a77157c171ba6d36e6dcd7ea399ace4 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 19 Jul 2024 14:23:41 +0200 Subject: [PATCH 11/87] last try --- .../views/projects/ProjectsIndex.tsx | 250 +++++++++++------- 1 file changed, 154 insertions(+), 96 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index a71b457d55..188f21d05d 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; +import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/router'; import { brandColors, @@ -54,24 +54,12 @@ interface IQueries { connectedWalletUserId?: number; } -interface FetchProjectsResponse { +interface Page { data: IProject[]; previousCursor?: number; nextCursor?: number; } -// interface FetchProjectsParams { -// queryKey: [ -// string, -// { -// isLoadMore: boolean; -// loadNum: number; -// userIdChanged: boolean; -// }, -// ]; -// pageParam?: number; -// } - const ProjectsIndex = (props: IProjectsView) => { const { formatMessage } = useIntl(); const { projects, totalCount: _totalCount } = props; @@ -104,14 +92,18 @@ const ProjectsIndex = (props: IProjectsView) => { // Default values for queryKey const [isLoadMore, setIsLoadMore] = useState(false); - const [loadNum, setLoadNum] = useState(0); + // const [loadNum, setLoadNum] = useState(1); const [userIdChanged, setUserIdChanged] = useState(false); const fetchProjects = useCallback( - async (pageParam: number | unknown): Promise => { - const currentPage = pageParam === undefined ? pageParam : 0; + async (pageParam: number | unknown): Promise => { + // const currentPage = pageParam === undefined ? pageParam : 0; + console.log('pageParam', pageParam); + const currentPage = typeof pageParam === 'number' ? pageParam : 0; - console.log('currentPage', currentPage); + console.log('currentPage', pageParam); + console.log('skip projects.length', projects.length); + console.log('skip', projects.length * (currentPage || 0)); const variables: IQueries = { limit: userIdChanged @@ -127,61 +119,106 @@ const ProjectsIndex = (props: IProjectsView) => { } setIsLoading(true); - if ( - contextVariables.mainCategory !== router.query?.slug?.toString() - ) { - return { data: [], previousCursor: 0, nextCursor: 0 }; - } - - client - .query({ - query: FETCH_ALL_PROJECTS, - variables: { - ...variables, - ...contextVariables, - mainCategory: isArchivedQF - ? undefined - : getMainCategorySlug(selectedMainCategory), - qfRoundSlug: isArchivedQF ? router.query.slug : null, - }, - }) - .then((res: { data: { allProjects: IFetchAllProjects } }) => { - const data = res.data?.allProjects?.projects; - console.log({ res }); - const count = res.data?.allProjects?.totalCount; - setTotalCount(count); - - setFilteredProjects(prevProjects => { - isInfiniteScrolling.current = - (data.length + prevProjects.length) % 45 !== 0; - return isLoadMore ? [...prevProjects, ...data] : data; - }); - setIsLoading(false); - - const result = { - data: data, - previousCursor: projects.length * (currentPage || 0), - nextCursor: projects.length * (currentPage || 0) + 15, - }; - - console.log('fetchProjects result', result); - return result; - }) - .catch((err: any) => { - setIsLoading(false); - showToastError(err); - captureException(err, { - tags: { - section: 'fetchAllProjects', - }, - }); - }); - - return { data: [], previousCursor: 0, nextCursor: 0 }; + // if ( + // contextVariables.mainCategory !== router.query?.slug?.toString() + // ) { + // console.log('run first'); + // return { + // data: filteredProjects, + // previousCursor: 0, + // nextCursor: 0, + // }; + // } + + // client + // .query({ + // query: FETCH_ALL_PROJECTS, + // variables: { + // ...variables, + // ...contextVariables, + // mainCategory: isArchivedQF + // ? undefined + // : getMainCategorySlug(selectedMainCategory), + // qfRoundSlug: isArchivedQF ? router.query.slug : null, + // }, + // }) + // .then((res: { data: { allProjects: IFetchAllProjects } }) => { + // const data = res.data?.allProjects?.projects; + // console.log({ res }); + // const count = res.data?.allProjects?.totalCount; + // setTotalCount(count); + + // setFilteredProjects(prevProjects => { + // isInfiniteScrolling.current = + // (data.length + prevProjects.length) % 45 !== 0; + // return isLoadMore ? [...prevProjects, ...data] : data; + // }); + // setIsLoading(false); + + // console.log('run third'); + + // const result = { + // data: data, + // previousCursor: projects.length * (currentPage || 0), + // nextCursor: projects.length * (currentPage || 0) + 15, + // }; + + // console.log('fetchProjects result', result); + // return result; + // }) + // .catch((err: any) => { + // setIsLoading(false); + // showToastError(err); + // captureException(err, { + // tags: { + // section: 'fetchAllProjects', + // }, + // }); + // }); + + // console.log('run second'); + + // return { + // data: filteredProjects, + // previousCursor: projects.length * (currentPage || 0), + // nextCursor: projects.length * (currentPage || 0) + 15, + // }; + + const res = await client.query({ + query: FETCH_ALL_PROJECTS, + variables: { + ...variables, + ...contextVariables, + mainCategory: isArchivedQF + ? undefined + : getMainCategorySlug(selectedMainCategory), + qfRoundSlug: isArchivedQF ? router.query.slug : null, + }, + }); + + const data = res.data?.allProjects?.projects; + console.log({ res }); + const count = res.data?.allProjects?.totalCount; + setTotalCount(count); + + setFilteredProjects(prevProjects => { + isInfiniteScrolling.current = + (data.length + prevProjects.length) % 45 !== 0; + return isLoadMore ? [...prevProjects, ...data] : data; + }); + + console.log('run second'); + + setIsLoading(false); + + return { + data: data, + previousCursor: currentPage ? currentPage - 1 : 0, + nextCursor: currentPage ? currentPage + 1 : 0, + }; }, [ contextVariables, - filteredProjects.length, isArchivedQF, projects.length, router.query.slug, @@ -189,6 +226,7 @@ const ProjectsIndex = (props: IProjectsView) => { user?.id, userIdChanged, isLoadMore, + filteredProjects, ], ); @@ -200,7 +238,7 @@ const ProjectsIndex = (props: IProjectsView) => { isError, isFetching, isFetchingNextPage, - } = useInfiniteQuery({ + } = useInfiniteQuery({ queryKey: ['projects'], queryFn: ({ pageParam = 0 }: QueryFunctionContext) => fetchProjects(pageParam), @@ -238,7 +276,7 @@ const ProjectsIndex = (props: IProjectsView) => { // fetchProjects(true, pageNum.current + 1); // pageNum.current = pageNum.current + 1; console.log('LOAD MORE'); - fetchNextPage(); + // fetchNextPage(); // }, [fetchProjects, isLoading]); }, [fetchNextPage]); @@ -255,28 +293,28 @@ const ProjectsIndex = (props: IProjectsView) => { const onProjectsPageOrActiveQFPage = !isQF || (isQF && activeQFRound); - useEffect(() => { - const handleObserver = (entities: any) => { - if (!isInfiniteScrolling.current) return; - const target = entities[0]; - if (target.isIntersecting) { - loadMore(); - } - }; - const option = { - root: null, - threshold: 1, - }; - const observer = new IntersectionObserver(handleObserver, option); - if (lastElementRef.current) { - observer.observe(lastElementRef.current); - } - return () => { - if (observer) { - observer.disconnect(); - } - }; - }, [loadMore]); + // useEffect(() => { + // const handleObserver = (entities: any) => { + // if (!isInfiniteScrolling.current) return; + // const target = entities[0]; + // if (target.isIntersecting) { + // loadMore(); + // } + // }; + // const option = { + // root: null, + // threshold: 1, + // }; + // const observer = new IntersectionObserver(handleObserver, option); + // if (lastElementRef.current) { + // observer.observe(lastElementRef.current); + // } + // return () => { + // if (observer) { + // observer.disconnect(); + // } + // }; + // }, [loadMore]); useEffect(() => { if ( @@ -291,6 +329,15 @@ const ProjectsIndex = (props: IProjectsView) => { if (isNotFound) return ; + // TODO: Add a loading spinner when isFetchingNextPage is true + if (isFetching && !isFetchingNextPage) { + return
Loading...
; + } + + if (isError) { + return
Error: {error.message}
; + } + return ( <> {isLoading && ( @@ -337,13 +384,24 @@ const ProjectsIndex = (props: IProjectsView) => { ) : ( )} - {filteredProjects.map((project, idx) => ( + {data?.pages.map((page, pageIndex) => ( + + {page.data.map((project, idx) => ( + + ))} + + ))} + {/* {filteredProjects.map((project, idx) => ( - ))} + ))} */} {/* */} From 164d4820b1ae604efa07170be609f721c1ae673f Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 22 Jul 2024 14:23:37 +0200 Subject: [PATCH 12/87] fetching fixed, removed unnecessary code --- .../views/projects/ProjectsIndex.tsx | 207 ++++-------------- 1 file changed, 48 insertions(+), 159 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 188f21d05d..688f1ad4e2 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -10,7 +10,6 @@ import { import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { captureException } from '@sentry/nextjs'; - import { QueryFunctionContext, useInfiniteQuery } from '@tanstack/react-query'; import ProjectCard from '@/components/project-card/ProjectCard'; import Routes from '@/lib/constants/Routes'; @@ -18,14 +17,12 @@ import { isUserRegistered, showToastError } from '@/lib/helpers'; import { FETCH_ALL_PROJECTS } from '@/apollo/gql/gqlProjects'; import { client } from '@/apollo/apolloClient'; import { IProject } from '@/apollo/types/types'; -import { IFetchAllProjects } from '@/apollo/types/gqlTypes'; import ProjectsNoResults from '@/components/views/projects/ProjectsNoResults'; import { BACKEND_QUERY_LIMIT, mediaQueries } from '@/lib/constants/constants'; import { useAppDispatch, useAppSelector } from '@/features/hooks'; import { setShowCompleteProfile } from '@/features/modal/modal.slice'; import { ProjectsBanner } from './ProjectsBanner'; import { useProjectsContext } from '@/context/projects.context'; - import { ProjectsMiddleBanner } from './MiddleBanners/ProjectsMiddleBanner'; import { ActiveQFProjectsBanner } from './qfBanner/ActiveQFProjectsBanner'; import { PassportBanner } from '@/components/PassportBanner'; @@ -54,6 +51,9 @@ interface IQueries { connectedWalletUserId?: number; } +/** + * A page of projects - return type in fetchProjects function + */ interface Page { data: IProject[]; previousCursor?: number; @@ -67,7 +67,6 @@ const ProjectsIndex = (props: IProjectsView) => { const { activeQFRound, mainCategories } = useAppSelector( state => state.general, ); - const [isLoading, setIsLoading] = useState(false); const [isNotFound, setIsNotFound] = useState(false); const [filteredProjects, setFilteredProjects] = useState(projects); @@ -84,27 +83,18 @@ const ProjectsIndex = (props: IProjectsView) => { } = useProjectsContext(); const router = useRouter(); - const pageNum = useRef(0); const lastElementRef = useRef(null); const isInfiniteScrolling = useRef(true); - router?.events?.on('routeChangeStart', () => setIsLoading(true)); - // Default values for queryKey const [isLoadMore, setIsLoadMore] = useState(false); - // const [loadNum, setLoadNum] = useState(1); const [userIdChanged, setUserIdChanged] = useState(false); const fetchProjects = useCallback( async (pageParam: number | unknown): Promise => { - // const currentPage = pageParam === undefined ? pageParam : 0; console.log('pageParam', pageParam); const currentPage = typeof pageParam === 'number' ? pageParam : 0; - console.log('currentPage', pageParam); - console.log('skip projects.length', projects.length); - console.log('skip', projects.length * (currentPage || 0)); - const variables: IQueries = { limit: userIdChanged ? filteredProjects.length > 50 @@ -118,72 +108,6 @@ const ProjectsIndex = (props: IProjectsView) => { variables.connectedWalletUserId = Number(user?.id); } - setIsLoading(true); - // if ( - // contextVariables.mainCategory !== router.query?.slug?.toString() - // ) { - // console.log('run first'); - // return { - // data: filteredProjects, - // previousCursor: 0, - // nextCursor: 0, - // }; - // } - - // client - // .query({ - // query: FETCH_ALL_PROJECTS, - // variables: { - // ...variables, - // ...contextVariables, - // mainCategory: isArchivedQF - // ? undefined - // : getMainCategorySlug(selectedMainCategory), - // qfRoundSlug: isArchivedQF ? router.query.slug : null, - // }, - // }) - // .then((res: { data: { allProjects: IFetchAllProjects } }) => { - // const data = res.data?.allProjects?.projects; - // console.log({ res }); - // const count = res.data?.allProjects?.totalCount; - // setTotalCount(count); - - // setFilteredProjects(prevProjects => { - // isInfiniteScrolling.current = - // (data.length + prevProjects.length) % 45 !== 0; - // return isLoadMore ? [...prevProjects, ...data] : data; - // }); - // setIsLoading(false); - - // console.log('run third'); - - // const result = { - // data: data, - // previousCursor: projects.length * (currentPage || 0), - // nextCursor: projects.length * (currentPage || 0) + 15, - // }; - - // console.log('fetchProjects result', result); - // return result; - // }) - // .catch((err: any) => { - // setIsLoading(false); - // showToastError(err); - // captureException(err, { - // tags: { - // section: 'fetchAllProjects', - // }, - // }); - // }); - - // console.log('run second'); - - // return { - // data: filteredProjects, - // previousCursor: projects.length * (currentPage || 0), - // nextCursor: projects.length * (currentPage || 0) + 15, - // }; - const res = await client.query({ query: FETCH_ALL_PROJECTS, variables: { @@ -197,7 +121,6 @@ const ProjectsIndex = (props: IProjectsView) => { }); const data = res.data?.allProjects?.projects; - console.log({ res }); const count = res.data?.allProjects?.totalCount; setTotalCount(count); @@ -207,14 +130,10 @@ const ProjectsIndex = (props: IProjectsView) => { return isLoadMore ? [...prevProjects, ...data] : data; }); - console.log('run second'); - - setIsLoading(false); - return { data: data, - previousCursor: currentPage ? currentPage - 1 : 0, - nextCursor: currentPage ? currentPage + 1 : 0, + previousCursor: currentPage - 1, + nextCursor: currentPage + 1, }; }, [ @@ -234,7 +153,6 @@ const ProjectsIndex = (props: IProjectsView) => { data, error, fetchNextPage, - hasNextPage, isError, isFetching, isFetchingNextPage, @@ -242,42 +160,23 @@ const ProjectsIndex = (props: IProjectsView) => { queryKey: ['projects'], queryFn: ({ pageParam = 0 }: QueryFunctionContext) => fetchProjects(pageParam), - getNextPageParam: (lastPage, fetchedData) => { - console.log('getNextPageParam called', lastPage, fetchedData); + getNextPageParam: lastPage => { return lastPage.nextCursor; }, initialPageParam: 0, }); useEffect(() => { - if (data) { - console.log('Data from React Query:', data); - } - if (hasNextPage !== undefined) { - console.log('Has Next Page:', hasNextPage); - } - if (isFetchingNextPage !== undefined) { - console.log('Is Fetching Next Page:', isFetchingNextPage); - } - }, [data, hasNextPage, isFetchingNextPage]); + setUserIdChanged(prevState => !prevState); + fetchNextPage({ cancelRefetch: true }); + }, [fetchNextPage, user?.id]); - // useEffect(() => { - // pageNum.current = 0; - // fetchProjects(false, 0, true); - // }, [user?.id]); - - // useEffect(() => { - // pageNum.current = 0; - // fetchProjects(false, 0); - // }, [contextVariables]); + useEffect(() => { + fetchNextPage({ cancelRefetch: true }); + }, [contextVariables]); const loadMore = useCallback(() => { - // if (isLoading) return; - // fetchProjects(true, pageNum.current + 1); - // pageNum.current = pageNum.current + 1; - console.log('LOAD MORE'); - // fetchNextPage(); - // }, [fetchProjects, isLoading]); + fetchNextPage(); }, [fetchNextPage]); const handleCreateButton = () => { @@ -293,28 +192,28 @@ const ProjectsIndex = (props: IProjectsView) => { const onProjectsPageOrActiveQFPage = !isQF || (isQF && activeQFRound); - // useEffect(() => { - // const handleObserver = (entities: any) => { - // if (!isInfiniteScrolling.current) return; - // const target = entities[0]; - // if (target.isIntersecting) { - // loadMore(); - // } - // }; - // const option = { - // root: null, - // threshold: 1, - // }; - // const observer = new IntersectionObserver(handleObserver, option); - // if (lastElementRef.current) { - // observer.observe(lastElementRef.current); - // } - // return () => { - // if (observer) { - // observer.disconnect(); - // } - // }; - // }, [loadMore]); + useEffect(() => { + const handleObserver = (entities: any) => { + if (!isInfiniteScrolling.current) return; + const target = entities[0]; + if (target.isIntersecting) { + loadMore(); + } + }; + const option = { + root: null, + threshold: 1, + }; + const observer = new IntersectionObserver(handleObserver, option); + if (lastElementRef.current) { + observer.observe(lastElementRef.current); + } + return () => { + if (observer) { + observer.disconnect(); + } + }; + }, [loadMore]); useEffect(() => { if ( @@ -329,18 +228,19 @@ const ProjectsIndex = (props: IProjectsView) => { if (isNotFound) return ; - // TODO: Add a loading spinner when isFetchingNextPage is true - if (isFetching && !isFetchingNextPage) { - return
Loading...
; - } - + // Handle fetching errors - if (isError) { - return
Error: {error.message}
; + showToastError(error); + captureException(error, { + tags: { + section: 'fetchAllProjects', + }, + }); } return ( <> - {isLoading && ( + {isFetching && !isFetchingNextPage && ( @@ -375,7 +275,9 @@ const ProjectsIndex = (props: IProjectsView) => { )} - {isLoading && } + {isFetching && !isFetchingNextPage && ( + + )} {filteredProjects?.length > 0 ? ( @@ -395,13 +297,6 @@ const ProjectsIndex = (props: IProjectsView) => { ))} ))} - {/* {filteredProjects.map((project, idx) => ( - - ))} */} {/* */} @@ -418,14 +313,14 @@ const ProjectsIndex = (props: IProjectsView) => {
@@ -441,12 +336,6 @@ const ProjectsIndex = (props: IProjectsView) => { /> )} - ); From 214333a0e337bf8937b28e645b3a68b12626ec38 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 22 Jul 2024 15:06:55 +0200 Subject: [PATCH 13/87] fixed loading duplication now I need to go through the all variables and states to check if something is missing --- .../views/projects/ProjectsIndex.tsx | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 688f1ad4e2..310b0bafb7 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -160,13 +160,16 @@ const ProjectsIndex = (props: IProjectsView) => { queryKey: ['projects'], queryFn: ({ pageParam = 0 }: QueryFunctionContext) => fetchProjects(pageParam), - getNextPageParam: lastPage => { + getNextPageParam: (lastPage, someData) => { + console.log('lastPage', lastPage); + console.log('someData', someData); return lastPage.nextCursor; }, initialPageParam: 0, }); useEffect(() => { + console.log('user id changed'); setUserIdChanged(prevState => !prevState); fetchNextPage({ cancelRefetch: true }); }, [fetchNextPage, user?.id]); @@ -240,7 +243,7 @@ const ProjectsIndex = (props: IProjectsView) => { return ( <> - {isFetching && !isFetchingNextPage && ( + {(isFetching || isFetchingNextPage) && ( @@ -275,9 +278,7 @@ const ProjectsIndex = (props: IProjectsView) => { )} - {isFetching && !isFetchingNextPage && ( - - )} + {isFetchingNextPage && } {filteredProjects?.length > 0 ? ( @@ -289,11 +290,14 @@ const ProjectsIndex = (props: IProjectsView) => { {data?.pages.map((page, pageIndex) => ( {page.data.map((project, idx) => ( - +
+ {project.id} + +
))}
))} @@ -313,14 +317,14 @@ const ProjectsIndex = (props: IProjectsView) => {
From b0492f4ab3fa9935f6900d1b07ca04d1aa846efa Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 23 Jul 2024 16:04:37 +0200 Subject: [PATCH 14/87] Added reset query option --- .../views/projects/ProjectsIndex.tsx | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 310b0bafb7..1d7ca1b920 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -10,7 +10,11 @@ import { import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { captureException } from '@sentry/nextjs'; -import { QueryFunctionContext, useInfiniteQuery } from '@tanstack/react-query'; +import { + QueryFunctionContext, + useInfiniteQuery, + useQueryClient, +} from '@tanstack/react-query'; import ProjectCard from '@/components/project-card/ProjectCard'; import Routes from '@/lib/constants/Routes'; import { isUserRegistered, showToastError } from '@/lib/helpers'; @@ -61,6 +65,7 @@ interface Page { } const ProjectsIndex = (props: IProjectsView) => { + const queryClient = useQueryClient(); const { formatMessage } = useIntl(); const { projects, totalCount: _totalCount } = props; const user = useAppSelector(state => state.user.userData); @@ -168,16 +173,25 @@ const ProjectsIndex = (props: IProjectsView) => { initialPageParam: 0, }); + // User signied in or singout reset query useEffect(() => { console.log('user id changed'); setUserIdChanged(prevState => !prevState); - fetchNextPage({ cancelRefetch: true }); - }, [fetchNextPage, user?.id]); + queryClient.resetQueries({ + queryKey: ['projects'], + exact: true, + }); + }, [queryClient, user?.id]); + // Reset query if contect variables change occurs useEffect(() => { - fetchNextPage({ cancelRefetch: true }); - }, [contextVariables]); + queryClient.resetQueries({ + queryKey: ['projects'], + exact: true, + }); + }, [contextVariables, queryClient]); + // Function that triggers when you scroll down - infinite loading const loadMore = useCallback(() => { fetchNextPage(); }, [fetchNextPage]); @@ -193,8 +207,13 @@ const ProjectsIndex = (props: IProjectsView) => { const showLoadMore = totalCount > filteredProjects?.length && !isInfiniteScrolling.current; + // Check if there any active QF const onProjectsPageOrActiveQFPage = !isQF || (isQF && activeQFRound); + /* + * This function will be called when the observed elements intersect with the viewport. + * Observed element is last project on the list that trigger another fetch projects to load. + */ useEffect(() => { const handleObserver = (entities: any) => { if (!isInfiniteScrolling.current) return; @@ -243,6 +262,17 @@ const ProjectsIndex = (props: IProjectsView) => { return ( <> + {(isFetching || isFetchingNextPage) && ( @@ -290,8 +320,7 @@ const ProjectsIndex = (props: IProjectsView) => { {data?.pages.map((page, pageIndex) => ( {page.data.map((project, idx) => ( -
- {project.id} +
Date: Fri, 26 Jul 2024 15:48:03 +0200 Subject: [PATCH 15/87] added scroll to row when you got back --- .../views/projects/ProjectsIndex.tsx | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 1d7ca1b920..929822376e 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -97,7 +97,6 @@ const ProjectsIndex = (props: IProjectsView) => { const fetchProjects = useCallback( async (pageParam: number | unknown): Promise => { - console.log('pageParam', pageParam); const currentPage = typeof pageParam === 'number' ? pageParam : 0; const variables: IQueries = { @@ -174,22 +173,26 @@ const ProjectsIndex = (props: IProjectsView) => { }); // User signied in or singout reset query - useEffect(() => { - console.log('user id changed'); - setUserIdChanged(prevState => !prevState); - queryClient.resetQueries({ - queryKey: ['projects'], - exact: true, - }); - }, [queryClient, user?.id]); + // TODO: need to refactor, only change when user loggin or out + // useEffect(() => { + // console.log('user id changed'); + // if (user?.id) { + // setUserIdChanged(prevState => !prevState); + // queryClient.resetQueries({ + // queryKey: ['projects'], + // exact: true, + // }); + // } + // }, [queryClient, user?.id]); // Reset query if contect variables change occurs - useEffect(() => { - queryClient.resetQueries({ - queryKey: ['projects'], - exact: true, - }); - }, [contextVariables, queryClient]); + // TODO: need to refactor, + // useEffect(() => { + // queryClient.resetQueries({ + // queryKey: ['projects'], + // exact: true, + // }); + // }, [contextVariables, queryClient]); // Function that triggers when you scroll down - infinite loading const loadMore = useCallback(() => { @@ -247,10 +250,30 @@ const ProjectsIndex = (props: IProjectsView) => { } }, [selectedMainCategory, mainCategories.length]); + // Save last clicked project + const handleProjectClick = (slug: string) => { + localStorage.setItem('lastProjectClicked', slug); + }; + + // Handle last clicked project, if it exist scroll to that position + useEffect(() => { + if (!isFetching && !isFetchingNextPage) { + const lastProjectClicked = + localStorage.getItem('lastProjectClicked'); + if (lastProjectClicked) { + window.scrollTo({ + top: document.getElementById(lastProjectClicked)?.offsetTop, + behavior: 'smooth', + }); + localStorage.removeItem('lastProjectClicked'); + } + } + }, [isFetching, isFetchingNextPage]); + if (isNotFound) return ; - // Handle fetching errors - + // Handle fetching errors from React Query if (isError) { showToastError(error); captureException(error, { @@ -262,17 +285,6 @@ const ProjectsIndex = (props: IProjectsView) => { return ( <> - {(isFetching || isFetchingNextPage) && ( @@ -320,7 +332,13 @@ const ProjectsIndex = (props: IProjectsView) => { {data?.pages.map((page, pageIndex) => ( {page.data.map((project, idx) => ( -
+
+ handleProjectClick(project.slug) + } + > Date: Mon, 29 Jul 2024 14:37:13 +0200 Subject: [PATCH 16/87] removing user.id dependencies --- .../views/projects/ProjectsIndex.tsx | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 929822376e..25648310cb 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -22,7 +22,7 @@ import { FETCH_ALL_PROJECTS } from '@/apollo/gql/gqlProjects'; import { client } from '@/apollo/apolloClient'; import { IProject } from '@/apollo/types/types'; import ProjectsNoResults from '@/components/views/projects/ProjectsNoResults'; -import { BACKEND_QUERY_LIMIT, mediaQueries } from '@/lib/constants/constants'; +import { mediaQueries } from '@/lib/constants/constants'; import { useAppDispatch, useAppSelector } from '@/features/hooks'; import { setShowCompleteProfile } from '@/features/modal/modal.slice'; import { ProjectsBanner } from './ProjectsBanner'; @@ -69,6 +69,7 @@ const ProjectsIndex = (props: IProjectsView) => { const { formatMessage } = useIntl(); const { projects, totalCount: _totalCount } = props; const user = useAppSelector(state => state.user.userData); + const { activeQFRound, mainCategories } = useAppSelector( state => state.general, ); @@ -91,21 +92,13 @@ const ProjectsIndex = (props: IProjectsView) => { const lastElementRef = useRef(null); const isInfiniteScrolling = useRef(true); - // Default values for queryKey - const [isLoadMore, setIsLoadMore] = useState(false); - const [userIdChanged, setUserIdChanged] = useState(false); - const fetchProjects = useCallback( async (pageParam: number | unknown): Promise => { const currentPage = typeof pageParam === 'number' ? pageParam : 0; const variables: IQueries = { - limit: userIdChanged - ? filteredProjects.length > 50 - ? BACKEND_QUERY_LIMIT - : filteredProjects.length - : projects.length, - skip: userIdChanged ? 0 : projects.length * (currentPage || 0), + limit: projects.length, + skip: projects.length * (currentPage || 0), }; if (user?.id) { @@ -128,11 +121,7 @@ const ProjectsIndex = (props: IProjectsView) => { const count = res.data?.allProjects?.totalCount; setTotalCount(count); - setFilteredProjects(prevProjects => { - isInfiniteScrolling.current = - (data.length + prevProjects.length) % 45 !== 0; - return isLoadMore ? [...prevProjects, ...data] : data; - }); + setFilteredProjects(prevProjects => [...prevProjects, ...data]); return { data: data, @@ -147,9 +136,6 @@ const ProjectsIndex = (props: IProjectsView) => { router.query.slug, selectedMainCategory, user?.id, - userIdChanged, - isLoadMore, - filteredProjects, ], ); @@ -172,19 +158,6 @@ const ProjectsIndex = (props: IProjectsView) => { initialPageParam: 0, }); - // User signied in or singout reset query - // TODO: need to refactor, only change when user loggin or out - // useEffect(() => { - // console.log('user id changed'); - // if (user?.id) { - // setUserIdChanged(prevState => !prevState); - // queryClient.resetQueries({ - // queryKey: ['projects'], - // exact: true, - // }); - // } - // }, [queryClient, user?.id]); - // Reset query if contect variables change occurs // TODO: need to refactor, // useEffect(() => { @@ -207,9 +180,6 @@ const ProjectsIndex = (props: IProjectsView) => { } }; - const showLoadMore = - totalCount > filteredProjects?.length && !isInfiniteScrolling.current; - // Check if there any active QF const onProjectsPageOrActiveQFPage = !isQF || (isQF && activeQFRound); @@ -248,7 +218,7 @@ const ProjectsIndex = (props: IProjectsView) => { ) { setIsNotFound(true); } - }, [selectedMainCategory, mainCategories.length]); + }, [selectedMainCategory, mainCategories.length, isArchivedQF]); // Save last clicked project const handleProjectClick = (slug: string) => { @@ -359,7 +329,7 @@ const ProjectsIndex = (props: IProjectsView) => { {totalCount > filteredProjects?.length && (
)} - {showLoadMore && ( + {(isFetching || isFetchingNextPage) && ( <> Date: Mon, 29 Jul 2024 14:50:34 +0200 Subject: [PATCH 17/87] fix loading button if something hook up --- src/components/views/projects/ProjectsIndex.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 25648310cb..385cbc592d 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -159,8 +159,9 @@ const ProjectsIndex = (props: IProjectsView) => { }); // Reset query if contect variables change occurs - // TODO: need to refactor, + // TODO: This is causing a bug where the page is refreshed when you come back // useEffect(() => { + // console.log('SOMETHING CHANGED'); // queryClient.resetQueries({ // queryKey: ['projects'], // exact: true, @@ -329,7 +330,7 @@ const ProjectsIndex = (props: IProjectsView) => { {totalCount > filteredProjects?.length && (
)} - {(isFetching || isFetchingNextPage) && ( + {!isFetching && !isFetchingNextPage && ( <> Date: Tue, 30 Jul 2024 14:56:06 +0200 Subject: [PATCH 18/87] fixed total count and removed unused useEffect --- .../views/projects/ProjectsIndex.tsx | 74 +++++++++---------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 385cbc592d..e3d3535071 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -121,7 +121,10 @@ const ProjectsIndex = (props: IProjectsView) => { const count = res.data?.allProjects?.totalCount; setTotalCount(count); - setFilteredProjects(prevProjects => [...prevProjects, ...data]); + setFilteredProjects(prevProjects => [ + ...prevProjects, + data.allProjects?.projects, + ]); return { data: data, @@ -158,16 +161,6 @@ const ProjectsIndex = (props: IProjectsView) => { initialPageParam: 0, }); - // Reset query if contect variables change occurs - // TODO: This is causing a bug where the page is refreshed when you come back - // useEffect(() => { - // console.log('SOMETHING CHANGED'); - // queryClient.resetQueries({ - // queryKey: ['projects'], - // exact: true, - // }); - // }, [contextVariables, queryClient]); - // Function that triggers when you scroll down - infinite loading const loadMore = useCallback(() => { fetchNextPage(); @@ -330,34 +323,37 @@ const ProjectsIndex = (props: IProjectsView) => { {totalCount > filteredProjects?.length && (
)} - {!isFetching && !isFetchingNextPage && ( - <> - -
- - ) - } - /> - - - )} + {!isFetching && + !isFetchingNextPage && + totalCount < filteredProjects?.length && ( + <> + +
+ + ) + } + /> + {totalCount} - {filteredProjects?.length} + + + )} ); From 78a9454b5d0546c6b33e55a0e386979d6db88dc6 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 30 Jul 2024 15:26:02 +0200 Subject: [PATCH 19/87] fixed total count, introduced total pages --- .../views/projects/ProjectsIndex.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index e3d3535071..2269dad0d7 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -76,7 +76,9 @@ const ProjectsIndex = (props: IProjectsView) => { const [isNotFound, setIsNotFound] = useState(false); const [filteredProjects, setFilteredProjects] = useState(projects); - const [totalCount, setTotalCount] = useState(_totalCount); + const [totalPages, setTotalPages] = useState( + Math.ceil(_totalCount / projects.length), + ); const isMobile = useMediaQuery(`(max-width: ${deviceSize.tablet - 1}px)`); const dispatch = useAppDispatch(); @@ -119,7 +121,8 @@ const ProjectsIndex = (props: IProjectsView) => { const data = res.data?.allProjects?.projects; const count = res.data?.allProjects?.totalCount; - setTotalCount(count); + const totalPages = Math.ceil(count / projects.length); + setTotalPages(totalPages); setFilteredProjects(prevProjects => [ ...prevProjects, @@ -163,8 +166,10 @@ const ProjectsIndex = (props: IProjectsView) => { // Function that triggers when you scroll down - infinite loading const loadMore = useCallback(() => { - fetchNextPage(); - }, [fetchNextPage]); + if (totalPages > (data?.pages?.length || 0)) { + fetchNextPage(); + } + }, [data?.pages.length, fetchNextPage, totalPages]); const handleCreateButton = () => { if (isUserRegistered(user)) { @@ -281,7 +286,7 @@ const ProjectsIndex = (props: IProjectsView) => { )} {onProjectsPageOrActiveQFPage && ( - + )} {isFetchingNextPage && } @@ -320,12 +325,12 @@ const ProjectsIndex = (props: IProjectsView) => { ) : ( )} - {totalCount > filteredProjects?.length && ( + {_totalCount > filteredProjects?.length && (
)} {!isFetching && !isFetchingNextPage && - totalCount < filteredProjects?.length && ( + totalPages > (data?.pages?.length || 0) && ( <> { ) } /> - {totalCount} - {filteredProjects?.length} Date: Tue, 30 Jul 2024 15:31:48 +0200 Subject: [PATCH 20/87] updated queryKey reference --- src/components/views/projects/ProjectsIndex.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index 2269dad0d7..b002787bbc 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -153,7 +153,12 @@ const ProjectsIndex = (props: IProjectsView) => { isFetching, isFetchingNextPage, } = useInfiniteQuery({ - queryKey: ['projects'], + queryKey: [ + 'projects', + contextVariables, + isArchivedQF, + selectedMainCategory, + ], queryFn: ({ pageParam = 0 }: QueryFunctionContext) => fetchProjects(pageParam), getNextPageParam: (lastPage, someData) => { From e2b9ae3755f172b46014ca0da4d86b105a7b5a01 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 16 Sep 2024 17:03:47 +0200 Subject: [PATCH 21/87] fixing total count --- src/components/views/projects/ProjectsIndex.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/projects/ProjectsIndex.tsx b/src/components/views/projects/ProjectsIndex.tsx index b002787bbc..b059e14b50 100644 --- a/src/components/views/projects/ProjectsIndex.tsx +++ b/src/components/views/projects/ProjectsIndex.tsx @@ -79,6 +79,7 @@ const ProjectsIndex = (props: IProjectsView) => { const [totalPages, setTotalPages] = useState( Math.ceil(_totalCount / projects.length), ); + const [totalCount, setTotalCount] = useState(_totalCount); const isMobile = useMediaQuery(`(max-width: ${deviceSize.tablet - 1}px)`); const dispatch = useAppDispatch(); @@ -123,6 +124,7 @@ const ProjectsIndex = (props: IProjectsView) => { const count = res.data?.allProjects?.totalCount; const totalPages = Math.ceil(count / projects.length); setTotalPages(totalPages); + setTotalCount(count); setFilteredProjects(prevProjects => [ ...prevProjects, @@ -291,7 +293,7 @@ const ProjectsIndex = (props: IProjectsView) => { )} {onProjectsPageOrActiveQFPage && ( - + )} {isFetchingNextPage && } From 2f07fb145cded0b893848f0978367b15ab29f3e9 Mon Sep 17 00:00:00 2001 From: Kilter Date: Tue, 24 Sep 2024 11:10:39 -0500 Subject: [PATCH 22/87] pending chain id change --- src/components/modals/SwitchNetwork.tsx | 32 ++++++++++++++++--------- src/providers/generalWalletProvider.tsx | 19 ++++++++++++++- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/modals/SwitchNetwork.tsx b/src/components/modals/SwitchNetwork.tsx index 618ece34e5..12f4c4528a 100644 --- a/src/components/modals/SwitchNetwork.tsx +++ b/src/components/modals/SwitchNetwork.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useState, useEffect } from 'react'; import { B, brandColors, @@ -28,6 +28,7 @@ const defaultNetworks = Object.keys(networksConfig).map(key => ({ networkId: Number(key), chainType: networksConfig[Number(key)].chainType, })); + interface ISwitchNetworkModal extends IModal { desc?: string; customNetworks?: INetworkIdWithChain[]; @@ -39,11 +40,15 @@ const SwitchNetwork: FC = ({ setShowModal, }) => { const { isAnimating, closeModal } = useModalAnimation(setShowModal); - const { switchChain } = useSwitchChain(); const { formatMessage } = useIntl(); - const { walletChainType, handleSingOutAndSignInWithEVM, chain } = - useGeneralWallet(); + const { + walletChainType, + handleSingOutAndSignInWithEVM, + pendingNetworkId, + setPendingNetworkId, + chain, + } = useGeneralWallet(); const chainId = (chain as Chain)?.id; const theme = useAppSelector(state => state.general.theme); @@ -55,6 +60,17 @@ const SwitchNetwork: FC = ({ }; }) || defaultNetworks; + const handleNetworkItemClick = (networkId: number) => { + if (walletChainType === ChainType.SOLANA) { + setPendingNetworkId(networkId); + handleSingOutAndSignInWithEVM(); + closeModal(); // Close the modal since we cannot control the wallet modal + } else { + switchChain?.({ chainId: networkId }); + closeModal(); + } + }; + return ( = ({ {desc &&

{desc}

} {networks?.map(({ networkId, chainType }) => ( { - if (walletChainType === ChainType.SOLANA) { - handleSingOutAndSignInWithEVM(); - } - switchChain?.({ chainId: networkId }); - closeModal(); - }} + onClick={() => handleNetworkItemClick(networkId)} $isSelected={networkId === chainId} key={networkId} $baseTheme={theme} diff --git a/src/providers/generalWalletProvider.tsx b/src/providers/generalWalletProvider.tsx index 8b0bb12e0e..6b0ad2f7e0 100644 --- a/src/providers/generalWalletProvider.tsx +++ b/src/providers/generalWalletProvider.tsx @@ -14,7 +14,7 @@ import { Transaction, SystemProgram, } from '@solana/web3.js'; -import { useBalance, useDisconnect, useAccount } from 'wagmi'; +import { useBalance, useDisconnect, useAccount, useSwitchChain } from 'wagmi'; import { getWalletClient } from '@wagmi/core'; import { WalletAdapterNetwork } from '@solana/wallet-adapter-base'; import { useWeb3Modal } from '@web3modal/wagmi/react'; @@ -58,6 +58,8 @@ interface IGeneralWalletContext { handleSignOutAndShowWelcomeModal: () => Promise; isOnSolana: boolean; isOnEVM: boolean; + pendingNetworkId: number | null; + setPendingNetworkId: (id: number | null) => void; } // Create the context export const GeneralWalletContext = createContext({ @@ -76,6 +78,8 @@ export const GeneralWalletContext = createContext({ handleSignOutAndShowWelcomeModal: async () => {}, isOnSolana: false, isOnEVM: false, + pendingNetworkId: null, + setPendingNetworkId: () => {}, }); const getPhantomSolanaProvider = () => { @@ -93,6 +97,9 @@ export const GeneralWalletProvider: React.FC<{ const [walletChainType, setWalletChainType] = useState( null, ); + const [pendingNetworkId, setPendingNetworkId] = useState( + null, + ); const [walletAddress, setWalletAddress] = useState(null); const [balance, setBalance] = useState(); const [isConnected, setIsConnected] = useState(false); @@ -106,6 +113,7 @@ export const GeneralWalletProvider: React.FC<{ const router = useRouter(); const { token } = useAppSelector(state => state.user); const { setVisible, visible } = useWalletModal(); + const { switchChain } = useSwitchChain(); const isGIVeconomyRoute = useMemo( () => checkIsGIVeconomyRoute(router.route), @@ -266,6 +274,13 @@ export const GeneralWalletProvider: React.FC<{ } }, [walletChainType, nonFormattedEvBalance, solanaBalance]); + useEffect(() => { + if (walletChainType === ChainType.EVM && pendingNetworkId !== null) { + switchChain?.({ chainId: pendingNetworkId }); + setPendingNetworkId(null); + } + }, [walletChainType, pendingNetworkId]); + const signMessage = async ( message: string, ): Promise => { @@ -408,6 +423,8 @@ export const GeneralWalletProvider: React.FC<{ handleSignOutAndShowWelcomeModal, isOnSolana, isOnEVM, + pendingNetworkId, + setPendingNetworkId, }; // Render the provider component with the provided context value From d9e7460a89b188e44962b58a612cb6bb4008614c Mon Sep 17 00:00:00 2001 From: Kilter Date: Tue, 24 Sep 2024 11:14:04 -0500 Subject: [PATCH 23/87] removing unused variable --- src/components/modals/SwitchNetwork.tsx | 3 +-- src/providers/generalWalletProvider.tsx | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/modals/SwitchNetwork.tsx b/src/components/modals/SwitchNetwork.tsx index 12f4c4528a..c203c6ba17 100644 --- a/src/components/modals/SwitchNetwork.tsx +++ b/src/components/modals/SwitchNetwork.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState, useEffect } from 'react'; +import React, { FC } from 'react'; import { B, brandColors, @@ -45,7 +45,6 @@ const SwitchNetwork: FC = ({ const { walletChainType, handleSingOutAndSignInWithEVM, - pendingNetworkId, setPendingNetworkId, chain, } = useGeneralWallet(); diff --git a/src/providers/generalWalletProvider.tsx b/src/providers/generalWalletProvider.tsx index 6b0ad2f7e0..112fcd1da2 100644 --- a/src/providers/generalWalletProvider.tsx +++ b/src/providers/generalWalletProvider.tsx @@ -58,7 +58,6 @@ interface IGeneralWalletContext { handleSignOutAndShowWelcomeModal: () => Promise; isOnSolana: boolean; isOnEVM: boolean; - pendingNetworkId: number | null; setPendingNetworkId: (id: number | null) => void; } // Create the context @@ -78,7 +77,6 @@ export const GeneralWalletContext = createContext({ handleSignOutAndShowWelcomeModal: async () => {}, isOnSolana: false, isOnEVM: false, - pendingNetworkId: null, setPendingNetworkId: () => {}, }); @@ -423,7 +421,6 @@ export const GeneralWalletProvider: React.FC<{ handleSignOutAndShowWelcomeModal, isOnSolana, isOnEVM, - pendingNetworkId, setPendingNetworkId, }; From 24c388b21336d04f7ec96e1e0ebfde6613115882 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Wed, 2 Oct 2024 15:05:01 +0200 Subject: [PATCH 24/87] Fix/email project verification on mobile and tablet devices --- .../verification/EmailVerificationIndex.tsx | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/views/verification/EmailVerificationIndex.tsx b/src/components/views/verification/EmailVerificationIndex.tsx index 6645e9c072..c852aa703b 100644 --- a/src/components/views/verification/EmailVerificationIndex.tsx +++ b/src/components/views/verification/EmailVerificationIndex.tsx @@ -1,4 +1,5 @@ import React, { FC, useEffect, useState } from 'react'; +import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { brandColors, H6, Lead, ButtonLink } from '@giveth/ui-design-system'; import Link from 'next/link'; @@ -9,6 +10,7 @@ import check_stars from '/public/images/icons/check_stars.svg'; import failed_stars from '/public/images/icons/failed_stars.svg'; import { SEND_EMAIL_VERIFICATION_TOKEN } from '@/apollo/gql/gqlVerification'; import { client } from '@/apollo/apolloClient'; +import { mediaQueries } from '@/lib/constants/constants'; import { slugToVerification } from '@/lib/routeCreators'; import { VCImageContainer, @@ -96,16 +98,18 @@ function Verified() { id: 'label.you_can_now_close_this_page_and_continue_verifying', })} - - - + + + + + ); } + +const LinkHolder = styled.div` + ${mediaQueries.tablet} { + margin-bottom: 205px; + } +`; From 422b4c9170f00a37e14d5bea05e859505b8b2979 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Thu, 3 Oct 2024 17:23:16 +0530 Subject: [PATCH 25/87] sort the rounds in project page --- .../project/projectDonations/QfRoundSelector.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/views/project/projectDonations/QfRoundSelector.tsx b/src/components/views/project/projectDonations/QfRoundSelector.tsx index 9f6feba4ea..848a1b11dc 100644 --- a/src/components/views/project/projectDonations/QfRoundSelector.tsx +++ b/src/components/views/project/projectDonations/QfRoundSelector.tsx @@ -37,9 +37,14 @@ export const QfRoundSelector: FC = ({ const navigationNextRef = useRef(null); const sortedRounds = - projectData?.qfRounds?.sort( - (a, b) => Number(b.isActive) - Number(a.isActive), - ) || []; + projectData?.qfRounds?.sort((a: IQFRound, b: IQFRound) => { + const activeFirstCompare = Number(b.isActive) - Number(a.isActive); + console.log(a.beginDate, b.beginDate); + if (activeFirstCompare === 0) { + return new Date(b.beginDate) > new Date(a.beginDate) ? 1 : -1; + } + return activeFirstCompare; + }) || []; const isRecurringSelected = projectDonationSwiperState.isRecurringSelected; const selectedQF = projectDonationSwiperState.selectedQF; From d7c2c1fc6177fa0b7d0c6089a53e196836e20527 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Thu, 3 Oct 2024 17:26:36 +0530 Subject: [PATCH 26/87] chore: remove log statements --- .../views/project/projectDonations/QfRoundSelector.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/project/projectDonations/QfRoundSelector.tsx b/src/components/views/project/projectDonations/QfRoundSelector.tsx index 848a1b11dc..726f585e86 100644 --- a/src/components/views/project/projectDonations/QfRoundSelector.tsx +++ b/src/components/views/project/projectDonations/QfRoundSelector.tsx @@ -39,7 +39,6 @@ export const QfRoundSelector: FC = ({ const sortedRounds = projectData?.qfRounds?.sort((a: IQFRound, b: IQFRound) => { const activeFirstCompare = Number(b.isActive) - Number(a.isActive); - console.log(a.beginDate, b.beginDate); if (activeFirstCompare === 0) { return new Date(b.beginDate) > new Date(a.beginDate) ? 1 : -1; } From 77d787340980f215c876e78ead99ce02abc27d63 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 4 Oct 2024 11:27:56 +0200 Subject: [PATCH 27/87] Fix/success email verification - missed one media query --- src/components/views/verification/EmailVerificationIndex.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/verification/EmailVerificationIndex.tsx b/src/components/views/verification/EmailVerificationIndex.tsx index c852aa703b..fa67ad2b1e 100644 --- a/src/components/views/verification/EmailVerificationIndex.tsx +++ b/src/components/views/verification/EmailVerificationIndex.tsx @@ -160,7 +160,10 @@ function Rejected() { } const LinkHolder = styled.div` - ${mediaQueries.tablet} { + ${mediaQueries.mobileS} { margin-bottom: 205px; } + ${mediaQueries.laptopS} { + margin-bottom: 0; + } `; From f57d735a495a63e87f619063f6c57b2ad44153df Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Fri, 4 Oct 2024 16:15:00 +0530 Subject: [PATCH 28/87] quadratic funding toast on donate success page --- lang/ca.json | 9 +++-- lang/en.json | 9 +++-- lang/es.json | 9 +++-- src/apollo/gql/gqlQF.ts | 1 + src/components/modals/PassportModal.tsx | 2 +- src/components/views/donate/DonateIndex.tsx | 5 +-- .../views/donate/OneTime/DonateModal.tsx | 5 +-- src/components/views/donate/QFToast.tsx | 38 +++++++++++++------ 8 files changed, 46 insertions(+), 32 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index fa5b4f6e37..b233e82f4c 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -1352,12 +1352,12 @@ "page.donate.matching_toast.bottom_invalid_p2": "són elegibles per a l'aparellament.", "page.donate.matching_toast.bottom_valid": "Els fons de finançament es destinaran al projecte seleccionat després que acabi la ronda. Dona a més projectes per rebre més finançament!", "page.donate.network_not_eligible_for_qf": "Les donacions de {network} no són aptes per coincidir", - "page.donate.passport_toast.description.eligible": "La teva donació és elegible per ser emparellada! Després del", - "page.donate.passport_toast.description.eligible_2": ", totes les donacions seran revisades per a la protecció contra frau i els fons d'emparellament seran enviats als projectes. Estigues atent a les notificacions :)", - "page.donate.passport_toast.description.non_eligible": "Obtén el teu emparellament de donació amb finançament quadràtic!\nComproveu la vostra elegibilitat QF abans", + "page.donate.passport_toast.description.eligible": "Sou elegible per a QF! Sempre que les vostres donacions siguin almenys $", + "page.donate.passport_toast.description.eligible_2": ", són aptes per ser emparellats", + "page.donate.passport_toast.description.non_eligible": "Les donacions superiors a ${usd_value} són aptes per ser igualades amb finançament quadràtic.\nVerifiqueu la vostra elegibilitat de QF abans", "page.donate.passport_toast.description.not_connected": "Obtén el teu emparellament de donació amb finançament quadràtic!\nVerifica el teu Gitcoin Passport abans de", "page.donate.passport_toast.title.eligible": "Finançament Quadràtic", - "page.donate.passport_toast.title.non_eligible": "No et perdis l'emparellament!", + "page.donate.passport_toast.title.non_eligible": "No us ho perdeu!", "page.donate.project_not_eligible_for_qf": "El projecte no és elegible per a la concordança QF.", "page.donate.project_not_givbacks_eligible": "El projecte no és elegible per a GIVbacks", "page.donate.title": "Donar", @@ -1709,6 +1709,7 @@ "public-goods": "Béns públics", "qf_donor_eligibility.banner.link.check_eligibility": "Comprovar elegibilitat", "qf_donor_eligibility.banner.link.recheck_eligibility": "Re-comprovar elegibilitat", + "qf_donor_eligibility.banner.link.back_to_project": "Tornar als projectes", "real-estate": "Béns immobles", "refi": "Refi", "registered-non-profits": "Organitzacions sense ànim de lucre", diff --git a/lang/en.json b/lang/en.json index c81997f83b..b483e9fc2d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1352,12 +1352,12 @@ "page.donate.matching_toast.bottom_invalid_p2": "are eligible for matching.", "page.donate.matching_toast.bottom_valid": "Matching funds will be sent to the selected project after the round ends. Donate to more projects to receive higher matching!", "page.donate.network_not_eligible_for_qf": "{network} donations aren’t eligible for matching", - "page.donate.passport_toast.description.eligible": "Your donation is eligible to be matched! After the", - "page.donate.passport_toast.description.eligible_2": ", all donations will be reviewed for fraud protection and matching funds will be sent to the projects. Stay tuned for notifications :)", - "page.donate.passport_toast.description.non_eligible": "Get your donation matched with quadratic funding!\nCheck your QF Eligibility before", + "page.donate.passport_toast.description.eligible": "You are QF-eligible! As long as your donations are at least $", + "page.donate.passport_toast.description.eligible_2": ", they are eligible to be matched in ", + "page.donate.passport_toast.description.non_eligible": "Donations above ${usd_value} are eligible to be matched with quadratic funding.\nVerify your QF Eligibility before ", "page.donate.passport_toast.description.not_connected": "Get your donation matched with quadratic funding!\nVerify your Gitcoin Passport before", "page.donate.passport_toast.title.eligible": "Quadratic Funding", - "page.donate.passport_toast.title.non_eligible": "Don’t miss out on matching!", + "page.donate.passport_toast.title.non_eligible": "Don't miss out!", "page.donate.project_not_eligible_for_qf": "Project is not eligible for QF matching.", "page.donate.project_not_givbacks_eligible": "Project is not GIVbacks eligible", "page.donate.title": "Donate", @@ -1710,6 +1710,7 @@ "public-goods": "Public Goods", "qf_donor_eligibility.banner.link.check_eligibility": "Check Eligibility", "qf_donor_eligibility.banner.link.recheck_eligibility": "Re-check Eligibility", + "qf_donor_eligibility.banner.link.back_to_project": "Back to projects", "real-estate": "Real Estate", "refi": "Refi", "registered-non-profits": "Registered Non Profits", diff --git a/lang/es.json b/lang/es.json index 757a50adb2..c610faf2d6 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1352,12 +1352,12 @@ "page.donate.matching_toast.bottom_invalid_p2": "son subvencionables.", "page.donate.matching_toast.bottom_valid": "Los fondos de emparejamiento se enviarán al proyecto seleccionado después de que termine la ronda. ¡Dona a más proyectos para recibir un mayor emparejamiento!", "page.donate.network_not_eligible_for_qf": "Las donaciones de {network} no son elegibles para igualar", - "page.donate.passport_toast.description.eligible": "¡Tu donación es elegible para ser complementada! Después de la", - "page.donate.passport_toast.description.eligible_2": ", todas las donaciones serán revisadas para protección contra fraudes y los fondos de complementarios se enviarán a los proyectos. ¡Mantente atento a las notificaciones! :)", - "page.donate.passport_toast.description.non_eligible": "¡Haz que tu donación sea complementada con financiamiento cuadrático!\nCompruebe su elegibilidad QF antes de", + "page.donate.passport_toast.description.eligible": "¡Eres elegible para QF! Siempre que tus donaciones sean de al menos $", + "page.donate.passport_toast.description.eligible_2": ", son elegibles para ser emparejados en", + "page.donate.passport_toast.description.non_eligible": "Las donaciones superiores a ${usd_value} son elegibles para ser igualadas con fondos cuadráticos.\nVerifique su elegibilidad para QF antes", "page.donate.passport_toast.description.not_connected": "¡Haz que tu donación sea complementada con financiamiento cuadrático! Verifica tu Gitcoin Passport antes de", "page.donate.passport_toast.title.eligible": "Financiamiento Cuadrático", - "page.donate.passport_toast.title.non_eligible": "¡No te pierdas la oportunidad!", + "page.donate.passport_toast.title.non_eligible": "¡No te lo pierdas!", "page.donate.project_not_eligible_for_qf": "El proyecto no es elegible para la financiación QF.", "page.donate.project_not_givbacks_eligible": "El proyecto no es elegible para GIVbacks", "page.donate.title": "Donar", @@ -1710,6 +1710,7 @@ "public-goods": "Bienes públicos", "qf_donor_eligibility.banner.link.check_eligibility": "Verificar elegibilidad", "qf_donor_eligibility.banner.link.recheck_eligibility": "Re-verificar elegibilidad", + "qf_donor_eligibility.banner.link.back_to_project": "Volver a proyectos", "real-estate": "Bienes Raíces", "refi": "Refi", "registered-non-profits": "Organizaciones sin ánimo de lucro", diff --git a/src/apollo/gql/gqlQF.ts b/src/apollo/gql/gqlQF.ts index e3686d6b17..aa165d145a 100644 --- a/src/apollo/gql/gqlQF.ts +++ b/src/apollo/gql/gqlQF.ts @@ -18,6 +18,7 @@ export const QF_ROUNDS_QUERY = ` allocatedFundUSDPreferred allocatedTokenSymbol minMBDScore + minimumValidUsdValue } `; diff --git a/src/components/modals/PassportModal.tsx b/src/components/modals/PassportModal.tsx index 1f843431b4..64fdeb0efc 100644 --- a/src/components/modals/PassportModal.tsx +++ b/src/components/modals/PassportModal.tsx @@ -296,7 +296,7 @@ const PassportModal: FC = props => { size={10} /> ) : ( - (passportScore ?? '--') + passportScore ?? '--' )} diff --git a/src/components/views/donate/DonateIndex.tsx b/src/components/views/donate/DonateIndex.tsx index f6f38df6ff..7602a5a6b6 100644 --- a/src/components/views/donate/DonateIndex.tsx +++ b/src/components/views/donate/DonateIndex.tsx @@ -157,10 +157,7 @@ const DonateIndex: FC = () => { if (!transactionId) return; const includeInQF = - activeStartedRound && - !!getDonationById.valueUsd && - getDonationById.valueUsd >= - (activeStartedRound?.minimumValidUsdValue || 0); + activeStartedRound && !!getDonationById.valueUsd; setSuccessDonation({ txHash: [ { diff --git a/src/components/views/donate/OneTime/DonateModal.tsx b/src/components/views/donate/OneTime/DonateModal.tsx index 5056cf274d..923bd350b9 100644 --- a/src/components/views/donate/OneTime/DonateModal.tsx +++ b/src/components/views/donate/OneTime/DonateModal.tsx @@ -138,10 +138,7 @@ const DonateModal: FC = props => { formatUnits(amount, token.decimals), token.decimals, ) || 0); - const includeInQF = - activeStartedRound && - isOnEligibleNetworks && - donationUsdValue >= (activeStartedRound.minimumValidUsdValue || 0); + const includeInQF = activeStartedRound && isOnEligibleNetworks; const chainvineReferred = getWithExpiry(StorageLabel.CHAINVINEREFERRED); const { title, addresses } = project || {}; diff --git a/src/components/views/donate/QFToast.tsx b/src/components/views/donate/QFToast.tsx index 0ee603faf3..3039b53600 100644 --- a/src/components/views/donate/QFToast.tsx +++ b/src/components/views/donate/QFToast.tsx @@ -10,6 +10,7 @@ import { FlexCenter, } from '@giveth/ui-design-system'; import { useIntl } from 'react-intl'; +import { useRouter } from 'next/router'; import { EQFElegibilityState, usePassport } from '@/hooks/usePassport'; import PassportModal from '@/components/modals/PassportModal'; @@ -19,6 +20,7 @@ const QFToast = () => { usePassport(); const { qfEligibilityState, passportState, passportScore, currentRound } = info; + const router = useRouter(); const [showModal, setShowModal] = useState(false); const isEligible = qfEligibilityState === EQFElegibilityState.ELIGIBLE; @@ -46,23 +48,25 @@ const QFToast = () => { formatMessage({ id: 'page.donate.passport_toast.description.eligible', }) + - ' ' + - currentRound?.name + + currentRound?.minimumValidUsdValue + ' ' + formatMessage({ - id: 'label.ends_on', + id: 'page.donate.passport_toast.description.eligible_2', }) + ' ' + - endDate + - formatMessage({ - id: 'page.donate.passport_toast.description.eligible_2', - }); + currentRound?.name + + '.'; } else { description = ( <> - {formatMessage({ - id: 'page.donate.passport_toast.description.non_eligible', - })}{' '} + {formatMessage( + { + id: 'page.donate.passport_toast.description.non_eligible', + }, + { + usd_value: currentRound?.minimumValidUsdValue, + }, + )}{' '} {endDate} ); @@ -75,7 +79,19 @@ const QFToast = () => { {title} {description} - {!isEligible && ( + {isEligible ? ( + +
+
+ {data} + + {chain?.id && ( + + )} +
{failedModalType && ( Date: Mon, 21 Oct 2024 13:25:44 +0330 Subject: [PATCH 50/87] add useFetchSubgraphDataForAllChains --- src/hooks/useFetchSubgraphDataForAllChains.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/hooks/useFetchSubgraphDataForAllChains.ts diff --git a/src/hooks/useFetchSubgraphDataForAllChains.ts b/src/hooks/useFetchSubgraphDataForAllChains.ts new file mode 100644 index 0000000000..f8acc552cd --- /dev/null +++ b/src/hooks/useFetchSubgraphDataForAllChains.ts @@ -0,0 +1,21 @@ +import { useQueries } from '@tanstack/react-query'; +import { Address } from 'viem'; +import { useAccount } from 'wagmi'; +import config from '@/configuration'; +import { fetchSubgraphData } from '@/services/subgraph.service'; + +export const useFetchSubgraphDataForAllChains = () => { + const { address } = useAccount(); + return useQueries({ + queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ + queryKey: ['subgraph', chain.id, address] as [ + string, + number, + Address, + ], + queryFn: async () => await fetchSubgraphData(chain.id, address), + staleTime: config.SUBGRAPH_POLLING_INTERVAL, + enabled: !!address, + })), + }); +}; From 1f7a2f062c1677258dc609712935a13c9d0dc35b Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:26:34 +0330 Subject: [PATCH 51/87] add useInteractedBlockNumber --- src/hooks/useInteractedBlockNumber.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/hooks/useInteractedBlockNumber.ts diff --git a/src/hooks/useInteractedBlockNumber.ts b/src/hooks/useInteractedBlockNumber.ts new file mode 100644 index 0000000000..02f4f0c410 --- /dev/null +++ b/src/hooks/useInteractedBlockNumber.ts @@ -0,0 +1,11 @@ +import { useQuery } from '@tanstack/react-query'; +import { useAccount } from 'wagmi'; + +export const useInteractedBlockNumber = (_chainId?: number) => { + const { chainId: accountChainId } = useAccount(); + return useQuery({ + queryKey: ['interactedBlockNumber', _chainId || accountChainId], + queryFn: () => 0, + staleTime: Infinity, + }); +}; From 6ed292692968218599fa7be63e0258e1eb603497 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:27:15 +0330 Subject: [PATCH 52/87] add useSubgraphInfo --- src/hooks/useSubgraphInfo.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/hooks/useSubgraphInfo.ts diff --git a/src/hooks/useSubgraphInfo.ts b/src/hooks/useSubgraphInfo.ts new file mode 100644 index 0000000000..653b4ed2ab --- /dev/null +++ b/src/hooks/useSubgraphInfo.ts @@ -0,0 +1,15 @@ +import { useQuery } from '@tanstack/react-query'; +import { useAccount } from 'wagmi'; +import config from '@/configuration'; +import { fetchSubgraphData } from '@/services/subgraph.service'; + +export const useSubgraphInfo = (chainId?: number) => { + const { address, chainId: accountChainId } = useAccount(); + const _chainId = chainId || accountChainId; + return useQuery({ + queryKey: ['subgraph', _chainId, address], + queryFn: async () => await fetchSubgraphData(_chainId, address), + enabled: !!_chainId, + staleTime: config.SUBGRAPH_POLLING_INTERVAL, + }); +}; From e99564866195f716ef74b8903312b51157676006 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:27:21 +0330 Subject: [PATCH 53/87] add useSubgraphSyncInfo --- src/hooks/useSubgraphSyncInfo.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/hooks/useSubgraphSyncInfo.ts diff --git a/src/hooks/useSubgraphSyncInfo.ts b/src/hooks/useSubgraphSyncInfo.ts new file mode 100644 index 0000000000..b79864f016 --- /dev/null +++ b/src/hooks/useSubgraphSyncInfo.ts @@ -0,0 +1,31 @@ +import { useAccount } from 'wagmi'; +import { useMemo } from 'react'; +import { useInteractedBlockNumber } from './useInteractedBlockNumber'; +import { useSubgraphInfo } from './useSubgraphInfo'; + +export const useSubgraphSyncInfo = (chainId?: number) => { + const { chainId: accountChainId } = useAccount(); + const _chainId = chainId || accountChainId; + const interactedBlockInfo = useInteractedBlockNumber(_chainId); + const subgraphInfo = useSubgraphInfo(); + + const isSynced = useMemo(() => { + if (!subgraphInfo.data?.indexedBlockNumber) return false; + if (interactedBlockInfo.data === undefined) return false; + try { + const indexedBlockNumber = Number( + subgraphInfo.data?.indexedBlockNumber, + ); + const interactedBlockNumber = interactedBlockInfo.data; + return indexedBlockNumber >= interactedBlockNumber; + } catch (error) { + return false; + } + }, [interactedBlockInfo.data, subgraphInfo.data?.indexedBlockNumber]); + + return { + isSynced, + interactedBlockNumber: interactedBlockInfo.data, + indexedBlockNumber: subgraphInfo.data?.indexedBlockNumber, + }; +}; From 3a3e983837745fa1c7d3ec5056c6a75b8ed4111d Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:28:55 +0330 Subject: [PATCH 54/87] use hooks in subgraph controller --- src/components/controller/subgraph.ctrl.tsx | 31 ++++++++------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/components/controller/subgraph.ctrl.tsx b/src/components/controller/subgraph.ctrl.tsx index d4d8300455..f69f0098da 100644 --- a/src/components/controller/subgraph.ctrl.tsx +++ b/src/components/controller/subgraph.ctrl.tsx @@ -1,31 +1,18 @@ import React, { useEffect, useRef } from 'react'; import { useAccount } from 'wagmi'; -import { useQueries, useQueryClient } from '@tanstack/react-query'; +import { useQueryClient } from '@tanstack/react-query'; import { Address } from 'viem'; -import config from '@/configuration'; -import { - fetchLatestIndexedBlock, - fetchSubgraphData, -} from '@/services/subgraph.service'; +import { fetchLatestIndexedBlock } from '@/services/subgraph.service'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; +import { useInteractedBlockNumber } from '@/hooks/useInteractedBlockNumber'; const SubgraphController: React.FC = () => { - const { address } = useAccount(); + const { address, chain } = useAccount(); const queryClient = useQueryClient(); const pollingTimeoutsRef = useRef<{ [key: number]: NodeJS.Timeout }>({}); const refetchedChainsRef = useRef>(new Set()); - - useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address] as [ - string, - number, - Address, - ], - queryFn: async () => await fetchSubgraphData(chain.id, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - enabled: !!address, - })), - }); + useFetchSubgraphDataForAllChains(); + useInteractedBlockNumber(chain?.id); useEffect(() => { const handleEvent = ( @@ -49,6 +36,10 @@ const SubgraphController: React.FC = () => { // Reset refetchedChainsRef for the current chain ID refetchedChainsRef.current.delete(eventChainId); + queryClient.setQueryData( + ['interactedBlockNumber', eventChainId], + blockNumber, + ); // Ensure any existing timeout is cleared if (pollingTimeoutsRef.current[eventChainId]) { From cdadfbd5576ff4fc97c22ad776698ccd5846ebc1 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:29:18 +0330 Subject: [PATCH 55/87] disable action buttons if subgraph is not synced --- .../StakingPoolInfoAndActions.tsx | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/cards/StakingCards/BaseStakingCard/StakingPoolInfoAndActions.tsx b/src/components/cards/StakingCards/BaseStakingCard/StakingPoolInfoAndActions.tsx index f110242078..c2668d0be8 100644 --- a/src/components/cards/StakingCards/BaseStakingCard/StakingPoolInfoAndActions.tsx +++ b/src/components/cards/StakingCards/BaseStakingCard/StakingPoolInfoAndActions.tsx @@ -54,6 +54,7 @@ import LockModal from '@/components/modals/StakeLock/Lock'; import { WhatIsStreamModal } from '@/components/modals/WhatIsStream'; import { LockupDetailsModal } from '@/components/modals/LockupDetailsModal'; import ExternalLink from '@/components/ExternalLink'; +import { useSubgraphSyncInfo } from '@/hooks/useSubgraphSyncInfo'; interface IStakingPoolInfoAndActionsProps { poolStakingConfig: PoolStakingConfig | RegenPoolStakingConfig; @@ -84,15 +85,16 @@ export const StakingPoolInfoAndActions: FC = ({ const [showWhatIsGIVstreamModal, setShowWhatIsGIVstreamModal] = useState(false); + const { formatMessage } = useIntl(); + const { setChainInfo } = useFarms(); + const router = useRouter(); + const subgraphSyncedInfo = useSubgraphSyncInfo(poolStakingConfig.network); const hold = showAPRModal || showStakeModal || showUnStakeModal || showHarvestModal || showLockModal; - const { formatMessage } = useIntl(); - const { setChainInfo } = useFarms(); - const router = useRouter(); const { apr, notStakedAmount: userNotStakedAmount, @@ -354,7 +356,12 @@ export const StakingPoolInfoAndActions: FC = ({ )} setShowHarvestModal(true)} label={formatMessage({ id: 'label.harvest_rewards', @@ -363,7 +370,10 @@ export const StakingPoolInfoAndActions: FC = ({ /> {isGIVpower && ( setShowLockModal(true)} label={ started @@ -386,7 +396,8 @@ export const StakingPoolInfoAndActions: FC = ({ disabled={ isDiscontinued || exploited || - userNotStakedAmount === 0n + userNotStakedAmount === 0n || + !subgraphSyncedInfo.isSynced } onClick={() => setShowStakeModal(true)} /> @@ -404,7 +415,10 @@ export const StakingPoolInfoAndActions: FC = ({ id: 'label.unstake', })} size='small' - disabled={availableStakedToken === 0n} + disabled={ + availableStakedToken === 0n || + !subgraphSyncedInfo.isSynced + } onClick={() => setShowUnStakeModal(true)} /> From 1f559c10b925b4319669a8720596d9ed05f824ef Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:31:36 +0330 Subject: [PATCH 56/87] add chainEvent to harvest --- src/components/modals/HarvestAll.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/modals/HarvestAll.tsx b/src/components/modals/HarvestAll.tsx index 4853230e28..4ea9f4f0dd 100644 --- a/src/components/modals/HarvestAll.tsx +++ b/src/components/modals/HarvestAll.tsx @@ -252,11 +252,19 @@ export const HarvestAllModal: FC = ({ if (txResponse) { setState(HarvestStates.SUBMITTED); setTxHash(txResponse); - const { status } = await waitForTransaction( + const { status, blockNumber } = await waitForTransaction( txResponse, isSafeEnv, ); - + const event = new CustomEvent('chainEvent', { + detail: { + type: 'success', + chainId: chainId, + blockNumber: blockNumber, + address: address, + }, + }); + window.dispatchEvent(event); setState( status ? HarvestStates.CONFIRMED : HarvestStates.ERROR, ); From 8f5288f4a0c62c0bb9188e39727da0e719d9b7ee Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 13:45:33 +0330 Subject: [PATCH 57/87] use new hooks --- pages/test2.tsx | 15 +++-------- src/components/GIVeconomyPages/GIVbacks.tsx | 16 ++++------- src/components/GIVeconomyPages/GIVpower.tsx | 13 ++------- src/components/GIVeconomyPages/GIVstream.tsx | 27 +++++-------------- .../GIVpowerCard/GIVpowerCardIntro.tsx | 12 ++------- src/components/givfarm/RegenStreamCard.tsx | 11 +++----- src/components/menu/RewardButtonWithMenu.tsx | 13 ++------- src/components/menu/RewardItems.tsx | 14 +++------- src/components/modals/Boost/BoostModal.tsx | 14 ++-------- src/components/modals/GIVdropHarvestModal.tsx | 12 +++------ .../modals/StakeLock/LockSlider.tsx | 12 ++------- .../modals/StakeLock/LockingBrief.tsx | 12 ++------- .../modals/StakeLock/TotalGIVpowerBox.tsx | 14 ++-------- src/components/views/claim/cards/Govern.tsx | 13 +++------ src/components/views/claim/cards/Stake.tsx | 20 +++----------- .../views/userProfile/ProfileOverviewTab.tsx | 14 ++-------- .../boostedTab/EmptyPowerBoosting.tsx | 14 ++-------- .../boostedTab/ProfileBoostedTab.tsx | 14 ++-------- src/hooks/useGIVTokenDistroHelper.ts | 13 ++------- src/hooks/useStakingPool.ts | 14 ++-------- src/hooks/useTokenDistroHelper.ts | 13 ++------- 21 files changed, 55 insertions(+), 245 deletions(-) diff --git a/pages/test2.tsx b/pages/test2.tsx index 9c4ce298bc..e607b757f8 100644 --- a/pages/test2.tsx +++ b/pages/test2.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useAccount } from 'wagmi'; import { PublicKey, @@ -14,23 +14,14 @@ import FailedDonation, { } from '@/components/modals/FailedDonation'; import { getTotalGIVpower } from '@/helpers/givpower'; import { formatWeiHelper } from '@/helpers/number'; -import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; const YourApp = () => { const [failedModalType, setFailedModalType] = useState(); const queryClient = useQueryClient(); const { address, chain } = useAccount(); - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); const { data } = useQuery({ queryKey: ['interactedBlockNumber', chain?.id], diff --git a/src/components/GIVeconomyPages/GIVbacks.tsx b/src/components/GIVeconomyPages/GIVbacks.tsx index a92296b817..7151fe5fd5 100644 --- a/src/components/GIVeconomyPages/GIVbacks.tsx +++ b/src/components/GIVeconomyPages/GIVbacks.tsx @@ -12,7 +12,6 @@ import { import Link from 'next/link'; import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { GIVbacksTopContainer, GIVbacksBottomContainer, @@ -43,9 +42,9 @@ import { NoWrap, TopInnerContainer } from './commons'; import links from '@/lib/constants/links'; import Routes from '@/lib/constants/Routes'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; -import { fetchSubgraphData } from '@/services/subgraph.service'; import { FETCH_ALLOCATED_GIVBACKS } from '@/apollo/gql/gqlGivbacks'; import { client } from '@/apollo/apolloClient'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; export const TabGIVbacksTop = () => { const { formatMessage } = useIntl(); @@ -53,17 +52,12 @@ export const TabGIVbacksTop = () => { const [showGivBackExplain, setShowGivBackExplain] = useState(false); const [givBackStream, setGivBackStream] = useState(0n); const { givTokenDistroHelper } = useGIVTokenDistroHelper(showHarvestModal); - const { chain, address } = useAccount(); + const { chainId } = useAccount(); const dataChainId = - chain?.id === config.OPTIMISM_NETWORK_NUMBER + chainId === config.OPTIMISM_NETWORK_NUMBER ? config.OPTIMISM_NETWORK_NUMBER : config.GNOSIS_NETWORK_NUMBER; - const values = useQuery({ - queryKey: ['subgraph', dataChainId, address], - queryFn: async () => await fetchSubgraphData(dataChainId, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const values = useSubgraphInfo(dataChainId); const givTokenDistroBalance = useMemo(() => { const sdh = new SubgraphDataHelper(values.data); return sdh.getGIVTokenDistroBalance(); @@ -107,7 +101,7 @@ export const TabGIVbacksTop = () => { actionCb={() => { setShowHarvestModal(true); }} - network={chain?.id} + network={chainId} targetNetworks={[ { networkId: config.GNOSIS_NETWORK_NUMBER, diff --git a/src/components/GIVeconomyPages/GIVpower.tsx b/src/components/GIVeconomyPages/GIVpower.tsx index 9d1720f2cc..26556f949b 100644 --- a/src/components/GIVeconomyPages/GIVpower.tsx +++ b/src/components/GIVeconomyPages/GIVpower.tsx @@ -16,7 +16,6 @@ import Link from 'next/link'; import { useIntl } from 'react-intl'; import { useWeb3Modal } from '@web3modal/wagmi/react'; import { useAccount } from 'wagmi'; -import { useQueries } from '@tanstack/react-query'; import { GIVpowerTopContainer, Title, @@ -61,21 +60,13 @@ import { formatWeiHelper } from '@/helpers/number'; import { getTotalGIVpower } from '@/helpers/givpower'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { ChainType } from '@/types/config'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; export function TabPowerTop() { const { formatMessage } = useIntl(); const { open: openConnectModal } = useWeb3Modal(); const { address } = useAccount(); - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); const givPower = getTotalGIVpower(subgraphValues, address); const givPowerFormatted = formatWeiHelper(givPower.total); const hasZeroGivPower = givPowerFormatted === '0'; diff --git a/src/components/GIVeconomyPages/GIVstream.tsx b/src/components/GIVeconomyPages/GIVstream.tsx index 7b20734b56..cf1047a615 100644 --- a/src/components/GIVeconomyPages/GIVstream.tsx +++ b/src/components/GIVeconomyPages/GIVstream.tsx @@ -19,7 +19,6 @@ import { } from '@giveth/ui-design-system'; import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { Bar, FlowRateRow, @@ -52,7 +51,7 @@ import { GridWrapper, } from './GIVstream.sc'; import { IconWithTooltip } from '../IconWithToolTip'; -import { getHistory, fetchSubgraphData } from '@/services/subgraph.service'; +import { getHistory } from '@/services/subgraph.service'; import { formatWeiHelper } from '@/helpers/number'; import config from '@/configuration'; import { durationToString, shortenAddress } from '@/lib/helpers'; @@ -64,6 +63,7 @@ import { IconGIV } from '../Icons/GIV'; import { givEconomySupportedNetworks } from '@/lib/constants/constants'; import Pagination from '../Pagination'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; export const TabGIVstreamTop = () => { const { formatMessage } = useIntl(); @@ -71,13 +71,8 @@ export const TabGIVstreamTop = () => { const [rewardLiquidPart, setRewardLiquidPart] = useState(0n); const [rewardStream, setRewardStream] = useState(0n); const { givTokenDistroHelper } = useGIVTokenDistroHelper(showModal); - const { chain, address } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const { chain } = useAccount(); + const currentValues = useSubgraphInfo(); const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValues.data); @@ -177,12 +172,7 @@ export const TabGIVstreamBottom = () => { const [remain, setRemain] = useState(''); useState(0n); const [streamAmount, setStreamAmount] = useState(0n); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(); const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValues.data); @@ -388,12 +378,7 @@ export const GIVstreamHistory: FC = () => { const [loading, setLoading] = useState(true); const [page, setPage] = useState(0); - const currentValue = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValue = useSubgraphInfo(); const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValue.data); diff --git a/src/components/cards/StakingCards/GIVpowerCard/GIVpowerCardIntro.tsx b/src/components/cards/StakingCards/GIVpowerCard/GIVpowerCardIntro.tsx index 967bf7c45e..8672405076 100644 --- a/src/components/cards/StakingCards/GIVpowerCard/GIVpowerCardIntro.tsx +++ b/src/components/cards/StakingCards/GIVpowerCard/GIVpowerCardIntro.tsx @@ -14,8 +14,6 @@ import { useState } from 'react'; import { useIntl } from 'react-intl'; import styled from 'styled-components'; import Link from 'next/link'; -import { useQuery } from '@tanstack/react-query'; -import { useAccount } from 'wagmi'; import links from '@/lib/constants/links'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; import Routes from '@/lib/constants/Routes'; @@ -24,7 +22,7 @@ import TotalGIVpowerBox from '@/components/modals/StakeLock/TotalGIVpowerBox'; import { StakeCardState } from '../BaseStakingCard/BaseStakingCard'; import { useStakingPool } from '@/hooks/useStakingPool'; import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; import type { Dispatch, FC, SetStateAction } from 'react'; interface IGIVpowerCardIntro { @@ -42,13 +40,7 @@ const GIVpowerCardIntro: FC = ({ config.EVM_NETWORKS_CONFIG[poolNetwork].GIVPOWER || config.GNOSIS_CONFIG.GIVPOWER, ); - const { chain, address } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(); const sdh = new SubgraphDataHelper(currentValues.data); const userGIVLocked = sdh.getUserGIVLockedBalance(); diff --git a/src/components/givfarm/RegenStreamCard.tsx b/src/components/givfarm/RegenStreamCard.tsx index 8af1fe404e..c74c7ee5e2 100644 --- a/src/components/givfarm/RegenStreamCard.tsx +++ b/src/components/givfarm/RegenStreamCard.tsx @@ -20,7 +20,6 @@ import BigNumber from 'bignumber.js'; import styled from 'styled-components'; import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { durationToString } from '@/lib/helpers'; import { Bar, GsPTooltip } from '@/components/GIVeconomyPages/GIVstream.sc'; import { IconWithTooltip } from '@/components/IconWithToolTip'; @@ -35,9 +34,9 @@ import { TokenDistroHelper } from '@/lib/contractHelper/TokenDistroHelper'; import { Relative } from '../styled-components/Position'; import { ArchiveAndNetworkCover } from '../ArchiveAndNetworkCover/ArchiveAndNetworkCover'; import { getSubgraphChainId } from '@/helpers/network'; -import { fetchSubgraphData } from '@/services/subgraph.service'; import { useFetchMainnetThirdPartyTokensPrice } from '@/hooks/useFetchMainnetThirdPartyTokensPrice'; import { useFetchGnosisThirdPartyTokensPrice } from '@/hooks/useFetchGnosisThirdPartyTokensPrice'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; interface RegenStreamProps { streamConfig: RegenStreamConfig; @@ -63,13 +62,9 @@ export const RegenStreamCard: FC = ({ streamConfig }) => { const [lockedAmount, setLockedAmount] = useState(0n); const [claimedAmount, setClaimedAmount] = useState(0n); - const { address, chain } = useAccount(); + const { chain } = useAccount(); const subgraphChainId = getSubgraphChainId(streamConfig.network); - const currentValues = useQuery({ - queryKey: ['subgraph', subgraphChainId, address], - queryFn: async () => await fetchSubgraphData(subgraphChainId, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(subgraphChainId); const chainId = chain?.id; const { diff --git a/src/components/menu/RewardButtonWithMenu.tsx b/src/components/menu/RewardButtonWithMenu.tsx index 2acc23fadc..4b2e634a7f 100644 --- a/src/components/menu/RewardButtonWithMenu.tsx +++ b/src/components/menu/RewardButtonWithMenu.tsx @@ -1,7 +1,5 @@ import React, { FC, useEffect, useState } from 'react'; -import { useAccount } from 'wagmi'; import { FlexSpacer } from '@giveth/ui-design-system'; -import { useQuery } from '@tanstack/react-query'; import { MenuAndButtonContainer, BalanceButton, @@ -22,8 +20,7 @@ import { MenuContainer } from './Menu.sc'; import { ItemsProvider } from '@/context/Items.context'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; import { formatWeiHelper } from '@/helpers/number'; -import { fetchSubgraphData } from '@/services/subgraph.service'; -import config from '@/configuration'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; interface IRewardButtonWithMenuProps extends IHeaderButtonProps {} @@ -100,13 +97,7 @@ export const RewardButtonWithMenu: FC = ({ }; const HeaderRewardButton = () => { - const { chain, address } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(); const sdh = new SubgraphDataHelper(currentValues.data); const givBalance = sdh.getGIVTokenBalance(); return ( diff --git a/src/components/menu/RewardItems.tsx b/src/components/menu/RewardItems.tsx index 4a379dfb1d..088f0a1bf9 100644 --- a/src/components/menu/RewardItems.tsx +++ b/src/components/menu/RewardItems.tsx @@ -10,7 +10,6 @@ import { useIntl } from 'react-intl'; import Image from 'next/image'; import Link from 'next/link'; import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import config from '@/configuration'; import useGIVTokenDistroHelper from '@/hooks/useGIVTokenDistroHelper'; import { formatWeiHelper } from '@/helpers/number'; @@ -37,7 +36,7 @@ import { setShowSwitchNetworkModal } from '@/features/modal/modal.slice'; import { getChainName } from '@/lib/network'; import { getNetworkConfig } from '@/helpers/givpower'; import { useIsSafeEnvironment } from '@/hooks/useSafeAutoConnect'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; export interface IRewardItemsProps { showWhatIsGIVstreamModal: boolean; @@ -55,17 +54,10 @@ export const RewardItems: FC = ({ const [givStreamLiquidPart, setGIVstreamLiquidPart] = useState(0n); const [flowRateNow, setFlowRateNow] = useState(0n); - const { address, chain } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !!chain, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const { chainId } = useAccount(); + const currentValues = useSubgraphInfo(); const { givTokenDistroHelper } = useGIVTokenDistroHelper(); const dispatch = useAppDispatch(); - - const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValues.data); const tokenDistroBalance = sdh.getGIVTokenDistroBalance(); diff --git a/src/components/modals/Boost/BoostModal.tsx b/src/components/modals/Boost/BoostModal.tsx index 074f2235a8..26e1e6a24f 100644 --- a/src/components/modals/Boost/BoostModal.tsx +++ b/src/components/modals/Boost/BoostModal.tsx @@ -1,7 +1,6 @@ import { IconRocketInSpace32 } from '@giveth/ui-design-system'; import { FC, useState } from 'react'; import { useIntl } from 'react-intl'; -import { useQueries } from '@tanstack/react-query'; import { useAccount } from 'wagmi'; import { IModal } from '@/types/common'; import { Modal } from '../Modal'; @@ -12,8 +11,7 @@ import { BoostModalContainer } from './BoostModal.sc'; import BoostedInnerModal from './BoostedInnerModal'; import BoostInnerModal from './BoostInnerModal'; import { getTotalGIVpower } from '@/helpers/givpower'; -import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; interface IBoostModalProps extends IModal { projectId: string; @@ -31,15 +29,7 @@ const BoostModal: FC = ({ setShowModal, projectId }) => { const [percentage, setPercentage] = useState(0); const [state, setState] = useState(EBoostModalState.BOOSTING); const { address } = useAccount(); - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); const givPower = getTotalGIVpower(subgraphValues, address); if (givPower.total.isZero()) { diff --git a/src/components/modals/GIVdropHarvestModal.tsx b/src/components/modals/GIVdropHarvestModal.tsx index f0ed5d0db5..617f0693e6 100644 --- a/src/components/modals/GIVdropHarvestModal.tsx +++ b/src/components/modals/GIVdropHarvestModal.tsx @@ -12,7 +12,6 @@ import BigNumber from 'bignumber.js'; import { captureException } from '@sentry/nextjs'; import { useAccount } from 'wagmi'; import { WriteContractReturnType } from 'viem'; -import { useQuery } from '@tanstack/react-query'; import { Modal } from './Modal'; import { ConfirmedInnerModal, @@ -41,8 +40,8 @@ import { IModal } from '@/types/common'; import { useIsSafeEnvironment } from '@/hooks/useSafeAutoConnect'; import { useModalAnimation } from '@/hooks/useModalAnimation'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; -import { fetchSubgraphData } from '@/services/subgraph.service'; import { useFetchGIVPrice } from '@/hooks/useGivPrice'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; enum ClaimState { UNKNOWN, @@ -80,14 +79,9 @@ export const GIVdropHarvestModal: FC = ({ const { isAnimating, closeModal } = useModalAnimation(setShowModal); const { givTokenDistroHelper } = useGIVTokenDistroHelper(); - const { address, chain } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const { address, chainId } = useAccount(); + const currentValues = useSubgraphInfo(); - const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValues.data); const givTokenDistroBalance = sdh.getGIVTokenDistroBalance(); const { data: givPrice } = useFetchGIVPrice(); diff --git a/src/components/modals/StakeLock/LockSlider.tsx b/src/components/modals/StakeLock/LockSlider.tsx index 89fc599b24..d6efc9a4e8 100644 --- a/src/components/modals/StakeLock/LockSlider.tsx +++ b/src/components/modals/StakeLock/LockSlider.tsx @@ -12,12 +12,10 @@ import { import styled from 'styled-components'; import { Dispatch, FC, SetStateAction, useState } from 'react'; import { useIntl } from 'react-intl'; -import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { smallFormatDate } from '@/lib/helpers'; import { getUnlockDate } from '@/helpers/givpower'; import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; import type { IGIVpower } from '@/types/subgraph'; const maxRound = 26; @@ -29,13 +27,7 @@ interface ILockSlider { const LockSlider: FC = ({ round, setRound }) => { const { formatMessage, locale } = useIntl(); const [isChanged, setIsChanged] = useState(false); - const { address } = useAccount(); - const gnosisValues = useQuery({ - queryKey: ['subgraph', config.GNOSIS_NETWORK_NUMBER, address], - queryFn: async () => - await fetchSubgraphData(config.GNOSIS_NETWORK_NUMBER, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const gnosisValues = useSubgraphInfo(config.GNOSIS_NETWORK_NUMBER); const givpowerInfo = gnosisValues.data?.givpowerInfo as IGIVpower; const unlockDate = new Date(getUnlockDate(givpowerInfo, round)); return ( diff --git a/src/components/modals/StakeLock/LockingBrief.tsx b/src/components/modals/StakeLock/LockingBrief.tsx index 9d2161d5bd..cd62724ecf 100644 --- a/src/components/modals/StakeLock/LockingBrief.tsx +++ b/src/components/modals/StakeLock/LockingBrief.tsx @@ -1,12 +1,10 @@ import { brandColors, H5, Flex } from '@giveth/ui-design-system'; import styled from 'styled-components'; -import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { formatWeiHelper } from '@/helpers/number'; import { smallFormatDate } from '@/lib/helpers'; import { getUnlockDate } from '@/helpers/givpower'; import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; import type { FC } from 'react'; import type { IGIVpower } from '@/types/subgraph'; @@ -20,13 +18,7 @@ const LockingBrief: FC = ({ amount, onLocking = false, }) => { - const { address } = useAccount(); - const gnosisValues = useQuery({ - queryKey: ['subgraph', config.GNOSIS_NETWORK_NUMBER, address], - queryFn: async () => - await fetchSubgraphData(config.GNOSIS_NETWORK_NUMBER, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const gnosisValues = useSubgraphInfo(config.GNOSIS_NETWORK_NUMBER); const givpowerInfo = gnosisValues.data?.givpowerInfo as IGIVpower; const unlockDate = new Date(getUnlockDate(givpowerInfo, round)); return ( diff --git a/src/components/modals/StakeLock/TotalGIVpowerBox.tsx b/src/components/modals/StakeLock/TotalGIVpowerBox.tsx index 57eade613b..86893863d0 100644 --- a/src/components/modals/StakeLock/TotalGIVpowerBox.tsx +++ b/src/components/modals/StakeLock/TotalGIVpowerBox.tsx @@ -9,27 +9,17 @@ import styled from 'styled-components'; import { useEffect, useState } from 'react'; import BigNumber from 'bignumber.js'; import { useAccount } from 'wagmi'; -import { useQueries } from '@tanstack/react-query'; import { formatWeiHelper } from '@/helpers/number'; import { WrappedSpinner } from '@/components/Spinner'; import { getTotalGIVpower } from '@/helpers/givpower'; import { getGIVpowerOnChain } from '@/lib/stakingPool'; -import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; const TotalGIVpowerBox = () => { const [totalGIVpower, setTotalGIVpower] = useState(); const { address, chain } = useAccount(); const chainId = chain?.id; - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); useEffect(() => { async function fetchTotalGIVpower() { diff --git a/src/components/views/claim/cards/Govern.tsx b/src/components/views/claim/cards/Govern.tsx index 7fb9ebabc4..78d3f715c0 100644 --- a/src/components/views/claim/cards/Govern.tsx +++ b/src/components/views/claim/cards/Govern.tsx @@ -7,7 +7,6 @@ import { captureException } from '@sentry/nextjs'; import { useIntl } from 'react-intl'; import { formatEther } from 'viem'; import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { APRRow, ArrowButton, @@ -35,7 +34,7 @@ import useGIVTokenDistroHelper from '@/hooks/useGIVTokenDistroHelper'; import { IClaimViewCardProps } from '../Claim.view'; import { WeiPerEther } from '@/lib/constants/constants'; import { InputWithUnit } from '@/components/input/InputWithUnit'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; const GovernCardContainer = styled(Card)` padding-left: 254px; @@ -109,15 +108,9 @@ const GovernCard: FC = ({ index }) => { const [earnEstimate, setEarnEstimate] = useState(0n); const [apr, setApr] = useState(null); - const { address, chain } = useAccount(); - const chainId = chain?.id; + const { chainId } = useAccount(); const { givTokenDistroHelper } = useGIVTokenDistroHelper(); - const gnosisValues = useQuery({ - queryKey: ['subgraph', config.GNOSIS_NETWORK_NUMBER, address], - queryFn: async () => - await fetchSubgraphData(config.GNOSIS_NETWORK_NUMBER, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const gnosisValues = useSubgraphInfo(config.GNOSIS_NETWORK_NUMBER); useEffect(() => { let _stacked = 0; diff --git a/src/components/views/claim/cards/Stake.tsx b/src/components/views/claim/cards/Stake.tsx index 22588e8738..def9267a08 100644 --- a/src/components/views/claim/cards/Stake.tsx +++ b/src/components/views/claim/cards/Stake.tsx @@ -4,8 +4,6 @@ import styled from 'styled-components'; import BigNumber from 'bignumber.js'; import { H2, H5, Lead, Flex } from '@giveth/ui-design-system'; import { useIntl } from 'react-intl'; -import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { APRRow, ArrowButton, @@ -34,7 +32,7 @@ import { getNowUnixMS } from '@/helpers/time'; import { IClaimViewCardProps } from '../Claim.view'; import { WeiPerEther } from '@/lib/constants/constants'; import { InputWithUnit } from '@/components/input/InputWithUnit'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useSubgraphInfo } from '@/hooks/useSubgraphInfo'; const InvestCardContainer = styled(Card)` &::before { @@ -95,21 +93,9 @@ const InvestCard: FC = ({ index }) => { const [earnEstimate, setEarnEstimate] = useState(0n); const [APR, setAPR] = useState(Zero); - const { address } = useAccount(); const { givTokenDistroHelper } = useGIVTokenDistroHelper(); - const gnosisValues = useQuery({ - queryKey: ['subgraph', config.GNOSIS_NETWORK_NUMBER, address], - queryFn: async () => - await fetchSubgraphData(config.GNOSIS_NETWORK_NUMBER, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); - - const mainnetValues = useQuery({ - queryKey: ['subgraph', config.MAINNET_NETWORK_NUMBER, address], - queryFn: async () => - await fetchSubgraphData(config.MAINNET_NETWORK_NUMBER, address), - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const gnosisValues = useSubgraphInfo(config.GNOSIS_NETWORK_NUMBER); + const mainnetValues = useSubgraphInfo(config.MAINNET_NETWORK_NUMBER); useEffect(() => { if (totalAmount) { diff --git a/src/components/views/userProfile/ProfileOverviewTab.tsx b/src/components/views/userProfile/ProfileOverviewTab.tsx index b6c4e7fb1f..dbd42d7624 100644 --- a/src/components/views/userProfile/ProfileOverviewTab.tsx +++ b/src/components/views/userProfile/ProfileOverviewTab.tsx @@ -17,7 +17,6 @@ import { import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; -import { useQueries } from '@tanstack/react-query'; import Routes from '@/lib/constants/Routes'; import { isUserRegistered } from '@/lib/helpers'; import { mediaQueries } from '@/lib/constants/constants'; @@ -36,9 +35,8 @@ import { useProfileContext } from '@/context/profile.context'; import { useIsSafeEnvironment } from '@/hooks/useSafeAutoConnect'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { QFDonorEligibilityCard } from '@/components/views/userProfile/QFDonorEligibilityCard'; -import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; import { getNowUnixMS } from '@/helpers/time'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; interface IBtnProps extends IButtonProps { outline?: boolean; @@ -115,15 +113,7 @@ const ProfileOverviewTab: FC = () => { const { activeQFRound } = useAppSelector(state => state.general); const boostedProjectsCount = userData?.boostedProjectsCount ?? 0; const { address } = useAccount(); - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); const givPower = getTotalGIVpower(subgraphValues, address); const { title, subtitle, buttons } = section; diff --git a/src/components/views/userProfile/boostedTab/EmptyPowerBoosting.tsx b/src/components/views/userProfile/boostedTab/EmptyPowerBoosting.tsx index 62c6266154..37f99a4e85 100644 --- a/src/components/views/userProfile/boostedTab/EmptyPowerBoosting.tsx +++ b/src/components/views/userProfile/boostedTab/EmptyPowerBoosting.tsx @@ -9,26 +9,16 @@ import { useIntl } from 'react-intl'; import Link from 'next/link'; import { FC } from 'react'; import { useAccount } from 'wagmi'; -import { useQueries } from '@tanstack/react-query'; import Routes from '@/lib/constants/Routes'; import { getTotalGIVpower } from '@/helpers/givpower'; -import config from '@/configuration'; -import { fetchSubgraphData } from '@/services/subgraph.service'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; interface IEmptyPowerBoosting { myAccount?: boolean; } export const EmptyPowerBoosting: FC = ({ myAccount }) => { const { address } = useAccount(); - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); const givPower = getTotalGIVpower(subgraphValues, address); const { formatMessage } = useIntl(); diff --git a/src/components/views/userProfile/boostedTab/ProfileBoostedTab.tsx b/src/components/views/userProfile/boostedTab/ProfileBoostedTab.tsx index 35b5d0cdea..ae78aa4e7c 100644 --- a/src/components/views/userProfile/boostedTab/ProfileBoostedTab.tsx +++ b/src/components/views/userProfile/boostedTab/ProfileBoostedTab.tsx @@ -4,7 +4,6 @@ import { captureException } from '@sentry/nextjs'; import { Col, Row } from '@giveth/ui-design-system'; import { useAccount } from 'wagmi'; -import { useQueries } from '@tanstack/react-query'; import { IUserProfileView } from '../UserProfile.view'; import BoostsTable from './BoostsTable'; import { IPowerBoosting } from '@/apollo/types/types'; @@ -29,8 +28,7 @@ import { formatWeiHelper } from '@/helpers/number'; import InlineToast, { EToastType } from '@/components/toasts/InlineToast'; import { useFetchPowerBoostingInfo } from './useFetchPowerBoostingInfo'; import { useProfileContext } from '@/context/profile.context'; -import { fetchSubgraphData } from '@/services/subgraph.service'; -import config from '@/configuration'; +import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; export const ProfileBoostedTab: FC = () => { const { user } = useProfileContext(); @@ -39,15 +37,7 @@ export const ProfileBoostedTab: FC = () => { const { address, chain } = useAccount(); const { userData } = useAppSelector(state => state.user); const boostedProjectsCount = userData?.boostedProjectsCount ?? 0; - const subgraphValues = useQueries({ - queries: config.CHAINS_WITH_SUBGRAPH.map(chain => ({ - queryKey: ['subgraph', chain.id, address], - queryFn: async () => { - return await fetchSubgraphData(chain.id, address); - }, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - })), - }); + const subgraphValues = useFetchSubgraphDataForAllChains(); const givPower = getTotalGIVpower(subgraphValues, address); const isZeroGivPower = givPower.total.isZero(); const dispatch = useAppDispatch(); diff --git a/src/hooks/useGIVTokenDistroHelper.ts b/src/hooks/useGIVTokenDistroHelper.ts index 32f367eddd..a61acd7525 100644 --- a/src/hooks/useGIVTokenDistroHelper.ts +++ b/src/hooks/useGIVTokenDistroHelper.ts @@ -1,11 +1,8 @@ import { useState, useEffect } from 'react'; import { AddressZero } from '@ethersproject/constants'; -import { useQuery } from '@tanstack/react-query'; -import { useAccount } from 'wagmi'; import { TokenDistroHelper } from '@/lib/contractHelper/TokenDistroHelper'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; -import { fetchSubgraphData } from '@/services/subgraph.service'; -import config from '@/configuration'; +import { useSubgraphInfo } from './useSubgraphInfo'; export const defaultTokenDistroHelper = new TokenDistroHelper({ contractAddress: AddressZero, @@ -23,13 +20,7 @@ const useGIVTokenDistroHelper = (hold = false) => { const [givTokenDistroHelper, setGIVTokenDistroHelper] = useState(defaultTokenDistroHelper); const [isLoaded, setIsLoaded] = useState(false); - const { chain, address } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', chain?.id, address], - queryFn: async () => await fetchSubgraphData(chain?.id, address), - enabled: !hold, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(); useEffect(() => { const updateHelper = () => { diff --git a/src/hooks/useStakingPool.ts b/src/hooks/useStakingPool.ts index 4f7737c74a..e93cf16556 100644 --- a/src/hooks/useStakingPool.ts +++ b/src/hooks/useStakingPool.ts @@ -1,7 +1,5 @@ import { useEffect, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { useAccount } from 'wagmi'; import { getGivStakingAPR, getLPStakingAPR, @@ -10,8 +8,7 @@ import { import { SimplePoolStakingConfig, StakingType } from '@/types/config'; import { APR, UserStakeInfo } from '@/types/poolInfo'; import { Zero } from '@/helpers/number'; -import { fetchSubgraphData } from '@/services/subgraph.service'; -import config from '@/configuration'; +import { useSubgraphInfo } from './useSubgraphInfo'; export interface IStakeInfo { apr: APR; @@ -30,14 +27,7 @@ export const useStakingPool = ( notStakedAmount: 0n, stakedAmount: 0n, }); - const { address } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', poolStakingConfig.network, address], - queryFn: async () => - await fetchSubgraphData(poolStakingConfig.network, address), - enabled: !hold, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(poolStakingConfig.network); useEffect(() => { const { network, type } = poolStakingConfig; diff --git a/src/hooks/useTokenDistroHelper.ts b/src/hooks/useTokenDistroHelper.ts index 1ad69f15ab..3f786a2897 100644 --- a/src/hooks/useTokenDistroHelper.ts +++ b/src/hooks/useTokenDistroHelper.ts @@ -1,11 +1,8 @@ import { useState, useEffect, useMemo } from 'react'; -import { useAccount } from 'wagmi'; -import { useQuery } from '@tanstack/react-query'; import { TokenDistroHelper } from '@/lib/contractHelper/TokenDistroHelper'; import { SubgraphDataHelper } from '@/lib/subgraph/subgraphDataHelper'; import { RegenStreamConfig } from '@/types/config'; -import { fetchSubgraphData } from '@/services/subgraph.service'; -import config from '@/configuration'; +import { useSubgraphInfo } from './useSubgraphInfo'; export const useTokenDistroHelper = ( poolNetwork: number, @@ -14,13 +11,7 @@ export const useTokenDistroHelper = ( ) => { const [tokenDistroHelper, setTokenDistroHelper] = useState(); - const { address } = useAccount(); - const currentValues = useQuery({ - queryKey: ['subgraph', poolNetwork, address], - queryFn: async () => await fetchSubgraphData(poolNetwork, address), - enabled: !hold, - staleTime: config.SUBGRAPH_POLLING_INTERVAL, - }); + const currentValues = useSubgraphInfo(poolNetwork); const sdh = useMemo( () => new SubgraphDataHelper(currentValues.data), [currentValues.data], From f8c556e241b8e5cd83aad3b5ecfa75284c614e6a Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 14:09:42 +0330 Subject: [PATCH 58/87] remove unused chain id --- src/components/controller/subgraph.ctrl.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/controller/subgraph.ctrl.tsx b/src/components/controller/subgraph.ctrl.tsx index f69f0098da..991f98597d 100644 --- a/src/components/controller/subgraph.ctrl.tsx +++ b/src/components/controller/subgraph.ctrl.tsx @@ -7,12 +7,12 @@ import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataFo import { useInteractedBlockNumber } from '@/hooks/useInteractedBlockNumber'; const SubgraphController: React.FC = () => { - const { address, chain } = useAccount(); + const { address } = useAccount(); const queryClient = useQueryClient(); const pollingTimeoutsRef = useRef<{ [key: number]: NodeJS.Timeout }>({}); const refetchedChainsRef = useRef>(new Set()); useFetchSubgraphDataForAllChains(); - useInteractedBlockNumber(chain?.id); + useInteractedBlockNumber(); useEffect(() => { const handleEvent = ( From d1f2f53b5981aa3d3567e3a1001795c4b8e4960e Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 14:16:26 +0330 Subject: [PATCH 59/87] update chain id --- src/components/GIVeconomyPages/GIVstream.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/GIVeconomyPages/GIVstream.tsx b/src/components/GIVeconomyPages/GIVstream.tsx index cf1047a615..1d80656cbf 100644 --- a/src/components/GIVeconomyPages/GIVstream.tsx +++ b/src/components/GIVeconomyPages/GIVstream.tsx @@ -71,10 +71,8 @@ export const TabGIVstreamTop = () => { const [rewardLiquidPart, setRewardLiquidPart] = useState(0n); const [rewardStream, setRewardStream] = useState(0n); const { givTokenDistroHelper } = useGIVTokenDistroHelper(showModal); - const { chain } = useAccount(); + const { chainId } = useAccount(); const currentValues = useSubgraphInfo(); - - const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValues.data); const { allocatedTokens, claimed, givback } = sdh.getGIVTokenDistroBalance(); @@ -164,7 +162,7 @@ export const TabGIVstreamTop = () => { }; export const TabGIVstreamBottom = () => { - const { chain, address } = useAccount(); + const { chainId } = useAccount(); const { givTokenDistroHelper } = useGIVTokenDistroHelper(); const { formatMessage } = useIntl(); @@ -174,7 +172,6 @@ export const TabGIVstreamBottom = () => { const [streamAmount, setStreamAmount] = useState(0n); const currentValues = useSubgraphInfo(); - const chainId = chain?.id; const sdh = new SubgraphDataHelper(currentValues.data); const givTokenDistroBalance = sdh.getGIVTokenDistroBalance(); const increaseSecRef = useRef(null); From ab4262f0259f755c870b82e71124a3bfb61724a0 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 21 Oct 2024 14:19:53 +0330 Subject: [PATCH 60/87] disable reward card action on not sync --- src/components/RewardCard.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/RewardCard.tsx b/src/components/RewardCard.tsx index 45f7a228af..938ac801e6 100644 --- a/src/components/RewardCard.tsx +++ b/src/components/RewardCard.tsx @@ -25,6 +25,7 @@ import { INetworkIdWithChain } from './views/donate/common/common.types'; import { ChainType } from '@/types/config'; import { EVMWrongNetworkSwitchModal } from './modals/WrongNetworkInnerModal'; import { useFetchGIVPrice } from '@/hooks/useGivPrice'; +import { useSubgraphSyncInfo } from '@/hooks/useSubgraphSyncInfo'; interface IRewardCardProps { cardName: string; @@ -63,6 +64,7 @@ export const RewardCard: FC = ({ useState(false); const { data: givPrice } = useFetchGIVPrice(); const { givTokenDistroHelper } = useGIVTokenDistroHelper(); + const subgraphSyncedInfo = useSubgraphSyncInfo(network); useEffect(() => { const price = @@ -132,7 +134,10 @@ export const RewardCard: FC = ({ label={actionLabel} onClick={actionCb} buttonType='primary' - disabled={liquidAmount === 0n} + disabled={ + liquidAmount === 0n || + !subgraphSyncedInfo.isSynced + } /> ) : ( From ec3fa78f9fe0a92740bceea9c62876a98172b802 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 21 Oct 2024 14:45:42 +0200 Subject: [PATCH 61/87] moved headers to new item of the array --- next.config.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/next.config.js b/next.config.js index 115c0884dc..00bc518a80 100644 --- a/next.config.js +++ b/next.config.js @@ -150,6 +150,20 @@ const moduleExports = withBundleAnalyzer({ }, headers: async () => { return [ + { + source: '/:path*', + locale: false, + headers: [ + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN', + }, + { + key: 'Content-Security-Policy', + value: "frame-ancestors 'self'", + }, + ], + }, { // Adding CORS headers for /manifest.json source: '/manifest.json', @@ -164,14 +178,6 @@ const moduleExports = withBundleAnalyzer({ key: 'Access-Control-Allow-Headers', value: 'X-Requested-With, content-type, Authorization', }, - { - key: 'X-Frame-Options', - value: 'SAMEORIGIN', - }, - { - key: 'Content-Security-Policy', - value: "frame-ancestors 'self'", - }, ], }, ]; From 85aa303bcc06d02569e7fde3dc03dafea3fb6d8f Mon Sep 17 00:00:00 2001 From: maryjaf Date: Tue, 22 Oct 2024 18:10:16 +0330 Subject: [PATCH 62/87] add sticky style add sticky style for passport banner --- src/components/PassportBanner.tsx | 276 +++++++++++++++--------------- 1 file changed, 136 insertions(+), 140 deletions(-) diff --git a/src/components/PassportBanner.tsx b/src/components/PassportBanner.tsx index e818aa5448..5cd2d684f0 100644 --- a/src/components/PassportBanner.tsx +++ b/src/components/PassportBanner.tsx @@ -111,171 +111,167 @@ export const PassportBannerData: IData = { icon: , }, }; - export const PassportBanner = () => { - const { info, updateState, fetchUserMBDScore, handleSign, refreshScore } = - usePassport(); - const { currentRound, passportState, passportScore, qfEligibilityState } = - info; - + const { info, updateState, fetchUserMBDScore, handleSign, refreshScore } = usePassport(); + const { currentRound, passportState, passportScore, qfEligibilityState } = info; const { formatMessage, locale } = useIntl(); const { connector } = useAccount(); const { isOnSolana, handleSingOutAndSignInWithEVM } = useGeneralWallet(); const [showModal, setShowModal] = useState(false); const [signWithWallet, setSignWithWallet] = useState(false); - + const isGSafeConnector = connector?.id === 'safe'; - + + // Check if the eligibility state or current round is not loaded yet + const isLoading = !qfEligibilityState || !currentRound; + + // Only render the banner when the data is available + if (isLoading) { + return null; // Or return a spinner or loading message if you'd like + } + return !isOnSolana ? ( - <> - - - - {PassportBannerData[qfEligibilityState].icon} - -

- {formatMessage( - { - id: PassportBannerData[qfEligibilityState] - .content, - }, - { - data: - qfEligibilityState === - EQFElegibilityState.NOT_STARTED && - currentRound - ? smallFormatDate( - new Date( - currentRound?.beginDate, - ), - ) - : undefined, - }, - )} - {currentRound && - qfEligibilityState === - EQFElegibilityState.RECHECK_ELIGIBILITY && ( - <> - {' '} - - {new Date(currentRound.endDate) - .toLocaleString(locale || 'en-US', { - day: 'numeric', - month: 'short', - }) - .replace(/,/g, '')} - - - )} -

-
- {qfEligibilityState === - EQFElegibilityState.CHECK_ELIGIBILITY && ( - fetchUserMBDScore()}> - - {formatMessage({ - id: 'qf_donor_eligibility.banner.link.check_eligibility', - })} - - - )} - {qfEligibilityState === - EQFElegibilityState.RECHECK_ELIGIBILITY && ( - setShowModal(true)}> - - {formatMessage({ - id: 'qf_donor_eligibility.banner.link.recheck_eligibility', - })} - - - )} - {qfEligibilityState === EQFElegibilityState.PROCESSING && ( - - {formatMessage({ - id: 'label.processing', - })} - - - )} - {qfEligibilityState === - EQFElegibilityState.MORE_INFO_NEEDED && ( - setShowModal(true)}> - - {formatMessage({ - id: 'label.add_more_info', - })} - - - )} - {qfEligibilityState === EQFElegibilityState.NOT_SIGNED && ( - setSignWithWallet(true)}> - - {formatMessage({ - id: 'label.sign_message', - })} - - - - )} -
- {showModal && ( - - )} - {signWithWallet && ( - { - setSignWithWallet(false); - }} - /> - )} - - ) : ( + <> + + + {PassportBannerData[qfEligibilityState].icon} +

+ {formatMessage( + { + id: PassportBannerData[qfEligibilityState].content, + }, + { + data: + qfEligibilityState === EQFElegibilityState.NOT_STARTED && + currentRound + ? smallFormatDate(new Date(currentRound?.beginDate)) + : undefined, + } + )} + {currentRound && qfEligibilityState === EQFElegibilityState.RECHECK_ELIGIBILITY && ( + <> + {' '} + + {new Date(currentRound.endDate) + .toLocaleString(locale || 'en-US', { + day: 'numeric', + month: 'short', + }) + .replace(/,/g, '')} + + + )} +

+
+ {qfEligibilityState === EQFElegibilityState.CHECK_ELIGIBILITY && ( + fetchUserMBDScore()}> + {formatMessage({ - id: 'label.to_activate_your_gitcoin_passport', + id: 'qf_donor_eligibility.banner.link.check_eligibility', })} -

- +
+
+ )} + {qfEligibilityState === EQFElegibilityState.RECHECK_ELIGIBILITY && ( + setShowModal(true)}> + {formatMessage({ - id: 'label.switch_to_evm', + id: 'qf_donor_eligibility.banner.link.recheck_eligibility', })} - + + + )} + {qfEligibilityState === EQFElegibilityState.PROCESSING && ( + + {formatMessage({ + id: 'label.processing', + })} + + + )} + {qfEligibilityState === EQFElegibilityState.MORE_INFO_NEEDED && ( + setShowModal(true)}> + + {formatMessage({ + id: 'label.add_more_info', + })} + + + )} + {qfEligibilityState === (EQFElegibilityState as any).NOT_SIGNED && ( + setSignWithWallet(true)}> + + {formatMessage({ + id: 'label.sign_message', + })} + + + + )}
+ {showModal && ( + + )} + {signWithWallet && ( + { + setSignWithWallet(false); + }} + /> + )} + + ) : ( + +

+ {formatMessage({ + id: 'label.to_activate_your_gitcoin_passport', + })} +

+ + {formatMessage({ + id: 'label.switch_to_evm', + })} + +
); -}; + }; + + interface IPassportBannerWrapperProps { $bgColor: EPBGState; } export const PassportBannerWrapper = styled(Flex)` - flex-direction: column; - background-color: ${props => bgColor[props.$bgColor]}; - padding: 16px; - align-items: center; - justify-content: center; - gap: 8px; - position: relative; - ${mediaQueries.tablet} { - flex-direction: row; - } + flex-direction: column; + background-color: ${props => bgColor[props.$bgColor]}; + padding: 16px; + align-items: center; + justify-content: center; + gap: 8px; + position: sticky; /* Change this to sticky */ + top: 0; /* This keeps it at the top as the user scrolls */ + z-index: 1000; /* Ensure it stays above other content */ + ${mediaQueries.tablet} { + flex-direction: row; + } `; + const IconWrapper = styled.div` width: 30px; `; From 9378cfe2a433e2db04519b102e4399d16f7d1d4c Mon Sep 17 00:00:00 2001 From: maryjaf Date: Wed, 23 Oct 2024 07:59:51 +0330 Subject: [PATCH 63/87] add prettier fixes add prettier fixes in passportBanner.tsx --- src/components/PassportBanner.tsx | 275 ++++++++++++++++-------------- 1 file changed, 144 insertions(+), 131 deletions(-) diff --git a/src/components/PassportBanner.tsx b/src/components/PassportBanner.tsx index 5cd2d684f0..70bdbe7037 100644 --- a/src/components/PassportBanner.tsx +++ b/src/components/PassportBanner.tsx @@ -112,166 +112,179 @@ export const PassportBannerData: IData = { }, }; export const PassportBanner = () => { - const { info, updateState, fetchUserMBDScore, handleSign, refreshScore } = usePassport(); - const { currentRound, passportState, passportScore, qfEligibilityState } = info; + const { info, updateState, fetchUserMBDScore, handleSign, refreshScore } = + usePassport(); + const { currentRound, passportState, passportScore, qfEligibilityState } = + info; const { formatMessage, locale } = useIntl(); const { connector } = useAccount(); const { isOnSolana, handleSingOutAndSignInWithEVM } = useGeneralWallet(); const [showModal, setShowModal] = useState(false); const [signWithWallet, setSignWithWallet] = useState(false); - + const isGSafeConnector = connector?.id === 'safe'; - + // Check if the eligibility state or current round is not loaded yet const isLoading = !qfEligibilityState || !currentRound; - + // Only render the banner when the data is available if (isLoading) { - return null; // Or return a spinner or loading message if you'd like + return null; // Or return a spinner or loading message if you'd like } - + return !isOnSolana ? ( - <> + <> + + + + {PassportBannerData[qfEligibilityState].icon} + +

+ {formatMessage( + { + id: PassportBannerData[qfEligibilityState] + .content, + }, + { + data: + qfEligibilityState === + EQFElegibilityState.NOT_STARTED && + currentRound + ? smallFormatDate( + new Date( + currentRound?.beginDate, + ), + ) + : undefined, + }, + )} + {currentRound && + qfEligibilityState === + EQFElegibilityState.RECHECK_ELIGIBILITY && ( + <> + {' '} + + {new Date(currentRound.endDate) + .toLocaleString(locale || 'en-US', { + day: 'numeric', + month: 'short', + }) + .replace(/,/g, '')} + + + )} +

+
+ {qfEligibilityState === + EQFElegibilityState.CHECK_ELIGIBILITY && ( + fetchUserMBDScore()}> + + {formatMessage({ + id: 'qf_donor_eligibility.banner.link.check_eligibility', + })} + + + )} + {qfEligibilityState === + EQFElegibilityState.RECHECK_ELIGIBILITY && ( + setShowModal(true)}> + + {formatMessage({ + id: 'qf_donor_eligibility.banner.link.recheck_eligibility', + })} + + + )} + {qfEligibilityState === EQFElegibilityState.PROCESSING && ( + + {formatMessage({ + id: 'label.processing', + })} + + + )} + {qfEligibilityState === + EQFElegibilityState.MORE_INFO_NEEDED && ( + setShowModal(true)}> + + {formatMessage({ + id: 'label.add_more_info', + })} + + + )} + {qfEligibilityState === + (EQFElegibilityState as any).NOT_SIGNED && ( + setSignWithWallet(true)}> + + {formatMessage({ + id: 'label.sign_message', + })} + + + + )} +
+ {showModal && ( + + )} + {signWithWallet && ( + { + setSignWithWallet(false); + }} + /> + )} + + ) : ( - - - {PassportBannerData[qfEligibilityState].icon} -

- {formatMessage( - { - id: PassportBannerData[qfEligibilityState].content, - }, - { - data: - qfEligibilityState === EQFElegibilityState.NOT_STARTED && - currentRound - ? smallFormatDate(new Date(currentRound?.beginDate)) - : undefined, - } - )} - {currentRound && qfEligibilityState === EQFElegibilityState.RECHECK_ELIGIBILITY && ( - <> - {' '} - - {new Date(currentRound.endDate) - .toLocaleString(locale || 'en-US', { - day: 'numeric', - month: 'short', - }) - .replace(/,/g, '')} - - - )} -

-
- {qfEligibilityState === EQFElegibilityState.CHECK_ELIGIBILITY && ( - fetchUserMBDScore()}> - - {formatMessage({ - id: 'qf_donor_eligibility.banner.link.check_eligibility', - })} - - - )} - {qfEligibilityState === EQFElegibilityState.RECHECK_ELIGIBILITY && ( - setShowModal(true)}> - {formatMessage({ - id: 'qf_donor_eligibility.banner.link.recheck_eligibility', + id: 'label.to_activate_your_gitcoin_passport', })} - - - )} - {qfEligibilityState === EQFElegibilityState.PROCESSING && ( - - {formatMessage({ - id: 'label.processing', - })} - - - )} - {qfEligibilityState === EQFElegibilityState.MORE_INFO_NEEDED && ( - setShowModal(true)}> - - {formatMessage({ - id: 'label.add_more_info', - })} - - - )} - {qfEligibilityState === (EQFElegibilityState as any).NOT_SIGNED && ( - setSignWithWallet(true)}> - +

+ {formatMessage({ - id: 'label.sign_message', + id: 'label.switch_to_evm', })} -
- -
- )} +
- {showModal && ( - - )} - {signWithWallet && ( - { - setSignWithWallet(false); - }} - /> - )} - - ) : ( - -

- {formatMessage({ - id: 'label.to_activate_your_gitcoin_passport', - })} -

- - {formatMessage({ - id: 'label.switch_to_evm', - })} - -
); - }; - - +}; interface IPassportBannerWrapperProps { $bgColor: EPBGState; } export const PassportBannerWrapper = styled(Flex)` - flex-direction: column; - background-color: ${props => bgColor[props.$bgColor]}; - padding: 16px; - align-items: center; - justify-content: center; - gap: 8px; - position: sticky; /* Change this to sticky */ - top: 0; /* This keeps it at the top as the user scrolls */ - z-index: 1000; /* Ensure it stays above other content */ - ${mediaQueries.tablet} { - flex-direction: row; - } + flex-direction: column; + background-color: ${props => bgColor[props.$bgColor]}; + padding: 16px; + align-items: center; + justify-content: center; + gap: 8px; + position: sticky; /* Change this to sticky */ + top: 0; /* This keeps it at the top as the user scrolls */ + z-index: 1000; /* Ensure it stays above other content */ + ${mediaQueries.tablet} { + flex-direction: row; + } `; - const IconWrapper = styled.div` width: 30px; `; From 6aa1880cc8ec984533b3dd44bd5de01e9eeadf2d Mon Sep 17 00:00:00 2001 From: maryjaf Date: Wed, 23 Oct 2024 13:42:05 +0330 Subject: [PATCH 64/87] edit z-index --- src/components/PassportBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PassportBanner.tsx b/src/components/PassportBanner.tsx index 70bdbe7037..6cb7c7db5e 100644 --- a/src/components/PassportBanner.tsx +++ b/src/components/PassportBanner.tsx @@ -279,7 +279,7 @@ export const PassportBannerWrapper = styled(Flex)` gap: 8px; position: sticky; /* Change this to sticky */ top: 0; /* This keeps it at the top as the user scrolls */ - z-index: 1000; /* Ensure it stays above other content */ + z-index: 5; /* Ensure it stays above other content */ ${mediaQueries.tablet} { flex-direction: row; } From a2e672517f2fb9d37f4ebaf723a3c0f8c6be5eb9 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 8 Nov 2024 12:06:43 +0100 Subject: [PATCH 65/87] Feat/Allow project boost if project is vouched --- lang/ca.json | 1 + lang/en.json | 1 + lang/es.json | 1 + .../views/project/ProjectGIVbackToast.tsx | 25 ++++- .../projectActionCard/AdminActions.tsx | 103 ++++++++++-------- .../ProjectCardNotification.tsx | 45 ++++++++ 6 files changed, 128 insertions(+), 48 deletions(-) create mode 100644 src/components/views/project/projectActionCard/ProjectCardNotification.tsx diff --git a/lang/ca.json b/lang/ca.json index 7023c9de42..e0184160d2 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -1678,6 +1678,7 @@ "project.givback_toast.title.verified_public_2": " del valor de la teva donació!", "project.givback_toast.title.verified_public_3": "Rep recompenses de fins a {percent}%", "project.givback_toast.title.verified_public_not_eligible": "Impulsa aquest projecte amb GIVpower!", + "project.givback_toast.complete_eligibility": "Completa el teu formulari d’elegibilitat per als GIVbacks", "projects_all": "Tots els Projectes", "projects_all_desc": "SUPORT A PROJECTES GLOBALS DE BÉS PÚBLICS, SOSTENIBILITAT I REGENERACIÓ AMB CRYPTODONACIONS", "projects_art-and-culture": "Art i Cultura", diff --git a/lang/en.json b/lang/en.json index 552db6df57..f9e629f0f7 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1679,6 +1679,7 @@ "project.givback_toast.title.verified_public_2": " of your donation value!", "project.givback_toast.title.verified_public_3": "Get rewarded with up to {percent}%", "project.givback_toast.title.verified_public_not_eligible": "Boost this project with GIVpower!", + "project.givback_toast.complete_eligibility": "Complete your GIVbacks Eligibility form", "projects_all": "All Projects", "projects_all_desc": "SUPPORT GLOBAL PROJECTS IN PUBLIC GOODS, SUSTAINABILITY, AND REGENERATION WITH CRYPTO DONATIONS", "projects_art-and-culture": "Art & Culture", diff --git a/lang/es.json b/lang/es.json index 410ca22183..602043da5e 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1679,6 +1679,7 @@ "project.givback_toast.title.verified_public_2": " del valor de tu donación!", "project.givback_toast.title.verified_public_3": "Recibe recompensas de hasta {percent}%", "project.givback_toast.title.verified_public_not_eligible": "Boostea este proyecto con GIVpower!", + "project.givback_toast.complete_eligibility": "Complete su formulario de elegibilidad para GIVbacks", "projects_all": "Todos los proyectos", "projects_all_desc": "APOYE PROYECTOS GLOBALES EN BIENES PÚBLICOS, SOSTENIBILIDAD Y REGENERACIÓN CON CRIPTODONACIONES", "projects_art-and-culture": "Arte & Cultura", diff --git a/src/components/views/project/ProjectGIVbackToast.tsx b/src/components/views/project/ProjectGIVbackToast.tsx index 62e26d268b..37829103ee 100644 --- a/src/components/views/project/ProjectGIVbackToast.tsx +++ b/src/components/views/project/ProjectGIVbackToast.tsx @@ -53,7 +53,7 @@ const ProjectGIVbackToast = () => { const isOwnerVerifiedNotEligible = isVerified && isAdmin && !isGivbackEligible; - const color = isOwnerGivbackEligible + let color = isOwnerGivbackEligible ? semanticColors.golden[600] : neutralColors.gray[900]; const { formatMessage } = useIntl(); @@ -227,6 +227,29 @@ const ProjectGIVbackToast = () => { }); icon = ; link = links.CANCELLED_PROJECTS_DOCS; + } else if (isOwnerVerifiedNotEligible) { + title = formatMessage( + { + id: `${useIntlTitle}verified_owner`, + }, + { + percent: givbackFactorPercent, + value: GIVBACKS_DONATION_QUALIFICATION_VALUE_USD, + }, + ); + description = formatMessage({ + id: `${useIntlDescription}verified_owner_not_eligible`, + }); + color = semanticColors.golden[600]; + icon = ; + link = links.GIVPOWER_DOC; + Button = ( + } + /> + ); } else { title = formatMessage({ id: `${useIntlTitle}non_verified_owner`, diff --git a/src/components/views/project/projectActionCard/AdminActions.tsx b/src/components/views/project/projectActionCard/AdminActions.tsx index d4c59c6b13..34b28d2577 100644 --- a/src/components/views/project/projectActionCard/AdminActions.tsx +++ b/src/components/views/project/projectActionCard/AdminActions.tsx @@ -31,6 +31,7 @@ import { EVerificationStatus } from '@/apollo/types/types'; import ClaimRecurringDonationModal from '../../userProfile/projectsTab/ClaimRecurringDonationModal'; import config from '@/configuration'; import { findAnchorContractAddress } from '@/helpers/superfluid'; +import { ProjectCardNotification } from './ProjectCardNotification'; interface IMobileActionsModalProps { setShowModal: (value: boolean) => void; @@ -163,55 +164,63 @@ export const AdminActions = () => { project={project} /> )} + ) : ( - setShowMobileActionsModal(true)} - > -
Project Actions
- - {showMobileActionsModal && ( - - {options.map(option => ( - - - {option.icon} -
{option.label}
-
-
- ))} - {showVerificationModal && ( - setShowVerificationModal(false)} - /> - )} - {deactivateModal && ( - - )} - {showShareModal && ( - - )} -
- )} - {showClaimModal && ( - - )} -
+ <> + setShowMobileActionsModal(true)} + > +
Project Actions
+ + {showMobileActionsModal && ( + + {options.map(option => ( + + + + {option.icon} + +
{option.label}
+
+
+ ))} + {showVerificationModal && ( + setShowVerificationModal(false)} + /> + )} + {deactivateModal && ( + + )} + {showShareModal && ( + + )} +
+ )} + {showClaimModal && ( + + )} +
+ + ); }; diff --git a/src/components/views/project/projectActionCard/ProjectCardNotification.tsx b/src/components/views/project/projectActionCard/ProjectCardNotification.tsx new file mode 100644 index 0000000000..b2154971af --- /dev/null +++ b/src/components/views/project/projectActionCard/ProjectCardNotification.tsx @@ -0,0 +1,45 @@ +import { useIntl } from 'react-intl'; +import styled from 'styled-components'; + +import { + semanticColors, + IconAlertCircle16, + Flex, +} from '@giveth/ui-design-system'; +import { useProjectContext } from '@/context/project.context'; + +export const ProjectCardNotification = () => { + const { formatMessage } = useIntl(); + const { isAdmin, projectData } = useProjectContext(); + + const isVerified = projectData?.verified; + const isGivbackEligible = projectData?.isGivbackEligible; + + const isOwnerVerifiedNotEligible = + isVerified && isAdmin && !isGivbackEligible; + + if (!isOwnerVerifiedNotEligible) { + return null; + } + + return ( + + + + {formatMessage({ + id: `project.givback_toast.complete_eligibility`, + })} + + + ); +}; + +const ProjectCardNotificationWrapper = styled(Flex)` + align-items: center; + padding-top: 12px; + font-size: 14px; + color: ${semanticColors.golden[600]}; + span { + margin-left: 8px; + } +`; From 1a3e4206ce17d74fdbacf0ad7838e09184c4443d Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 11 Nov 2024 10:21:15 +0100 Subject: [PATCH 66/87] started verification of the user email --- src/apollo/gql/gqlUser.ts | 6 ++++++ src/components/modals/EditUserModal.tsx | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/apollo/gql/gqlUser.ts b/src/apollo/gql/gqlUser.ts index b3dfd670fa..f0dbc63949 100644 --- a/src/apollo/gql/gqlUser.ts +++ b/src/apollo/gql/gqlUser.ts @@ -256,3 +256,9 @@ export const FETCH_USERS_GIVPOWER_BY_ADDRESS = ` } } }`; + +export const SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW = gql` + mutation SendUserEmailConfirmationCodeFlow($email: String!) { + sendUserEmailConfirmationCodeFlow(email: $email) + } +`; diff --git a/src/components/modals/EditUserModal.tsx b/src/components/modals/EditUserModal.tsx index 341d6bdcad..f229549964 100644 --- a/src/components/modals/EditUserModal.tsx +++ b/src/components/modals/EditUserModal.tsx @@ -8,7 +8,10 @@ import { captureException } from '@sentry/nextjs'; import { useForm } from 'react-hook-form'; import { Modal } from './Modal'; import { client } from '@/apollo/apolloClient'; -import { UPDATE_USER } from '@/apollo/gql/gqlUser'; +import { + SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, + UPDATE_USER, +} from '@/apollo/gql/gqlUser'; import { IUser } from '@/apollo/types/types'; import { gToast, ToastType } from '../toasts'; import { @@ -88,6 +91,20 @@ const EditUserModal = ({ } }; + const testMe = async () => { + try { + const { data } = await client.mutate({ + mutation: SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, + variables: { + email: 'test£gmail.com', + }, + }); + console.log(data); + } catch (error) { + console.log(error); + } + }; + const onSubmit = async (formData: Inputs) => { setIsLoading(true); try { @@ -163,6 +180,11 @@ const EditUserModal = ({
+
+ +
{inputFields.map(field => ( Date: Thu, 14 Nov 2024 14:57:22 +0100 Subject: [PATCH 67/87] stil testing --- src/components/modals/EditUserModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modals/EditUserModal.tsx b/src/components/modals/EditUserModal.tsx index f229549964..a50855f6fd 100644 --- a/src/components/modals/EditUserModal.tsx +++ b/src/components/modals/EditUserModal.tsx @@ -96,7 +96,7 @@ const EditUserModal = ({ const { data } = await client.mutate({ mutation: SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, variables: { - email: 'test£gmail.com', + email: 'kkatusic@gmail.com', }, }); console.log(data); From cd0c808aeb6a579fdf98aa75f64ecc214ee9d80c Mon Sep 17 00:00:00 2001 From: kkatusic Date: Fri, 15 Nov 2024 15:28:24 +0100 Subject: [PATCH 68/87] Fix/Mobile search --- src/components/SearchInput.tsx | 65 ++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/components/SearchInput.tsx b/src/components/SearchInput.tsx index 50751654e7..06314630b1 100644 --- a/src/components/SearchInput.tsx +++ b/src/components/SearchInput.tsx @@ -42,36 +42,49 @@ export const SearchInput: FC = ({ setTerm, className }) => { setValue(event.target.value); } + function handleFormSubmit(inputValue: string) { + if (inputValue.length > 2) { + setTerm(inputValue); + } + } + const [inputRef] = useFocus(); return ( - - - {value.length > 0 ? ( - { - setValue(''); - setTerm(''); - }} - > - - - ) : ( - - - - )} - + { + e.preventDefault(); + handleFormSubmit(value); + }} + > + + + {value.length > 0 ? ( + { + setValue(''); + setTerm(''); + }} + > + + + ) : ( + + + + )} + + {value.length > 0 ? ( value.length > 2 ? ( From 89c774704435a0ef90e7527bf6e0567ab3b006d4 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Sat, 16 Nov 2024 18:13:07 +0100 Subject: [PATCH 69/87] created separated input for user email verification --- lang/ca.json | 7 +- lang/en.json | 9 +- lang/es.json | 3 + src/apollo/types/types.ts | 1 + src/components/InputUserEmailVerify.tsx | 443 ++++++++++++++++++++++++ src/components/modals/EditUserModal.tsx | 59 ++-- src/features/user/user.queries.ts | 1 + 7 files changed, 495 insertions(+), 28 deletions(-) create mode 100644 src/components/InputUserEmailVerify.tsx diff --git a/lang/ca.json b/lang/ca.json index 7023c9de42..1d185cc249 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -383,6 +383,9 @@ "label.eligible_networks_for_matching": "Xarxes aptes per a la concordança QF", "label.email": "correu electrònic", "label.email_address": "Adreça electrònica", + "label.email_verified": "Correu electrònic verificat", + "label.email_verify": "Verifica el correu electrònic", + "label.email_already_verified": "El teu correu electrònic ha estat verificat. Ara pots desar la informació del teu perfil.", "label.enable_change": "Habilita el canvi", "label.enable_recurring_donations": "Habilitar Donacions Recurrents", "label.ends_on": "acaba el", @@ -610,7 +613,7 @@ "label.loading": "Carregant", "label.loading_data": "Carregant Dades", "label.location": "Ubicació", - "label.location_optional": "ubicació (opcional)", + "label.location_optional": "Ubicació (opcional)", "label.locekd_giv": "GIV Bloquejat", "label.locked_for": "Bloquejat per", "label.locked_giv_details": "Detalls del GIV bloquejat", @@ -1204,7 +1207,7 @@ "label.wallet": "CARTERA", "label.wallet_connect": "Connexió de la cartera", "label.want_to_use_another_wallet": "Vols usar una altra cartera?", - "label.website_or_url": "lloc web o URL", + "label.website_or_url": "Lloc web o URL", "label.week": "setmana", "label.welcome_giver": "Benvingut, Giver", "label.welcome_to_the": "Benvingut a", diff --git a/lang/en.json b/lang/en.json index 552db6df57..45c6487c75 100644 --- a/lang/en.json +++ b/lang/en.json @@ -381,8 +381,11 @@ "label.elevate_projects": "Elevate Projects", "label.eligible_for_matching": "Eligible for Matching", "label.eligible_networks_for_matching": "Eligible networks for QF matching", - "label.email": "email", + "label.email": "Email", "label.email_address": "Email Address", + "label.email_verified": "Email Verified", + "label.email_verify": "Verify Email", + "label.email_already_verified": "Your email has been verified. You can now save your profile information.", "label.enable_change": "Enable Change", "label.enable_recurring_donations": "Enable Recurring Donations", "label.ends_on": "ends on", @@ -610,7 +613,7 @@ "label.loading": "Loading", "label.loading_data": "Loading Data", "label.location": "Location", - "label.location_optional": "location (optional)", + "label.location_optional": "Location (optional)", "label.locekd_giv": "Locked GIV", "label.locked_for": "Locked for", "label.locked_giv_details": "Locked GIV Details", @@ -1204,7 +1207,7 @@ "label.wallet": "WALLET", "label.wallet_connect": "Wallet Connect", "label.want_to_use_another_wallet": "Want to use another wallet?", - "label.website_or_url": "website or url", + "label.website_or_url": "Website or url", "label.week": "week", "label.welcome_giver": "Welcome, Giver", "label.welcome_to_the": "Welcome to the", diff --git a/lang/es.json b/lang/es.json index 410ca22183..9438eaaed5 100644 --- a/lang/es.json +++ b/lang/es.json @@ -383,6 +383,9 @@ "label.eligible_networks_for_matching": "Redes elegibles para la asignación de QF", "label.email": "Email", "label.email_address": "Dirección de Email", + "label.email_verified": "Correo electrónico verificado", + "label.email_verify": "Verificar correo electrónico", + "label.email_already_verified": "Tu correo electrónico ha sido verificado. Ahora puedes guardar la información de tu perfil.", "label.enable_change": "Ayuda al Cambio", "label.enable_recurring_donations": "Habilitar Donaciones Recurrentes", "label.ends_on": "termina el", diff --git a/src/apollo/types/types.ts b/src/apollo/types/types.ts index b461427456..5187903452 100644 --- a/src/apollo/types/types.ts +++ b/src/apollo/types/types.ts @@ -237,6 +237,7 @@ export interface IUser { wasReferred?: boolean; isReferrer?: boolean; chainvineId?: string; + isEmailVerified?: boolean; } export interface IPassportInfo { diff --git a/src/components/InputUserEmailVerify.tsx b/src/components/InputUserEmailVerify.tsx new file mode 100644 index 0000000000..a060bfbad5 --- /dev/null +++ b/src/components/InputUserEmailVerify.tsx @@ -0,0 +1,443 @@ +import { + GLink, + IIconProps, + neutralColors, + semanticColors, + SublineBold, + FlexCenter, + brandColors, + Flex, + IconEmptyCircle, + IconCheckCircleFilled, +} from '@giveth/ui-design-system'; +import React, { + forwardRef, + InputHTMLAttributes, + ReactElement, + useCallback, + useId, + useRef, + useState, +} from 'react'; +import styled, { css } from 'styled-components'; +import { useIntl } from 'react-intl'; +import { EInputValidation, IInputValidation } from '@/types/inputValidation'; +import InputStyled from './styled-components/Input'; +import { getTextWidth } from '@/helpers/text'; +import { + inputSizeToFontSize, + inputSizeToPaddingLeft, + inputSizeToVerticalPadding, +} from '@/helpers/styledComponents'; +import { Spinner } from './Spinner'; +import { useProfileContext } from '@/context/profile.context'; +import { SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW } from '@/apollo/gql/gqlUser'; +import { client } from '@/apollo/apolloClient'; +import type { + DeepRequired, + FieldError, + FieldErrorsImpl, + Merge, + RegisterOptions, + UseFormRegister, +} from 'react-hook-form'; +export enum InputSize { + SMALL, + MEDIUM, + LARGE, +} + +interface IInputLabelProps { + $required?: boolean; + $disabled?: boolean; +} + +interface IInput extends InputHTMLAttributes { + label?: string; + caption?: string; + isValidating?: boolean; + size?: InputSize; + LeftIcon?: ReactElement; + error?: ICustomInputError; + suffix?: ReactElement; +} + +interface ICustomInputError { + message?: string; +} + +interface IInputWithRegister extends IInput { + register: UseFormRegister; + registerName: string; + registerOptions?: RegisterOptions; + error?: + | FieldError + | undefined + | ICustomInputError + | Merge>>>; +} + +const InputSizeToLinkSize = (size: InputSize) => { + switch (size) { + case InputSize.SMALL: + return 'Tiny'; + case InputSize.MEDIUM: + return 'Small'; + case InputSize.LARGE: + return 'Medium'; + default: + return 'Small'; + } +}; + +type InputType = + | IInputWithRegister + | ({ + registerName?: never; + register?: never; + registerOptions?: never; + } & IInput); + +const InputUserEmailVerify = forwardRef( + (props, inputRef) => { + const { formatMessage } = useIntl(); + const { user } = useProfileContext(); + + const [email, setEmail] = useState(user.email); + const [verified, setVerified] = useState(user.isEmailVerified); + const [disableVerifyButton, setDisableVerifyButton] = useState( + !user.isEmailVerified && !user.email, + ); + const [isVerificationProcess, setIsVerificationProcess] = + useState(false); + + const { + label, + caption, + size = InputSize.MEDIUM, + disabled, + LeftIcon, + register, + registerName, + registerOptions = { required: false }, + error, + maxLength, + value, + isValidating, + suffix, + className, + ...rest + } = props; + const id = useId(); + const canvasRef = useRef(); + + const [validationStatus, setValidationStatus] = useState( + !error || isValidating + ? EInputValidation.NORMAL + : EInputValidation.ERROR, + ); + + // const inputRef = useRef(null); + + const calcLeft = useCallback(() => { + if ( + suffix && + !canvasRef.current && + typeof document !== 'undefined' + ) { + canvasRef.current = document.createElement('canvas'); + } + if (canvasRef.current) { + const width = getTextWidth( + value?.toString() || '', + `normal ${inputSizeToFontSize(size)}px Red Hat Text`, + canvasRef.current, + ); + return inputSizeToPaddingLeft(size, !!LeftIcon) + width; + } + return 15; + }, [suffix, value, size, LeftIcon]); + + const { ref = undefined, ...restRegProps } = + registerName && register + ? register(registerName, registerOptions) + : {}; + + // Setup label button on condition + let labelButton = verified + ? formatMessage({ + id: 'label.email_verified', + }) + : formatMessage({ + id: 'label.email_verify', + }); + + // Enable verification process "button" if email input value was empty and not verified yet + // and setup email if input value was changed and has more than 3 characters + const handleInputChange = (e: React.ChangeEvent) => { + if (e.target.value.length > 3) { + setEmail(e.target.value); + setDisableVerifyButton(false); + } else { + setDisableVerifyButton(true); + } + }; + + const verificationEmailHandler = async () => { + console.log({ email }); + try { + const { data } = await client.mutate({ + mutation: SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, + variables: { + email: 'kkatusic@gmail.com', + }, + }); + if (data.sendUserEmailConfirmationCodeFlow === 'EMAIL_EXIST') { + setValidationStatus(EInputValidation.WARNING); + setDisableVerifyButton(true); + } + } catch (error) { + console.log(error); + } + }; + + return ( + + {label && ( + + )} + + {LeftIcon && ( + + {LeftIcon} + + )} + { + ref !== undefined && ref(e); + if (inputRef) (inputRef as any).current = e; + }} + data-testid='styled-input' + onChange={handleInputChange} + /> + + {suffix} + + + + + {!isVerificationProcess && !verified && ( + + )} + {!isVerificationProcess && verified && ( + + )} + {!!isVerificationProcess && ( + + )} + {labelButton} + + + + + {isValidating && } + {maxLength && ( + + {value ? String(value)?.length : 0}/{maxLength} + + )} + + + {error?.message ? ( + + {error.message as string} + + ) : ( + + {verified && + formatMessage({ + id: 'label.email_already_verified', + })} + //hidden char + )} + + ); + }, +); + +InputUserEmailVerify.displayName = 'Input'; + +const Absolute = styled(FlexCenter)` + position: absolute; + right: 10px; + top: 0; + bottom: 0; +`; + +const CharLength = styled(SublineBold)` + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + background: ${neutralColors.gray[300]}; + color: ${neutralColors.gray[700]}; + font-weight: 500; + border-radius: 64px; + width: 52px; + height: 30px; + margin-right: 6px; +`; + +const InputContainer = styled.div` + flex: 1; +`; + +const InputLabel = styled(GLink)` + padding-bottom: 4px; + color: ${props => + props.$disabled ? neutralColors.gray[600] : neutralColors.gray[900]}; + &::after { + content: '*'; + display: ${props => (props.$required ? 'inline-block' : 'none')}; + padding: 0 4px; + color: ${semanticColors.punch[500]}; + } +`; + +const InputDesc = styled(GLink)` + padding-top: 4px; + color: ${neutralColors.gray[900]}; + display: block; +`; + +const InputValidation = styled(GLink)` + padding-top: 4px; + display: block; + color: ${props => { + switch (props.$validation) { + case EInputValidation.NORMAL: + return neutralColors.gray[900]; + case EInputValidation.WARNING: + return semanticColors.golden[600]; + case EInputValidation.ERROR: + return semanticColors.punch[500]; + case EInputValidation.SUCCESS: + return semanticColors.jade[500]; + default: + return neutralColors.gray[300]; + } + }}; +`; + +const InputWrapper = styled.div` + position: relative; + display: flex; +`; + +interface IInputWrapper { + $inputSize: InputSize; +} + +const LeftIconWrapper = styled.div` + position: absolute; + transform: translateY(-50%); + + border-right: 1px solid ${neutralColors.gray[400]}; + top: 50%; + left: 0; + overflow: hidden; + ${props => { + switch (props.$inputSize) { + case InputSize.SMALL: + return css` + width: 28px; + height: 16px; + padding-left: 8px; + `; + case InputSize.MEDIUM: + return css` + width: 36px; + height: 24px; + padding-top: 4px; + padding-left: 16px; + `; + case InputSize.LARGE: + return css` + width: 36px; + height: 24px; + padding-top: 4px; + padding-left: 16px; + `; + } + }} + padding-right: 4px; +`; + +const SuffixWrapper = styled.span` + position: absolute; + /* width: 16px; + height: 16px; */ +`; + +type VerifyInputButtonWrapperProps = { + $verified?: boolean; +}; + +const VerifyInputButtonWrapper = styled.button` + outline: none; + cursor: pointer; + background-color: ${({ $verified }) => + $verified ? 'transparent' : brandColors.giv[50]}; + border: 1px solid + ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[50]}; + border-radius: 8px; + padding: 3px 8px; + span { + color: ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[500]}; + font-size: 10px; + font-weight: 400; + line-height: 13.23px; + text-align: left; + } + svg { + color: ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[500]}; + } + &:disabled { + opacity: 0.5; + } +`; + +export default InputUserEmailVerify; diff --git a/src/components/modals/EditUserModal.tsx b/src/components/modals/EditUserModal.tsx index a50855f6fd..ef25834db4 100644 --- a/src/components/modals/EditUserModal.tsx +++ b/src/components/modals/EditUserModal.tsx @@ -26,6 +26,7 @@ import { requiredOptions, validators } from '@/lib/constants/regex'; import { useModalAnimation } from '@/hooks/useModalAnimation'; import useUpload from '@/hooks/useUpload'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; +import InputUserEmailVerify from '../InputUserEmailVerify'; interface IEditUserModal extends IModal { user: IUser; @@ -186,28 +187,36 @@ const EditUserModal = ({
- {inputFields.map(field => ( - - ))} - ", + "label.email_confirm_code": "Confirma el codi", "label.enable_change": "Habilita el canvi", "label.enable_recurring_donations": "Habilitar Donacions Recurrents", "label.ends_on": "acaba el", diff --git a/lang/en.json b/lang/en.json index 45c6487c75..da6ef53f22 100644 --- a/lang/en.json +++ b/lang/en.json @@ -386,6 +386,12 @@ "label.email_verified": "Email Verified", "label.email_verify": "Verify Email", "label.email_already_verified": "Your email has been verified. You can now save your profile information.", + "label.email_used": "This email address will be used to send you important communications.", + "label.email_used_another": "This email that has already been verified on another profile!", + "label.email_sent_to": "Verification code sent to {email}", + "label.email_please_verify": "Please Verify your email. Enter the confirmation code sent to your email.", + "label.email_get_resend": "Didn't get the email? Check your spam folder or ", + "label.email_confirm_code": "Confirm Code", "label.enable_change": "Enable Change", "label.enable_recurring_donations": "Enable Recurring Donations", "label.ends_on": "ends on", diff --git a/lang/es.json b/lang/es.json index 9438eaaed5..dd18a568a4 100644 --- a/lang/es.json +++ b/lang/es.json @@ -386,6 +386,12 @@ "label.email_verified": "Correo electrónico verificado", "label.email_verify": "Verificar correo electrónico", "label.email_already_verified": "Tu correo electrónico ha sido verificado. Ahora puedes guardar la información de tu perfil.", + "label.email_used": "Esta dirección de correo electrónico se utilizará para enviarte comunicaciones importantes.", + "label.email_used_another": "¡Este correo electrónico ya ha sido verificado en otro perfil!", + "label.email_sent_to": "Código de verificación enviado a {email}", + "label.email_please_verify": "Por favor, verifica tu correo electrónico. Ingresa el código de confirmación enviado a tu correo.", + "label.email_get_resend": "¿No recibiste el correo? Revisa tu carpeta de spam o ", + "label.email_confirm_code": "Confirmar código", "label.enable_change": "Ayuda al Cambio", "label.enable_recurring_donations": "Habilitar Donaciones Recurrentes", "label.ends_on": "termina el", diff --git a/src/apollo/gql/gqlUser.ts b/src/apollo/gql/gqlUser.ts index f0dbc63949..17b0d819f8 100644 --- a/src/apollo/gql/gqlUser.ts +++ b/src/apollo/gql/gqlUser.ts @@ -262,3 +262,9 @@ export const SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW = gql` sendUserEmailConfirmationCodeFlow(email: $email) } `; + +export const SEND_USER_CONFIRMATION_CODE_FLOW = gql` + mutation SendUserConfirmationCodeFlow($verifyCode: String!) { + sendUserConfirmationCodeFlow(verifyCode: $verifyCode) + } +`; diff --git a/src/components/InputUserEmailVerify.tsx b/src/components/InputUserEmailVerify.tsx index a060bfbad5..f46f7b301a 100644 --- a/src/components/InputUserEmailVerify.tsx +++ b/src/components/InputUserEmailVerify.tsx @@ -9,6 +9,7 @@ import { Flex, IconEmptyCircle, IconCheckCircleFilled, + IconAlertCircle, } from '@giveth/ui-design-system'; import React, { forwardRef, @@ -20,7 +21,7 @@ import React, { useState, } from 'react'; import styled, { css } from 'styled-components'; -import { useIntl } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { EInputValidation, IInputValidation } from '@/types/inputValidation'; import InputStyled from './styled-components/Input'; import { getTextWidth } from '@/helpers/text'; @@ -31,7 +32,10 @@ import { } from '@/helpers/styledComponents'; import { Spinner } from './Spinner'; import { useProfileContext } from '@/context/profile.context'; -import { SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW } from '@/apollo/gql/gqlUser'; +import { + SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, + SEND_USER_CONFIRMATION_CODE_FLOW, +} from '@/apollo/gql/gqlUser'; import { client } from '@/apollo/apolloClient'; import type { DeepRequired, @@ -98,6 +102,10 @@ type InputType = registerOptions?: never; } & IInput); +interface IExtendedInputLabelProps extends IInputLabelProps { + $validation?: EInputValidation; +} + const InputUserEmailVerify = forwardRef( (props, inputRef) => { const { formatMessage } = useIntl(); @@ -110,10 +118,19 @@ const InputUserEmailVerify = forwardRef( ); const [isVerificationProcess, setIsVerificationProcess] = useState(false); + const [inputDescription, setInputDescription] = useState( + verified + ? formatMessage({ + id: 'label.email_already_verified', + }) + : formatMessage({ + id: 'label.email_used', + }), + ); + const codeInputRef = useRef(null); const { label, - caption, size = InputSize.MEDIUM, disabled, LeftIcon, @@ -137,6 +154,12 @@ const InputUserEmailVerify = forwardRef( : EInputValidation.ERROR, ); + const [validationCodeStatus, setValidationCodeStatus] = useState( + EInputValidation.SUCCESS, + ); + const [disableCodeVerifyButton, setDisableCodeVerifyButton] = + useState(true); + // const inputRef = useRef(null); const calcLeft = useCallback(() => { @@ -183,18 +206,66 @@ const InputUserEmailVerify = forwardRef( } }; + // Verification email handler, it will be called on button click + // It will send request to backend to check if email exists and if it's not verified yet + // or email is already exist on another user account + // If email isn't verified it will send email with verification code to user const verificationEmailHandler = async () => { - console.log({ email }); try { const { data } = await client.mutate({ mutation: SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, variables: { - email: 'kkatusic@gmail.com', + email: email, }, }); + if (data.sendUserEmailConfirmationCodeFlow === 'EMAIL_EXIST') { setValidationStatus(EInputValidation.WARNING); setDisableVerifyButton(true); + setInputDescription( + formatMessage({ + id: 'label.email_used_another', + }), + ); + } + + if ( + data.sendUserEmailConfirmationCodeFlow === + 'VERIFICATION_SENT' + ) { + setIsVerificationProcess(true); + } + } catch (error) { + console.log(error); + } + }; + + // Verification code handler, it will be called on button click + const handleInputCodeChange = ( + e: React.ChangeEvent, + ) => { + const value = e.target.value; + value.length >= 5 + ? setDisableCodeVerifyButton(false) + : setDisableCodeVerifyButton(true); + }; + + // Sent verification code to backend to check if it's correct + const handleButtonCodeChange = async () => { + try { + const { data } = await client.mutate({ + mutation: SEND_USER_CONFIRMATION_CODE_FLOW, + variables: { + verifyCode: codeInputRef.current?.value, + }, + }); + + if ( + data.sendUserConfirmationCodeFlow === 'VERIFICATION_SUCCESS' + ) { + setIsVerificationProcess(false); + setDisableCodeVerifyButton(true); + setVerified(true); } } catch (error) { console.log(error); @@ -209,6 +280,11 @@ const InputUserEmailVerify = forwardRef( $disabled={disabled} size={InputSizeToLinkSize(size)} $required={Boolean(registerOptions.required)} + $validation={ + isVerificationProcess + ? EInputValidation.ERROR + : undefined + } > {label} @@ -221,13 +297,16 @@ const InputUserEmailVerify = forwardRef( )} { @@ -245,27 +324,22 @@ const InputUserEmailVerify = forwardRef( > {suffix} - - - - {!isVerificationProcess && !verified && ( - - )} - {!isVerificationProcess && verified && ( - - )} - {!!isVerificationProcess && ( - - )} - {labelButton} - - - + {!isVerificationProcess && ( + + + + {!verified && } + {verified && } + {labelButton} + + + + )} {isValidating && } {maxLength && ( @@ -283,12 +357,84 @@ const InputUserEmailVerify = forwardRef( {error.message as string} ) : ( - - {verified && - formatMessage({ - id: 'label.email_already_verified', - })} - //hidden char + + {inputDescription} + + )} + {isVerificationProcess && ( + + + + {formatMessage( + { + id: 'label.email_sent_to', + }, + { email }, + )} + + + + + + + + {!verified && } + {verified && } + + {formatMessage({ + id: 'label.email_confirm_code', + })} + + + + + + + ( + + ), + }} + /> + + )} ); @@ -322,10 +468,12 @@ const InputContainer = styled.div` flex: 1; `; -const InputLabel = styled(GLink)` +const InputLabel = styled(GLink)` padding-bottom: 4px; color: ${props => - props.$disabled ? neutralColors.gray[600] : neutralColors.gray[900]}; + props.$validation === EInputValidation.ERROR + ? semanticColors.punch[600] + : neutralColors.gray[900]}; &::after { content: '*'; display: ${props => (props.$required ? 'inline-block' : 'none')}; @@ -334,9 +482,22 @@ const InputLabel = styled(GLink)` } `; -const InputDesc = styled(GLink)` +const InputDesc = styled(GLink)<{ validationStatus: EInputValidation }>` padding-top: 4px; - color: ${neutralColors.gray[900]}; + color: ${props => { + switch (props.validationStatus) { + case EInputValidation.NORMAL: + return neutralColors.gray[900]; + case EInputValidation.WARNING: + return semanticColors.golden[600]; + case EInputValidation.ERROR: + return semanticColors.punch[500]; + case EInputValidation.SUCCESS: + return semanticColors.jade[500]; + default: + return neutralColors.gray[300]; + } + }}; display: block; `; @@ -440,4 +601,41 @@ const VerifyInputButtonWrapper = styled.button` } `; +const ValidationCode = styled(Flex)` + flex-direction: column; + margin-top: 30px; + margin-bottom: 25px; +`; + +const EmailSentNotification = styled(Flex)` + width: 100%; + margin-bottom: 20px; + border: 1px solid ${brandColors.giv[200]}; + padding: 16px; + border-radius: 8px; + font-size: 12px; + font-weight: 400; + line-height: 15.88px; + text-align: left; + color: ${brandColors.giv[500]}; + svg { + color: ${brandColors.giv[500]}; + } +`; + +const InputCodeDesc = styled(GLink)` + padding-top: 4px; + font-size: 0.625rem; + line-height: 132%; + & button { + background: none; + border: none; + padding: 0; + color: ${brandColors.pinky[400]}; + font-size: 0.625rem; + line-height: 132%; + cursor: pointer; + } +`; + export default InputUserEmailVerify; From 77373cd7a24e692f007b1961fc072e0761cef736 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 19 Nov 2024 14:31:22 +0100 Subject: [PATCH 71/87] completed all verification process and restriction --- lang/ca.json | 2 + lang/en.json | 2 + lang/es.json | 2 + pages/account.tsx | 2 + pages/project/[projectIdSlug]/index.tsx | 2 - src/apollo/gql/gqlUser.ts | 7 +- src/components/InputUserEmailVerify.tsx | 34 ++- src/components/modals/EditUserModal.tsx | 24 +-- src/components/views/project/ProjectIndex.tsx | 3 + .../projectActionCard/AdminActions.tsx | 196 +++++++++++------- .../views/userProfile/VerifyEmailBanner.tsx | 59 ++++++ .../projectsTab/ProfileProjectsTab.tsx | 12 +- src/context/profile.context.tsx | 14 +- src/context/project.context.tsx | 5 + 14 files changed, 252 insertions(+), 112 deletions(-) create mode 100644 src/components/views/userProfile/VerifyEmailBanner.tsx diff --git a/lang/ca.json b/lang/ca.json index fda44e8977..71a9150fcf 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -392,6 +392,8 @@ "label.email_please_verify": "Si us plau, verifica el teu correu electrònic. Introdueix el codi de confirmació enviat al teu correu.", "label.email_get_resend": "No has rebut el correu? Revisa la teva carpeta de correu brossa o ", "label.email_confirm_code": "Confirma el codi", + "label.email_verify_banner": "Obté l'accés complet als teus comptes i projectes. Verifica la teva adreça de correu electrònic! ", + "label.email_actions_text": "Verifica el teu correu electrònic per gestionar els teus projectes!", "label.enable_change": "Habilita el canvi", "label.enable_recurring_donations": "Habilitar Donacions Recurrents", "label.ends_on": "acaba el", diff --git a/lang/en.json b/lang/en.json index da6ef53f22..39961f8327 100644 --- a/lang/en.json +++ b/lang/en.json @@ -392,6 +392,8 @@ "label.email_please_verify": "Please Verify your email. Enter the confirmation code sent to your email.", "label.email_get_resend": "Didn't get the email? Check your spam folder or ", "label.email_confirm_code": "Confirm Code", + "label.email_verify_banner": "Get the full access to your accounts and projects. Verify your email address! ", + "label.email_actions_text": "Verify your email to manage your projects!", "label.enable_change": "Enable Change", "label.enable_recurring_donations": "Enable Recurring Donations", "label.ends_on": "ends on", diff --git a/lang/es.json b/lang/es.json index dd18a568a4..1a12d5fbdc 100644 --- a/lang/es.json +++ b/lang/es.json @@ -392,6 +392,8 @@ "label.email_please_verify": "Por favor, verifica tu correo electrónico. Ingresa el código de confirmación enviado a tu correo.", "label.email_get_resend": "¿No recibiste el correo? Revisa tu carpeta de spam o ", "label.email_confirm_code": "Confirmar código", + "label.email_verify_banner": "Obtén acceso completo a tus cuentas y proyectos. ¡Verifica tu dirección de correo electrónico! ", + "label.email_actions_text": "¡Verifica tu correo electrónico para gestionar tus proyectos!", "label.enable_change": "Ayuda al Cambio", "label.enable_recurring_donations": "Habilitar Donaciones Recurrentes", "label.ends_on": "termina el", diff --git a/pages/account.tsx b/pages/account.tsx index 1d13e12c95..4714c88d4f 100644 --- a/pages/account.tsx +++ b/pages/account.tsx @@ -6,6 +6,7 @@ import WalletNotConnected from '@/components/WalletNotConnected'; import UserNotSignedIn from '@/components/UserNotSignedIn'; import UserProfileView from '@/components/views/userProfile/UserProfile.view'; import { ProfileProvider } from '@/context/profile.context'; +import VerifyEmailBanner from '@/components/views/userProfile/VerifyEmailBanner'; const AccountRoute = () => { const { isSignedIn, isEnabled, userData, isLoading } = useAppSelector( @@ -27,6 +28,7 @@ const AccountRoute = () => { {userData?.name} | Giveth + {!userData?.isEmailVerified && } diff --git a/pages/project/[projectIdSlug]/index.tsx b/pages/project/[projectIdSlug]/index.tsx index d4f4e58c07..3b701dd79f 100644 --- a/pages/project/[projectIdSlug]/index.tsx +++ b/pages/project/[projectIdSlug]/index.tsx @@ -10,8 +10,6 @@ import { ProjectProvider } from '@/context/project.context'; const ProjectRoute: FC = ({ project }) => { useReferral(); - console.log({ project }); - return ( diff --git a/src/apollo/gql/gqlUser.ts b/src/apollo/gql/gqlUser.ts index 17b0d819f8..975eb9da1b 100644 --- a/src/apollo/gql/gqlUser.ts +++ b/src/apollo/gql/gqlUser.ts @@ -264,7 +264,10 @@ export const SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW = gql` `; export const SEND_USER_CONFIRMATION_CODE_FLOW = gql` - mutation SendUserConfirmationCodeFlow($verifyCode: String!) { - sendUserConfirmationCodeFlow(verifyCode: $verifyCode) + mutation SendUserConfirmationCodeFlow( + $verifyCode: String! + $email: String! + ) { + sendUserConfirmationCodeFlow(verifyCode: $verifyCode, email: $email) } `; diff --git a/src/components/InputUserEmailVerify.tsx b/src/components/InputUserEmailVerify.tsx index f46f7b301a..4cc9845601 100644 --- a/src/components/InputUserEmailVerify.tsx +++ b/src/components/InputUserEmailVerify.tsx @@ -37,6 +37,7 @@ import { SEND_USER_CONFIRMATION_CODE_FLOW, } from '@/apollo/gql/gqlUser'; import { client } from '@/apollo/apolloClient'; +import { showToastError } from '@/lib/helpers'; import type { DeepRequired, FieldError, @@ -109,7 +110,7 @@ interface IExtendedInputLabelProps extends IInputLabelProps { const InputUserEmailVerify = forwardRef( (props, inputRef) => { const { formatMessage } = useIntl(); - const { user } = useProfileContext(); + const { user, updateUser } = useProfileContext(); const [email, setEmail] = useState(user.email); const [verified, setVerified] = useState(user.isEmailVerified); @@ -204,6 +205,13 @@ const InputUserEmailVerify = forwardRef( } else { setDisableVerifyButton(true); } + + // Check if user is changing email address + if (e.target.value !== user.email) { + setVerified(false); + } else { + setVerified(true); + } }; // Verification email handler, it will be called on button click @@ -234,6 +242,12 @@ const InputUserEmailVerify = forwardRef( 'VERIFICATION_SENT' ) { setIsVerificationProcess(true); + setValidationStatus(EInputValidation.NORMAL); + setInputDescription( + formatMessage({ + id: 'label.email_used', + }), + ); } } catch (error) { console.log(error); @@ -257,17 +271,29 @@ const InputUserEmailVerify = forwardRef( mutation: SEND_USER_CONFIRMATION_CODE_FLOW, variables: { verifyCode: codeInputRef.current?.value, + email: email, }, }); if ( data.sendUserConfirmationCodeFlow === 'VERIFICATION_SUCCESS' ) { + // Reset states setIsVerificationProcess(false); setDisableCodeVerifyButton(true); setVerified(true); + setValidationCodeStatus(EInputValidation.SUCCESS); + + // Update user data + updateUser({ + email: email, + isEmailVerified: true, + }); } } catch (error) { + if (error instanceof Error) { + showToastError(error.message); + } console.log(error); } }; @@ -359,7 +385,7 @@ const InputUserEmailVerify = forwardRef( ) : ( {inputDescription} @@ -482,10 +508,10 @@ const InputLabel = styled(GLink)` } `; -const InputDesc = styled(GLink)<{ validationStatus: EInputValidation }>` +const InputDesc = styled(GLink)<{ $validationstatus: EInputValidation }>` padding-top: 4px; color: ${props => { - switch (props.validationStatus) { + switch (props.$validationstatus) { case EInputValidation.NORMAL: return neutralColors.gray[900]; case EInputValidation.WARNING: diff --git a/src/components/modals/EditUserModal.tsx b/src/components/modals/EditUserModal.tsx index ef25834db4..64f7dead4f 100644 --- a/src/components/modals/EditUserModal.tsx +++ b/src/components/modals/EditUserModal.tsx @@ -8,10 +8,7 @@ import { captureException } from '@sentry/nextjs'; import { useForm } from 'react-hook-form'; import { Modal } from './Modal'; import { client } from '@/apollo/apolloClient'; -import { - SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, - UPDATE_USER, -} from '@/apollo/gql/gqlUser'; +import { UPDATE_USER } from '@/apollo/gql/gqlUser'; import { IUser } from '@/apollo/types/types'; import { gToast, ToastType } from '../toasts'; import { @@ -92,20 +89,6 @@ const EditUserModal = ({ } }; - const testMe = async () => { - try { - const { data } = await client.mutate({ - mutation: SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, - variables: { - email: 'kkatusic@gmail.com', - }, - }); - console.log(data); - } catch (error) { - console.log(error); - } - }; - const onSubmit = async (formData: Inputs) => { setIsLoading(true); try { @@ -181,11 +164,6 @@ const EditUserModal = ({
-
- -
{inputFields.map(field => { // We load different input components for email becasue validation is different diff --git a/src/components/views/project/ProjectIndex.tsx b/src/components/views/project/ProjectIndex.tsx index 0e9cc06347..dd2c8e00ff 100644 --- a/src/components/views/project/ProjectIndex.tsx +++ b/src/components/views/project/ProjectIndex.tsx @@ -49,6 +49,7 @@ import Routes from '@/lib/constants/Routes'; import { ChainType } from '@/types/config'; import { useAppSelector } from '@/features/hooks'; import { EndaomentProjectsInfo } from '@/components/views/project/EndaomentProjectsInfo'; +import VerifyEmailBanner from '../userProfile/VerifyEmailBanner'; const ProjectDonations = dynamic( () => import('./projectDonations/ProjectDonations.index'), @@ -84,6 +85,7 @@ const ProjectIndex: FC = () => { hasActiveQFRound, isCancelled, isAdmin, + isAdminEmailVerified, isLoading, } = useProjectContext(); @@ -134,6 +136,7 @@ const ProjectIndex: FC = () => { return ( + {!isAdminEmailVerified && } {hasActiveQFRound && !isOnSolana && } {title && `${title} |`} Giveth diff --git a/src/components/views/project/projectActionCard/AdminActions.tsx b/src/components/views/project/projectActionCard/AdminActions.tsx index d4c59c6b13..35abcfc87b 100644 --- a/src/components/views/project/projectActionCard/AdminActions.tsx +++ b/src/components/views/project/projectActionCard/AdminActions.tsx @@ -9,6 +9,7 @@ import { Flex, FlexCenter, IconArrowDownCircle16, + semanticColors, } from '@giveth/ui-design-system'; import React, { FC, useState } from 'react'; import { useIntl } from 'react-intl'; @@ -55,6 +56,8 @@ export const AdminActions = () => { const { switchChain } = useSwitchChain(); const chainId = chain?.id; + const { isAdminEmailVerified } = useProjectContext(); + const isVerificationDisabled = isGivbackEligible || verificationFormStatus === EVerificationStatus.SUBMITTED || @@ -132,86 +135,109 @@ export const AdminActions = () => { }; return !isMobile ? ( - - - {showVerificationModal && ( - setShowVerificationModal(false)} - /> - )} - {deactivateModal && ( - + <> + {!isAdminEmailVerified && ( + + {formatMessage({ + id: 'label.email_actions_text', + })} + )} - {showShareModal && ( - + - )} - {showClaimModal && ( - - )} - + {showVerificationModal && ( + setShowVerificationModal(false)} + /> + )} + {deactivateModal && ( + + )} + {showShareModal && ( + + )} + {showClaimModal && ( + + )} + + ) : ( - setShowMobileActionsModal(true)} - > -
Project Actions
- - {showMobileActionsModal && ( - - {options.map(option => ( - - - {option.icon} -
{option.label}
-
-
- ))} - {showVerificationModal && ( - setShowVerificationModal(false)} - /> - )} - {deactivateModal && ( - - )} - {showShareModal && ( - - )} -
- )} - {showClaimModal && ( - + <> + {!isAdminEmailVerified && ( + + {formatMessage({ + id: 'label.email_actions_text', + })} + )} -
+ setShowMobileActionsModal(true)} + $verified={isAdminEmailVerified} + > +
Project Actions
+ + {showMobileActionsModal && ( + + {options.map(option => ( + + + + {option.icon} + +
{option.label}
+
+
+ ))} + {showVerificationModal && ( + setShowVerificationModal(false)} + /> + )} + {deactivateModal && ( + + )} + {showShareModal && ( + + )} +
+ )} + {showClaimModal && ( + + )} +
+ ); }; @@ -232,18 +258,26 @@ const MobileActionsModal: FC = ({ ); }; -const Wrapper = styled.div` +interface WrapperProps { + $verified: boolean; +} + +const Wrapper = styled.div` order: 1; margin-bottom: 16px; + opacity: ${({ $verified }) => ($verified ? 1 : 0.5)}; + pointer-events: ${({ $verified }) => ($verified ? 'auto' : 'none')}; ${mediaQueries.tablet} { margin-bottom: 5px; order: unset; } `; -const MobileWrapper = styled(FlexCenter)` +const MobileWrapper = styled(FlexCenter)` padding: 10px 16px; background-color: ${neutralColors.gray[300]}; + opacity: ${({ $verified }) => ($verified ? 1 : 0.5)}; + pointer-events: ${({ $verified }) => ($verified ? 'auto' : 'none')}; border-radius: 8px; `; @@ -251,3 +285,9 @@ const MobileActionModalItem = styled(Flex)` padding: 16px 24px; border-bottom: ${neutralColors.gray[400]} 1px solid; `; + +const VerifyNotification = styled.div` + font-size: 14px; + text-align: center; + color: ${semanticColors.golden[600]}; +`; diff --git a/src/components/views/userProfile/VerifyEmailBanner.tsx b/src/components/views/userProfile/VerifyEmailBanner.tsx new file mode 100644 index 0000000000..ea992647a8 --- /dev/null +++ b/src/components/views/userProfile/VerifyEmailBanner.tsx @@ -0,0 +1,59 @@ +import styled from 'styled-components'; +import { useRouter } from 'next/router'; +import { brandColors, FlexCenter } from '@giveth/ui-design-system'; +import { FormattedMessage } from 'react-intl'; +import Routes from '@/lib/constants/Routes'; + +const VerifyEmailBanner = () => { + const router = useRouter(); + return ( + + + ( + + ), + }} + /> + + + ); +}; + +const PStyled = styled.div` + display: flex; + gap: 4px; + @media (max-width: 768px) { + flex-direction: column; + } + + & button { + background: none; + border: none; + padding: 0; + font-size: 16px; + color: ${brandColors.giv[500]}; + cursor: pointer; + } +`; + +const Wrapper = styled(FlexCenter)` + flex-wrap: wrap; + padding: 16px; + text-align: center; + gap: 4px; + background: ${brandColors.mustard[200]}; + z-index: 99; + position: sticky; +`; + +export default VerifyEmailBanner; diff --git a/src/components/views/userProfile/projectsTab/ProfileProjectsTab.tsx b/src/components/views/userProfile/projectsTab/ProfileProjectsTab.tsx index 388a42d693..f41d40c928 100644 --- a/src/components/views/userProfile/projectsTab/ProfileProjectsTab.tsx +++ b/src/components/views/userProfile/projectsTab/ProfileProjectsTab.tsx @@ -49,7 +49,7 @@ const ProfileProjectsTab: FC = () => { )} )} - + {!isLoading && data?.totalCount === 0 ? ( = () => { ); }; -export const ProjectsContainer = styled.div` +interface ProjectsContainerProps { + $verified: boolean; +} + +export const ProjectsContainer = styled.div` margin-bottom: 40px; + background-color: ${({ $verified }) => + $verified ? 'transparent' : '#f0f0f0'}; + opacity: ${({ $verified }) => ($verified ? 1 : 0.5)}; + pointer-events: ${({ $verified }) => ($verified ? 'auto' : 'none')}; `; export const Loading = styled(Flex)` diff --git a/src/context/profile.context.tsx b/src/context/profile.context.tsx index 51d4283cfe..2b5ebaafb4 100644 --- a/src/context/profile.context.tsx +++ b/src/context/profile.context.tsx @@ -12,12 +12,14 @@ interface ProfileContext { user: IUser; myAccount: boolean; givpowerBalance: string; + updateUser: (updatedUser: Partial) => void; } const ProfileContext = createContext({ user: {} as IUser, myAccount: false, givpowerBalance: '0', + updateUser: () => {}, }); ProfileContext.displayName = 'ProfileContext'; @@ -27,9 +29,18 @@ export const ProfileProvider = (props: { myAccount: boolean; children: ReactNode; }) => { - const { user, myAccount, children } = props; + const { user: initialUser, myAccount, children } = props; + const [user, setUser] = useState(initialUser); const [balance, setBalance] = useState('0'); + // Update user data + const updateUser = (updatedUser: Partial) => { + setUser(prevUser => ({ + ...prevUser, + ...updatedUser, + })); + }; + useEffect(() => { const fetchTotal = async () => { try { @@ -52,6 +63,7 @@ export const ProfileProvider = (props: { user, myAccount, givpowerBalance: balance, + updateUser, }} > {children} diff --git a/src/context/project.context.tsx b/src/context/project.context.tsx index 925bb815aa..0b2e95ecee 100644 --- a/src/context/project.context.tsx +++ b/src/context/project.context.tsx @@ -56,6 +56,7 @@ interface IProjectContext { isActive: boolean; isDraft: boolean; isAdmin: boolean; + isAdminEmailVerified: boolean; hasActiveQFRound: boolean; totalDonationsCount: number; isCancelled: boolean; @@ -73,6 +74,7 @@ const ProjectContext = createContext({ isActive: true, isDraft: false, isAdmin: false, + isAdminEmailVerified: false, hasActiveQFRound: false, totalDonationsCount: 0, isCancelled: false, @@ -110,6 +112,8 @@ export const ProjectProvider = ({ user?.walletAddress, ); + const isAdminEmailVerified = !!(isAdmin && user?.isEmailVerified); + const hasActiveQFRound = hasActiveRound(projectData?.qfRounds); const fetchProjectBySlug = useCallback(async () => { @@ -313,6 +317,7 @@ export const ProjectProvider = ({ isActive, isDraft, isAdmin, + isAdminEmailVerified, hasActiveQFRound, totalDonationsCount, isCancelled, From 681535cf8544f239f850e553f6f9851894c12ef3 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Wed, 20 Nov 2024 13:21:41 +0100 Subject: [PATCH 72/87] fixing small bugs bypassing first update of the user --- src/apollo/gql/gqlUser.ts | 2 ++ src/components/InputUserEmailVerify.tsx | 5 ++++- src/components/views/onboarding/InfoStep.tsx | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/apollo/gql/gqlUser.ts b/src/apollo/gql/gqlUser.ts index 975eb9da1b..d95f31deac 100644 --- a/src/apollo/gql/gqlUser.ts +++ b/src/apollo/gql/gqlUser.ts @@ -179,6 +179,7 @@ export const UPDATE_USER = gql` $firstName: String $avatar: String $newUser: Boolean + $isFirstUpdate: Boolean ) { updateUser( url: $url @@ -188,6 +189,7 @@ export const UPDATE_USER = gql` lastName: $lastName avatar: $avatar newUser: $newUser + isFirstUpdate: $isFirstUpdate ) } `; diff --git a/src/components/InputUserEmailVerify.tsx b/src/components/InputUserEmailVerify.tsx index 4cc9845601..4d05855d49 100644 --- a/src/components/InputUserEmailVerify.tsx +++ b/src/components/InputUserEmailVerify.tsx @@ -209,7 +209,7 @@ const InputUserEmailVerify = forwardRef( // Check if user is changing email address if (e.target.value !== user.email) { setVerified(false); - } else { + } else if (e.target.value !== user.email && user.isEmailVerified) { setVerified(true); } }; @@ -250,6 +250,9 @@ const InputUserEmailVerify = forwardRef( ); } } catch (error) { + if (error instanceof Error) { + showToastError(error.message); + } console.log(error); } }; diff --git a/src/components/views/onboarding/InfoStep.tsx b/src/components/views/onboarding/InfoStep.tsx index ceb5de5406..fc2bd04f8b 100644 --- a/src/components/views/onboarding/InfoStep.tsx +++ b/src/components/views/onboarding/InfoStep.tsx @@ -80,6 +80,7 @@ const InfoStep: FC = ({ setStep }) => { variables: { ...formData, newUser: true, + isFirstUpdate: true, }, }); if (response.updateUser) { From 035394a3f721a7caf81d5a4651e2c870a5b050ce Mon Sep 17 00:00:00 2001 From: kkatusic Date: Thu, 21 Nov 2024 12:21:19 +0100 Subject: [PATCH 73/87] adding verification process to the new user update form and removing bypassing --- src/apollo/gql/gqlUser.ts | 2 - src/components/views/onboarding/InfoStep.tsx | 350 ++++++++++++++++++- 2 files changed, 345 insertions(+), 7 deletions(-) diff --git a/src/apollo/gql/gqlUser.ts b/src/apollo/gql/gqlUser.ts index d95f31deac..975eb9da1b 100644 --- a/src/apollo/gql/gqlUser.ts +++ b/src/apollo/gql/gqlUser.ts @@ -179,7 +179,6 @@ export const UPDATE_USER = gql` $firstName: String $avatar: String $newUser: Boolean - $isFirstUpdate: Boolean ) { updateUser( url: $url @@ -189,7 +188,6 @@ export const UPDATE_USER = gql` lastName: $lastName avatar: $avatar newUser: $newUser - isFirstUpdate: $isFirstUpdate ) } `; diff --git a/src/components/views/onboarding/InfoStep.tsx b/src/components/views/onboarding/InfoStep.tsx index fc2bd04f8b..80b89534d8 100644 --- a/src/components/views/onboarding/InfoStep.tsx +++ b/src/components/views/onboarding/InfoStep.tsx @@ -1,10 +1,25 @@ -import { FC, useEffect, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { useMutation } from '@apollo/client'; -import { H6, neutralColors, Col, Row } from '@giveth/ui-design-system'; +import { + H6, + neutralColors, + Col, + Row, + brandColors, + semanticColors, + IconAlertCircle, + Flex, + GLink, +} from '@giveth/ui-design-system'; import styled from 'styled-components'; import { captureException } from '@sentry/nextjs'; import { useForm } from 'react-hook-form'; -import { UPDATE_USER } from '@/apollo/gql/gqlUser'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { + SEND_USER_CONFIRMATION_CODE_FLOW, + SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, + UPDATE_USER, +} from '@/apollo/gql/gqlUser'; import { SkipOnboardingModal } from '@/components/modals/SkipOnboardingModal'; import { gToast, ToastType } from '@/components/toasts'; import { @@ -21,6 +36,10 @@ import { setShowSignWithWallet } from '@/features/modal/modal.slice'; import { fetchUserByAddress } from '@/features/user/user.thunks'; import { requiredOptions, validators } from '@/lib/constants/regex'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; +import { client } from '@/apollo/apolloClient'; +import { showToastError } from '@/lib/helpers'; +import InputStyled from '@/components/styled-components/Input'; +import { EInputValidation } from '@/types/inputValidation'; export interface IUserInfo { email: string; @@ -38,7 +57,17 @@ enum EUserInfo { URL = 'url', } +interface IInputLabelProps { + $required?: boolean; + $disabled?: boolean; +} + +interface IExtendedInputLabelProps extends IInputLabelProps { + $validation?: EInputValidation; +} + const InfoStep: FC = ({ setStep }) => { + const { formatMessage } = useIntl(); const [isLoading, setIsLoading] = useState(false); const [updateUser] = useMutation(UPDATE_USER); const [showModal, setShowModal] = useState(false); @@ -47,6 +76,141 @@ const InfoStep: FC = ({ setStep }) => { const dispatch = useAppDispatch(); const { isSignedIn, userData } = useAppSelector(state => state.user); + // States for email verification + const { userData: currentDBUser } = useAppSelector(state => state.user); + const [verified, setVerified] = useState( + currentDBUser?.isEmailVerified || false, + ); + const [disableVerifyButton, setDisableVerifyButton] = useState( + !currentDBUser?.isEmailVerified && !currentDBUser?.email, + ); + const [disableCodeVerifyButton, setDisableCodeVerifyButton] = + useState(true); + const [email, setEmail] = useState(currentDBUser?.email || ''); + const [isVerificationProcess, setIsVerificationProcess] = useState(false); + const [validationStatus, setValidationStatus] = useState( + EInputValidation.NORMAL, + ); + const [inputDescription, setInputDescription] = useState( + verified + ? formatMessage({ + id: 'label.email_already_verified', + }) + : formatMessage({ + id: 'label.email_used', + }), + ); + const codeInputRef = useRef(null); + const [validationCodeStatus, setValidationCodeStatus] = useState( + EInputValidation.SUCCESS, + ); + + // Setup label button on condition + let labelButton = verified + ? formatMessage({ + id: 'label.email_verified', + }) + : formatMessage({ + id: 'label.email_verify', + }); + + // Enable verification process "button" if email input value was empty and not verified yet + // and setup email if input value was changed and has more than 3 characters + const handleInputChange = (e: React.ChangeEvent) => { + if (e.target.value.length > 3) { + setEmail(e.target.value); + setDisableVerifyButton(false); + } else { + setDisableVerifyButton(true); + } + + // Check if user is changing email address + if (e.target.value !== currentDBUser?.email) { + setVerified(false); + } else if ( + e.target.value !== currentDBUser.email && + currentDBUser.isEmailVerified + ) { + setVerified(true); + } + }; + + // Verification email handler, it will be called on button click + // It will send request to backend to check if email exists and if it's not verified yet + // or email is already exist on another user account + // If email isn't verified it will send email with verification code to user + const verificationEmailHandler = async () => { + try { + const { data } = await client.mutate({ + mutation: SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW, + variables: { + email: email, + }, + }); + + if (data.sendUserEmailConfirmationCodeFlow === 'EMAIL_EXIST') { + setValidationStatus(EInputValidation.WARNING); + setDisableVerifyButton(true); + showToastError( + formatMessage({ + id: 'label.email_used_another', + }), + ); + } + + if ( + data.sendUserEmailConfirmationCodeFlow === 'VERIFICATION_SENT' + ) { + setIsVerificationProcess(true); + setValidationStatus(EInputValidation.NORMAL); + setInputDescription( + formatMessage({ + id: 'label.email_used', + }), + ); + } + } catch (error) { + if (error instanceof Error) { + showToastError(error.message); + } + console.log(error); + } + }; + + // Verification code handler, it will be called on button click + const handleInputCodeChange = (e: React.ChangeEvent) => { + const value = e.target.value; + value.length >= 5 + ? setDisableCodeVerifyButton(false) + : setDisableCodeVerifyButton(true); + }; + + // Sent verification code to backend to check if it's correct + const handleButtonCodeChange = async () => { + try { + const { data } = await client.mutate({ + mutation: SEND_USER_CONFIRMATION_CODE_FLOW, + variables: { + verifyCode: codeInputRef.current?.value, + email: email, + }, + }); + + if (data.sendUserConfirmationCodeFlow === 'VERIFICATION_SUCCESS') { + // Reset states + setIsVerificationProcess(false); + setDisableCodeVerifyButton(true); + setVerified(true); + setValidationCodeStatus(EInputValidation.SUCCESS); + } + } catch (error) { + if (error instanceof Error) { + showToastError(error.message); + } + console.log(error); + } + }; + const { register, handleSubmit, @@ -80,7 +244,6 @@ const InfoStep: FC = ({ setStep }) => { variables: { ...formData, newUser: true, - isFirstUpdate: true, }, }); if (response.updateUser) { @@ -144,8 +307,87 @@ const InfoStep: FC = ({ setStep }) => { type='email' registerOptions={requiredOptions.email} error={errors.email} + caption={inputDescription} + onChange={handleInputChange} /> + {!isVerificationProcess && ( + + + {labelButton} + + + )} + {isVerificationProcess && ( + <> + + + + {formatMessage( + { + id: 'label.email_sent_to', + }, + { email }, + )} + + + + + + + ( + + ), + }} + /> + + + + + {formatMessage({ + id: 'label.email_confirm_code', + })} + + + + )} Where are you?
@@ -179,7 +421,7 @@ const InfoStep: FC = ({ setStep }) => { @@ -209,4 +451,102 @@ const SectionHeader = styled(H6)` border-bottom: 1px solid ${neutralColors.gray[400]}; `; +type VerifyInputButtonWrapperProps = { + $verified?: boolean; +}; + +const VerifyInputButtonWrapper = styled.button` + outline: none; + cursor: pointer; + margin-top: 24px; + background-color: ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[500]}; + border: 1px solid + ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[50]}; + border-radius: 8px; + padding: 20px 20px; + color: #ffffff; + font-size: 16px; + font-weight: 500; + line-height: 13.23px; + text-align: left; + &:hover { + opacity: 0.85; + } + &:disabled { + opacity: 0.5; + } +`; + +const VerifyCodeButtonWrapper = styled.button` + outline: none; + cursor: pointer; + margin-top: 48px; + background-color: ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[500]}; + border: 1px solid + ${({ $verified }) => + $verified ? semanticColors.jade[500] : brandColors.giv[50]}; + border-radius: 8px; + padding: 20px 20px; + color: #ffffff; + font-size: 16px; + font-weight: 500; + line-height: 13.23px; + text-align: left; + &:hover { + opacity: 0.85; + } + &:disabled { + opacity: 0.5; + } +`; + +const EmailSentNotification = styled(Flex)` + width: 100%; + margin-top: 20px; + margin-bottom: 20px; + border: 1px solid ${brandColors.giv[200]}; + padding: 16px; + border-radius: 8px; + font-size: 1em; + font-weight: 400; + line-height: 15.88px; + text-align: left; + color: ${brandColors.giv[500]}; + svg { + color: ${brandColors.giv[500]}; + } +`; + +const InputLabel = styled(GLink)` + padding-bottom: 4px; + color: ${props => + props.$validation === EInputValidation.ERROR + ? semanticColors.punch[600] + : neutralColors.gray[900]}; + &::after { + content: '*'; + display: ${props => (props.$required ? 'inline-block' : 'none')}; + padding: 0 4px; + color: ${semanticColors.punch[500]}; + } +`; + +const InputCodeDesc = styled(GLink)` + padding-top: 4px; + font-size: 0.75rem; + line-height: 132%; + & button { + background: none; + border: none; + padding: 0; + color: ${brandColors.pinky[400]}; + font-size: 0.75rem; + line-height: 132%; + cursor: pointer; + } +`; + export default InfoStep; From bfc5521a3382a8e99241f65461820afda549cade Mon Sep 17 00:00:00 2001 From: kkatusic Date: Thu, 21 Nov 2024 14:16:06 +0100 Subject: [PATCH 74/87] fixing toast messages on user edit form --- lang/ca.json | 1 + lang/en.json | 1 + lang/es.json | 1 + src/components/views/onboarding/InfoStep.tsx | 23 ++++++++++++++++---- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index 71a9150fcf..ff79705c6c 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -394,6 +394,7 @@ "label.email_confirm_code": "Confirma el codi", "label.email_verify_banner": "Obté l'accés complet als teus comptes i projectes. Verifica la teva adreça de correu electrònic! ", "label.email_actions_text": "Verifica el teu correu electrònic per gestionar els teus projectes!", + "label.email_error_verify": "Error de verificació del correu electrònic", "label.enable_change": "Habilita el canvi", "label.enable_recurring_donations": "Habilitar Donacions Recurrents", "label.ends_on": "acaba el", diff --git a/lang/en.json b/lang/en.json index 39961f8327..13e9659b3a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -394,6 +394,7 @@ "label.email_confirm_code": "Confirm Code", "label.email_verify_banner": "Get the full access to your accounts and projects. Verify your email address! ", "label.email_actions_text": "Verify your email to manage your projects!", + "label.email_error_verify": "Error verification email", "label.enable_change": "Enable Change", "label.enable_recurring_donations": "Enable Recurring Donations", "label.ends_on": "ends on", diff --git a/lang/es.json b/lang/es.json index 1a12d5fbdc..fc3a0c5389 100644 --- a/lang/es.json +++ b/lang/es.json @@ -394,6 +394,7 @@ "label.email_confirm_code": "Confirmar código", "label.email_verify_banner": "Obtén acceso completo a tus cuentas y proyectos. ¡Verifica tu dirección de correo electrónico! ", "label.email_actions_text": "¡Verifica tu correo electrónico para gestionar tus proyectos!", + "label.email_error_verify": "Error de verificación del correo electrónico", "label.enable_change": "Ayuda al Cambio", "label.enable_recurring_donations": "Habilitar Donaciones Recurrentes", "label.ends_on": "termina el", diff --git a/src/components/views/onboarding/InfoStep.tsx b/src/components/views/onboarding/InfoStep.tsx index 80b89534d8..dc0cc4cb9a 100644 --- a/src/components/views/onboarding/InfoStep.tsx +++ b/src/components/views/onboarding/InfoStep.tsx @@ -37,7 +37,6 @@ import { fetchUserByAddress } from '@/features/user/user.thunks'; import { requiredOptions, validators } from '@/lib/constants/regex'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { client } from '@/apollo/apolloClient'; -import { showToastError } from '@/lib/helpers'; import InputStyled from '@/components/styled-components/Input'; import { EInputValidation } from '@/types/inputValidation'; @@ -151,10 +150,16 @@ const InfoStep: FC = ({ setStep }) => { if (data.sendUserEmailConfirmationCodeFlow === 'EMAIL_EXIST') { setValidationStatus(EInputValidation.WARNING); setDisableVerifyButton(true); - showToastError( + gToast( formatMessage({ id: 'label.email_used_another', }), + { + type: ToastType.DANGER, + title: formatMessage({ + id: 'label.email_error_verify', + }), + }, ); } @@ -171,7 +176,12 @@ const InfoStep: FC = ({ setStep }) => { } } catch (error) { if (error instanceof Error) { - showToastError(error.message); + gToast(error.message, { + type: ToastType.DANGER, + title: formatMessage({ + id: 'label.email_error_verify', + }), + }); } console.log(error); } @@ -205,7 +215,12 @@ const InfoStep: FC = ({ setStep }) => { } } catch (error) { if (error instanceof Error) { - showToastError(error.message); + gToast(error.message, { + type: ToastType.DANGER, + title: formatMessage({ + id: 'label.email_error_verify', + }), + }); } console.log(error); } From e8eaac8e91fa34645b23b67f174ab6c9e2cd6a11 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 25 Nov 2024 14:41:06 +0100 Subject: [PATCH 75/87] Fix/Email verification bugs --- lang/ca.json | 2 +- lang/en.json | 2 +- lang/es.json | 2 +- src/components/InputUserEmailVerify.tsx | 11 ++++++++++- src/components/modals/EditUserModal.tsx | 6 +++++- src/components/views/project/ProjectIndex.tsx | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index ff79705c6c..ee05908859 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -390,7 +390,7 @@ "label.email_used_another": "Aquest correu electrònic ja ha estat verificat en un altre perfil!", "label.email_sent_to": "Codi de verificació enviat a {email}", "label.email_please_verify": "Si us plau, verifica el teu correu electrònic. Introdueix el codi de confirmació enviat al teu correu.", - "label.email_get_resend": "No has rebut el correu? Revisa la teva carpeta de correu brossa o ", + "label.email_get_resend": "No has rebut el correu electrònic? Revisa la teva carpeta de correu brossa o ", "label.email_confirm_code": "Confirma el codi", "label.email_verify_banner": "Obté l'accés complet als teus comptes i projectes. Verifica la teva adreça de correu electrònic! ", "label.email_actions_text": "Verifica el teu correu electrònic per gestionar els teus projectes!", diff --git a/lang/en.json b/lang/en.json index 13e9659b3a..031b61d825 100644 --- a/lang/en.json +++ b/lang/en.json @@ -390,7 +390,7 @@ "label.email_used_another": "This email that has already been verified on another profile!", "label.email_sent_to": "Verification code sent to {email}", "label.email_please_verify": "Please Verify your email. Enter the confirmation code sent to your email.", - "label.email_get_resend": "Didn't get the email? Check your spam folder or ", + "label.email_get_resend": "Didn't get the email? Check your spam folder or ", "label.email_confirm_code": "Confirm Code", "label.email_verify_banner": "Get the full access to your accounts and projects. Verify your email address! ", "label.email_actions_text": "Verify your email to manage your projects!", diff --git a/lang/es.json b/lang/es.json index fc3a0c5389..0ecd7837c8 100644 --- a/lang/es.json +++ b/lang/es.json @@ -390,7 +390,7 @@ "label.email_used_another": "¡Este correo electrónico ya ha sido verificado en otro perfil!", "label.email_sent_to": "Código de verificación enviado a {email}", "label.email_please_verify": "Por favor, verifica tu correo electrónico. Ingresa el código de confirmación enviado a tu correo.", - "label.email_get_resend": "¿No recibiste el correo? Revisa tu carpeta de spam o ", + "label.email_get_resend": "¿No recibiste el correo electrónico? Revisa tu carpeta de spam o ", "label.email_confirm_code": "Confirmar código", "label.email_verify_banner": "Obtén acceso completo a tus cuentas y proyectos. ¡Verifica tu dirección de correo electrónico! ", "label.email_actions_text": "¡Verifica tu correo electrónico para gestionar tus proyectos!", diff --git a/src/components/InputUserEmailVerify.tsx b/src/components/InputUserEmailVerify.tsx index 4d05855d49..37244252ed 100644 --- a/src/components/InputUserEmailVerify.tsx +++ b/src/components/InputUserEmailVerify.tsx @@ -96,11 +96,14 @@ const InputSizeToLinkSize = (size: InputSize) => { }; type InputType = - | IInputWithRegister + | (IInputWithRegister & { + verifiedSaveButton?: (verified: boolean) => void; + }) | ({ registerName?: never; register?: never; registerOptions?: never; + verifiedSaveButton?: (verified: boolean) => void; } & IInput); interface IExtendedInputLabelProps extends IInputLabelProps { @@ -209,8 +212,13 @@ const InputUserEmailVerify = forwardRef( // Check if user is changing email address if (e.target.value !== user.email) { setVerified(false); + props.verifiedSaveButton && props.verifiedSaveButton(false); } else if (e.target.value !== user.email && user.isEmailVerified) { setVerified(true); + props.verifiedSaveButton && props.verifiedSaveButton(true); + } else if (e.target.value === user.email && user.isEmailVerified) { + setVerified(true); + props.verifiedSaveButton && props.verifiedSaveButton(true); } }; @@ -285,6 +293,7 @@ const InputUserEmailVerify = forwardRef( setIsVerificationProcess(false); setDisableCodeVerifyButton(true); setVerified(true); + props.verifiedSaveButton && props.verifiedSaveButton(true); setValidationCodeStatus(EInputValidation.SUCCESS); // Update user data diff --git a/src/components/modals/EditUserModal.tsx b/src/components/modals/EditUserModal.tsx index 64f7dead4f..235995dcff 100644 --- a/src/components/modals/EditUserModal.tsx +++ b/src/components/modals/EditUserModal.tsx @@ -58,6 +58,7 @@ const EditUserModal = ({ const { walletAddress: address } = useGeneralWallet(); const [updateUser] = useMutation(UPDATE_USER); + const [verified, setVerified] = useState(user.isEmailVerified); const { isAnimating, closeModal } = useModalAnimation(setShowModal); const onSaveAvatar = async () => { @@ -191,6 +192,9 @@ const EditUserModal = ({ register={register} error={(errors as any)[field.name]} registerOptions={field.registerOptions} + {...(field.type === 'email' && { + verifiedSaveButton: setVerified, + })} /> ); })} @@ -199,7 +203,7 @@ const EditUserModal = ({ label={formatMessage({ id: 'label.save', })} - disabled={isLoading} + disabled={isLoading || !verified} type='submit' /> = () => { return ( - {!isAdminEmailVerified && } + {!isAdminEmailVerified && isAdmin && } {hasActiveQFRound && !isOnSolana && } {title && `${title} |`} Giveth From 3920020e1c110683c8b9b208bb3e36ded9366418 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 25 Nov 2024 17:37:35 +0100 Subject: [PATCH 76/87] updating actions cards --- .../views/project/projectActionCard/AdminActions.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/project/projectActionCard/AdminActions.tsx b/src/components/views/project/projectActionCard/AdminActions.tsx index 6ad468605b..70fdf1c25f 100644 --- a/src/components/views/project/projectActionCard/AdminActions.tsx +++ b/src/components/views/project/projectActionCard/AdminActions.tsx @@ -32,6 +32,7 @@ import { EVerificationStatus } from '@/apollo/types/types'; import ClaimRecurringDonationModal from '../../userProfile/projectsTab/ClaimRecurringDonationModal'; import config from '@/configuration'; import { findAnchorContractAddress } from '@/helpers/superfluid'; +import { ProjectCardNotification } from './ProjectCardNotification'; interface IMobileActionsModalProps { setShowModal: (value: boolean) => void; @@ -174,8 +175,8 @@ export const AdminActions = () => { project={project} /> )} - - + + ) : ( <> @@ -238,6 +239,7 @@ export const AdminActions = () => { /> )} + ); }; From 3041ecf4f690624b2063e0610fa14e71951a0e10 Mon Sep 17 00:00:00 2001 From: Mitch Oz Date: Mon, 25 Nov 2024 14:51:02 -0600 Subject: [PATCH 77/87] update banner text for email verification --- lang/ca.json | 2 +- lang/en.json | 2 +- lang/es.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index 7b4cdd8f92..a27ad5f828 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -392,7 +392,7 @@ "label.email_please_verify": "Si us plau, verifica el teu correu electrònic. Introdueix el codi de confirmació enviat al teu correu.", "label.email_get_resend": "No has rebut el correu electrònic? Revisa la teva carpeta de correu brossa o ", "label.email_confirm_code": "Confirma el codi", - "label.email_verify_banner": "Obté l'accés complet als teus comptes i projectes. Verifica la teva adreça de correu electrònic! ", + "label.email_verify_banner": " i verifica la propietat de la teva adreça de correu electrònic per recuperar l'accés als teus projectes.", "label.email_actions_text": "Verifica el teu correu electrònic per gestionar els teus projectes!", "label.email_error_verify": "Error de verificació del correu electrònic", "label.enable_change": "Habilita el canvi", diff --git a/lang/en.json b/lang/en.json index d5de6b3ddd..9150f71c69 100644 --- a/lang/en.json +++ b/lang/en.json @@ -392,7 +392,7 @@ "label.email_please_verify": "Please Verify your email. Enter the confirmation code sent to your email.", "label.email_get_resend": "Didn't get the email? Check your spam folder or ", "label.email_confirm_code": "Confirm Code", - "label.email_verify_banner": "Get the full access to your accounts and projects. Verify your email address! ", + "label.email_verify_banner": " & verify ownership of your email address to regain access to your projects.", "label.email_actions_text": "Verify your email to manage your projects!", "label.email_error_verify": "Error verification email", "label.enable_change": "Enable Change", diff --git a/lang/es.json b/lang/es.json index 6c1a78c378..8aa45bc4cc 100644 --- a/lang/es.json +++ b/lang/es.json @@ -392,7 +392,7 @@ "label.email_please_verify": "Por favor, verifica tu correo electrónico. Ingresa el código de confirmación enviado a tu correo.", "label.email_get_resend": "¿No recibiste el correo electrónico? Revisa tu carpeta de spam o ", "label.email_confirm_code": "Confirmar código", - "label.email_verify_banner": "Obtén acceso completo a tus cuentas y proyectos. ¡Verifica tu dirección de correo electrónico! ", + "label.email_verify_banner": " y verifica la propiedad de tu dirección de correo electrónico para recuperar el acceso a tus proyectos.", "label.email_actions_text": "¡Verifica tu correo electrónico para gestionar tus proyectos!", "label.email_error_verify": "Error de verificación del correo electrónico", "label.enable_change": "Ayuda al Cambio", From 304457bac2d00493952184e5fd0f94b4c66c0341 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 26 Nov 2024 15:25:37 +0100 Subject: [PATCH 78/87] Fixing bugs and adding new features --- lang/ca.json | 5 + lang/en.json | 8 ++ lang/es.json | 5 + src/components/Header/Header.tsx | 5 + src/components/InputUserEmailVerify.tsx | 4 +- src/components/controller/modal.ctrl.tsx | 10 ++ src/components/modals/VerifyEmailModal.tsx | 110 ++++++++++++++++++ .../views/project/ProjectGIVbackToast.tsx | 9 +- src/components/views/project/ProjectIndex.tsx | 6 +- src/components/views/project/ProjectTabs.tsx | 26 ++++- .../views/userProfile/UserProfile.view.tsx | 8 +- .../views/userProfile/VerifyEmailBanner.tsx | 9 +- src/features/modal/modal.slice.ts | 5 + 13 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 src/components/modals/VerifyEmailModal.tsx diff --git a/lang/ca.json b/lang/ca.json index ee05908859..c29e58af10 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -395,6 +395,11 @@ "label.email_verify_banner": "Obté l'accés complet als teus comptes i projectes. Verifica la teva adreça de correu electrònic! ", "label.email_actions_text": "Verifica el teu correu electrònic per gestionar els teus projectes!", "label.email_error_verify": "Error de verificació del correu electrònic", + "label.email_modal_verify_your": "Verifica la teva adreça de correu electrònic", + "label.email_modal_need_verify": "Hauràs de verificar la teva adreça de correu electrònic abans de poder crear un nou projecte.", + "label.email_modal_verifying": "Verificar la teva adreça de correu electrònic assegura que puguem comunicar-nos amb tu sobre qualsevol canvi important a la plataforma. La teva adreça de correu electrònic no es compartirà públicament.", + "label.email_modal_to_verifying": "Per verificar la teva adreça de correu electrònic, edita el teu perfil i actualitza el teu correu electrònic.", + "label.email_modal_button": "Actualitza el perfil", "label.enable_change": "Habilita el canvi", "label.enable_recurring_donations": "Habilitar Donacions Recurrents", "label.ends_on": "acaba el", diff --git a/lang/en.json b/lang/en.json index 031b61d825..3edd465357 100644 --- a/lang/en.json +++ b/lang/en.json @@ -395,6 +395,14 @@ "label.email_verify_banner": "Get the full access to your accounts and projects. Verify your email address! ", "label.email_actions_text": "Verify your email to manage your projects!", "label.email_error_verify": "Error verification email", + "label.email_modal_verify_your": "Verify your email address", + "label.email_modal_need_verify": "You'll need to verify your email address before being able to create a new project.", + "label.email_modal_verifying": "Verifying your email address ensures we can communicate with you about any important changes on the platform. Your email address will not be shared publicly.", + "label.email_modal_to_verifying": "To verify your email address edit your profile and update your email.", + "label.email_modal_button": "Update profile", + + + "label.enable_change": "Enable Change", "label.enable_recurring_donations": "Enable Recurring Donations", "label.ends_on": "ends on", diff --git a/lang/es.json b/lang/es.json index 0ecd7837c8..ad2a7b3fb1 100644 --- a/lang/es.json +++ b/lang/es.json @@ -395,6 +395,11 @@ "label.email_verify_banner": "Obtén acceso completo a tus cuentas y proyectos. ¡Verifica tu dirección de correo electrónico! ", "label.email_actions_text": "¡Verifica tu correo electrónico para gestionar tus proyectos!", "label.email_error_verify": "Error de verificación del correo electrónico", + "label.email_modal_verify_your": "Verifica tu dirección de correo electrónico", + "label.email_modal_need_verify": "Necesitarás verificar tu dirección de correo electrónico antes de poder crear un nuevo proyecto.", + "label.email_modal_verifying": "Verificar tu dirección de correo electrónico asegura que podamos comunicarnos contigo sobre cualquier cambio importante en la plataforma. Tu dirección de correo electrónico no se compartirá públicamente.", + "label.email_modal_to_verifying": "Para verificar tu dirección de correo electrónico, edita tu perfil y actualiza tu correo electrónico.", + "label.email_modal_button": "Actualizar perfil", "label.enable_change": "Ayuda al Cambio", "label.enable_recurring_donations": "Habilitar Donaciones Recurrentes", "label.ends_on": "termina el", diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 2ef881021f..2e73698ce5 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -35,6 +35,7 @@ import { ETheme } from '@/features/general/general.slice'; import { setShowCompleteProfile, setShowSearchModal, + setShowVerifyEmailModal, } from '@/features/modal/modal.slice'; import { slugToProjectView } from '@/lib/routeCreators'; import { useModalCallback } from '@/hooks/useModalCallback'; @@ -138,6 +139,10 @@ const Header: FC = () => { openWalletConnectModal(); } else if (!isSignedIn) { signInThenCreate(); + } else if (!isUserRegistered(userData)) { + dispatch(setShowCompleteProfile(true)); + } else if (!userData?.isEmailVerified) { + dispatch(setShowVerifyEmailModal(true)); } else if (isUserRegistered(userData)) { router.push(Routes.CreateProject); } else { diff --git a/src/components/InputUserEmailVerify.tsx b/src/components/InputUserEmailVerify.tsx index 37244252ed..39ca986626 100644 --- a/src/components/InputUserEmailVerify.tsx +++ b/src/components/InputUserEmailVerify.tsx @@ -367,7 +367,7 @@ const InputUserEmailVerify = forwardRef( @@ -635,7 +635,7 @@ const VerifyInputButtonWrapper = styled.button` $verified ? semanticColors.jade[500] : brandColors.giv[500]}; } &:disabled { - opacity: 0.5; + opacity: ${({ $verified }) => ($verified ? '1' : '0.5')}; } `; diff --git a/src/components/controller/modal.ctrl.tsx b/src/components/controller/modal.ctrl.tsx index 84a25bc2a8..17934e8d77 100644 --- a/src/components/controller/modal.ctrl.tsx +++ b/src/components/controller/modal.ctrl.tsx @@ -4,6 +4,7 @@ import WelcomeModal from '@/components/modals/WelcomeModal'; import { FirstWelcomeModal } from '@/components/modals/FirstWelcomeModal'; import { SignWithWalletModal } from '@/components/modals/SignWithWalletModal'; import { CompleteProfileModal } from '@/components/modals/CompleteProfileModal'; +import { VerifyEmailModal } from '@/components/modals/VerifyEmailModal'; import { useIsSafeEnvironment } from '@/hooks/useSafeAutoConnect'; import { useAppDispatch, useAppSelector } from '@/features/hooks'; import { @@ -13,6 +14,7 @@ import { setShowSearchModal, setShowSwitchNetworkModal, setShowWelcomeModal, + setShowVerifyEmailModal, } from '@/features/modal/modal.slice'; import { isUserRegistered } from '@/lib/helpers'; import { SearchModal } from '../modals/SearchModal'; @@ -27,6 +29,7 @@ const ModalController = () => { showWelcomeModal, showSearchModal, showSwitchNetwork, + showVerifyEmailModal, } = useAppSelector(state => state.modal); const { userData, isSignedIn } = useAppSelector(state => state.user); @@ -103,6 +106,13 @@ const ModalController = () => { } /> )} + {showVerifyEmailModal && ( + + dispatch(setShowVerifyEmailModal(state)) + } + /> + )} ); }; diff --git a/src/components/modals/VerifyEmailModal.tsx b/src/components/modals/VerifyEmailModal.tsx new file mode 100644 index 0000000000..b9b30e95d8 --- /dev/null +++ b/src/components/modals/VerifyEmailModal.tsx @@ -0,0 +1,110 @@ +import { FC } from 'react'; +import router from 'next/router'; +import styled from 'styled-components'; +import { + brandColors, + Button, + H5, + IconProfile, + Lead, +} from '@giveth/ui-design-system'; +import { useIntl } from 'react-intl'; + +import { Modal } from '@/components/modals/Modal'; +import Routes from '@/lib/constants/Routes'; +import { IModal } from '@/types/common'; +import { useAppSelector } from '@/features/hooks'; +import { ETheme } from '@/features/general/general.slice'; +import { useModalAnimation } from '@/hooks/useModalAnimation'; + +export const VerifyEmailModal: FC = ({ setShowModal }) => { + const theme = useAppSelector(state => state.general.theme); + const { isAnimating, closeModal } = useModalAnimation(setShowModal); + const { formatMessage } = useIntl(); + + const handleClick = () => { + router.push( + { + pathname: Routes.MyAccount, + query: { opencheck: 'true' }, + }, + undefined, + { shallow: true }, + ); + closeModal(); + }; + + return ( + } + headerTitle={formatMessage({ id: 'label.complete_your_profile' })} + headerTitlePosition='left' + > + + + {formatMessage({ + id: 'label.email_modal_verify_your', + })} + +
+ {formatMessage({ + id: 'label.email_modal_need_verify', + })} +
+
+
+ {formatMessage({ + id: 'label.email_modal_verifying', + })} +
+
+
+ {formatMessage({ + id: 'label.email_modal_to_verifying', + })} +
+ + +
+
+ ); +}; + +const Container = styled(Lead)` + max-width: 528px; + padding: 24px; + text-align: left; + color: ${brandColors.deep[900]}; +`; + +const OkButton = styled(Button)` + width: 300px; + height: 48px; + margin: 48px auto 0; +`; + +const SkipButton = styled(Button)` + width: 300px; + margin: 0 auto 0; + background: transparent !important; + color: ${brandColors.pinky[500]} !important; + &:hover { + background: transparent !important; + color: ${brandColors.pinky[300]} !important; + } +`; + +const Title = styled(H5)` + margin: 24px 0; + font-weight: 700; +`; diff --git a/src/components/views/project/ProjectGIVbackToast.tsx b/src/components/views/project/ProjectGIVbackToast.tsx index 62e26d268b..d7aeda77f7 100644 --- a/src/components/views/project/ProjectGIVbackToast.tsx +++ b/src/components/views/project/ProjectGIVbackToast.tsx @@ -39,7 +39,8 @@ import { GIVBACKS_DONATION_QUALIFICATION_VALUE_USD } from '@/lib/constants/const const ProjectGIVbackToast = () => { const [showBoost, setShowBoost] = useState(false); const [showVerification, setShowVerification] = useState(false); - const { projectData, isAdmin, activateProject } = useProjectContext(); + const { projectData, isAdmin, activateProject, isAdminEmailVerified } = + useProjectContext(); const verStatus = projectData?.verificationFormStatus; const projectStatus = projectData?.status.name; const isGivbackEligible = projectData?.isGivbackEligible; @@ -74,6 +75,7 @@ const ProjectGIVbackToast = () => { const handleBoostClick = () => { if (isSSRMode) return; + if (!isAdminEmailVerified) return; if (!isEnabled) { openConnectModal?.(); } else if (!isSignedIn) { @@ -329,7 +331,7 @@ const ProjectGIVbackToast = () => { return ( <> - + {icon}
@@ -405,7 +407,7 @@ const Content = styled(Flex)` } `; -const Wrapper = styled(Flex)` +const Wrapper = styled(Flex)<{ $isverified: boolean }>` justify-content: space-between; align-items: center; gap: 24px; @@ -417,6 +419,7 @@ const Wrapper = styled(Flex)` ${mediaQueries.laptopL} { flex-direction: row; } + opacity: ${({ $isverified }) => ($isverified ? '1' : '0.75')}; `; const InnerLink = styled.a` diff --git a/src/components/views/project/ProjectIndex.tsx b/src/components/views/project/ProjectIndex.tsx index 55acb70dbc..965b6c69fb 100644 --- a/src/components/views/project/ProjectIndex.tsx +++ b/src/components/views/project/ProjectIndex.tsx @@ -221,7 +221,11 @@ const ProjectIndex: FC = () => { {projectData && !isDraft && ( - + )} diff --git a/src/components/views/project/ProjectTabs.tsx b/src/components/views/project/ProjectTabs.tsx index 744aaf581b..9aeaf8686f 100644 --- a/src/components/views/project/ProjectTabs.tsx +++ b/src/components/views/project/ProjectTabs.tsx @@ -17,6 +17,7 @@ import { Shadow } from '@/components/styled-components/Shadow'; interface IProjectTabs { activeTab: number; slug: string; + verified?: boolean; } const badgeCount = (count?: number) => { @@ -24,7 +25,7 @@ const badgeCount = (count?: number) => { }; const ProjectTabs = (props: IProjectTabs) => { - const { activeTab, slug } = props; + const { activeTab, slug, verified } = props; const { projectData, totalDonationsCount, boostersData } = useProjectContext(); const { totalProjectUpdates } = projectData || {}; @@ -61,8 +62,22 @@ const ProjectTabs = (props: IProjectTabs) => { i.query ? `?tab=${i.query}` : '' }`} scroll={false} + onClick={e => { + if ( + !verified && + i.query === EProjectPageTabs.UPDATES + ) { + e.preventDefault(); // Prevent the link from navigating from unverified users + } + }} > - + {formatMessage({ id: i.title })} {badgeCount(i.badge) && ( ` display: flex; padding: 9px 24px; border-radius: 48px; @@ -117,6 +136,7 @@ const Tab = styled(P)` color: ${brandColors.pinky[500]}; background: ${neutralColors.gray[200]}; } + opacity: ${({ $unverified }) => ($unverified ? '0.5' : '1')}; `; const Wrapper = styled.div` diff --git a/src/components/views/userProfile/UserProfile.view.tsx b/src/components/views/userProfile/UserProfile.view.tsx index 9235c4b532..2fbd335488 100644 --- a/src/components/views/userProfile/UserProfile.view.tsx +++ b/src/components/views/userProfile/UserProfile.view.tsx @@ -47,6 +47,8 @@ import { useGeneralWallet } from '@/providers/generalWalletProvider'; export interface IUserProfileView {} const UserProfileView: FC = () => { + const router = useRouter(); + const [showModal, setShowModal] = useState(false); // follow this state to refresh user content on screen const [showUploadProfileModal, setShowUploadProfileModal] = useState(false); const [showIncompleteWarning, setShowIncompleteWarning] = useState(false); @@ -57,12 +59,16 @@ const UserProfileView: FC = () => { const [pfpData, setPfpData] = useState(); const { walletChainType, chain } = useGeneralWallet(); const { user, myAccount } = useProfileContext(); - const router = useRouter(); const pfpToken = useGiverPFPToken(user?.walletAddress, user?.avatar); const showCompleteProfile = user && !isUserRegistered(user) && showIncompleteWarning && myAccount; + // Update the modal state if the query changes + useEffect(() => { + setShowModal(!!router.query.opencheck); + }, [router.query.opencheck]); + useEffect(() => { if (user && !isUserRegistered(user) && myAccount) { setShowIncompleteWarning(true); diff --git a/src/components/views/userProfile/VerifyEmailBanner.tsx b/src/components/views/userProfile/VerifyEmailBanner.tsx index ea992647a8..0a3dfe9741 100644 --- a/src/components/views/userProfile/VerifyEmailBanner.tsx +++ b/src/components/views/userProfile/VerifyEmailBanner.tsx @@ -16,7 +16,14 @@ const VerifyEmailBanner = () => {