Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions components/identities/availableIdentities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,21 @@ import IdentitiesSkeleton from "./skeletons/identitiesSkeleton";
import { Tooltip } from "@mui/material";
import { isIdentityExpired } from "../../utils/dateService";
import DomainExpiredModal from "@/components/UI/domainExpiredModal";
import { useIdentityRefresh } from "../../hooks/useIdentityRefresh";

const AvailableIdentities = ({ tokenId }: { tokenId: string }) => {
const router = useRouter();
const { address } = useAccount();
//const tokenId: string = router.query.tokenId as string;

let registerRefreshCallback: ((tokenId: string, callback: () => void) => void) | null = null;
let unregisterRefreshCallback: ((tokenId: string) => void) | null = null;
try {
const context = useIdentityRefresh();
registerRefreshCallback = context.registerRefreshCallback;
unregisterRefreshCallback = context.unregisterRefreshCallback;
} catch {
console.error("Unable to refresh user identity.");
}
Comment on lines +38 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Fix conditional hook usage violation.

The useIdentityRefresh hook is being called conditionally inside a try-catch block, which violates React's Rules of Hooks. This can cause unpredictable behavior and state corruption.

Apply this diff to fix the hooks violation:

-  let registerRefreshCallback: ((tokenId: string, callback: () => void) => void) | null = null;
-  let unregisterRefreshCallback: ((tokenId: string) => void) | null = null;
-  try {
-    const context = useIdentityRefresh();
-    registerRefreshCallback = context.registerRefreshCallback;
-    unregisterRefreshCallback = context.unregisterRefreshCallback;
-  } catch {
-    console.error("Unable to refresh user identity.");
-  }
+  const { registerRefreshCallback, unregisterRefreshCallback } = useIdentityRefresh();

If you need to handle the case where the provider might not be available, consider using a default context value or ensuring the provider is always present in the component tree.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let registerRefreshCallback: ((tokenId: string, callback: () => void) => void) | null = null;
let unregisterRefreshCallback: ((tokenId: string) => void) | null = null;
try {
const context = useIdentityRefresh();
registerRefreshCallback = context.registerRefreshCallback;
unregisterRefreshCallback = context.unregisterRefreshCallback;
} catch {
console.error("Unable to refresh user identity.");
}
const { registerRefreshCallback, unregisterRefreshCallback } = useIdentityRefresh();
🧰 Tools
🪛 Biome (2.1.2)

[error] 41-41: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In components/identities/availableIdentities.tsx around lines 38 to 46, the
useIdentityRefresh hook is called inside a try-catch block, causing a
conditional hook usage violation. To fix this, move the useIdentityRefresh call
outside of any conditional or try-catch blocks so it is always called
unconditionally at the top level of the component. Then handle the case where
the context might be undefined by providing a default context value or checking
the context after the hook call, rather than catching errors from the hook call
itself.

const [identity, setIdentity] = useState<Identity>();
const [isIdentityADomain, setIsIdentityADomain] = useState<
boolean | undefined
Expand Down Expand Up @@ -150,9 +160,19 @@ const AvailableIdentities = ({ tokenId }: { tokenId: string }) => {
if (tokenId) {
refreshData();
const timer = setInterval(() => refreshData(), 30e3);
return () => clearInterval(timer);

if (registerRefreshCallback) {
registerRefreshCallback(tokenId, refreshData);
}

return () => {
clearInterval(timer);
if (unregisterRefreshCallback) {
unregisterRefreshCallback(tokenId);
}
};
}
}, [tokenId, refreshData]);
}, [tokenId, refreshData, registerRefreshCallback, unregisterRefreshCallback]);

function mint() {
execute();
Expand Down
37 changes: 37 additions & 0 deletions components/providers/IdentityRefreshProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useCallback, useRef } from 'react';
import { IdentityRefreshContext } from '../../hooks/useIdentityRefresh';

interface IdentityRefreshProviderProps {
children: React.ReactNode;
}

export const IdentityRefreshProvider: React.FC<IdentityRefreshProviderProps> = ({ children }) => {
const refreshCallbacks = useRef<Map<string, () => void>>(new Map());

const registerRefreshCallback = useCallback((tokenId: string, callback: () => void) => {
refreshCallbacks.current.set(tokenId, callback);
}, []);

const unregisterRefreshCallback = useCallback((tokenId: string) => {
refreshCallbacks.current.delete(tokenId);
}, []);

const refreshIdentity = useCallback((tokenId: string) => {
const callback = refreshCallbacks.current.get(tokenId);
if (callback) {
callback();
}
}, []);

return (
<IdentityRefreshContext.Provider
value={{
refreshIdentity,
registerRefreshCallback,
unregisterRefreshCallback,
}}
>
{children}
</IdentityRefreshContext.Provider>
);
};
17 changes: 17 additions & 0 deletions hooks/useIdentityRefresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createContext, useContext } from 'react';

interface IdentityRefreshContextType {
refreshIdentity: (tokenId: string) => void;
registerRefreshCallback: (tokenId: string, callback: () => void) => void;
unregisterRefreshCallback: (tokenId: string) => void;
}

export const IdentityRefreshContext = createContext<IdentityRefreshContextType | null>(null);

