@@ -257,28 +181,4 @@ const PersonalInfo = () => {
);
};
-const EmailSection = styled(Flex)`
- gap: 0 24px;
- align-items: center;
- flex-wrap: wrap;
- > :first-child {
- width: 100%;
- min-width: 250px;
- }
-`;
-
-const LightBotton = styled(Button)`
- background-color: transparent;
- color: ${brandColors.deep[400]};
- &:hover {
- background-color: transparent;
- color: ${brandColors.deep[600]};
- }
-`;
-
-const ResendEmailButton = styled(ButtonStyled)`
- min-width: 200px;
- width: 220px;
-`;
-
export default PersonalInfo;
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,
diff --git a/src/features/modal/modal.slice.ts b/src/features/modal/modal.slice.ts
index f3a635b4a2..628bfccb7c 100644
--- a/src/features/modal/modal.slice.ts
+++ b/src/features/modal/modal.slice.ts
@@ -8,6 +8,7 @@ const initialState = {
showCompleteProfile: false,
showSearchModal: false,
showSwitchNetwork: false,
+ showVerifyEmailModal: false,
};
export const ModalSlice = createSlice({
@@ -35,6 +36,9 @@ export const ModalSlice = createSlice({
setShowSwitchNetworkModal: (state, action: PayloadAction) => {
state.showSwitchNetwork = action.payload;
},
+ setShowVerifyEmailModal: (state, action: PayloadAction) => {
+ state.showVerifyEmailModal = action.payload;
+ },
},
});
@@ -46,6 +50,7 @@ export const {
setShowWelcomeModal,
setShowSearchModal,
setShowSwitchNetworkModal,
+ setShowVerifyEmailModal,
} = ModalSlice.actions;
export default ModalSlice.reducer;
diff --git a/src/features/user/user.queries.ts b/src/features/user/user.queries.ts
index 8705b6d20b..426e221b22 100644
--- a/src/features/user/user.queries.ts
+++ b/src/features/user/user.queries.ts
@@ -22,6 +22,7 @@ export const GET_USER_BY_ADDRESS = `query UserByAddress($address: String!) {
isReferrer
wasReferred
activeQFMBDScore
+ isEmailVerified
}
}`;
diff --git a/src/helpers/projects.ts b/src/helpers/projects.ts
index c33fa4b82f..a6d4a50e92 100644
--- a/src/helpers/projects.ts
+++ b/src/helpers/projects.ts
@@ -39,11 +39,13 @@ export function checkVerificationStep(
case EVerificationSteps.BEFORE_START:
return true;
case EVerificationSteps.PERSONAL_INFO:
- return (
- verificationData !== undefined &&
- verificationData.personalInfo !== null &&
- verificationData.emailConfirmed !== false
- );
+ // Removed because we are doing these confirmation on user profile
+ // return (
+ // verificationData !== undefined &&
+ // verificationData.personalInfo !== null &&
+ // verificationData.emailConfirmed !== false
+ // );
+ return true;
case EVerificationSteps.SOCIAL_PROFILES:
return (
verificationData !== undefined &&
diff --git a/src/helpers/url.tsx b/src/helpers/url.tsx
index 2185e90b39..4fc2092f9d 100644
--- a/src/helpers/url.tsx
+++ b/src/helpers/url.tsx
@@ -140,3 +140,111 @@ export function removeQueryParamAndRedirect(
export const convertIPFSToHTTPS = (url: string) => {
return url.replace('ipfs://', 'https://ipfs.io/ipfs/');
};
+
+export const getSocialMediaHandle = (
+ socialMediaUrl: string,
+ socialMediaType: string,
+) => {
+ let cleanedUrl = socialMediaUrl
+ .replace(/^https?:\/\//, '')
+ .replace('www.', '');
+
+ // Remove trailing slash if present
+ if (cleanedUrl.endsWith('/')) {
+ cleanedUrl = cleanedUrl.slice(0, -1);
+ }
+
+ // Match against different social media types using custom regex
+ const lowerCaseType = socialMediaType.toLowerCase();
+
+ switch (lowerCaseType) {
+ case 'github':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /github\.com\/([^\/]+)/,
+ );
+ case 'x': // Former Twitter
+ return extractUsernameFromPattern(cleanedUrl, /x\.com\/([^\/]+)/);
+ case 'facebook':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /facebook\.com\/([^\/]+)/,
+ );
+ case 'instagram':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /instagram\.com\/([^\/]+)/,
+ );
+ case 'linkedin':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /linkedin\.com\/(?:in|company)\/([^\/]+)/,
+ );
+ case 'youtube':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /youtube\.com\/channel\/([^\/]+)/,
+ );
+ case 'reddit':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /reddit\.com\/r\/([^\/]+)/,
+ );
+ case 'telegram':
+ return extractUsernameFromPattern(cleanedUrl, /t\.me\/([^\/]+)/);
+ case 'discord':
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /discord\.gg\/([^\/]+)/,
+ );
+ case 'farcaster':
+ // Assuming Farcaster uses a pattern like 'farcaster.xyz/username'
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /farcaster\.xyz\/([^\/]+)/,
+ );
+ case 'lens':
+ // Assuming Lens uses a pattern like 'lens.xyz/username'
+ return extractUsernameFromPattern(
+ cleanedUrl,
+ /lens\.xyz\/([^\/]+)/,
+ );
+ case 'website':
+ default:
+ return cleanedUrl; // Return cleaned URL for generic websites or unsupported social media
+ }
+};
+
+// Function to extract username from URL based on the regex pattern
+export const extractUsernameFromPattern = (
+ url: string,
+ regex: RegExp,
+): string => {
+ const match = url.match(regex);
+ if (match && match[1]) {
+ return `@${match[1]}`; // Return '@username'
+ }
+ return url; // Fallback to original URL if no match is found
+};
+
+/**
+ * Ensures that a given URL uses the https:// protocol.
+ * If the URL starts with http://, it will be replaced with https://.
+ * If the URL does not start with any protocol, https:// will be added.
+ * If the URL already starts with https://, it will remain unchanged.
+ *
+ * @param {string} url - The URL to be checked and possibly modified.
+ * @returns {string} - The modified URL with https://.
+ */
+export function ensureHttps(url: string): string {
+ if (!url.startsWith('https://')) {
+ if (url.startsWith('http://')) {
+ // Replace http:// with https://
+ url = url.replace('http://', 'https://');
+ } else {
+ // Add https:// if no protocol is present
+ url = 'https://' + url;
+ }
+ }
+ return url;
+}
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,
+ })),
+ });
+};
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/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,
+ });
+};
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/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,
+ });
+};
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,
+ };
+};
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],
diff --git a/src/lib/subgraph/subgraphDataTransform.ts b/src/lib/subgraph/subgraphDataTransform.ts
index 4a29566861..493ba5c415 100644
--- a/src/lib/subgraph/subgraphDataTransform.ts
+++ b/src/lib/subgraph/subgraphDataTransform.ts
@@ -198,6 +198,10 @@ export const transformUserGIVLocked = (info: any = {}): ITokenBalance => {
};
};
+const transformIndexedBlockInfo = (info: any = {}): number => {
+ return info?.block?.number || 0;
+};
+
export const transformSubgraphData = (data: any = {}): ISubgraphState => {
const result: ISubgraphState = {};
Object.entries(data).forEach(([key, value]) => {
@@ -229,6 +233,9 @@ export const transformSubgraphData = (data: any = {}): ISubgraphState => {
case key === 'userGIVLocked':
result[key] = transformUserGIVLocked(value);
break;
+ case key === '_meta':
+ result['indexedBlockNumber'] = transformIndexedBlockInfo(value);
+ break;
default:
}
diff --git a/src/lib/subgraph/subgraphQueryBuilder.ts b/src/lib/subgraph/subgraphQueryBuilder.ts
index 9e2ea70c4f..c5ea401093 100644
--- a/src/lib/subgraph/subgraphQueryBuilder.ts
+++ b/src/lib/subgraph/subgraphQueryBuilder.ts
@@ -13,6 +13,15 @@ export class SubgraphQueryBuilder {
`;
};
+ static getIndexedBlockQuery = (): string => {
+ return `_meta {
+ block {
+ number
+ }
+ }
+ `;
+ };
+
static getBalanceQuery = (
networkConfig: NetworkConfig,
userAddress?: string,
@@ -233,6 +242,7 @@ export class SubgraphQueryBuilder {
const givpowerConfig = networkConfig?.GIVPOWER;
return `
{
+ ${SubgraphQueryBuilder.getIndexedBlockQuery()}
${SubgraphQueryBuilder.getBalanceQuery(networkConfig, userAddress)}
${SubgraphQueryBuilder.generateTokenDistroQueries(networkConfig, userAddress)}
${SubgraphQueryBuilder.generateFarmingQueries(
diff --git a/src/providers/generalWalletProvider.tsx b/src/providers/generalWalletProvider.tsx
index 8b0bb12e0e..112fcd1da2 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,7 @@ interface IGeneralWalletContext {
handleSignOutAndShowWelcomeModal: () => Promise;
isOnSolana: boolean;
isOnEVM: boolean;
+ setPendingNetworkId: (id: number | null) => void;
}
// Create the context
export const GeneralWalletContext = createContext({
@@ -76,6 +77,7 @@ export const GeneralWalletContext = createContext({
handleSignOutAndShowWelcomeModal: async () => {},
isOnSolana: false,
isOnEVM: false,
+ setPendingNetworkId: () => {},
});
const getPhantomSolanaProvider = () => {
@@ -93,6 +95,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 +111,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 +272,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 +421,7 @@ export const GeneralWalletProvider: React.FC<{
handleSignOutAndShowWelcomeModal,
isOnSolana,
isOnEVM,
+ setPendingNetworkId,
};
// Render the provider component with the provided context value