export const useIdentityRefresh = () => {
const context = useContext(IdentityRefreshContext);
if (!context) {
throw new Error('useIdentityRefresh must be used within an IdentityRefreshProvider');
}
return context;
};
28 changes: 26 additions & 2 deletions hooks/useNotificationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { useEffect } from "react";
import { hexToDecimal } from "../utils/feltService";
import { NotificationType } from "../utils/constants";
import { NotificationType, TransactionType } from "../utils/constants";
import {
RejectedTransactionReceiptResponse,
RevertedTransactionReceiptResponse,
} from "starknet";
import { useIdentityRefresh } from "./useIdentityRefresh";

const notificationsAtom = atomWithStorage<SIDNotification<NotificationData>[]>(
"userNotifications_SID",
Expand All @@ -18,6 +19,14 @@ export function useNotificationManager() {
const { provider } = useProvider();
const { address } = useAccount();
const [notifications, setNotifications] = useAtom(notificationsAtom);

let refreshIdentity: ((tokenId: string) => void) | null = null;
try {
const context = useIdentityRefresh();
refreshIdentity = context.refreshIdentity;
} catch {

}
Comment on lines +23 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Fix conditional hook usage and empty catch block.

Two issues here:

  1. The useIdentityRefresh hook is called conditionally, violating React's Rules of Hooks
  2. Empty catch block provides no error handling

Apply this diff to fix both issues:

-  let refreshIdentity: ((tokenId: string) => void) | null = null;
-  try {
-    const context = useIdentityRefresh();
-    refreshIdentity = context.refreshIdentity;
-  } catch {
-    
-  }
+  const { refreshIdentity } = useIdentityRefresh();

If the provider might not be available, consider using a default context value or ensuring the provider is always present.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let refreshIdentity: ((tokenId: string) => void) | null = null;
try {
const context = useIdentityRefresh();
refreshIdentity = context.refreshIdentity;
} catch {
}
const { refreshIdentity } = useIdentityRefresh();
🧰 Tools
🪛 Biome (2.1.2)

[error] 25-25: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 ESLint

[error] 27-29: Empty block statement.

(no-empty)

🤖 Prompt for AI Agents
In hooks/useNotificationManager.ts around lines 23 to 29, the useIdentityRefresh
hook is called inside a try-catch block, causing conditional hook usage which
violates React's Rules of Hooks, and the catch block is empty providing no error
handling. To fix this, remove the try-catch and call useIdentityRefresh
unconditionally. Handle the case where the provider might not be available by
using a default context value in the provider or by ensuring the provider is
always present, so the hook never throws. This eliminates conditional hook calls
and removes the need for an empty catch block.


useEffect(() => {
const checkTransactionStatus = async (
Expand Down Expand Up @@ -58,6 +67,21 @@ export function useNotificationManager() {
transactionReceipt.finality_status;
updatedTransactions[index].data.status = "success";
setNotifications(updatedTransactions);

const verificationTypes = [
TransactionType.VERIFIER_GITHUB,
TransactionType.VERIFIER_TWITTER,
TransactionType.VERIFIER_DISCORD,
TransactionType.VERIFIER_POP
];

if (verificationTypes.includes(transaction.type) && refreshIdentity) {
const subtextMatch = notification.subtext?.match(/Starknet ID #(\d+)/);
if (subtextMatch) {
const tokenId = subtextMatch[1];
setTimeout(() => refreshIdentity(tokenId), 1000);
}
}
}
}
};
Expand All @@ -67,7 +91,7 @@ export function useNotificationManager() {
}, 5000);

return () => clearInterval(intervalId);
}, [notifications, address, provider, setNotifications]);
}, [notifications, address, provider, setNotifications, refreshIdentity]);

const filteredNotifications = address
? notifications.filter(
Expand Down
33 changes: 18 additions & 15 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import posthog from "posthog-js";
import AcceptCookies from "../components/legal/acceptCookies";
import { Chain, sepolia, mainnet } from "@starknet-react/chains";
import { getConnectors } from "@/utils/connectorWrapper";
import { IdentityRefreshProvider } from "../components/providers/IdentityRefreshProvider";

if (typeof window !== "undefined") {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, {
Expand Down Expand Up @@ -52,21 +53,23 @@ function MyApp({ Component, pageProps }: AppProps) {
autoConnect
>
<StarknetIdJsProvider>
<ThemeProvider theme={theme}>
<Head>
<title>Starknet.id</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
</Head>
<Navbar />
<AcceptCookies message="We'd love to count you on our traffic stats to ensure you get the best experience on our website !" />
<PostHogProvider client={posthog}>
<Component {...pageProps} />
</PostHogProvider>
</ThemeProvider>
<Analytics />
<IdentityRefreshProvider>
<ThemeProvider theme={theme}>
<Head>
<title>Starknet.id</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
</Head>
<Navbar />
<AcceptCookies message="We'd love to count you on our traffic stats to ensure you get the best experience on our website !" />
<PostHogProvider client={posthog}>
<Component {...pageProps} />
</PostHogProvider>
</ThemeProvider>
<Analytics />
</IdentityRefreshProvider>
</StarknetIdJsProvider>
</StarknetConfig>
</>
Expand Down